/*   sequin5.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:  sequin5.c
*
* Author:  Jonathan Kans
*
* Version Creation Date:   8/26/97
*
* $Revision: 6.798 $
*
* File Description:
*
* Modifications:  
* --------------------------------------------------------------------------
* Date     Name        Description of modification
* -------  ----------  -----------------------------------------------------
*
*
* ==========================================================================
*/

#define USE_BLAST3

#include "sequin.h"
#include <document.h>
#include <biosrc.h>
#include <jzcoll.h>
#include <objsub.h>
#ifdef USE_BLAST3
#include <netblap3.h> 
#else
#include <netblap2.h>
#include <blast2.h>
#endif
#include <dust.h>
#include <pobutil.h>
#include <saledit.h>
#include <salstruc.h>
#include <salfiles.h>
#include <salign.h>
#include <salsap.h>
#include <gbfeat.h>
#include <gbftdef.h>
#include <satutil.h>
#include <rpsutil.h>
#include <subutil.h>
#include <explore.h>
#include <import.h>
#include <salutil.h>

#include <asn2gnbp.h> /* added for  parse to flatfile */
#include <sqnutils.h> /* added to include SeqEdTranslateOneCDS */
#include <seqpanel.h>
#include <salpanel.h>
#include <tax3api.h> /* added for specific-host corrections */
#include <findrepl.h>
#include <valid.h> /* added for latloncountry conflict checking */
#include <vsmutil.h>

#define NLM_GENERATED_CODE_PROTO
#include <objmacro.h>
#include <macroapi.h>


static void CommonLaunchBioseqViewer (SeqEntryPtr sep, CharPtr path, Boolean directToEditor)

{
  BioseqPtr     bsp;
  BioseqSetPtr  bssp;
  Pointer       dataptr;
  Uint2         datatype;
  Uint2         entityID;
  Int2          handled;

  if (sep != NULL) {
    if (IS_Bioseq (sep)) {
      datatype = OBJ_BIOSEQ;
    } else if (IS_Bioseq_set (sep)) {
      datatype = OBJ_BIOSEQSET;
    } else {
      sep = SeqEntryFree (sep);
      return;
    }
    dataptr = (Pointer) sep->data.ptrvalue;
    entityID = ObjMgrRegister (datatype, dataptr);
    if (dataptr != NULL && entityID > 0) {
      if (datatype == OBJ_SEQSUB || datatype == OBJ_SEQENTRY ||
          datatype == OBJ_BIOSEQ || datatype == OBJ_BIOSEQSET) {
        WatchCursor ();
        sep = GetTopSeqEntryForEntityID (entityID);
        if (sep == NULL) {
          sep = SeqEntryNew ();
          if (sep != NULL) {
            if (datatype == OBJ_BIOSEQ) {
              bsp = (BioseqPtr) dataptr;
              sep->choice = 1;
              sep->data.ptrvalue = bsp;
              SeqMgrSeqEntry (SM_BIOSEQ, (Pointer) bsp, sep);
            } else if (datatype == OBJ_BIOSEQSET) {
              bssp = (BioseqSetPtr) dataptr;
              sep->choice = 2;
              sep->data.ptrvalue = bssp;
              SeqMgrSeqEntry (SM_BIOSEQSET, (Pointer) bssp, sep);
            } else {
              sep = SeqEntryFree (sep);
            }
          }
          sep = GetTopSeqEntryForEntityID (entityID);
        }
        if (sep != NULL) {
          SeqEntryPack (sep);
        }
        if (directToEditor) {
          handled = GatherProcLaunch (OMPROC_EDIT, FALSE, entityID, 1,
                                      OBJ_BIOSEQ, 0, 0, OBJ_BIOSEQ, 0);
        } else {
          if (sep != NULL) {
            if (! leaveAsOldAsn) {
              MySeqEntryToAsn3 (sep, TRUE, FALSE, FALSE);
            }
          }
          seqviewprocs.filepath = path;
          seqviewprocs.forceSeparateViewer = TRUE;
          handled = GatherProcLaunch (OMPROC_VIEW, FALSE, entityID, 1,
                                      OBJ_BIOSEQ, 0, 0, OBJ_BIOSEQ, 0);
          seqviewprocs.filepath = NULL;
        }
        ArrowCursor ();
        if (handled != OM_MSG_RET_DONE || handled == OM_MSG_RET_NOPROC) {
          Message (MSG_FATAL, "Unable to launch viewer.");
          SeqEntryFree (sep);
          return;
        } else {
          SendHelpScrollMessage (helpForm, "Editing the Record", NULL);
        }
        ObjMgrSetOptions (OM_OPT_FREE_IF_NO_VIEW, entityID);
        ObjMgrSetDirtyFlag (entityID, TRUE);
      } else {
        Message (MSG_ERROR, "Unable to process object type %d.", (int) datatype);
        ObjMgrDelete (datatype, dataptr);
      }
    }
  }
}

#define BUFSIZE 128

static CharPtr GetSequenceString (BioseqPtr bsp)

{
  Char          buf [BUFSIZE + 5];
  Int2          j;
  ByteStorePtr  raw;
  CharPtr       sequence = NULL;
  SeqPortPtr    spp;
  Uint1         u1Residue;

  if (bsp == NULL) return NULL;
  if (! ISA_na (bsp->mol)) return NULL;
  spp = SeqPortNew (bsp, 0, -1, 0, Seq_code_iupacna);
  if (spp == NULL) return NULL;
  raw = BSNew (bsp->length + 5);
  if (raw == NULL) {
    SeqPortFree (spp);
    return NULL;
  }

  j = 0;
  buf [0] = '\0';
  while ((u1Residue = SeqPortGetResidue (spp)) != SEQPORT_EOF) {
    if (IS_residue (u1Residue)) {
      u1Residue = TO_UPPER(u1Residue);
      if (u1Residue == 'U') {
        u1Residue = 'T';
      }
      buf [j] = (Char) u1Residue;
      j++;
      if (j >= BUFSIZE) {
        BSWrite (raw, buf, j * sizeof (Char));
        j = 0;
      }
    }
  }
  buf [j] = '\0';
  if (j >= 0) {
    BSWrite (raw, buf, j * sizeof (Char));
  }
  sequence = BSMerge (raw, NULL);
  BSFree (raw);
  SeqPortFree (spp);
  return sequence;
}

static Boolean LookForSearchString (CharPtr title, CharPtr str, CharPtr tmp, size_t maxsize)

{
  CharPtr  ptr;

  ptr = StringISearch (title, str);
  if (ptr != NULL) {
    StringNCpy_0 (tmp, ptr + StringLen (str), maxsize);
     ptr = StringChr (tmp, ']');
     if (ptr != NULL) {
       *ptr = '\0';
     }
    return TRUE;
  }
  return FALSE;
}

typedef struct geneextendlist {
  GeneRefPtr  grp;
  SeqLocPtr   slp;
  ObjMgrPtr   omp;
  Boolean     rsult;
  Char        label [41];
} GeneExtendList, PNTR GeneExtendPtr;

static Boolean GeneExtendFunc (GatherContextPtr gcp)

{
  BioseqPtr      bsp;
  GeneExtendPtr  gep;
  GeneRefPtr     grp;
  Boolean        hasNulls;
  ObjMgrTypePtr  omtp;
  SeqFeatPtr     sfp;
  SeqLocPtr      slp;
  Char           thislabel [41];

  if (gcp == NULL) return TRUE;

  gep = (GeneExtendPtr) gcp->userdata;
  if (gep == NULL ) return TRUE;

  thislabel [0] = '\0';

  if (gcp->thistype == OBJ_SEQFEAT) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp != NULL && sfp->data.choice == SEQFEAT_GENE && sfp->data.value.ptrvalue != NULL) {
      grp = (GeneRefPtr) sfp->data.value.ptrvalue;
      omtp = ObjMgrTypeFind (gep->omp, gcp->thistype, NULL, NULL);
      if (omtp == NULL) {
        return TRUE;
      }
      if (omtp->labelfunc != NULL) {
        (*(omtp->labelfunc)) (gcp->thisitem, thislabel, 40, OM_LABEL_CONTENT);
      }
      if (thislabel [0] != '\0') {
        if (StringICmp (thislabel, gep->label) == 0) {
          if (/* SeqLocCompare (gep->slp, sfp->location) != SLC_NO_MATCH */
              SeqLocAinB (gep->slp, sfp->location) <= 0) {
            bsp = GetBioseqGivenSeqLoc (sfp->location, gcp->entityID);
            if (bsp != NULL) {
              slp = SeqLocMerge (bsp, sfp->location, gep->slp, TRUE, FALSE, FALSE);
              if (slp != NULL) {
                sfp->location = SeqLocFree (sfp->location);
                sfp->location = slp;
                if (bsp->repr == Seq_repr_seg) {
                  slp = SegLocToPartsEx (bsp, sfp->location, TRUE);
                  sfp->location = SeqLocFree (sfp->location);
                  sfp->location = slp;
                  hasNulls = LocationHasNullsBetween (sfp->location);
                  sfp->partial = (sfp->partial || hasNulls);
                }
                FreeAllFuzz (slp);
                gep->rsult = TRUE;
              }
            }
          }
          return FALSE;
        }
      }
    }
  }
  return TRUE;
}

static Boolean OligoExtendGene (GeneRefPtr grp, SeqEntryPtr nsep, SeqLocPtr slp)

{
  GeneExtendList  gel;
  GatherScope     gs;
  ObjMgrTypePtr   omtp;
  SeqFeatPtr      sfp;

  if (grp == NULL || nsep == NULL || slp == NULL) return FALSE;
  gel.grp = grp;
  gel.slp = slp;
  gel.omp = ObjMgrGet ();
  gel.label [0] = '\0';
  gel.rsult = FALSE;
  omtp = ObjMgrTypeFind (gel.omp, OBJ_SEQFEAT, NULL, NULL);
  if (omtp != NULL && omtp->labelfunc != NULL) {
    sfp = SeqFeatNew ();
    if (sfp != NULL) {
      sfp->data.choice = SEQFEAT_GENE;
      sfp->data.value.ptrvalue = (Pointer) grp;
      (*(omtp->labelfunc)) ((Pointer) sfp, gel.label, 40, OM_LABEL_CONTENT);
      sfp->data.value.ptrvalue = NULL;
      SeqFeatFree (sfp);
    }
  }
  MemSet ((Pointer)(&gs), 0, sizeof (GatherScope));
  gs.seglevels = 1;
  gs.get_feats_location = TRUE;
  MemSet((Pointer)(gs.ignore), (int)(TRUE), (size_t)(OBJ_MAX * sizeof(Boolean)));
  gs.ignore[OBJ_BIOSEQ] = FALSE;
  gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
  gs.ignore[OBJ_SEQFEAT] = FALSE;
  gs.ignore[OBJ_SEQANNOT] = FALSE;
  GatherSeqEntry (nsep, (Pointer) &gel, GeneExtendFunc, &gs);
  return gel.rsult;
}

static void AddGBQual (SeqFeatPtr sfp, CharPtr qual, CharPtr val)

{
  GBQualPtr  gbq;
  GBQualPtr  last;

  if (sfp == NULL || StringHasNoText (qual) || StringHasNoText (val)) return;
  gbq = GBQualNew ();
  if (gbq == NULL) return;
  gbq->qual = StringSave (qual);
  gbq->val = StringSave (val);
  if (sfp->qual == NULL) {
    sfp->qual = gbq;
  } else {
    last = sfp->qual;
    while (last->next != NULL) {
      last = last->next;
    }
    last->next = gbq;
  }
}

static void ProcessOligoDefline (SeqFeatPtr sfp, CharPtr title, SeqEntryPtr nsep)

{
  BioseqPtr   bsp;
  GeneRefPtr  grp;
  SeqFeatPtr  gsfp;
  SeqLocPtr   gslp;
  Boolean     hasNulls;
  SeqIdPtr    sip;
  Char        tmp [256];

  if (sfp == NULL || StringHasNoText (title) || nsep == NULL) return;
  if (LookForSearchString (title, "[comment=", tmp, sizeof (tmp) - 1)) {
    sfp->comment = StringSave (tmp);
  }
  if (LookForSearchString (title, "[stdname=", tmp, sizeof (tmp) - 1)) {
    AddGBQual (sfp, "standard_name", tmp);
  }
  if (LookForSearchString (title, "[conditions=", tmp, sizeof (tmp) - 1)) {
    AddGBQual (sfp, "PCR_conditions", tmp);
  }
  if (LookForSearchString (title, "[gene=", tmp, sizeof (tmp) - 1)) {
    grp = CreateNewGeneRef (tmp, NULL, NULL, FALSE);
    if (grp != NULL) {
      /*
      slp = AsnIoMemCopy ((Pointer) sfp->location,
                          (AsnReadFunc) SeqLocAsnRead,
                          (AsnWriteFunc) SeqLocAsnWrite);
      if (slp != NULL || slp->choice == SEQLOC_INT) {
        sintp = (SeqIntPtr) slp->data.ptrvalue;
        if (sintp != NULL) {
          if (sintp->strand == Seq_strand_minus) {
            sintp->strand = Seq_strand_plus;
          } else if (sintp->strand == Seq_strand_plus) {
            sintp->strand = Seq_strand_minus;
          }
        }
      }
      */
      if (OligoExtendGene (grp, nsep, sfp->location) /* || OligoExtendGene (grp, nsep, slp) */) {
        grp = GeneRefFree (grp);
      } else {
        gsfp = CreateNewFeature (nsep, NULL, SEQFEAT_GENE, NULL);
        if (gsfp != NULL) {
          gsfp->data.value.ptrvalue = (Pointer) grp;
          gsfp->location = SeqLocFree (gsfp->location);
          gsfp->location = AsnIoMemCopy ((Pointer) sfp->location,
                                        (AsnReadFunc) SeqLocAsnRead,
                                        (AsnWriteFunc) SeqLocAsnWrite);
          sip = SeqLocId (gsfp->location);
          if (sip != NULL) {
            bsp = BioseqFind (sip);
          } else {
            bsp = (BioseqPtr) nsep->data.ptrvalue;
          }
          if (bsp != NULL) {
            gslp = SeqLocMerge (bsp, gsfp->location, NULL, TRUE, FALSE, FALSE);
            if (gslp != NULL) {
              gsfp->location = SeqLocFree (gsfp->location);
              gsfp->location = gslp;
              if (bsp->repr == Seq_repr_seg) {
                gslp = SegLocToPartsEx (bsp, gsfp->location, TRUE);
                gsfp->location = SeqLocFree (gsfp->location);
                gsfp->location = gslp;
                hasNulls = LocationHasNullsBetween (gsfp->location);
                gsfp->partial = (gsfp->partial || hasNulls);
              }
              FreeAllFuzz (gslp);
            }
          }
        }
      }
      /* SeqLocFree (slp); */
    }
  }
}

static void ProcessOligo (SeqEntryPtr sep, CharPtr sequence, CharPtr title)

{
  BioseqSetPtr  bssp;
  ImpFeatPtr    ifp;
  BioseqPtr     nbsp;
  SeqEntryPtr   nsep;
  SeqFeatPtr    sfp;
  SeqLocPtr     slp;

  if (sep == NULL || sequence == NULL) return;
  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL && (bssp->_class == 7 ||
                         (IsPopPhyEtcSet (bssp->_class)))) {
      for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
        ProcessOligo (sep, sequence, title);
      }
      return;
    }
  }
  nsep = FindNucSeqEntry (sep);
  if (nsep == NULL) return;
  if (! IS_Bioseq (nsep)) return;
  nbsp = (BioseqPtr) nsep->data.ptrvalue;
  if (nbsp == NULL) return;
  slp = StringSearchInBioseq (nbsp->id, sequence);
  if (slp == NULL) return;
  ifp = ImpFeatNew ();
  if (ifp != NULL) {
    ifp->key = StringSave ("primer_bind");
    sfp = CreateNewFeature (nsep, NULL, SEQFEAT_IMP, NULL);
    if (sfp != NULL) {
      sfp->data.value.ptrvalue = (Pointer) ifp;
      sfp->location = SeqLocFree (sfp->location);
      sfp->location = slp;
      ProcessOligoDefline (sfp, title, nsep);
    }
  }
}


typedef struct edlocdata {
  FORM_MESSAGE_BLOCK
  SeqIdPtr      sip;
  BioseqPtr     bsp;
  TexT          locusname;
  PrompT        accnnumber;
  Boolean       refgene;
} EdLocData, PNTR EdLocPtr;

static CharPtr AllToUpper (CharPtr str)

{
  Char          ch;
  CharPtr       ptr;

  if (str != NULL) {
    ptr = str;
    ch = *ptr;
    while (ch != '\0') {
      *ptr = TO_UPPER (ch);
      ptr++;
      ch = *ptr;
    }
  }
  return str;
}

static void EditLocusActnProc (ForM f)

{
  BioseqPtr     bsp;
  Uint1         choice = SEQID_GENBANK;
  Int2          count;
  SeqIdPtr      id;
  EdLocPtr      elp;
  Int2          len;
  Char          num [16];
  BioseqPtr     part;
  CharPtr       ptr;
  Char          str [64];
  SeqIdPtr      sip;
  SeqLocPtr     slp;
  Char          tmp [76];
  TextSeqIdPtr  tsip;
  ValNode       vn;

  elp = (EdLocPtr) GetObjectExtra (f);
  if (elp != NULL) {
    sip = elp->sip;
    bsp = elp->bsp;
    if (elp->refgene) {
      choice = SEQID_OTHER;
    }
    if (indexerVersion && sip == NULL && bsp != NULL && bsp->id != NULL) {
      id = bsp->id;
      while (id->next != NULL) {
        id = id->next;
      }
      tsip = TextSeqIdNew ();
      if (tsip != NULL) {
        sip = ValNodeNew (NULL);
        if (sip != NULL) {
          sip->choice = choice;
          sip->data.ptrvalue = (Pointer) tsip;
        } else {
          TextSeqIdFree (tsip);
        }
        id->next = sip;
        SeqMgrReplaceInBioseqIndex (bsp);
      }
    }
    if (sip == NULL) return;
    tsip = (TextSeqIdPtr) sip->data.ptrvalue;
    if (tsip == NULL) return;
    tsip->accession = MemFree (tsip->accession);
    GetTitle (elp->accnnumber, str, sizeof (str) - 1);
    TrimSpacesAroundString (str);
    if (! StringHasNoText (str)) {
      tsip->accession = StringSave (AllToUpper (str));
    }
    tsip->name = MemFree (tsip->name);
    GetTitle (elp->locusname, str, sizeof (str) - 1);
    if (str [0] != '\0') {
      if (elp->refgene) {
        tsip->name = StringSave (str);
      } else {
        tsip->name = StringSave (AllToUpper (str));
      }
    }
    if (bsp != NULL && bsp->repr == Seq_repr_seg && bsp->seq_ext != NULL) {
      if (StringNCmp (str, "SEG_", 4) != 0) {
        sprintf (tmp, "SEG_%s", str);
        tsip->name = MemFree (tsip->name);
        tsip->name = StringSave (tmp);
        SeqMgrReplaceInBioseqIndex (bsp);
      }
      vn.choice = SEQLOC_MIX;
      vn.next = NULL;
      vn.data.ptrvalue = bsp->seq_ext;
      count = 0;
      slp = SeqLocFindNext (&vn, NULL);
      while (slp != NULL) {
        if (slp->choice != SEQLOC_NULL) {
          count++;
        }
        slp = SeqLocFindNext (&vn, slp);
      }
      if (count < 10) {
        len = 1;
      } else if (count < 100) {
        len = 2;
      } else {
        len = 3;
      }
      count = 0;
      slp = SeqLocFindNext (&vn, NULL);
      while (slp != NULL) {
        if (slp->choice != SEQLOC_NULL) {
          count++;
          sprintf (num, "%*d", (int) len, (int) count);
          for (ptr = num; *ptr != '\0'; ptr++) {
            if (*ptr == ' ') {
              *ptr = '0';
            }
          }
          sprintf (tmp, "%s%s", str, num);
          sip = SeqLocId (slp);
          if (sip != NULL) {
            part = BioseqFind (sip);
            if (part != NULL && part->id != NULL) {
              id = part->id;
              while (id->choice != choice && id->next != NULL) {
                id = id->next;
              }
              if (id->choice != choice) {
                tsip = TextSeqIdNew ();
                if (tsip != NULL) {
                  sip = ValNodeNew (NULL);
                  if (sip != NULL) {
                    sip->choice = choice;
                    sip->data.ptrvalue = (Pointer) tsip;
                  } else {
                    TextSeqIdFree (tsip);
                  }
                  id->next = sip;
                  id = sip;
                }
              }
              if (id != NULL && id->choice == choice) {
                tsip = (TextSeqIdPtr) id->data.ptrvalue;
                if (tsip != NULL) {
                  tsip->name = MemFree (tsip->name);
                  if (tmp [0] != '\0') {
                    if (elp->refgene) {
                      tsip->name = StringSave (tmp);
                    } else {
                      tsip->name = StringSave (AllToUpper (tmp));
                    }
                  }
                }
              }
              SeqMgrReplaceInBioseqIndex (part);
            }
          }
        }
        slp = SeqLocFindNext (&vn, slp);
      }
    }
    ObjMgrSetDirtyFlag (elp->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, elp->input_entityID, 0, 0);
  }
}

static void EditLocusMessage (ForM f, Int2 mssg)

{
  EdLocPtr  elp;

  elp = (EdLocPtr) GetObjectExtra (f);
  if (elp != NULL) {
    switch (mssg) {
      case VIB_MSG_CLOSE :
        Remove (f);
        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 (elp->appmessage != NULL) {
          elp->appmessage (f, mssg);
        }
        break;
    }
  }
}

#define NUM_ORDER 16

static SeqIdPtr SeqIdFindGenBankOrOther (SeqIdPtr sip)

{
  Uint1  order [NUM_ORDER];

  SeqIdBestRank (order, NUM_ORDER);
  order [SEQID_LOCAL] = 255;
  order [SEQID_GENBANK] = 1;
  order [SEQID_EMBL] = 2;
  order [SEQID_PIR] = 255;
  order [SEQID_SWISSPROT] = 255;
  order [SEQID_DDBJ] = 3;
  order [SEQID_PRF] = 255;
  order [SEQID_PDB] = 255;
  order [SEQID_PATENT] = 255;
  order [SEQID_OTHER] = 2;
  order [SEQID_GENERAL] = 255;
  order [SEQID_GIBBSQ] = 255;
  order [SEQID_GIBBMT] = 255;
  order [SEQID_GIIM] = 255;
  order [SEQID_GI] = 255;
  return SeqIdSelect (sip, order, NUM_ORDER);
}

void EditLocusProc (IteM i)

{
  ButtoN             b;
  BaseFormPtr        bfp;
  BioseqPtr          bsp;
  GrouP              c;
  EdLocPtr           elp;
  GrouP              g;
  StdEditorProcsPtr  sepp;
  SeqIdPtr           sip;
  TextSeqIdPtr       tsip;
  WindoW             w;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  if (bfp->input_itemtype != OBJ_BIOSEQ) return;
  bsp = GetBioseqGivenIDs (bfp->input_entityID, bfp->input_itemID, bfp->input_itemtype);
  if (bsp == NULL) return;
  sip = SeqIdFindGenBankOrOther (bsp->id);
  /*if (sip == NULL) return;*/
  elp = (EdLocPtr) MemNew (sizeof (EdLocData));
  if (elp == NULL) return;
  w = MovableModalWindow (-50, -33, -10, -10,
                          "Locus and Accession Number",
                          StdCloseWindowProc);
  SetObjectExtra (w, elp, StdCleanupFormProc);
  elp->form = (ForM) w;
  elp->actproc = EditLocusActnProc;
  elp->formmessage = EditLocusMessage;

  elp->input_entityID = bfp->input_entityID;
  elp->input_itemID = bfp->input_itemID;
  elp->input_itemtype = bfp->input_itemtype;

#ifndef WIN_MAC
  CreateStdEditorFormMenus (w);
#endif

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

  g = HiddenGroup (w, 2, 0, NULL);
  elp->sip = sip;
  elp->bsp = bsp;
  StaticPrompt (g, "Locus: ", 0, dialogTextHeight, systemFont, 'l');
  elp->locusname = DialogText (g, "", 20, NULL);
  StaticPrompt (g, "Accession: ", 0, stdLineHeight, systemFont, 'l');
  elp->accnnumber = StaticPrompt (g, "", 20 * stdCharWidth, stdLineHeight, systemFont, 'l');
  c = HiddenGroup (w, 2, 0, NULL);
  b = DefaultButton (c, "Accept", StdAcceptFormButtonProc);
  SetObjectExtra (b, elp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) c, NULL);
  if (sip != NULL) {
    tsip = (TextSeqIdPtr) sip->data.ptrvalue;
    if (tsip != NULL) {
      SetTitle (elp->locusname, tsip->name);
      SetTitle (elp->accnnumber, tsip->accession);
    }
    if (sip->choice == SEQID_OTHER) {
      elp->refgene = TRUE;
    }
  }
  RealizeWindow (w);
  Select (elp->locusname);
  Show (w);
  Select (w);
  Update ();
}

typedef struct accntolocal 
{
  Boolean make_secondary;
  Boolean convert_prots;
  Boolean convert_nucs;
} AccnToLocalData, PNTR AccnToLocalPtr;

static SeqIdPtr LocalSeqIdFromAccession (SeqIdPtr sip, CharPtr str, Int4 str_size)
{
  TextSeqIdPtr  tsip;
  CharPtr       text;
  SeqIdPtr      new_sip;
  ObjectIdPtr   oip;

  if (sip == NULL || str == NULL || str_size < 1)
  {
    return NULL;
  }
  if (sip->choice != SEQID_GENBANK 
      && sip->choice != SEQID_EMBL 
      && sip->choice != SEQID_DDBJ)
  {
    return NULL;
  }
  if (sip->data.ptrvalue == NULL)
  {
    return NULL;
  }
  new_sip = ValNodeNew (NULL);
  if (new_sip == NULL)
  {
    return NULL;
  }
  tsip = (TextSeqIdPtr) sip->data.ptrvalue;
  text = NULL;
  str [0] = '\0';
  if (tsip->accession != NULL) {
    text = tsip->accession;
  } else if (tsip->name != NULL) {
    text = tsip->name;
  }
  if (text != NULL) {
    StringNCpy_0 (str, text, str_size);
    oip = ObjectIdNew ();
    if (oip != NULL) {
      oip->str = StringSave (text);
      new_sip->choice = SEQID_LOCAL;
      new_sip->data.ptrvalue = (Pointer) oip;
    }
  }
  return new_sip;
}

static void SeqIdSwap (SeqIdPtr sip1, SeqIdPtr sip2)
{
  ValNode       tmp_sip;

  if (sip1 == NULL || sip2 == NULL)
  {
    return;
  }
  
  tmp_sip.choice = sip1->choice;
  tmp_sip.data.ptrvalue = sip1->data.ptrvalue;
  sip1->choice = sip2->choice;
  sip1->data.ptrvalue = sip2->data.ptrvalue;
  sip2->choice = tmp_sip.choice;
  sip2->data.ptrvalue = tmp_sip.data.ptrvalue;
}

static void ConvertSeqIdListToLocalID (SeqIdPtr sip, SeqEntryPtr sep, AccnToLocalPtr atlp)

{
  GBBlockPtr    gbp;
  Char          str [32];
  ValNodePtr    vnp;
  BioseqPtr     bsp;
  SeqIdPtr      new_sip = NULL;
  
  if (atlp == NULL) return;

  while (sip != NULL) 
  {
    bsp = BioseqFind (sip);
    if (bsp == NULL)
    {
      new_sip = LocalSeqIdFromAccession (sip, str, sizeof (str));
      if (new_sip != NULL)
      {
        bsp = BioseqFind (new_sip);
      }
    }
    if (bsp == NULL)
    {
      /* have to skip */
    }
    else if (ISA_na (bsp->mol) && !atlp->convert_nucs)
    {
      /* don't convert the nucleotide ID */
    }
    else if (ISA_aa (bsp->mol) && ! atlp->convert_prots)
    {
      /* don't convert the protein ID */
    }
    else if ((sip->choice == SEQID_GENBANK 
          || sip->choice == SEQID_EMBL 
          || sip->choice == SEQID_DDBJ)
          && sip->data.ptrvalue != NULL)
    {
      new_sip = LocalSeqIdFromAccession (sip, str, sizeof (str));
      if (new_sip != NULL)
      {
        /* replace old sip values with new  - new_sip will now be ready for freeing */
        SeqIdSwap (sip, new_sip);
                
        /* make secondary if needed */
        if (atlp->make_secondary && sep != NULL)
        {
          vnp = SeqEntryGetSeqDescr (sep, Seq_descr_genbank, NULL);
          if (vnp == NULL) {
            vnp = CreateNewDescriptor (sep, Seq_descr_genbank);
            if (vnp != NULL) {
              gbp = GBBlockNew ();
              vnp->data.ptrvalue = (Pointer) gbp;
            }
          }
          if (vnp != NULL) {
            gbp = (GBBlockPtr) vnp->data.ptrvalue;
            if (gbp != NULL) {
              for (vnp = gbp->extra_accessions;
                   vnp != NULL && StringICmp (vnp->data.ptrvalue, str) != 0;
                   vnp = vnp->next) continue;
              if (vnp == NULL) {
                vnp = ValNodeCopyStr (&(gbp->extra_accessions), 0, str);
              }
            }
          }
        }
      }
    }
    new_sip = SeqIdFree (new_sip);
    sip = sip->next;
  }
}

static void ConvertSeqLocListToLocalID (SeqLocPtr slp, AccnToLocalPtr atlp)

{
  SeqLocPtr      loc;
  PackSeqPntPtr  psp;
  SeqBondPtr     sbp;
  SeqIntPtr      sinp;
  SeqIdPtr       sip;
  SeqPntPtr      spp;

  while (slp != NULL) {
    switch (slp->choice) {
      case SEQLOC_NULL :
        break;
      case SEQLOC_EMPTY :
      case SEQLOC_WHOLE :
        sip = (SeqIdPtr) slp->data.ptrvalue;
        ConvertSeqIdListToLocalID (sip, NULL, atlp);
        break;
      case SEQLOC_INT :
        sinp = (SeqIntPtr) slp->data.ptrvalue;
        if (sinp != NULL) {
          sip = sinp->id;
          ConvertSeqIdListToLocalID (sip, NULL, atlp);
        }
        break;
      case SEQLOC_PNT :
        spp = (SeqPntPtr) slp->data.ptrvalue;
        if (spp != NULL) {
          sip = spp->id;
          ConvertSeqIdListToLocalID (sip, NULL, atlp);
        }
        break;
      case SEQLOC_PACKED_PNT :
        psp = (PackSeqPntPtr) slp->data.ptrvalue;
        if (psp != NULL) {
          sip = psp->id;
          ConvertSeqIdListToLocalID (sip, NULL, atlp);
        }
        break;
      case SEQLOC_PACKED_INT :
      case SEQLOC_MIX :
      case SEQLOC_EQUIV :
        loc = (SeqLocPtr) slp->data.ptrvalue;
        while (loc != NULL) {
          ConvertSeqLocListToLocalID (loc, atlp);
          loc = loc->next;
        }
        break;
      case SEQLOC_BOND :
        sbp = (SeqBondPtr) slp->data.ptrvalue;
        if (sbp != NULL) {
          spp = (SeqPntPtr) sbp->a;
          if (spp != NULL) {
            sip = spp->id;
            ConvertSeqIdListToLocalID (sip, NULL, atlp);
          }
          spp = (SeqPntPtr) sbp->b;
          if (spp != NULL) {
            sip = spp->id;
            ConvertSeqIdListToLocalID (sip, NULL, atlp);
          }
        }
        break;
      case SEQLOC_FEAT :
        break;
      default :
        break;
    }
    slp = slp->next;
  }
}

static Boolean ChangeAccessionToLocalID (GatherContextPtr gcp)

{
  BioseqPtr      bsp;
  AccnToLocalPtr atlp;
  SeqEntryPtr    sep;
  SeqFeatPtr     sfp;
  SeqIdPtr       sip;
  SeqLocPtr      slp;

  if (gcp == NULL || gcp->userdata == NULL) return TRUE;
  atlp = (AccnToLocalPtr) gcp->userdata;
  if (gcp->thisitem == NULL) return TRUE;
  switch (gcp->thistype) {
    case OBJ_BIOSEQ :
      sep = NULL;
      bsp = (BioseqPtr) gcp->thisitem;
      if (atlp->make_secondary)
      {
        sep = SeqMgrGetSeqEntryForData (bsp);
      }
      sip = bsp->id;
      ConvertSeqIdListToLocalID (sip, sep, atlp);
      SeqMgrReplaceInBioseqIndex (bsp);
      break;
    case OBJ_SEQFEAT :
      sfp = (SeqFeatPtr) gcp->thisitem;
      slp = sfp->location;
      ConvertSeqLocListToLocalID (slp, atlp);
      slp = sfp->product;
      ConvertSeqLocListToLocalID (slp, atlp);
      break;
    default :
      break;
  }
  return TRUE;
}

static void ConvertToLocalProc (IteM i, Boolean convert_nucs, Boolean convert_prots)

{
  MsgAnswer       ans;
  BaseFormPtr     bfp;
  GatherScope     gs;
  AccnToLocalData atld;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  if (bfp->input_itemtype != OBJ_BIOSEQ) return;
  ans = Message (MSG_OKC, "Are you sure you want to convert accessions to local IDs?");
  if (ans == ANS_CANCEL) return;
  ans = Message (MSG_OKC, "Are you REALLY sure you want to convert accessions to local IDs?");
  if (ans == ANS_CANCEL) return;
  ans = Message (MSG_YN, "Do you want to make the original accessions secondary?");
  atld.make_secondary = (Boolean) (ans == ANS_YES);
  atld.convert_nucs = convert_nucs;
  atld.convert_prots = convert_prots;
  MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
  gs.seglevels = 1;
  MemSet((Pointer) (gs.ignore), (int) (TRUE), (size_t) (OBJ_MAX * sizeof (Boolean)));
  gs.ignore[OBJ_BIOSEQ] = FALSE;
  gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
  gs.ignore[OBJ_SEQFEAT] = FALSE;
  gs.ignore[OBJ_SEQANNOT] = FALSE;
  GatherEntity (bfp->input_entityID, (Pointer) &atld, ChangeAccessionToLocalID, &gs);
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}

extern void ConvertToLocalProcOnlyNucs (IteM i)
{
  ConvertToLocalProc (i, TRUE, FALSE);
}

extern void ConvertToLocalProcOnlyProts (IteM i)
{
  ConvertToLocalProc (i, FALSE, TRUE);
}

extern void ConvertToLocalProcAll (IteM i)
{
  ConvertToLocalProc (i, TRUE, TRUE);
}
 
static SeqIdPtr SeqIdFindBestForPromotion (SeqIdPtr sip)

{
  return SeqIdFindBest (sip, 0);
}

static SeqIdPtr SeqIdFindWorstForPromotion (SeqIdPtr sip)

{
  return SeqIdFindWorst (sip);
}

static void PromoteSeqId (SeqIdPtr sip, Boolean alsoCheckLocalAccn, Boolean findWorst)

{
  SeqIdPtr     bestid, newid, oldid;
  BioseqPtr    bsp;
  ObjectIdPtr  oip;
  TextSeqId    tsi;
  SeqId        vn;

  bsp = BioseqFind (sip);
  if (bsp == NULL && alsoCheckLocalAccn && sip->choice == SEQID_LOCAL) {
    oip = (ObjectIdPtr) sip->data.ptrvalue;
    if (oip != NULL && (! StringHasNoText (oip->str))) {
      MemSet ((Pointer) &vn, 0, sizeof (SeqId));
      MemSet ((Pointer) &tsi, 0, sizeof (TextSeqId));
      tsi.accession = oip->str;
      vn.choice = SEQID_GENBANK;
      vn.data.ptrvalue = (Pointer) &tsi;
      bsp = BioseqFind (&vn);
    }
  }
  if (bsp == NULL) return;

  if (findWorst) {
    bestid = SeqIdFindWorstForPromotion (bsp->id);
  } else {
    bestid = SeqIdFindBestForPromotion (bsp->id);
  }
  if (bestid == NULL) return;
  newid = SeqIdDup (bestid);
  if (newid == NULL) return;

  oldid = ValNodeNew (NULL);
  if (oldid == NULL) return;

  MemCopy (oldid, sip, sizeof (ValNode));
  oldid->next = NULL;

  sip->choice = newid->choice;
  sip->data.ptrvalue = newid->data.ptrvalue;

  SeqIdFree (oldid);
  ValNodeFree (newid);

  SeqIdStripLocus (sip);
}

static void PromoteSeqIdList (SeqIdPtr sip, Boolean alsoCheckLocalAccn, Boolean findWorst)

{
  while (sip != NULL) {
    PromoteSeqId (sip, alsoCheckLocalAccn, findWorst);
    sip = sip->next;
  }
}

static void PromoteSeqLocList (SeqLocPtr slp, Boolean alsoCheckLocalAccn, Boolean findWorst)

{
  SeqLocPtr      loc;
  PackSeqPntPtr  psp;
  SeqBondPtr     sbp;
  SeqIntPtr      sinp;
  SeqIdPtr       sip;
  SeqPntPtr      spp;

  while (slp != NULL) {
    switch (slp->choice) {
      case SEQLOC_NULL :
        break;
      case SEQLOC_EMPTY :
      case SEQLOC_WHOLE :
        sip = (SeqIdPtr) slp->data.ptrvalue;
        PromoteSeqIdList (sip, alsoCheckLocalAccn, findWorst);
        break;
      case SEQLOC_INT :
        sinp = (SeqIntPtr) slp->data.ptrvalue;
        if (sinp != NULL) {
          sip = sinp->id;
          PromoteSeqIdList (sip, alsoCheckLocalAccn, findWorst);
        }
        break;
      case SEQLOC_PNT :
        spp = (SeqPntPtr) slp->data.ptrvalue;
        if (spp != NULL) {
          sip = spp->id;
          PromoteSeqIdList (sip, alsoCheckLocalAccn, findWorst);
        }
        break;
      case SEQLOC_PACKED_PNT :
        psp = (PackSeqPntPtr) slp->data.ptrvalue;
        if (psp != NULL) {
          sip = psp->id;
          PromoteSeqIdList (sip, alsoCheckLocalAccn, findWorst);
        }
        break;
      case SEQLOC_PACKED_INT :
      case SEQLOC_MIX :
      case SEQLOC_EQUIV :
        loc = (SeqLocPtr) slp->data.ptrvalue;
        while (loc != NULL) {
          PromoteSeqLocList (loc, alsoCheckLocalAccn, findWorst);
          loc = loc->next;
        }
        break;
      case SEQLOC_BOND :
        sbp = (SeqBondPtr) slp->data.ptrvalue;
        if (sbp != NULL) {
          spp = (SeqPntPtr) sbp->a;
          if (spp != NULL) {
            sip = spp->id;
            PromoteSeqIdList (sip, alsoCheckLocalAccn, findWorst);
          }
          spp = (SeqPntPtr) sbp->b;
          if (spp != NULL) {
            sip = spp->id;
            PromoteSeqIdList (sip, alsoCheckLocalAccn, findWorst);
          }
        }
        break;
      case SEQLOC_FEAT :
        break;
      default :
        break;
    }
    slp = slp->next;
  }
}


static void ChangeGBNameIDs (SeqIdPtr sip, Pointer userdata)

{
  Char          buf [80];
  ObjectIdPtr   oip;
  TextSeqIdPtr  tsip;

  if (sip == NULL || sip->choice != SEQID_GENBANK) return;
  tsip = (TextSeqIdPtr) sip->data.ptrvalue;
  if (tsip == NULL) return;
  if (tsip->accession != NULL || StringHasNoText (tsip->name)) return;
  StringNCpy (buf, tsip->name, sizeof (buf));
  TrimSpacesAroundString (buf);
  oip = ObjectIdNew ();
  if (oip == NULL) return;
  oip->str = StringSave (buf);
  sip->choice = SEQID_LOCAL;
  sip->data.ptrvalue = (Pointer) oip;
  TextSeqIdFree (tsip);
}

static void ChangeGBNameBioseq (BioseqPtr bsp, Pointer userdata)

{
  VisitSeqIdsInBioseq (bsp, userdata, ChangeGBNameIDs);
  SeqMgrReplaceInBioseqIndex (bsp);
}

static void ChangeGBNameFeature (SeqFeatPtr sfp, Pointer userdata)

{
  VisitSeqIdsInSeqFeat (sfp, userdata, ChangeGBNameIDs);
}

void ChangeGenBankNameToLocal (IteM i)

{
  BaseFormPtr  bfp;
  SeqEntryPtr  oldscope, sep;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;

  oldscope = SeqEntrySetScope (sep);

  VisitBioseqsInSep (sep, NULL, ChangeGBNameBioseq);
  VisitFeaturesInSep (sep, NULL, ChangeGBNameFeature);

  SeqEntrySetScope (oldscope);

  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}


static SeqEntryPtr GetParentForSeqAnnot (SeqAnnotPtr sap)
{
  if (sap == NULL || sap->idx.parentptr == NULL) {
    return NULL;
  }
  if (sap->idx.parenttype == OBJ_BIOSEQSET) {
    return SeqMgrGetSeqEntryForData (sap->idx.parentptr);
  } else if (sap->idx.parenttype == OBJ_BIOSEQ) {
    return SeqMgrGetSeqEntryForData (sap->idx.parentptr);
  } else {
    return NULL;
  }
}


extern void PromoteAlignIDsProc (SeqAnnotPtr sp, Pointer data)

{
  DenseDiagPtr  ddp;
  DenseSegPtr   dsp;
  PackSegPtr    psp;
  SeqAlignPtr   sap;
  SeqLocPtr     slp;
  StdSegPtr     ssp;
  SeqEntryPtr   orig_scope, new_scope = NULL;

  if (sp == NULL || sp->type != 2) {
    return;
  }
  sap = (SeqAlignPtr) sp->data;
  if (sap == NULL) return;

  new_scope = GetParentForSeqAnnot(sp);
 
  orig_scope = SeqEntrySetScope (new_scope);

  switch (sap->segtype) {
    case SAS_DENDIAG :
      for (ddp = sap->segs; ddp != NULL; ddp = ddp->next) {
        PromoteSeqIdList (ddp->id, TRUE, FALSE);
      }
      break;
    case SAS_DENSEG :
      dsp = sap->segs;
      if (dsp != NULL) {
        PromoteSeqIdList (dsp->ids, TRUE, FALSE);
      }
      break;
    case SAS_STD :
      for (ssp = sap->segs; ssp != NULL; ssp = ssp->next) {
        PromoteSeqIdList (ssp->ids, TRUE, FALSE);
        for (slp = ssp->loc; slp != NULL; slp = slp->next) {
          PromoteSeqLocList (slp, TRUE, FALSE);
        }
      }
      break;
    case SAS_PACKED :
      psp = (PackSegPtr) sap->segs;
      if (psp != NULL) {
        PromoteSeqIdList (psp->ids, TRUE, FALSE);
      }
      break;
    default :
      break;
  }

  SeqEntrySetScope (orig_scope);
}


static Boolean RemoveGBIDsProc (GatherObjectPtr gop)

{
  BioseqPtr      bsp;
  SeqIdPtr       nextid, sip;
  SeqIdPtr PNTR  previd;

  if (gop->itemtype != OBJ_BIOSEQ) return TRUE;
  bsp = (BioseqPtr) gop->dataptr;
  if (bsp == NULL) return TRUE;

  previd = (SeqIdPtr PNTR) &(bsp->id);
  sip = bsp->id;
  while (sip != NULL) {
    nextid = sip->next;
    if (sip->choice == SEQID_GENBANK) {
      *previd = sip->next;
      sip->next = NULL;
      SeqIdFree (sip);
    } else {
      previd = (SeqIdPtr PNTR) &(sip->next);
    }
    sip = sip->next;
  }

  return TRUE;
}

void RemoveGBIDsFromBioseqs (IteM i)

{
  MsgAnswer    ans;
  BaseFormPtr  bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  ans = Message (MSG_OKC, "Currently deletes all GenBank SeqIDs");
  if (ans == ANS_CANCEL) return;

  GatherObjectsInEntity (bfp->input_entityID, 0, NULL, RemoveGBIDsProc, NULL, NULL);

  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}

static Boolean RemoveGBProtIDsProc (GatherObjectPtr gop)

{
  BioseqPtr      bsp;
  SeqIdPtr       nextid, sip;
  SeqIdPtr PNTR  previd;

  if (gop->itemtype != OBJ_BIOSEQ) return TRUE;
  bsp = (BioseqPtr) gop->dataptr;
  if (bsp == NULL) return TRUE;
  if (! ISA_aa (bsp->mol)) return TRUE;

  previd = (SeqIdPtr PNTR) &(bsp->id);
  sip = bsp->id;
  while (sip != NULL) {
    nextid = sip->next;
    if (sip->choice == SEQID_GENBANK) {
      *previd = sip->next;
      sip->next = NULL;
      SeqIdFree (sip);
    } else {
      previd = (SeqIdPtr PNTR) &(sip->next);
    }
    sip = sip->next;
  }

  return TRUE;
}

void RemoveGBIDsFromProteins (IteM i)

{
  MsgAnswer    ans;
  BaseFormPtr  bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  ans = Message (MSG_OKC, "Currently deletes all GenBank SeqIDs");
  if (ans == ANS_CANCEL) return;

  GatherObjectsInEntity (bfp->input_entityID, 0, NULL, RemoveGBProtIDsProc, NULL, NULL);

  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}

static Boolean RemoveGIProc (GatherObjectPtr gop)

{
  BioseqPtr      bsp;
  SeqIdPtr       nextid, sip;
  SeqIdPtr PNTR  previd;

  if (gop->itemtype != OBJ_BIOSEQ) return TRUE;
  bsp = (BioseqPtr) gop->dataptr;
  if (bsp == NULL) return TRUE;

  previd = (SeqIdPtr PNTR) &(bsp->id);
  sip = bsp->id;
  while (sip != NULL) {
    nextid = sip->next;
    if (sip->choice == SEQID_GI) {
      *previd = sip->next;
      sip->next = NULL;
      SeqIdFree (sip);
    } else {
      previd = (SeqIdPtr PNTR) &(sip->next);
    }
    sip = sip->next;
  }

  return TRUE;
}

void RemoveGIsFromBioseqs (IteM i)

{
  MsgAnswer    ans;
  BaseFormPtr  bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  ans = Message (MSG_OKC, "Currently deletes all GI SeqIDs");
  if (ans == ANS_CANCEL) return;

  GatherObjectsInEntity (bfp->input_entityID, 0, NULL, RemoveGIProc, NULL, NULL);

  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}


extern Boolean DoBuildContig (void)

{
  MsgAnswer    ans;
  BioseqPtr    bsp;
  Pointer      dataptr;
  Uint2        datatype;
  Uint2        entityID;
  FILE         *fp;
  Int2         handled;
  Char         path [PATH_MAX];
  SeqEntryPtr  sep;

  if (! GetInputFileName (path, sizeof (path), NULL, "TEXT")) return FALSE;
  fp = FileOpen (path, "r");
  if (fp == NULL) return FALSE;
  ans = Message (MSG_YN, "Are coordinates on the master (as opposed to the individual accession)?");
  sep = ReadContigList (fp, (Boolean) (ans == ANS_YES));
  FileClose (fp);
  if (sep == NULL || sep->choice != 1) return FALSE;

  bsp = (BioseqPtr) sep->data.ptrvalue;
  if (bsp == NULL) return FALSE;
  datatype = OBJ_BIOSEQ;
  dataptr = (Pointer) bsp;
  entityID = ObjMgrRegister (datatype, dataptr);
  if (dataptr == NULL || entityID == 0) return FALSE;

  WatchCursor ();
  Update ();
  handled = GatherProcLaunch (OMPROC_VIEW, FALSE, entityID, 1,
                              OBJ_BIOSEQ, 0, 0, OBJ_BIOSEQ, 0);
  ArrowCursor ();
  Update ();

  if (handled != OM_MSG_RET_DONE || handled == OM_MSG_RET_NOPROC) {
    Message (MSG_FATAL, "Unable to launch viewer.");
    SeqEntryFree (sep);
    return FALSE;
  }

  ObjMgrSetOptions (OM_OPT_FREE_IF_NO_VIEW, entityID);
  ObjMgrSetDirtyFlag (entityID, TRUE);
  return TRUE;
}

static void DoParseTrinomial (BioSourcePtr biop, Pointer userdata)

{
  BinomialOrgNamePtr  bonp;
  OrgModPtr           omp;
  OrgNamePtr          onp;
  OrgRefPtr           orp;
  CharPtr             str;

  if (biop == NULL) return;
  orp = biop->org;
  if (orp == NULL) return;
  onp = orp->orgname;
  if (onp == NULL) return;
  if (onp->choice != 1) return;
  bonp = (BinomialOrgNamePtr) onp->data;
  if (bonp == NULL) return;
  if (StringHasNoText (bonp->subspecies)) return;
  for (omp = onp->mod; omp != NULL; omp = omp->next) {
    if (omp->subtype == ORGMOD_sub_species) return;
  }
  str = bonp->subspecies;
  if (StringNICmp (str, "subsp. ", 7) == 0) {
    str += 7;
    if (StringHasNoText (str)) return;
  }
  omp = OrgModNew ();
  if (omp == NULL) return;
  omp->subtype = ORGMOD_sub_species;
  omp->subname = StringSave (str);
  omp->next = onp->mod;
  onp->mod = omp;
}

extern void ParseTrinomial (IteM i);
extern void ParseTrinomial (IteM i)

{
  BaseFormPtr  bfp;
  SeqEntryPtr  sep;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;

  VisitBioSourcesInSep (sep, NULL, DoParseTrinomial);

  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
  Update ();
}



#define NUM_SITES 27
static CharPtr siteString [NUM_SITES] = {
  "", "active site", "binding site", "cleavage site", "inhibition site", "modified site",
  "glycosylation site", "myristoylation site", "mutagenized site", "metal binding site",
  "phosphorylation site", "acetylation site", "amidation site", "methylation site",
  "hydroxylation site", "sulfatation site", "oxidative deamination site",
  "pyrrolidone carboxylic acid site", "gamma carboxyglutamic acid site",
  "blocked site", "lipid binding site", "np binding site", "DNA binding site",
  "signal peptide", "transit peptide", "transmembrane region", "nitrosylation"
};

/*=========================================================================*/
/*                                                                         */
/* SeqLocAdjustByOffset ()                                                 */
/*                                                                         */
/*=========================================================================*/

extern void SeqLocAdjustByOffset (SeqLocPtr slp,
                  Int4 offset)
{
  SeqIntPtr sinp;

  switch (slp->choice) {
  case SEQLOC_INT :
    sinp = (SeqIntPtr) slp->data.ptrvalue;
    if (NULL == sinp)
      break;
    sinp->from += offset;
    sinp->to   += offset;
    break;
  case SEQLOC_EMPTY :
  case SEQLOC_NULL :
  case SEQLOC_WHOLE :
  case SEQLOC_PNT :
  case SEQLOC_PACKED_PNT :
  case SEQLOC_PACKED_INT :
  case SEQLOC_MIX :
  case SEQLOC_EQUIV :
  case SEQLOC_BOND :
  case SEQLOC_FEAT :
  default :
    break;
  }
}

      
static Boolean ConvertImpToSpecialRNA 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  RnaRefPtr          rrp;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_IMP)
  {
    return FALSE;
  }
  rrp = RnaRefNew ();
  if (rrp != NULL) {
    sfp->data.value.ptrvalue = ImpFeatFree ((ImpFeatPtr) sfp->data.value.ptrvalue);
    sfp->data.choice = SEQFEAT_RNA;
    sfp->data.value.ptrvalue = (Pointer) rrp;
    if (featdef_to == FEATDEF_precursor_RNA) {
      rrp->type = 1;
    } else {
      rrp->type = 255;
    }
  }
  return TRUE;
}

static Boolean ConvertRegionToRNA 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  RnaRefPtr  rrp;
  CharPtr    str;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_REGION)
  {
    return FALSE;
  }

  rrp = RnaRefNew ();
  if (NULL == rrp)
  {
    return FALSE;
  }

  str = (CharPtr) sfp->data.value.ptrvalue;
  sfp->data.choice = SEQFEAT_RNA;
  sfp->data.value.ptrvalue = (Pointer) rrp;

  if (featdef_to == FEATDEF_precursor_RNA) {
    rrp->type = 1;
  } else {
    rrp->type = 255;
  }

  if (! StringHasNoText (str)) {
    rrp->ext.choice = 1;
    rrp->ext.value.ptrvalue = str;
  }
  return TRUE;
}


static Boolean CommentShouldBeProduct (CharPtr comment)
{
  CharPtr search1 = "internal transcribed spacer ";
  CharPtr search2 = "ITS";
  CharPtr found;
  Int4 len;

  if (StringHasNoText (comment)) {
    return FALSE;
  }
  if ((found = StringISearch (comment, search1)) != NULL
      && (len = StringLen (search1)) < StringLen (found)
      && (*(found + len) == '1' || *(found + len) == '2' || *(found + len) == '3')) {
    return TRUE;
  }

  if ((found = StringISearch (comment, search2)) != NULL
      && (len = StringLen (search2)) < StringLen (found)
      && (*(found + len) == '1' || *(found + len) == '2' || *(found + len) == '3')) {
    return TRUE;
  }
  return FALSE;
}


static Boolean ConvertImpToRNA 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  RnaRefPtr  rrp;
  RNAGenPtr  rgp;
  Boolean    was_misc_feat = FALSE;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_IMP)
  {
    return FALSE;
  }
  if (sfp->idx.subtype == FEATDEF_misc_feature) {
    was_misc_feat = TRUE;
  }

  rrp = RnaRefNew ();
  if (NULL == rrp)
    return FALSE;

  sfp->data.value.ptrvalue =
    ImpFeatFree ((ImpFeatPtr) sfp->data.value.ptrvalue);
  sfp->data.choice = SEQFEAT_RNA;
  sfp->data.value.ptrvalue = (Pointer) rrp;

  switch (featdef_to) {
  case FEATDEF_preRNA :
    rrp->type = 1;
    break;
  case FEATDEF_mRNA :
    rrp->type = 2;
    break;
  case FEATDEF_tRNA :
    rrp->type = 3;
    break;
  case FEATDEF_rRNA :
    rrp->type = 4;
    break;
  case FEATDEF_snRNA :
    rrp->type = 5;
    break;
  case FEATDEF_scRNA :
    rrp->type = 6;
    break;
  case FEATDEF_snoRNA :
    rrp->type = 7;
    break;
  case FEATDEF_ncRNA :
    rrp->type = 8;
    rrp->ext.choice = 3;
    rrp->ext.value.ptrvalue = RNAGenNew ();
    break;
  case FEATDEF_tmRNA :
    rrp->type = 9;
    rrp->ext.choice = 3;
    rrp->ext.value.ptrvalue = RNAGenNew ();
    break;
  case FEATDEF_misc_RNA:
    rrp->type = 10;
    rrp->ext.choice = 3;
    rgp = RNAGenNew ();
    rrp->ext.value.ptrvalue = rgp;
    if (was_misc_feat && CommentShouldBeProduct(sfp->comment)) {
      rgp->product = sfp->comment;
      sfp->comment = NULL;
    }
    break;
  case FEATDEF_otherRNA :
    if (was_misc_feat && CommentShouldBeProduct(sfp->comment)) {
      rrp->type = 10;
      rrp->ext.choice = 3;
      rgp = RNAGenNew ();
      rrp->ext.value.ptrvalue = rgp;
      rgp->product = sfp->comment;
      sfp->comment = NULL;
    } else {      
      rrp->type = 255;
      rrp->ext.choice = 1;
      rrp->ext.value.ptrvalue = StringSave ("misc_RNA");
    }
    break;
  default :
    break;
  }
  return TRUE;
}

static Boolean ConvertCommentToMiscFeat (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  ImpFeatPtr ifp;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_COMMENT || sfp->data.value.ptrvalue != NULL)
  {
    return FALSE;
  }
  
  ifp = ImpFeatNew ();
  if (ifp != NULL) {
    ifp->key = StringSave ("misc_feature");
    sfp->data.choice = SEQFEAT_IMP;
    sfp->data.value.ptrvalue = (Pointer) ifp;
    return TRUE;
  }
  return FALSE;
}

static Boolean ConvertGeneToImpFeat 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  return ConvertGeneToImpFeatFunc (sfp, featdef_to);
}

static Boolean ConvertRNAToMiscFeat
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  return ConvertRNAToImpFeat (sfp, featdef_to);
}

static Boolean ConvertSiteToMiscFeat
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  GBQualPtr  gbqual;
  ImpFeatPtr ifp;
  Int2       sitetype;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_SITE)
  {
    return FALSE;
  }

  ifp = ImpFeatNew ();
  if (NULL == ifp)
  {
    return FALSE;
  }

  sitetype = (Int2) sfp->data.value.intvalue;
  sfp->data.choice = SEQFEAT_IMP;
  sfp->data.value.ptrvalue = (Pointer) ifp;
  ifp->key = StringSave ("misc_feature");
  if (sitetype > 0 && sitetype < NUM_SITES) {
    gbqual = GBQualNew ();
    if (gbqual != NULL) {
      gbqual->qual = StringSave ("note");
      gbqual->val = StringSave (siteString [sitetype]);
      gbqual->next = sfp->qual;
      sfp->qual = gbqual;
    }
  }
  return TRUE;
}

static Boolean ConvertProtToRegion 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  ProtRefPtr prp;
  ValNodePtr vnp;
  CharPtr    str;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_PROT)
  {
    return FALSE;
  }
  prp = (ProtRefPtr) sfp->data.value.ptrvalue;
  if (NULL == prp)
  {
    return FALSE;
  }

  vnp = prp->name;
  if (vnp != NULL && vnp->next == NULL) {
    str = (CharPtr) vnp->data.ptrvalue;
    if (! StringHasNoText (str)) {
      vnp->data.ptrvalue = NULL;
      sfp->data.value.ptrvalue = ProtRefFree (prp);
      sfp->data.choice = SEQFEAT_REGION;
      sfp->data.value.ptrvalue = (Pointer) str;
    }
  }
  return TRUE;
}


static Boolean ConvertRegionToProt
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  return ConvertRegionToProtFunc (sfp, featdef_to);
}


static Boolean ConvertRNAToNcRNA
(SeqFeatPtr sfp)
{
  RnaRefPtr  rrp;
  tRNAPtr    trp;
  RNAGenPtr  rgp = NULL;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA) return FALSE;
  
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (NULL == rrp) {
    rrp = RnaRefNew ();
    sfp->data.value.ptrvalue = rrp;
  }

  if (rrp->ext.choice == 2) {
    trp = (tRNAPtr) rrp->ext.value.ptrvalue;
    if (trp != NULL) {
      trp->anticodon = SeqLocFree (trp->anticodon);
      trp = MemFree (trp);
      rrp->ext.value.ptrvalue = trp;
    }
    rrp->ext.choice = 0;
  } 

  /* might be converting from other ncRNA, tmRNA, or misc_RNA */
  if (rrp->ext.choice == 1) {
    rgp = RNAGenNew ();
    rgp->product = rrp->ext.value.ptrvalue;
    rrp->ext.choice = 3;
    rrp->ext.value.ptrvalue = rgp;
  } else if (rrp->ext.choice == 0) {
    rgp = RNAGenNew ();
    rrp->ext.choice = 3;
    rrp->ext.value.ptrvalue = rgp;
  } else if (rrp->ext.choice == 3) {
    rgp = rrp->ext.value.ptrvalue;
    if (rgp == NULL) {
      rgp = RNAGenNew ();
      rrp->ext.value.ptrvalue = rgp;
    }
  }

  if (rgp != NULL) {
    if (rrp->type == 5) {
      rgp->_class = StringSave ("snRNA");
    } else if (rrp->type == 6) {
      rgp->_class = StringSave ("scRNA");
    } else if (rrp->type == 7) {
      rgp->_class = StringSave ("snoRNA");
    }
  }

  rrp->type = 8;
  return TRUE;
}


static void ConvertFromncRNAtoMiscRNA (SeqFeatPtr sfp)
{
  RnaRefPtr rrp;
  RNAGenPtr rgp;
  CharPtr tmp;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA) return;
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (rrp == NULL || rrp->ext.choice != 3) {
    return;
  }

  rgp = (RNAGenPtr) rrp->ext.value.ptrvalue;
  if (rgp == NULL) {
    return;
  }

  /* if no product but has comment, promote comment to product */
  if (rgp->product == NULL && !StringHasNoText (sfp->comment)) {
    rgp->product = sfp->comment;
    sfp->comment = NULL;
  }
  
  /* move ncRNA_class elsewhere */
  if (!StringHasNoText (rgp->_class)) {
    if (StringCmp (rgp->_class, "other") == 0) {
      rgp->_class = MemFree (rgp->_class);
    } else if (rgp->product == NULL) {
      rgp->product = rgp->_class;
      rgp->_class = NULL;
    } else {
      if (StringHasNoText (sfp->comment)) {
        sfp->comment = MemFree (sfp->comment);
        sfp->comment = StringSave (rgp->_class);
      } else {
        tmp = (CharPtr) MemNew (sizeof (Char) * (StringLen (sfp->comment) + StringLen (rgp->_class) + 3));
        sprintf (tmp, "%s; %s", sfp->comment, rgp->_class);
        sfp->comment = MemFree (sfp->comment);
        sfp->comment = tmp;
      }
      rgp->_class = MemFree (rgp->_class);
    }
  }    
}


static Boolean ConvertRNAToRNA 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  RnaRefPtr  rrp;
  Boolean    from_misc = FALSE, to_misc = FALSE;
  GBQualPtr  gbq, gbq_p = NULL;

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (NULL == rrp)
    return FALSE;

  if (rrp->type == 255) {
    from_misc = TRUE;
  }

  switch (featdef_to) {
    case FEATDEF_preRNA :
      rrp->type = 1;
      break;
    case FEATDEF_mRNA :
      rrp->type = 2;
      break;
    case FEATDEF_tRNA :
      rrp->type = 3;
      break;
    case FEATDEF_rRNA :
      rrp->type = 4;
      break;
    case FEATDEF_snRNA :
      rrp->type = 5;
      to_misc = TRUE;
      break;
    case FEATDEF_scRNA :
      rrp->type = 6;
      to_misc = TRUE;
      break;
    case FEATDEF_snoRNA :
      rrp->type = 7;
      to_misc = TRUE;
      break;
    case FEATDEF_ncRNA :
      return ConvertRNAToNcRNA (sfp);
      break;
    case FEATDEF_otherRNA :
      rrp->type = 255;
      ConvertFromncRNAtoMiscRNA (sfp);
      break;
    default:
      break;
  }
  if (from_misc && !to_misc && rrp->ext.choice == 1 && StringCmp (rrp->ext.value.ptrvalue, "misc_RNA") == 0) {
    /* move product qual to string */
    gbq = sfp->qual;
    while (gbq != NULL && StringCmp (gbq->qual, "product") != 0) {
      gbq_p = gbq;
      gbq = gbq->next;
    }
    if (gbq == NULL) {
      rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
      rrp->ext.choice = 0;
    } else {
      rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
      rrp->ext.value.ptrvalue = gbq->val;
      gbq->val = NULL;
      if (gbq_p == NULL) {
        sfp->qual = gbq->next;
      } else {
        gbq_p->next = gbq->next;
      }
      gbq->next = NULL;
      gbq = GBQualFree (gbq);
    }
  }
  return TRUE;
}

static Boolean ConvertncRNAToMiscBinding
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  RnaRefPtr  rrp;
  RNAGenPtr  rgp;
  Boolean    asked = TRUE, replace = FALSE, use_semicolon = TRUE;
  ImpFeatPtr ifp;

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (NULL == rrp)
    return FALSE;

  if (rrp->ext.choice == 1) {
    /* move product to note */
    AppendOrReplaceString (&(sfp->comment), rrp->ext.value.ptrvalue, &asked, &replace, &use_semicolon);
  } else if (rrp->ext.choice == 3 && (rgp = (RNAGenPtr) rrp->ext.value.ptrvalue) != NULL
             && !StringHasNoText (rgp->product)) {
    AppendOrReplaceString (&(sfp->comment), rgp->product, &asked, &replace, &use_semicolon);
  }
  rrp = RnaRefFree (rrp);
  sfp->data.choice = SEQFEAT_IMP;
  ifp = ImpFeatNew ();
  ifp->key = StringSave ("misc_binding");
  sfp->data.value.ptrvalue = ifp;
  sfp->idx.subtype = 0;

  return TRUE;
}

static Boolean ConvertProtToProt 
(SeqFeatPtr sfp,
 Uint2      featdef_to,
 Pointer    extradata)
{
  return ConvertProtToProtFunc (sfp, featdef_to);
}


static Boolean ConvertImpToProt (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertImpToProtFunc (sfp, featdef_to);
}


static Boolean ConvertProtToImp (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertProtToImpFunc (sfp, featdef_to);
}


static Boolean ConvertBioSrcToRepeatRegionEx (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertBioSrcToRepeatRegion (sfp, featdef_to);
}


static Boolean ConvertCDSToMatPeptide (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return AutoConvertCDSToMiscFeat (sfp, extradata == NULL ? TRUE : !(*((BoolPtr) extradata)));
}


static Boolean ConvertMiscFeatureToCDSFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertMiscFeatToCodingRegion (sfp);
}


static Boolean ConvertmRNAToCodingRegionFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertmRNAToCodingRegion (sfp);
}


static Boolean ConverttRNAToGeneFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConverttRNAToGene (sfp);
}


static Boolean ConvertMiscFeatureToGeneFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertMiscFeatToGene (sfp);
}


extern EnumFieldAssoc  enum_bond_alist [];
extern EnumFieldAssoc  enum_site_alist [];


static GbFeatName EditQualifierList[] = {
 {"allele", Class_text}, 
 {"anticodon", Class_pos_aa},
 {"bound_moiety", Class_text},
 {"chromosome", Class_text},
 {"citation", Class_bracket_int},
 {"codon", Class_seq_aa},
 {"codon_start", Class_int_or}, 
 {"compare", Class_text },
 {"cons_splice", Class_site},
 {"db_xref", Class_text},
 {"direction", Class_L_R_B}, 
 {"EC_number", Class_ecnum},
 {"evidence", Class_exper}, 
 {"exception", Class_text},
 {"experiment", Class_text},
 {"frequency", Class_text}, 
 {"function", Class_text},
 {"gene", Class_text}, 
 {"gdb_xref", Class_text},
 {"label", Class_token},
 {"map", Class_text},
 {"mobile_element", Class_text}, 
 {"mod_base", Class_token}, {"note", Class_note},
 {"number", Class_number}, 
#ifdef INTERNAL_NCBI_SEQUIN
 {"old_locus_tag", Class_text},
#endif
 {"operon", Class_text},
 {"organism", Class_text},
 {"partial", Class_none}, {"PCR_conditions", Class_text},
 {"phenotype", Class_text},
 {"plasmid", Class_text}, {"product", Class_text},
 {"pseudo", Class_none},
 {"rearranged", Class_none}, { "replace", Class_text},
 {"rpt_family", Class_text}, {"rpt_type", Class_rpt},
 {"rpt_unit", Class_token}, {"rpt_unit_seq", Class_token}, {"rpt_unit_range", Class_token},
 {"satellite", Class_text},
 {"sequenced_mol", Class_text},
 {"standard_name", Class_text},
 {"translation", Class_text}, {"transl_except", Class_pos_aa},
 {"transl_table", Class_int},
 {"usedin", Class_token},
 {"focus", Class_none},
 {"protein_id", Class_text},
 {"organelle", Class_text}, {"transcript_id", Class_text},
 {"transgenic", Class_none}, {"environmental_sample", Class_none},
 {"locus_tag", Class_text}, {"mol_type", Class_text},
 {"segment", Class_text}
};

const Int4 NumEditQualifiers = sizeof (EditQualifierList) / sizeof (GbFeatName);
#define QUAL_EVIDENCE 13
#define QUAL_EXPERIMENT 15


static ENUM_ALIST(biosource_genome_alistX)
  {" ",                    0},
  {"Apicoplast",          16},
  {"Chloroplast",          2},
  {"Chromatophore",       22},
  {"Chromoplast",          3},
  {"Chromosome",          21},
  {"Cyanelle",            12},
  {"Endogenous-virus",    19},
  {"Extrachromosomal",     8},
  {"Genomic",              1},
  {"Hydrogenosome",       20},
  {"Insertion Sequence",  11},
  {"Kinetoplast",          4},
  {"Leucoplast",          17},
  {"Macronuclear",         7},
  {"Mitochondrion",        5},
  {"Nucleomorph",         15},
  {"Plasmid",              9},
  {"Plastid",              6},
  {"Proplastid",          18},
  {"Proviral",            13},
  {"Transposon",          10},
END_ENUM_ALIST

static ENUM_ALIST(orgref_textfield_alist)
  {" ",                    0},
  {"Scientific Name",      1},
  {"Common Name",          2},
  {"Lineage",              3},
  {"Division",             4},
END_ENUM_ALIST

typedef struct sourceformdata {
  FEATURE_FORM_BLOCK

  Int2           type;
  ButtoN         applyToParts;
  TexT           onlyThisPart;
  GrouP          sourceGroup;
  GrouP          modGrp;
  GrouP          genGrp;
  GrouP          refGrp;
  GrouP          txtGrp;
  GrouP          originGrp;
  PopuP          frommod;
  PopuP          tomod;
  PopuP          fromgen;
  PopuP          togen;
  PopuP          fromref;
  PopuP          toref;
  PopuP          toorigin;
  PopuP          fromorigin;
  TexT           findthis;
  TexT           replacewith;
  GrouP          applyChoiceGrp;  /* This is the group for the radio buttons for the
                                   * constraint on which biosources to operate on.
                                   */
  PopuP          qual_to_have;    /* This is the control that indicates which qualifier
                                   * should be present to operate on a biosource.
                                   */
  TexT           text_to_have;    /* This is the control that indicates the text that
                                   * should be present in the biosource to be operated on.
                                   */
  Int4           applyChoiceVal;  /* This is the value of the applyChoiceGrp choice. */
  CharPtr        text_to_have_str;/* This is the contents of the text_to_have box. */
  Int4           qual_to_have_val;/* This is the subtype for the qual to have. */

  Int2           choice;
  Int2           fromval;
  Int2           toval;
  Int2           onlythis;
  CharPtr        findStr;
  CharPtr        replaceStr;

  Boolean        replaceOldAsked;
  Boolean        doReplaceAll;
  Boolean        use_semicolon;
  ButtoN         leaveDlgUp;
} SourceFormData, PNTR SourceFormPtr;

Uint2 mod_widths [] = {
  0, 25
};

Uint2 mod_types [] = {
  TAGLIST_POPUP, TAGLIST_TEXT
};


extern Uint1 FindTypeForModNameText (CharPtr cp)
{
  Uint1 subtype;

  subtype = EquivalentSubSourceEx (cp, TRUE);
  if (subtype == 0) {
    subtype = EquivalentOrgMod (cp);
    if (subtype > 200) {
      /* function that calls this expects less than 100 vals for orgmod */
      /* need to adjust for old-lineage and old-name */
      subtype -= 200;
    }
  } else {
    /* function that calls this uses >100 to decide if subsource */
    subtype += 100;
  }
  if (subtype == 0) {
    subtype = SUBSRC_other;
  }
  return subtype;
}


extern void AppendOrReplaceString (
  CharPtr PNTR string_loc,
  CharPtr new_value,
  Boolean PNTR asked_question,
  Boolean PNTR do_replace,
  Boolean PNTR use_semicolon
)
{
  MsgAnswer ans;
  CharPtr   tmp_value, tmp_new_value;

  if (string_loc == NULL
    || new_value == NULL
    || asked_question == NULL
    || do_replace == NULL
    || use_semicolon == NULL)
  {
    return;
  }

  if (! *asked_question && !StringHasNoText (*string_loc))
  {
    *asked_question = TRUE;
    ArrowCursor ();
    ans = Message (MSG_YN, "Do you wish to overwrite existing content?");
    *do_replace = (Boolean) (ans == ANS_YES);
    if (! *use_semicolon)
    {
      if (! *do_replace)
      {
        ans = Message (MSG_YN, "Separate items with semicolon?");
        *use_semicolon = (Boolean) (ans == ANS_YES);
      }
    }
    WatchCursor ();
    Update ();
  }
  if (*do_replace || StringHasNoText (*string_loc))
  {
    MemFree (*string_loc);
    *string_loc = StringSave (new_value);
  }
  else
  {
    tmp_value = MemNew (StringLen (*string_loc) + StringLen ( new_value) + 3);
    if (tmp_value == NULL) return;
    StringCpy (tmp_value, *string_loc);
    TrimSpacesAroundString (tmp_value);
    if (*use_semicolon)
    {
      StringCat (tmp_value, "; ");
    }
    else
    {
      StringCat (tmp_value, " ");
    }
    tmp_new_value = StringSave (new_value);
    TrimSpacesAroundString (tmp_new_value);
    StringCat (tmp_value, tmp_new_value);
    MemFree (tmp_new_value);
    MemFree (*string_loc);
    *string_loc = tmp_value;
  }
}

static ENUM_ALIST(origin_alist)
{" ",               ORG_DEFAULT    },
{"Natural",         ORG_NATURAL    },
{"Natural Mutant",  ORG_NATMUT     },
{"Mutant",          ORG_MUT        },
{"Artificial",      ORG_ARTIFICIAL },
{"Synthetic",       ORG_SYNTHETIC  },
{"Other",           ORG_OTHER      },
END_ENUM_ALIST


typedef struct setsample
{
  GetFeatureFieldString    fieldstring_func;
  GetDescriptorFieldString descrstring_func;
  Uint2                    entityID;
  ValNodePtr               field_list;
  FreeValNodeProc          free_vn_proc;
  CopyValNodeDataProc      copy_vn_proc;
  MatchValNodeProc         match_vn_proc;
  NameFromValNodeProc      label_vn_proc;  
  
  FilterSetPtr             fsp;
} SetSampleData, PNTR SetSamplePtr;

static ValNodePtr IntValNodeCopy (ValNodePtr vnp)
{
  ValNodePtr vnp_new;
  
  if (vnp == NULL)
  {
    return NULL;
  }
  vnp_new = ValNodeNew (NULL);
  if (vnp_new != NULL)
  {
    vnp_new->choice = vnp->choice;
    vnp_new->data.intvalue = vnp->data.intvalue;
  }
  return vnp_new;
}

static Boolean IntValNodeMatch (ValNodePtr vnp1, ValNodePtr vnp2)
{
  if (vnp1 == NULL && vnp2 == NULL)
  {
    return TRUE;
  }
  else if (vnp1 == NULL || vnp2 == NULL)
  {
    return FALSE;
  }
  else if (vnp1->data.intvalue == vnp2->data.intvalue)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

static void LocationConstraintXClearText (LocationConstraintXPtr lcp)
{
  if (lcp != NULL)
  {
    lcp->left = -1;
    lcp->right = -1;
  }
}

static LocationConstraintXPtr LocationConstraintXFree (LocationConstraintXPtr lcp)
{
  lcp = MemFree (lcp);
  return lcp;
}

static LocationConstraintXPtr LocationConstraintXCopy (LocationConstraintXPtr lcp)
{
  LocationConstraintXPtr lcp_new;
  if (lcp == NULL)
  {
    return NULL;
  }
  lcp_new = (LocationConstraintXPtr) MemNew (sizeof (LocationConstraintXData));
  if (lcp_new != NULL)
  {
    lcp_new->left = lcp->left;
    lcp_new->right = lcp->right;
    lcp_new->interval_end_choice = lcp->interval_end_choice;
    lcp_new->match_choice = lcp->match_choice;
    lcp_new->strand = lcp->strand;
    lcp_new->sequence_type = lcp->sequence_type;
  }
  return lcp_new;
}

extern StringConstraintXPtr StringConstraintXFree (StringConstraintXPtr scp)
{
  if (scp == NULL) return NULL;
  scp->match_text = MemFree (scp->match_text);
  scp = MemFree (scp);
  return scp;  
}

static void StringConstraintClearText (StringConstraintXPtr scp)
{
  if (scp != NULL)
  {
    scp->match_text = MemFree (scp->match_text);
  }
}

static StringConstraintXPtr StringConstraintCopy (StringConstraintXPtr scp)
{
  StringConstraintXPtr scp_new;
  if (scp == NULL)
  {
    return NULL;
  }
  
  scp_new = (StringConstraintXPtr) MemNew (sizeof (StringConstraintData));
  if (scp_new != NULL)
  {
    scp_new->match_text = StringSave (scp->match_text);
    scp_new->match_location = scp->match_location;
    scp_new->insensitive = scp->insensitive;
    scp_new->whole_word = scp->whole_word;
    scp_new->not_present = scp->not_present;   
  }
  return scp_new;
}

extern ValNodePtr ValNodeFuncFree (ValNodePtr vnp, FreeValNodeProc free_vn_proc)
{
  if (vnp == NULL)
  {
    return NULL;
  }
  vnp->next = ValNodeFuncFree (vnp->next, free_vn_proc);
  if (free_vn_proc != NULL)
  {
    free_vn_proc (vnp);
  }
  ValNodeFree (vnp);
  return NULL;
}

extern ChoiceConstraintPtr ChoiceConstraintFree (ChoiceConstraintPtr scp)
{
  if (scp == NULL) return NULL;
  scp->qual_choice = ValNodeFuncFree (scp->qual_choice, scp->free_vn_proc);
  scp->qual_choice_match = ValNodeFuncFree (scp->qual_choice, scp->free_vn_proc);
  scp->string_constraint = StringConstraintXFree (scp->string_constraint);
  scp->pseudo_constraint = MemFree (scp->pseudo_constraint);
  scp = MemFree (scp);
  return scp;
}

static ValNodePtr QualChoiceCopy (ValNodePtr vnp, CopyValNodeDataProc copy_vn_proc)
{
  ValNodePtr new_list = NULL;
  
  if (vnp == NULL || copy_vn_proc == NULL)
  {
    return NULL;
  }
  
  new_list = copy_vn_proc (vnp);
  new_list->next = QualChoiceCopy (vnp->next, copy_vn_proc);
  return new_list;
}

static ChoiceConstraintPtr ChoiceConstraintCopy (ChoiceConstraintPtr ccp)
{
  ChoiceConstraintPtr ccp_new;
  if (ccp == NULL || ccp->copy_vn_proc == NULL)
  {
    return NULL;
  }
  ccp_new = (ChoiceConstraintPtr) MemNew (sizeof (ChoiceConstraintData));
  if (ccp_new != NULL)
  {
    ccp_new->constraint_type = ccp->constraint_type;
    ccp_new->qual_choice = QualChoiceCopy (ccp->qual_choice, ccp->copy_vn_proc);
    ccp_new->qual_choice_match = QualChoiceCopy (ccp->qual_choice_match, ccp->copy_vn_proc);
    ccp_new->string_constraint = StringConstraintCopy (ccp->string_constraint);
    ccp_new->copy_vn_proc = ccp->copy_vn_proc;
    ccp_new->free_vn_proc = ccp->free_vn_proc;
  }
  return ccp_new;
}

static void ChoiceConstraintClearText (ChoiceConstraintPtr scp)
{
  if (scp != NULL)
  {
    StringConstraintClearText (scp->string_constraint);
  }
}


extern void FilterSetClearText (FilterSetPtr fsp)
{
  if (fsp != NULL)
  {
    StringConstraintClearText (fsp->scp);
    ChoiceConstraintClearText (fsp->ccp);
    LocationConstraintXClearText (fsp->lcp);
    StringConstraintClearText (fsp->id_list);
  }
}

extern FilterSetPtr FilterSetNew (void)
{
  FilterSetPtr fsp_new;
  fsp_new = (FilterSetPtr) MemNew (sizeof (FilterSetData));
  if (fsp_new != NULL)
  {
    fsp_new->scp = NULL;
    fsp_new->ccp = NULL;
    fsp_new->lcp = NULL;
    fsp_new->cgp = NULL;
    fsp_new->id_list = NULL;
  }
  return fsp_new;
}

extern FilterSetPtr FilterSetFree (FilterSetPtr fsp)
{
  if (fsp != NULL)
  {
    fsp->scp = StringConstraintXFree (fsp->scp);
    fsp->ccp = ChoiceConstraintFree (fsp->ccp);
    fsp->lcp = LocationConstraintXFree (fsp->lcp);
    fsp->cgp = ChoiceConstraintFree (fsp->cgp);
    fsp->id_list = StringConstraintXFree (fsp->id_list);
    fsp = MemFree (fsp);
  }
  return fsp;
}

static FilterSetPtr FilterSetCopy (FilterSetPtr fsp)
{
  FilterSetPtr fsp_new;
  
  if (fsp == NULL)
  {
    return NULL;
  }
  fsp_new = (FilterSetPtr) MemNew (sizeof (FilterSetData));
  if (fsp_new != NULL)
  {
    fsp_new->scp = StringConstraintCopy (fsp->scp);
    fsp_new->ccp = ChoiceConstraintCopy (fsp->ccp);
    fsp_new->lcp = LocationConstraintXCopy (fsp->lcp);
    fsp_new->cgp = ChoiceConstraintCopy (fsp->cgp);
    fsp_new->id_list = StringConstraintCopy (fsp->id_list);
  }
  return fsp_new;
}


static SetSamplePtr SetSampleFree (SetSamplePtr ssp)
{
  ValNodePtr vnp;
  
  if (ssp == NULL)
  {
    return NULL;
  }
  if (ssp->free_vn_proc != NULL)
  {
    for (vnp = ssp->field_list; vnp != NULL; vnp = vnp->next)
    {
      (ssp->free_vn_proc)(vnp);
    }
  }
  ssp->field_list = ValNodeFree (ssp->field_list);
   
  ssp = MemFree (ssp);
  return ssp;
}

static SetSamplePtr SetSampleCopy (SetSamplePtr ssp)
{
  SetSamplePtr ssp_new;
  ValNodePtr   vnp_old, vnp_new, vnp_last = NULL;
  
  if (ssp == NULL)
  {
    return NULL;
  }
  ssp_new = (SetSamplePtr) MemNew (sizeof (SetSampleData));
  if (ssp_new != NULL)
  {
    ssp_new->fieldstring_func = ssp->fieldstring_func;
    ssp_new->descrstring_func = ssp->descrstring_func;
    ssp_new->free_vn_proc = ssp->free_vn_proc;
    ssp_new->copy_vn_proc = ssp->copy_vn_proc;
    ssp_new->match_vn_proc = ssp->match_vn_proc;
    ssp_new->label_vn_proc = ssp->label_vn_proc;  
    
    ssp_new->entityID = ssp->entityID;
    ssp_new->field_list = NULL;
    for (vnp_old = ssp->field_list; vnp_old != NULL; vnp_old = vnp_old->next)
    {
      vnp_new = (ssp_new->copy_vn_proc)(vnp_old);
      if (vnp_last == NULL)
      {
        ssp_new->field_list = vnp_new;
      }
      else
      {
        vnp_last->next = vnp_new;
      }
      vnp_last = vnp_new;
    }
    ssp_new->fsp = FilterSetCopy (ssp->fsp);
  }
  return ssp_new;
}

#define PARSE_FIELD_DEFLINE       1
#define PARSE_FIELD_BIOSRC_STRING 2
#define PARSE_FIELD_SOURCE_QUAL   3
#define PARSE_FIELD_GENE_FIELD    4
#define PARSE_FIELD_RNA_FIELD    5
#define PARSE_FIELD_CDS_COMMENT   6
#define PARSE_FIELD_PROTEIN_FIELD 7
#define PARSE_FIELD_IMPORT_QUAL   8
#define PARSE_FIELD_FEATURE_NOTE  9
#define PARSE_FIELD_COMMENT_DESC  10
#define PARSE_FIELD_DBXREF        11

#define MAX_PARSE_FIELD_TYPE   11
#define SEARCH_FIELD_PUBLICATION  12

#define PARSE_FIELD_FIRST_FEATURE 4
#define PARSE_FIELD_LAST_FEATURE  9

extern Boolean DoesLocationMatchConstraint (SeqLocPtr slp, LocationConstraintXPtr lcp);

typedef struct acceptpolicy 
{
  Boolean leave_dlg_up;
} AcceptPolicyData, PNTR AcceptPolicyPtr;

typedef struct acceptcanceldialog 
{
  DIALOG_MESSAGE_BLOCK
  Nlm_AcceptActnProc    accept_actn;
  Nlm_CancelActnProc    cancel_actn;
  Nlm_ClearActnProc     clear_actn;
  Nlm_ClearTextActnProc clear_text_actn;
  WindoW                w;
  
  ButtoN                accept_btn;
  
  ButtoN                leave_dlg_up_btn;
} AcceptCancelDialogData, PNTR AcceptCancelDialogPtr;

static void AcceptCancelDialogAccept (ButtoN b)
{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (b);
  if (acdp == NULL) return;
  
  if (acdp->accept_actn != NULL)
  {
    if (!((acdp->accept_actn) (acdp->userdata)))
    {
      return;
    }
  }
  if (! GetStatus (acdp->leave_dlg_up_btn))
  {
    Remove (acdp->w);
  }
}

static void AcceptCancelDialogCancel (ButtoN b)
{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (b);
  if (acdp == NULL) return;
  
  if (acdp->cancel_actn != NULL)
  {
    (acdp->cancel_actn) (acdp->userdata);    
  }
  Remove (acdp->w);
}

static void AcceptCancelDialogClear (ButtoN b)
{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (b);
  if (acdp == NULL || acdp->clear_actn == NULL) return;
  
  (acdp->clear_actn) (acdp->userdata);
}

static void AcceptCancelDialogClearText (ButtoN b)
{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (b);
  if (acdp == NULL || acdp->clear_text_actn == NULL) return;
  
  (acdp->clear_text_actn) (acdp->userdata);
}

static void AcceptPolicyToAcceptCancelDialog (DialoG d, Pointer data)

{
  AcceptCancelDialogPtr   acdp;
  AcceptPolicyPtr         app;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (d);
  app = (AcceptPolicyPtr) data;
  if (acdp == NULL || app == NULL)
  {
    return;
  }

  SetStatus (acdp->leave_dlg_up_btn, app->leave_dlg_up);  
}

static Pointer AcceptCancelDialogToAcceptPolicy (DialoG d)
{
  AcceptCancelDialogPtr acdp;
  AcceptPolicyPtr       app;
  
  acdp = (AcceptCancelDialogPtr) GetObjectExtra (d);
  if (acdp == NULL) 
  {
    return NULL;
  }
  
  app = (AcceptPolicyPtr) MemNew (sizeof (AcceptPolicyData));
  if (app != NULL)
  {
    app->leave_dlg_up = GetStatus (acdp->leave_dlg_up_btn);
  }
  return app;
}

static void AcceptCancelMessage (DialoG d, Int2 mssg)

{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (d);
  if (acdp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset accept policy */
        SetStatus (acdp->leave_dlg_up_btn, FALSE);
        break;
      case VIB_MSG_ENTER :
        Select (acdp->accept_btn);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestAcceptCancelDialog (DialoG d)

{
  return NULL;
}

extern DialoG AcceptCancelDialog 
(GrouP                 parent,
 Nlm_AcceptActnProc    accept_actn,
 Nlm_CancelActnProc    cancel_actn,
 Nlm_ClearActnProc     clear_actn,
 Nlm_ClearTextActnProc clear_text_actn,
 Pointer               userdata,
 WindoW                w)
{
  AcceptCancelDialogPtr acdp;
  GrouP                 grp, policy_grp = NULL, other_grp;
  ButtoN                b;
  
  acdp = (AcceptCancelDialogPtr) MemNew (sizeof (AcceptCancelDialogData));
  grp = HiddenGroup (parent, -1, 0, NULL);
  SetObjectExtra (grp, acdp, StdCleanupExtraProc);
  SetGroupSpacing (grp, 10, 10);

  acdp->dialog = (DialoG) grp;
  acdp->todialog = AcceptPolicyToAcceptCancelDialog;
  acdp->fromdialog = AcceptCancelDialogToAcceptPolicy;
  acdp->dialogmessage = AcceptCancelMessage;
  acdp->testdialog = TestAcceptCancelDialog;

  acdp->accept_actn             = accept_actn;
  acdp->cancel_actn             = cancel_actn;
  acdp->clear_actn              = clear_actn;
  acdp->clear_text_actn         = clear_text_actn;
  acdp->userdata                = userdata;
  acdp->w                       = w;

  policy_grp = HiddenGroup (grp, 3, 0, NULL);
  SetGroupSpacing (policy_grp, 10, 10);
  
  b = PushButton (policy_grp, "Clear", AcceptCancelDialogClear);
  SetObjectExtra (b, acdp, NULL);
 
  b = PushButton (policy_grp, "Clear Text", AcceptCancelDialogClearText);
  SetObjectExtra (b, acdp, NULL);
  
  acdp->leave_dlg_up_btn = CheckBox (policy_grp, "Leave Dialog Up", NULL);
  
  other_grp = HiddenGroup (grp, 5, 0, NULL);
  SetGroupSpacing (other_grp, 10, 10);
  acdp->accept_btn = PushButton (other_grp, "Accept", AcceptCancelDialogAccept);
  SetObjectExtra (acdp->accept_btn, acdp, NULL);
  
  b = PushButton (other_grp, "Cancel", AcceptCancelDialogCancel);
  SetObjectExtra (b, acdp, NULL);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) policy_grp, (HANDLE) other_grp, NULL);
  
  return (DialoG) grp;
}


extern void EnableAcceptCancelDialogAccept (DialoG d)
{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (d);

  if (acdp == NULL) return;
  
  InvalObject (acdp->accept_btn);
  SafeEnable (acdp->accept_btn);
  Update ();
}

extern void DisableAcceptCancelDialogAccept (DialoG d)
{
  AcceptCancelDialogPtr acdp;

  acdp = (AcceptCancelDialogPtr) GetObjectExtra (d);

  if (acdp == NULL) return;
  
  InvalObject (acdp->accept_btn);
  SafeDisable (acdp->accept_btn);
  Update ();
}





static ValNodePtr ValNodeAppend (ValNodePtr list, ValNodePtr second_list)
{
  ValNodePtr last_vnp;
  
  if (list == NULL)
  {
    list = second_list;
  }
  else if (second_list != NULL)
  {
    last_vnp = list;
    while (last_vnp != NULL && last_vnp->next != NULL)
    {
      last_vnp = last_vnp->next;
    }
    last_vnp->next = second_list;
  }
  return list;
}

static void FindFeatureTypeCallback (SeqFeatPtr sfp, Pointer userdata)
{
  BoolPtr type_list;
  
  if (sfp == NULL || userdata == NULL) return;
  
  type_list = (BoolPtr) userdata;
  type_list[sfp->idx.subtype] = TRUE;
}


static void AddFeatureToList (ValNodePtr PNTR list, FeatDefPtr curr)
{
  Char        str [256];

  if (list == NULL || curr == NULL) return;
  
  if (curr->featdef_key == FEATDEF_PUB)
  {
    StringNCpy_0 (str, curr->typelabel, sizeof (str) - 15);
    StringCat (str, " (Publication)");
    ValNodeAddPointer (list, curr->featdef_key, StringSave (str));
  }
  else if (curr->featdef_key == FEATDEF_BIOSRC)
  {
    ValNodeAddPointer (list, FEATDEF_BIOSRC, StringSave ("BioSource"));
  }
  else if (curr->featdef_key == FEATDEF_otherRNA)
  {
    ValNodeAddPointer (list, FEATDEF_otherRNA, StringSave ("misc_RNA"));
  }
  else if (curr->featdef_key == FEATDEF_misc_RNA ||
           curr->featdef_key == FEATDEF_precursor_RNA ||
           curr->featdef_key == FEATDEF_mat_peptide ||
           curr->featdef_key == FEATDEF_sig_peptide ||
           curr->featdef_key == FEATDEF_transit_peptide ||
           curr->featdef_key == FEATDEF_Imp_CDS)
  {
    StringNCpy_0 (str, curr->typelabel, sizeof (str) - 10);
    StringCat (str, "_imp");
    ValNodeAddPointer (list, curr->featdef_key, StringSave (str));
  }
  else
  {
    ValNodeAddPointer (list, curr->featdef_key, StringSave (curr->typelabel));
  }
}

extern ValNodePtr BuildFeatureDialogList (Boolean list_most_used_first, SeqEntryPtr sep)
{
  ValNodePtr  feature_choice_list = NULL;
  ValNodePtr  import_feature_list = NULL;
  ValNodePtr  least_liked_feature_list = NULL;
  ValNodePtr  most_used_feature_list = NULL;
  ValNodePtr  unused_list = NULL;
  FeatDefPtr  curr;
  Uint1       key;
  CharPtr     label = NULL;
  Boolean     feature_type_list[256];
  Boolean     only_most_used = TRUE;
  Int4        i;

  if (sep == NULL) {
      for (i = 0; i < 255; i++) {
          feature_type_list[i] = TRUE;
      }
  } else {
      for (i = 0; i < 255; i++) {
          feature_type_list[i] = FALSE;
      }
      VisitFeaturesInSep (sep, feature_type_list, FindFeatureTypeCallback);
  }

  curr = FeatDefFindNext (NULL, &key, &label, FEATDEF_ANY, TRUE);
  while (curr != NULL) {
    if (key == FEATDEF_BAD
        || key == FEATDEF_Imp_CDS
        || key == FEATDEF_source
        || key == FEATDEF_ORG
        || IsUnwantedFeatureType (key)
        || IsRegulatorySubtype (key))
    {
      curr = FeatDefFindNext (curr, &key, &label, FEATDEF_ANY, TRUE);
      continue;
    }
    else if (!feature_type_list[key]) 
    {
      AddFeatureToList (&unused_list, curr);
      curr = FeatDefFindNext (curr, &key, &label, FEATDEF_ANY, TRUE);
      continue;
    }
    
    if (curr->featdef_key == FEATDEF_otherRNA) {
      AddFeatureToList (&feature_choice_list, curr);
      if (list_most_used_first) {
        AddFeatureToList (&most_used_feature_list, curr);
      }
    }
    else if (curr->featdef_key == FEATDEF_misc_RNA ||
            curr->featdef_key == FEATDEF_precursor_RNA ||
            curr->featdef_key == FEATDEF_mat_peptide ||
            curr->featdef_key == FEATDEF_sig_peptide ||
            curr->featdef_key == FEATDEF_transit_peptide ||
            curr->featdef_key == FEATDEF_Imp_CDS)
    {
      AddFeatureToList (&import_feature_list, curr);
    }
    else if (curr->featdef_key == FEATDEF_TXINIT)
    {
      AddFeatureToList (&least_liked_feature_list, curr);
    }
    else
    {
      ValNodeAddPointer (&feature_choice_list, curr->featdef_key, StringSave (curr->typelabel));
    }
    
    if (list_most_used_first)
    {
      /* also build most used list */
      if (curr->featdef_key == FEATDEF_CDS
          || curr->featdef_key == FEATDEF_exon
          || curr->featdef_key == FEATDEF_GENE
          || curr->featdef_key == FEATDEF_intron
          || curr->featdef_key == FEATDEF_mRNA
          || curr->featdef_key == FEATDEF_rRNA
          || curr->featdef_key == FEATDEF_PROT)
      {
        ValNodeAddPointer (&most_used_feature_list, curr->featdef_key, 
                           StringSave (curr->typelabel));
      }
      else
      {
        only_most_used = FALSE;
      }
    }
    
    curr = FeatDefFindNext (curr, &key, &label, FEATDEF_ANY, TRUE);
  }

  most_used_feature_list = SortValNode (most_used_feature_list,
                                        CompareFeatureValNodeStrings);
  feature_choice_list = SortValNode (feature_choice_list,
                                     CompareFeatureValNodeStrings);
  import_feature_list = SortValNode (import_feature_list,
                                     CompareFeatureValNodeStrings);
  least_liked_feature_list = SortValNode (least_liked_feature_list,
                                          CompareFeatureValNodeStrings);
  unused_list = SortValNode (unused_list, CompareFeatureValNodeStrings);       

  if (most_used_feature_list != NULL && only_most_used) {
    feature_choice_list = ValNodeFreeData (feature_choice_list);
    feature_choice_list = most_used_feature_list;
    least_liked_feature_list = ValNodeFreeData(least_liked_feature_list);
    import_feature_list = ValNodeFreeData(least_liked_feature_list);
  } else {
    feature_choice_list = ValNodeAppend (most_used_feature_list, feature_choice_list);
    feature_choice_list = ValNodeAppend (feature_choice_list, least_liked_feature_list);
    feature_choice_list = ValNodeAppend (feature_choice_list, import_feature_list);
  }
  feature_choice_list = ValNodeAppend (feature_choice_list, unused_list);
  
  return feature_choice_list;
}

static ValNodePtr BuildImportList (void)
{
  ValNodePtr  import_feature_list = NULL;
  FeatDefPtr  curr;
  Uint1       key;
  CharPtr     label = NULL;
  Char        str [256];

  curr = FeatDefFindNext (NULL, &key, &label, FEATDEF_ANY, TRUE);
  while (curr != NULL) {
    if (FindFeatFromFeatDefType (key) == SEQFEAT_IMP && !IsUnwantedFeatureType(key))
    {
      ValNodeAddPointer (&import_feature_list, curr->featdef_key, StringSave (str));
    }
    
    curr = FeatDefFindNext (curr, &key, &label, FEATDEF_ANY, TRUE);
  } 
  return import_feature_list; 
}

static void RemoveFeatureSelectionDuplicates (ValNodePtr orig_list)
{
  ValNodePtr vnp1, vnp2, prev_vnp, next_vnp;
  
  if (orig_list == NULL || orig_list->next == NULL)
  {
    return;
  }
  
  vnp1 = orig_list;
  while (vnp1 != NULL && vnp1->next != NULL)
  {
    vnp2 = vnp1->next;
    prev_vnp = vnp1;
    while (vnp2 != NULL)
    {
      next_vnp = vnp2->next;
      if (vnp2->choice == vnp1->choice)
      {
        prev_vnp->next = next_vnp;
        vnp2->next = NULL;
        vnp2 = ValNodeFreeData (vnp2);
      }
      else
      {
        prev_vnp = vnp2;
      }
      vnp2 = next_vnp;
    }
    vnp1 = vnp1->next;
  }
}

static ValNodePtr FeatureSelectionRemap (ValNodePtr orig_list)
{
  ValNodePtr vnp, vnp_next, prev_vnp, repl_vnp = NULL;
  
  if (orig_list == NULL)
  {
    return NULL;
  }
  
  vnp = orig_list;
  prev_vnp = NULL;
  while (vnp != NULL && repl_vnp == NULL)
  {
    vnp_next = vnp->next;
    /* replace FEATDEF_IMP with list of import features */
    if (vnp->choice == FEATDEF_IMP)
    {
      /* build replacement list */
      repl_vnp = BuildImportList ();
      
      ValNodeLink (&repl_vnp, vnp->next);
      
      if (prev_vnp == NULL)
      {
        orig_list = repl_vnp;
      }
      else
      {
        prev_vnp->next = repl_vnp;
      }

      vnp->next = NULL;
      ValNodeFreeData (vnp);
    }
    else
    {
      prev_vnp = vnp;
    }
    vnp = vnp_next;
  }
  
  RemoveFeatureSelectionDuplicates (orig_list);
  
  return orig_list; 
}

extern DialoG 
FeatureSelectionDialogEx 
(GrouP                    h,
 Boolean                  allow_multi,
 SeqEntryPtr              sep,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  DialoG      dlg;
  ValNodePtr  feature_choice_list, vnp, vnp_next, vnp_prev;
  Boolean     found_import = FALSE;

  feature_choice_list = BuildFeatureDialogList (TRUE, sep);
  
  if (!allow_multi)
  {
    /* remove Import option */
    vnp_prev = NULL;
    for (vnp = feature_choice_list; vnp != NULL && !found_import; vnp = vnp_next)
    {
      vnp_next = vnp->next;
      if (vnp->choice == FEATDEF_IMP)
      {
        if (vnp_prev == NULL)
        {
          feature_choice_list = vnp_next;
        }
        else
        {
          vnp_prev->next = vnp_next;
        }
        vnp->next = NULL;
        vnp = ValNodeFreeData (vnp);
        found_import = TRUE;
      }
      else
      {
        vnp_prev = vnp;
      }
    }
  }
  
  /* note - the ValNodeSelectionDialog will free the feature_choice_list when done */
  
  dlg = ValNodeSelectionDialogEx (h, feature_choice_list, TALL_SELECTION_LIST, 
                                ValNodeStringName,
                                ValNodeSimpleDataFree, ValNodeStringCopy,
                                ValNodeChoiceMatch, "feature list", 
                                change_notify, change_userdata, allow_multi, FALSE,
                                allow_multi ? FeatureSelectionRemap : NULL);
  return dlg;
}

extern DialoG 
FeatureSelectionDialog 
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureSelectionDialogEx(h, allow_multi, NULL, change_notify, change_userdata);
}

extern DialoG
DescriptorSelectionDialog
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  ValNodePtr descriptor_list = BuildDescriptorValNodeList ();
  return ValNodeSelectionDialogEx (h, descriptor_list, TALL_SELECTION_LIST,
                                ValNodeStringName,
                                ValNodeSimpleDataFree, ValNodeStringCopy,
                                ValNodeChoiceMatch, "descriptor list", 
                                change_notify, change_userdata, allow_multi, FALSE,
                                NULL);
}


extern CharPtr SourceQualValNodeName (ValNodePtr vnp)
{
  SourceQualDescPtr           sqdp;
  
  if (vnp == NULL || vnp->data.ptrvalue == NULL)
  {
    return NULL;
  }
  else if (vnp->choice == 0)
  {
    sqdp = (SourceQualDescPtr) vnp->data.ptrvalue;
    return StringSave (sqdp->name);
  }
  else
  {
    return StringSave (vnp->data.ptrvalue);
  }
}

extern ValNodePtr SourceQualValNodeDataCopy (ValNodePtr vnp)
{
  SourceQualDescPtr           sqdp, new_sqdp = NULL;
  ValNodePtr                  vnp_copy = NULL;
  
  if (vnp != NULL && vnp->data.ptrvalue != NULL)
  {
    if (vnp->choice == 0)
    {
      sqdp = (SourceQualDescPtr) vnp->data.ptrvalue;
      new_sqdp = (SourceQualDescPtr) MemNew (sizeof (SourceQualDescData));
      if (new_sqdp != NULL)
      {
        MemCpy (new_sqdp, sqdp, sizeof (SourceQualDescData));
      }
      ValNodeAddPointer (&vnp_copy, vnp->choice, new_sqdp);
    }
    else
    {
      ValNodeAddPointer (&vnp_copy, vnp->choice, StringSave (vnp->data.ptrvalue));
    }
  }
  return vnp_copy;
}


NLM_EXTERN ValNodePtr LIBCALL SourceQualValNodeCopy (ValNodePtr vnp)
{
  return SourceQualValNodeDataCopy(vnp);
}


extern Boolean SourceQualValNodeMatch (ValNodePtr vnp1, ValNodePtr vnp2)
{
  SourceQualDescPtr  sqdp1, sqdp2;
  Boolean            rval = FALSE;
  
  if (vnp1 == NULL || vnp2 == NULL
      || vnp1->data.ptrvalue == NULL || vnp2->data.ptrvalue == NULL)
  {
    rval = FALSE;
  }
  else if (vnp1->choice != vnp2->choice)
  {
    rval = FALSE;
  }
  else if (vnp1->choice == 0)
  {
    sqdp1 = (SourceQualDescPtr) vnp1->data.ptrvalue;
    sqdp2 = (SourceQualDescPtr) vnp2->data.ptrvalue;
  
    if (sqdp1->subtype == sqdp2->subtype
        && ((sqdp1->isOrgMod && sqdp2->isOrgMod)
            || (!sqdp1->isOrgMod && ! sqdp2->isOrgMod)))
    {
      rval = TRUE;
    }
    else
    {
      rval = FALSE;
    }
  }
  else
  {
    if (StringCmp (vnp1->data.ptrvalue, vnp2->data.ptrvalue) == 0)
    {
      rval = TRUE;
    }
    else
    {
      rval = FALSE;
    }
  }
  return rval;
}

static DialoG SourceQualTypeSelectionDialogEx
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Int2                     list_height,
 Boolean                  show_discouraged,
 Boolean                  show_discontinued)

{
  DialoG     dlg;
  ValNodePtr qual_choice_list;

  if (allow_multi)
  {
    qual_choice_list = ValNodeNew (NULL);
    qual_choice_list->choice = 1;
    qual_choice_list->data.ptrvalue = StringSave ("All Notes");
    qual_choice_list->next = GetSourceQualDescList (TRUE, TRUE, show_discouraged, show_discontinued);
  }
  else
  {
    qual_choice_list = GetSourceQualDescList (TRUE, TRUE, show_discouraged, show_discontinued);
  }
  /* note - the ValNodeSelectionDialog will free the qual_choice_list when done */                                            
  dlg = ValNodeSelectionDialog (h, qual_choice_list, list_height, SourceQualValNodeName,
                                ValNodeSimpleDataFree, SourceQualValNodeDataCopy,
                                SourceQualValNodeMatch, "feature list", 
                                change_notify, change_userdata, allow_multi);

  return dlg;
}

extern DialoG SourceQualTypeSelectionDialog 
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
 
{
  return SourceQualTypeSelectionDialogEx (h, allow_multi, change_notify,
                                          change_userdata, 6, FALSE, FALSE);
}

static DialoG SourceQualTypeDiscSelectionDialog 
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
 
{
  return SourceQualTypeSelectionDialogEx (h, allow_multi, change_notify,
                                          change_userdata, 6, TRUE, TRUE);
}

static DialoG SourceQualTypeConstraintSelectionDialog
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)

{
  return SourceQualTypeSelectionDialogEx (h, allow_multi, change_notify,
                                          change_userdata, 4, TRUE, FALSE);
}

static DialoG SourceStringConstraintSelectionDialog
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)

{
  DialoG     dlg;
  ValNodePtr qual_choice_list = NULL;

  /* ignore allow_multi */
  
  ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Organism or Any Qual"));
  ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Organism"));
  qual_choice_list->next->next = GetSourceQualDescList (TRUE, TRUE, TRUE, FALSE);
  ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Lineage"));
  ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Location"));

  /* note - the ValNodeSelectionDialog will free the qual_choice_list when done */                                            
  dlg = ValNodeSelectionDialog (h, qual_choice_list, 4, SourceQualValNodeName,
                                ValNodeSimpleDataFree, SourceQualValNodeDataCopy,
                                SourceQualValNodeMatch, "feature list", 
                                change_notify, change_userdata, FALSE);

  return dlg;
}

static Boolean OrgModSpecialMatch (OrgModPtr mod, FilterSetPtr fsp)
{
  SourceQualDescPtr sqdp;
  
  if (mod == NULL)
  {
    return FALSE;
  }
  if (fsp == NULL
      || fsp->ccp == NULL 
      || fsp->ccp->constraint_type != CHOICE_CONSTRAINT_STRING
      || fsp->ccp->string_constraint == NULL
      || fsp->ccp->qual_choice == NULL
      || fsp->ccp->qual_choice->data.ptrvalue == NULL)
  {
    return TRUE;
  }
  
  sqdp = (SourceQualDescPtr) fsp->ccp->qual_choice->data.ptrvalue;
  if (sqdp->subtype != mod->subtype)
  {
    return TRUE;
  }
  
  if (DoesStringMatchConstraintX (mod->subname, fsp->ccp->string_constraint))
  {
    if (fsp->ccp->string_constraint->not_present)
    {
      return FALSE;
    }
    else
    {
      return TRUE;
    }
  }
  else
  {
    if (fsp->ccp->string_constraint->not_present)
    {
      return TRUE;
    }
    else
    {
      return FALSE;
    }
  }
}

static Boolean SubSrcSpecialMatch (SubSourcePtr ssp, FilterSetPtr fsp)
{
  SourceQualDescPtr sqdp;
  
  if (ssp == NULL)
  {
    return FALSE;
  }
  if (fsp == NULL
      || fsp->ccp == NULL 
      || fsp->ccp->constraint_type != CHOICE_CONSTRAINT_STRING
      || fsp->ccp->string_constraint == NULL
      || fsp->ccp->qual_choice == NULL
      || fsp->ccp->qual_choice->data.ptrvalue == NULL)
  {
    return TRUE;
  }
  
  sqdp = (SourceQualDescPtr) fsp->ccp->qual_choice->data.ptrvalue;
  if (sqdp->subtype != ssp->subtype)
  {
    return TRUE;
  }
  if (DoesStringMatchConstraintX (ssp->name, fsp->ccp->string_constraint))
  {
    if (fsp->ccp->string_constraint->not_present)
    {
      return FALSE;
    }
    else
    {
      return TRUE;
    }
  }
  else
  {
    if (fsp->ccp->string_constraint->not_present)
    {
      return TRUE;
    }
    else
    {
      return FALSE;
    }
  }
}


static CharPtr GetSubfield (CharPtr str, Uint1 subfield)
{
  CharPtr cp, cp2;
  Int4    num_colons = 0;
  CharPtr new_val = NULL;

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

  cp = StringChr (str, ':');
  while (cp != NULL) {
    num_colons ++;
    cp = StringChr (cp + 1, ':');
  }

  if (subfield == 0) {
    new_val = StringSave (str);
  } else if (subfield == 1) {
    if (num_colons == 0) {
      return NULL;
    } else {
      cp = StringChr (str, ':');
      new_val = (CharPtr) MemNew (sizeof (Char) * (cp - str + 1));
      StringNCpy (new_val, str, cp - str);
      new_val[cp - str] = 0;
    }
  } else if (subfield == 2) {
    if (num_colons == 0 || num_colons == 1) {
      return NULL;
    } else {
      cp = StringChr (str, ':');
      cp2 = StringChr (cp + 1, ':');
      new_val = (CharPtr) MemNew (sizeof (Char) * (cp2 - cp));
      StringNCpy (new_val, cp + 1, cp2 - cp - 1);
      new_val[cp2 - cp - 1] = 0;
    }
  } else {
    if (num_colons == 0) {
      new_val = StringSave (str);
    } else {
      cp = StringRChr (str, ':');
      new_val = StringSave (cp + 1);
    }
  }
  return new_val;
}


static CharPtr 
GetSourceQualQualValueFromBiop 
(BioSourcePtr      biop,
 SourceQualDescPtr sqdp,
 FilterSetPtr      fsp)
{
  CharPtr            str = NULL, field, tmp_str;
  OrgModPtr          mod;
  SubSourcePtr       ssp;
  
  if (biop == NULL || sqdp == NULL)
  {
    return NULL;
  }

  if (sqdp->isOrgMod)
  {
    if (biop->org != NULL && biop->org->orgname != NULL)
    {
      mod = biop->org->orgname->mod;
      while (mod != NULL)
      {
        if (mod->subtype == sqdp->subtype
            && !StringHasNoText (mod->subname)
            && OrgModSpecialMatch (mod, fsp))
        {
          field = GetSubfield (mod->subname, sqdp->subfield);
          if (!StringHasNoText (field)) {
            if (str == NULL) {
              str = field;
            } else {
              tmp_str = (CharPtr) MemNew ((StringLen (str) + StringLen (field) + 3) * sizeof (Char));
              StringCpy (tmp_str, str);
              StringCat (tmp_str, "; ");
              StringCat (tmp_str, field);
              field = MemFree (field);
              str = MemFree (str);
              str = tmp_str;
            }
          } else {
            field = MemFree (field);
          }
        }
        mod = mod->next;
      }
    }
  }
  else
  {
    ssp = biop->subtype;
    while (ssp != NULL)
    {
      if (ssp->subtype == sqdp->subtype
          && !StringHasNoText (ssp->name)
          && SubSrcSpecialMatch (ssp, fsp))
      {
        field = GetSubfield (ssp->name, sqdp->subfield);
        if (!StringHasNoText (field)) {
          if (str == NULL) {
            str = field;
          } else {
            tmp_str = (CharPtr) MemNew ((StringLen (str) + StringLen (field) + 3) * sizeof (Char));
            StringCpy (tmp_str, str);
            StringCat (tmp_str, "; ");
            StringCat (tmp_str, field);
            field = MemFree (field);
            str = MemFree (str);
            str = tmp_str;
          }
        } else {
          field = MemFree (field);
        }
      }
      ssp = ssp->next;
    }
  }
  return str;  
}

static CharPtr GetLocationFromSource (BioSourcePtr biop, ValNodePtr vnp)
{
  Nlm_EnumFieldAssocPtr eap;
  
  if (biop == NULL)
  {
    return NULL;
  }
  
  for (eap = biosource_genome_alistX;
       eap->name != NULL && eap->value != biop->genome;
       eap++)
  {
  }
  
  if (eap != NULL && eap->name != NULL && !StringHasNoText (eap->name))
  {
    return StringSave (eap->name);
  }
  else
  {
    return NULL;
  }  
}

static CharPtr GetLocationFromSourceFeature (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    return GetLocationFromSource (sfp->data.value.ptrvalue, vnp);
  }
  else
  {
    return NULL;
  }
}

static CharPtr 
GetLocationFromSourceDescriptor 
(SeqDescrPtr  sdp,
 ValNodePtr   vnp, 
 FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    return GetLocationFromSource (sdp->data.ptrvalue, vnp);
  }
  else
  {
    return NULL;
  }
}

static CharPtr GetOriginFromSource (BioSourcePtr biop, ValNodePtr vnp)
{
  Nlm_EnumFieldAssocPtr eap;
  
  if (biop == NULL)
  {
    return NULL;
  }
  
  for (eap = origin_alist;
       eap->name != NULL && eap->value != biop->origin;
       eap++)
  {
  }
  
  if (eap != NULL && eap->name != NULL && !StringHasNoText (eap->name))
  {
    return StringSave (eap->name);
  }
  else
  {
    return NULL;
  }  
}

static CharPtr 
GetOriginFromSourceFeature 
(SeqFeatPtr   sfp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    return GetOriginFromSource (sfp->data.value.ptrvalue, vnp);
  }
  else
  {
    return NULL;
  }
}

static CharPtr 
GetOriginFromSourceDescriptor 
(SeqDescrPtr  sdp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    return GetOriginFromSource (sdp->data.ptrvalue, vnp);
  }
  else
  {
    return NULL;
  }
}

static CharPtr GetStringFromSource (BioSourcePtr biop, ValNodePtr vnp)
{
  Nlm_EnumFieldAssocPtr eap;
  
  if (biop == NULL)
  {
    return NULL;
  }
  
  for (eap = orgref_textfield_alist;
       eap->name != NULL && StringCmp (vnp->data.ptrvalue, eap->name) != 0;
       eap++)
  {
  }
  
  if (eap != NULL && eap->name != NULL)
  {
    switch (eap->value)
    {
      case 1:
        if (biop->org != NULL && !StringHasNoText (biop->org->taxname))
        {
          return StringSave (biop->org->taxname);
        }
        break;
      case 2:
        if (biop->org != NULL && !StringHasNoText (biop->org->common))
        {
          return StringSave (biop->org->common);
        }
        break;
      case 3:
        if (biop->org != NULL && biop->org->orgname != NULL
            && !StringHasNoText (biop->org->orgname->lineage))
        {
          return StringSave (biop->org->orgname->lineage);
        }
        break;
      case 4:
        if (biop->org != NULL && biop->org->orgname != NULL
            && !StringHasNoText (biop->org->orgname->div))
        {
          return StringSave (biop->org->orgname->div);
        }
        break;
    }
  }
  return NULL;  
}

static CharPtr GetStringFromSourceFeature (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    return GetStringFromSource (sfp->data.value.ptrvalue, vnp);
  }
  else
  {
    return NULL;
  }
}

static CharPtr StringAppend (CharPtr str1, CharPtr str2)
{
  CharPtr new_str;
  
  if (str1 == NULL && str2 == NULL)
  {
    return NULL;
  }
  else if (str1 == NULL)
  {
    return StringSave (str2);
  }
  else if (str2 == NULL)
  {
    return StringSave (str1);
  }
  else
  {
    new_str = (CharPtr) MemNew ((StringLen (str1) + StringLen (str2) + 3) * sizeof (Char));
    if (new_str != NULL)
    {
      sprintf (new_str, "%s; %s", str1, str2);  
    }
    return new_str;
  }
}

static CharPtr GetStringFromSourceDescriptor (SeqDescrPtr sdp, ValNodePtr vnp, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    return GetStringFromSource (sdp->data.ptrvalue, vnp);
  }
  else
  {
    return NULL;
  }
}

static CharPtr 
GetSourceQualStringFromBioSource 
(BioSourcePtr biop,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  CharPtr            str = NULL, str1 = NULL, str2 = NULL;
  SourceQualDescData sqdd;
  
  if (biop == NULL || vnp == NULL)
  {
    return NULL;
  }
  
  while (vnp != NULL && str == NULL)
  {
    if (vnp->choice == 0)
    {
      str = GetSourceQualQualValueFromBiop (biop, vnp->data.ptrvalue, fsp);   
    }
    else if (vnp->choice == 1)
    {
      if (StringCmp (vnp->data.ptrvalue, "Location") == 0)
      {
        str = GetLocationFromSource (biop, vnp);
      }
      else if (StringCmp (vnp->data.ptrvalue, "Origin") == 0)
      {
        str = GetOriginFromSource (biop, vnp);
      }
      else if (StringCmp (vnp->data.ptrvalue, "All Notes") == 0)
      {
        sqdd.name = "note";
        sqdd.isOrgMod = TRUE;
        sqdd.subtype = 255;
        str1 = GetSourceQualQualValueFromBiop (biop, &sqdd, fsp);
        sqdd.isOrgMod = FALSE;
        str2 = GetSourceQualQualValueFromBiop (biop, &sqdd, fsp);
        str = StringAppend (str1, str2);
        str1 = MemFree (str1);
        str2 = MemFree (str2);
      }
      else
      {
        str = GetStringFromSource (biop, vnp);
      }
    }
    vnp = vnp->next;
  }
  return str;
}

static CharPtr 
GetSourceQualDescrString 
(SeqDescrPtr  sdp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sdp == NULL || sdp->choice != Seq_descr_source || vnp == NULL)
  {
    return NULL;
  }
  return GetSourceQualStringFromBioSource (sdp->data.ptrvalue, vnp, fsp);
}

static CharPtr 
GetSourceQualFeatureString 
(SeqFeatPtr   sfp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || vnp == NULL)
  {
    return NULL;
  }
  
  return GetSourceQualStringFromBioSource (sfp->data.value.ptrvalue, vnp, fsp);
}

static void ApplyLocationToBiop (BioSourcePtr biop, ApplyValuePtr avp)
{
  if (biop == NULL || avp == NULL || avp->field_list == NULL)
  {
    return;
  }
  
  if (avp->etp == NULL 
      || avp->etp->existing_text_choice == eExistingTextChoiceReplaceOld
      || biop->genome == 0)
  {
    biop->genome = avp->field_list->choice;
  }
}

static void ApplyLocationToSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    ApplyLocationToBiop (sfp->data.value.ptrvalue, userdata);
  }
}

static void ApplyLocationToSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    ApplyLocationToBiop (sdp->data.ptrvalue, userdata);
  }
}

static void RemoveLocationFromBiop (BioSourcePtr biop, ApplyValuePtr avp)
{
  if (biop == NULL || avp == NULL || avp->field_list == NULL)
  {
    return;
  }
  
  if (biop->genome == avp->field_list->choice)
  {
    biop->genome = 0;
  }
}

static void RemoveLocationFromSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    RemoveLocationFromBiop (sfp->data.value.ptrvalue, userdata);
  }
}

static void RemoveLocationFromSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    RemoveLocationFromBiop (sdp->data.ptrvalue, userdata);
  }
}

static void ApplyOriginToBiop (BioSourcePtr biop, ApplyValuePtr avp)
{
  if (biop == NULL || avp == NULL || avp->field_list == NULL)
  {
    return;
  }
  
  if (avp->etp == NULL 
      || avp->etp->existing_text_choice == eExistingTextChoiceReplaceOld
      || biop->origin == 0)
  {
    biop->origin = avp->field_list->choice;
  }
}

static void ApplyOriginToSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    ApplyOriginToBiop (sfp->data.value.ptrvalue, userdata);
  }
}

static void ApplyOriginToSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    ApplyOriginToBiop (sdp->data.ptrvalue, userdata);
  }
}

static void RemoveOriginFromBiop (BioSourcePtr biop, ApplyValuePtr avp)
{
  if (biop == NULL || avp == NULL || avp->field_list == NULL)
  {
    return;
  }
  
  if (biop->origin == avp->field_list->choice)
  {
    biop->origin = 0;
  }
}

static void RemoveOriginFromSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    RemoveOriginFromBiop (sfp->data.value.ptrvalue, userdata);
  }
}

static void RemoveOriginFromSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    RemoveOriginFromBiop (sdp->data.ptrvalue, userdata);
  }
}

static void ApplyStringToSource (BioSourcePtr biop, Pointer userdata)
{
  ApplyValuePtr         avp;

  if (biop == NULL || userdata == NULL)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp == NULL || avp->field_list == NULL)
  {
    return;
  }

  if (StringHasNoText (avp->text_to_replace))
  {
    if (biop->org == NULL)
    {
      biop->org = OrgRefNew();
    }
    if (biop->org->orgname == NULL 
        && (avp->field_list->choice == 3
            || avp->field_list->choice == 4))
    {
      biop->org->orgname = OrgNameNew ();
    }
  }
  
  switch (avp->field_list->choice)
  {
    case 1:
      biop->org->taxname = HandleApplyValue (biop->org->taxname, avp);
      break;
    case 2:
      biop->org->common = HandleApplyValue (biop->org->common, avp);
      break;
    case 3:
      biop->org->orgname->lineage = HandleApplyValue (biop->org->orgname->lineage, avp);
      break;
    case 4:
      biop->org->orgname->div = HandleApplyValue (biop->org->orgname->div, avp);
      break;
  }  
}

static void ApplyStringToSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    ApplyStringToSource (sfp->data.value.ptrvalue, userdata);
  }
}

static void ApplyStringToSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    ApplyStringToSource (sdp->data.ptrvalue, userdata);
  }
}

static void RemoveStringFromSource (BioSourcePtr biop, Pointer userdata)
{
  ApplyValuePtr avp;
  
  if (biop == NULL || biop->org == NULL || userdata == NULL)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp == NULL || avp->field_list == NULL)
  {
    return;
  }
  
  switch (avp->field_list->choice)
  {
    case 1:
      biop->org->taxname = MemFree (biop->org->taxname);
      break;
    case 2:
      biop->org->common = MemFree (biop->org->common);
      break;
    case 3:
      if (biop->org->orgname != NULL)
      {
        biop->org->orgname->lineage = MemFree (biop->org->orgname->lineage);
      }
      break;
    case 4:
      if (biop->org->orgname != NULL)
      {
        biop->org->orgname->div = MemFree (biop->org->orgname->div);
      }
      break;
  }   
}

static void RemoveStringFromSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp != NULL && sfp->data.choice == SEQFEAT_BIOSRC)
  {
    RemoveStringFromSource (sfp->data.value.ptrvalue, userdata);
  }
}

static void RemoveStringFromSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source)
  {
    RemoveStringFromSource (sdp->data.ptrvalue, userdata);
  }
}

static void SqnRemoveSourceQualFromBioSource (BioSourcePtr biop, Pointer userdata, FilterSetPtr fsp);

static CharPtr RemoveSubfield (CharPtr orig, Uint1 subfield)
{
  CharPtr cp, cp2;
  Int4    num_colons = 0;
  CharPtr new_val;

  cp = StringChr (orig, ':');
  while (cp != NULL) {
    num_colons ++;
    cp = StringChr (cp + 1, ':');
  }
  new_val = orig;

  if (subfield == 0) {
    new_val = MemFree (new_val);
  } else if (subfield == 1) {
    if (num_colons == 0) {
      /* no INST, just specID, nothing to remove */
    } else if (num_colons == 1) {
      cp = StringChr (orig, ':');
      new_val = StringSave (cp + 1);
      orig = MemFree (orig);
    } else {
      cp = StringChr (orig, ':');
      new_val = StringSave (cp);
      orig = MemFree (orig);
    }
  } else if (subfield == 2) {
    if (num_colons == 0 || num_colons == 1) {
      /* no COLL, nothing to remove */
    } else {
      cp = StringChr (orig, ':');
      cp2 = StringChr (cp + 1, ':');
      new_val = (CharPtr) MemNew (sizeof (Char) * (cp - orig + StringLen (cp2) + 1));
      StringNCpy (new_val, orig, cp - orig);
      StringCat (new_val, cp2);
      orig = MemFree (orig);
      if (StringCmp (new_val, ":") == 0) {
        new_val = MemFree (new_val);
      }
    }
  } else {
    /* specid */
    if (num_colons == 0) {
      new_val = MemFree (new_val);
    } else {
      cp = StringRChr (new_val, ':');
      *(cp + 1) = 0;
    }
  }
  return new_val;
}


static CharPtr ApplyToSubfield (CharPtr orig, ApplyValuePtr avp, Uint1 subfield)
{
  CharPtr cp, cp2;
  Int4    num_colons = 0, len;
  CharPtr new_field, new_val;

  cp = StringChr (orig, ':');
  while (cp != NULL) {
    num_colons ++;
    cp = StringChr (cp + 1, ':');
  }

  new_val = orig;
  if (subfield == 0) {
    /* whole field */
    new_val = HandleApplyValue (orig, avp);
  } else if (subfield == 1) {
    /* INST */
    if (num_colons == 0) {
      /* all we have is the specID, we are adding INST */
      new_field = HandleApplyValue (NULL, avp);
      if (!StringHasNoText (new_field)) {
        new_val = (CharPtr) MemNew (sizeof (Char) * (StringLen (new_field) + StringLen (orig) + 2));
        sprintf (new_val, "%s:%s", new_field, orig == NULL ? "" : orig);
        orig = MemFree (orig);
      } else {
        new_val = orig;
      }
      new_field = MemFree (new_field);
    } else {
      cp = StringChr (orig, ':');
      len = cp - orig + 1;
      new_field = (CharPtr) MemNew (sizeof (Char) * len);
      StringNCpy (new_field, orig, len - 1);
      new_field[len - 1] = 0;
      new_field = HandleApplyValue (new_field, avp);
      new_val = (CharPtr) MemNew (sizeof (Char) * (StringLen (new_field) + StringLen (cp) + 1));
      sprintf (new_val, "%s%s", new_field == NULL ? "" : new_field, cp);
      orig = MemFree (orig);
      new_field = MemFree (new_field);
    }
  } else if (subfield == 2) {
    /* COLL */
    if (num_colons == 0) {
      /* add blank INST, then COLL, then existing specID */
      new_field = HandleApplyValue (NULL, avp);
      if (!StringHasNoText (new_field)) {
        new_val = (CharPtr) MemNew (sizeof (Char) * (3 + StringLen (new_field) +  StringLen (orig)));
        sprintf (new_val, ":%s:%s", new_field, orig == NULL ? "" : orig);
        orig = MemFree (orig);
      } else {
        new_val = orig;
      }
      new_field = MemFree (new_field);
    } else if (num_colons == 1) {
      /* have INST and specID */
      new_field = HandleApplyValue (NULL, avp);
      if (!StringHasNoText (new_field)) {
        new_val = (CharPtr) MemNew (sizeof (Char) * (3 + StringLen (new_field) +  StringLen (orig)));
        cp = StringChr (orig, ':');
        len = cp - orig;
        StringNCpy (new_val, orig, len);
        StringCat (new_val, ":");
        StringCat (new_val, new_field);
        StringCat (new_val, cp);
        orig = MemFree (orig);
      } else {
        new_val = orig;
      }
      new_field = MemFree (new_field);
    } else {
      cp = StringChr (orig, ':');
      cp2 = StringChr (cp + 1, ':');
      len = cp2 - cp;
      new_field = (CharPtr) MemNew (sizeof (Char) * len);
      StringNCpy (new_field, cp + 1, len - 1);
      new_field[len - 1] = 0;
      new_field = HandleApplyValue (new_field, avp);
      new_val = (CharPtr) MemNew (sizeof (Char) * (cp - orig + 1 + StringLen (new_field) + StringLen (cp2) + 1));
      StringNCpy (new_val, orig, cp - orig + 1);
      StringCat (new_val, new_field);
      StringCat (new_val, cp2);
      orig = MemFree (orig);
      new_field = MemFree (new_field);
    }
  } else {
    /* specid */
    if (num_colons == 0) {
      new_val = HandleApplyValue (orig, avp);
    } else {
      cp = StringRChr (orig, ':');
      new_field = StringSave (cp + 1);
      new_field = HandleApplyValue (new_field, avp);
      new_val = (CharPtr) MemNew (sizeof (Char) + (cp - orig + 1 + StringLen (new_field) + 1));
      StringNCpy (new_val, orig, cp - orig + 1);
      StringCat (new_val, new_field);
      orig = MemFree (orig);
      new_field = MemFree (new_field);
    }
  }
  return new_val;
}


static void ApplySourceQualBioSourceCallback (BioSourcePtr biop, Pointer userdata, FilterSetPtr fsp)
{
  ApplyValuePtr         avp;
  SourceQualDescPtr     sqdp;
  OrgModPtr             mod = NULL, last_mod = NULL;
  SubSourcePtr          ssp = NULL, last_ssp = NULL;
  Boolean               found = FALSE;
  Boolean               is_nontext;
  
  if (biop == NULL || userdata == NULL)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL || avp->field_list->data.ptrvalue == NULL)
  {
    return;
  }
  
  /* don't apply to All Notes */
  if (avp->field_list->choice != 0)
  {
    return;
  }
  
  sqdp = (SourceQualDescPtr) avp->field_list->data.ptrvalue;
  
  is_nontext = IsNonTextModifier (sqdp->name);
  if (is_nontext && StringHasNoText (avp->new_text))
  {
    SqnRemoveSourceQualFromBioSource (biop, avp, fsp);
    return;
  }
  
  if (!StringHasNoText (avp->text_to_replace))
  {
    found = TRUE;
  }
  
  if (sqdp->isOrgMod)
  {
    if (biop->org == NULL)
    {
      biop->org = OrgRefNew ();
    }
    if (biop->org != NULL && biop->org->orgname == NULL)
    {
      biop->org->orgname = OrgNameNew ();
    }
    if (biop->org != NULL && biop->org->orgname != NULL)
    {
      mod = biop->org->orgname->mod;
      while (mod != NULL)
      {
        if (mod->subtype == sqdp->subtype)
        {
          if (!is_nontext)
          {
            mod->subname = ApplyToSubfield (mod->subname, avp, sqdp->subfield);
          }
          found = TRUE;
        }
        last_mod = mod;
        mod = mod->next;
      }
      if (!found)
      {
        mod = OrgModNew ();
        mod->subtype = sqdp->subtype;
        if (is_nontext)
        {
          mod->subname = StringSave ("");
        }
        else
        {
          mod->subname = ApplyToSubfield (mod->subname, avp, sqdp->subfield);
        }
        if (last_mod == NULL)
        {
          biop->org->orgname->mod = mod;
        }
        else
        {
          last_mod->next = mod;
        }
      }
    }
  }
  else
  {
    ssp = biop->subtype;
    while (ssp != NULL)
    {
      if (ssp->subtype == sqdp->subtype)
      {
        if (! is_nontext)
        {
          ssp->name = ApplyToSubfield (ssp->name, avp, sqdp->subfield);
        }
        found = TRUE;
      }
      last_ssp = ssp;
      ssp = ssp->next;
    }
    if (!found)
    {
      ssp = SubSourceNew ();
      ssp->subtype = sqdp->subtype;
      if (is_nontext)
      {
        ssp->name = StringSave ("");
      }
      else
      {
        ssp->name = ApplyToSubfield (ssp->name, avp, sqdp->subfield);
      }
      if (last_ssp == NULL)
      {
        biop->subtype = ssp;
      }
      else
      {
        last_ssp->next = ssp;
      }
    }
  }
}

static void ApplySourceQualFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || userdata == NULL)
  {
    return;
  }
  ApplySourceQualBioSourceCallback (sfp->data.value.ptrvalue, userdata, fsp);
}

static void ApplySourceQualDescriptorCallback (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp == NULL || sdp->choice != Seq_descr_source || userdata == NULL)
  {
    return;
  }
  ApplySourceQualBioSourceCallback (sdp->data.ptrvalue, userdata, fsp);
}

static void RemoveOrgModFromBioSource (BioSourcePtr biop, Uint1 subtype, Uint1 subfield, FilterSetPtr fsp)
{
  OrgModPtr mod = NULL, prev_mod = NULL, next_mod;
  
  if (biop != NULL && biop->org != NULL && biop->org->orgname != NULL)
  {
    mod = biop->org->orgname->mod;
    while (mod != NULL)
    {
      next_mod = mod->next;
      if (mod->subtype == subtype
          && OrgModSpecialMatch (mod, fsp))
      {
        mod->subname = RemoveSubfield (mod->subname, subfield);
        if (StringHasNoText (mod->subname)) {
          if (prev_mod == NULL)
          {
            biop->org->orgname->mod = mod->next;
          }
          else
          {
            prev_mod->next = mod->next;
          }
          mod->next = NULL;
          OrgModFree (mod);
        } else {
          prev_mod = mod;
        }
      }
      else
      {
        prev_mod = mod;
      }
      mod = next_mod;
    }
  }
}

static void RemoveSubSourceFromBioSource (BioSourcePtr biop, Uint2 subtype, Uint1 subfield, FilterSetPtr fsp)
{
  SubSourcePtr ssp = NULL, prev_ssp = NULL, next_ssp;

  if (biop != NULL)
  {
    ssp = biop->subtype;
    while (ssp != NULL)
    { 
      next_ssp = ssp->next;
      if (ssp->subtype == subtype
          && SubSrcSpecialMatch (ssp, fsp))
      {
        ssp->name = RemoveSubfield (ssp->name, subfield);
        if (StringHasNoText (ssp->name)) {
          if (prev_ssp == NULL)
          {
            biop->subtype = ssp->next;
          }
          else
          {
            prev_ssp->next = ssp->next;
          }
          ssp->next = NULL;
          SubSourceFree (ssp);
        } else {
          prev_ssp = ssp;
        }
      }
      else
      {
        prev_ssp = ssp;
      }
      ssp = next_ssp;
    }
  }
}

static void SqnRemoveSourceQualFromBioSource (BioSourcePtr biop, Pointer userdata, FilterSetPtr fsp)
{
  ApplyValuePtr         avp;
  SourceQualDescPtr     sqdp;
  
  if (biop == NULL || userdata == NULL)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL || avp->field_list->data.ptrvalue == NULL)
  {
    return;
  }
  if (avp->field_list->choice == 0)
  {
    sqdp = (SourceQualDescPtr) avp->field_list->data.ptrvalue;
    if (sqdp->isOrgMod)
    {
      RemoveOrgModFromBioSource (biop, sqdp->subtype, sqdp->subfield, fsp);
    }
    else
    {
      RemoveSubSourceFromBioSource (biop, sqdp->subtype, sqdp->subfield, fsp);
    }
  }
  else if (avp->field_list->choice == 1 
           && StringCmp (avp->field_list->data.ptrvalue, "All Notes") == 0)
  {
    RemoveOrgModFromBioSource (biop, 255, 0, fsp);
    RemoveSubSourceFromBioSource (biop, 255, 0, fsp);
  }
}

static void RemoveSourceQualFromSourceFeature (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || userdata == NULL)
  {
    return;
  }
  SqnRemoveSourceQualFromBioSource (sfp->data.value.ptrvalue, userdata, fsp);
}

static void RemoveSourceQualFromSourceDescriptor (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  if (sdp == NULL || sdp->choice != Seq_descr_source || userdata == NULL)
  {
    return;
  }
  SqnRemoveSourceQualFromBioSource (sdp->data.ptrvalue, userdata, fsp);
}

static DialoG SourceLocationSelectionDialogEx 
(GrouP h,
 Boolean                  allow_multi,
 Boolean                  allow_discontinued,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)

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

  while (eap->name != NULL)
  {
    if (!StringHasNoText (eap->name) 
        && (allow_discontinued ||
            (StringCmp (eap->name, "Insertion Sequence") != 0
             && StringCmp (eap->name, "Transposon") != 0)))
    {
      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, "Location",
                                change_notify, change_userdata, allow_multi);

  return dlg;
}


static DialoG SourceLocationSelectionDialog
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return SourceLocationSelectionDialogEx (h, allow_multi, FALSE,
                                          change_notify, change_userdata);
}


static DialoG SourceLocationDiscSelectionDialog
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return SourceLocationSelectionDialogEx (h, allow_multi, TRUE,
                                          change_notify, change_userdata);
}


static DialoG SourceOriginSelectionDialog
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)

{
  return EnumAssocSelectionDialog (h, origin_alist, "Origin",
                                   allow_multi, change_notify, change_userdata);
}

static DialoG SourceStringSelectionDialog
(GrouP h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)

{
  return EnumAssocSelectionDialog (h, orgref_textfield_alist, "string",
                                   allow_multi, change_notify, change_userdata);
}


static CharPtr 
GetStringFromStringDescriptor 
(SeqDescrPtr  sdp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sdp == NULL || vnp == NULL || sdp->choice != vnp->data.intvalue)
  {
    return NULL;
  }
  return StringSave (sdp->data.ptrvalue);
}

static DialoG GBQualSelectionDialog 
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)

{
  DialoG                    dlg;
  ValNodePtr            choice_name_list = NULL;
  Int4                      i;
     
  for (i = 0; i < NumEditQualifiers; i++)
  {
    ValNodeAddPointer (&choice_name_list, 0, EditQualifierList[i].name);
  }

  dlg = SelectionDialog (h, change_notify, change_userdata, 
                         allow_multi, "genbank qualifier",
                         choice_name_list, TALL_SELECTION_LIST);
  ValNodeFree (choice_name_list); 
  return dlg;
}

static Int4 GetDbtagStringLen (DbtagPtr db_tag)
{
  Int4 len;
  
  if (db_tag == NULL)
  {
    return 0;
  }
  
  len = StringLen (db_tag->db) + 2;
  if (db_tag->tag != NULL)
  {
    if (db_tag->tag->str != NULL)
    {
      len += StringLen (db_tag->tag->str);
    }
    else
    {
      len += 10;
    }
  }
  return len;
}

static CharPtr GetDbxrefString (SeqFeatPtr sfp)
{
  ValNodePtr vnp;
  Int4       len = 0;
  CharPtr    str = NULL, cp;
  
  if (sfp == NULL || sfp->dbxref == NULL)
  {
    return NULL;
  }
  
  for (vnp = sfp->dbxref; vnp != NULL; vnp = vnp->next)
  {
    len += GetDbtagStringLen (vnp->data.ptrvalue) + 1;
  }
  
  if (len == 0)
  {
    return NULL;
  }
  
  str = (CharPtr) MemNew ((len + 1) * sizeof (Char));
  if (str != NULL)
  {
    for (vnp = sfp->dbxref; vnp != NULL; vnp = vnp->next)
    {
      cp = GetDbtagString (vnp->data.ptrvalue);
      if (cp != NULL)
      {
        StringCat (str, cp);
        StringCat (str, ";");
      }
    }
  }
  if (StringLen (str) >1)
  {
    /* remove final semicolon */
    str [StringLen (str) - 2] = 0;
  }
  return str;
}

static CharPtr GetGBQualString (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  GBQualPtr  gbqual;
  CharPtr    str = NULL;
  Int4       field_choice;
  RnaRefPtr  rrp;
  GeneRefPtr grp;
  
  if (sfp == NULL)
  {
    return NULL;
  }
  while (vnp != NULL && str == NULL)
  {
    field_choice = vnp->data.intvalue;
    if (StringICmp (EditQualifierList[field_choice - 1].name, "note") == 0)
    {
      if (!StringHasNoText (sfp->comment))
      {
        str = StringSave (sfp->comment);
      }
    }
    else if (StringICmp (EditQualifierList[field_choice - 1].name, "exception") == 0)
    {
      if (!StringHasNoText (sfp->except_text))
      {
        str = StringSave (sfp->except_text);
      }
    }
    else if (StringICmp (EditQualifierList[field_choice - 1].name, "product") == 0
             && sfp->data.choice == SEQFEAT_RNA)
    {
      rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
      if (rrp != NULL && rrp->ext.choice == 1
          && !StringHasNoText (rrp->ext.value.ptrvalue))
      {
        str = StringSave (rrp->ext.value.ptrvalue);        
      }
    }
    else if (StringICmp (EditQualifierList [field_choice - 1].name, "map") == 0
             && sfp->data.choice == SEQFEAT_GENE)
    {
      grp = (GeneRefPtr) sfp->data.value.ptrvalue;
      if (grp != NULL && !StringHasNoText (grp->maploc))
      {
        str = StringSave (grp->maploc);
      }
    }
    else if (StringICmp (EditQualifierList [field_choice - 1].name, "db_xref") == 0)
    {
        str = GetDbxrefString (sfp);
    }
    else if (StringICmp (EditQualifierList [field_choice - 1].name, "evidence") == 0)
    {
      if (sfp->exp_ev == 1)
      {
        str = StringSave ("experimental");
      }
      else if (sfp->exp_ev == 2)
      {
        str = StringSave ("non-experimental");
      }
    }
    else
    {
      gbqual = sfp->qual;
      while (gbqual != NULL && str == NULL)
      {
        if (field_choice > 0 && field_choice < NumEditQualifiers + 1
            && StringCmp (gbqual->qual, EditQualifierList[field_choice - 1].name) == 0
            && !StringHasNoText (gbqual->val))
        {
          str = StringSave (gbqual->val);
        }
        gbqual = gbqual->next;
      }
    }
    vnp = vnp->next;
  }
  return str;
}

static DbtagPtr DbtagFromString (CharPtr str)
{
  DbtagPtr db_tag;
  CharPtr  cp, idp;
  Boolean  use_id = TRUE;
  
  if (StringHasNoText (str))
  {
    return NULL;
  }
  
  cp = StringChr (str, ':');
  if (cp == NULL)
  {
    return NULL;
  }
  
  db_tag = DbtagNew ();
  if (db_tag != NULL)
  {
    db_tag->db = (CharPtr) MemNew ((cp - str + 1) * sizeof (Char));
    StringNCpy (db_tag->db, str, cp - str);
    db_tag->db [cp - str] = 0;
    
    idp = cp + 1;
    while (*idp != 0 && use_id)
    {
      if (!isdigit (*idp))
      {
        use_id = FALSE;
      }
      idp++;
    }
    db_tag->tag = ObjectIdNew ();
    if (use_id)
    {
      db_tag->tag->id = atoi (cp + 1);
      db_tag->tag->str = NULL;
    }
    else
    {
      db_tag->tag->id = 0;
      db_tag->tag->str = StringSave (cp + 1);
    }
  }
  return db_tag;
}

static void HandleApplyValueForObjectID (ObjectIdPtr oip, ApplyValuePtr avp)
{
  CharPtr tmp_str = NULL;
  CharPtr cp;
  Boolean is_num = TRUE;
  
  if (oip == NULL || avp == NULL)
  {
    return;
  }
  
  if (oip->str == NULL)
  {
    tmp_str = (CharPtr) MemNew (10 * sizeof (Char));
    sprintf (tmp_str, "%d", oip->id);
    
  }
  else
  {
    tmp_str = oip->str;
    oip->str = NULL;
  }
  tmp_str = HandleApplyValue (tmp_str, avp);
  
  for (cp = tmp_str; cp != NULL && *cp != 0 && is_num; cp++)
  {
    if (!isdigit (*cp))
    {
      is_num = FALSE;
    }
  }
  if (is_num)
  {
    oip->id = atoi (tmp_str);
    tmp_str = MemFree (tmp_str);
  }
  else
  {
    oip->str = tmp_str;
  }
}

static void EditDbxref (SeqFeatPtr sfp, ApplyValuePtr avp, FilterSetPtr fsp)
{
  ValNodePtr vnp;
  DbtagPtr   db_tag_new, db_tag_to_replace, db_tag;
  CharPtr    cp;
  
  if (sfp == NULL || avp == NULL)
  {
    return;
  }
  
  db_tag_new = DbtagFromString (avp->new_text);
  db_tag_to_replace = DbtagFromString (avp->text_to_replace);
  
  if (db_tag_new == NULL && db_tag_to_replace == NULL)
  {
    for (vnp = sfp->dbxref; vnp != NULL; vnp = vnp->next)
    {
      db_tag = (DbtagPtr) vnp->data.ptrvalue;
      if (db_tag != NULL)
      {
        db_tag->db = HandleApplyValue (db_tag->db, avp);
        HandleApplyValueForObjectID (db_tag->tag, avp);
      }
    }
  }
  else if (db_tag_new != NULL && db_tag_to_replace != NULL)
  {
    for (vnp = sfp->dbxref; vnp != NULL; vnp = vnp->next)
    {
      db_tag = (DbtagPtr) vnp->data.ptrvalue;
      if (db_tag != NULL)
      {
        cp = GetDbtagString (db_tag);
        cp = HandleApplyValue (cp, avp);
        db_tag = DbtagFree (db_tag);
        db_tag = DbtagFromString (cp);
        vnp->data.ptrvalue = db_tag;
        cp = MemFree (cp);
      }
    }
  }  
}

static ValNodePtr RemoveDbxrefList (ValNodePtr vnp)

{
  ValNodePtr  next;

  while (vnp != NULL) {
    next = vnp->next;
    DbtagFree ((DbtagPtr) vnp->data.ptrvalue);
    MemFree (vnp);
    vnp = next;
  }
  return NULL;
}

static void SetDbxrefFromString (SeqFeatPtr sfp, ApplyValuePtr avp, FilterSetPtr fsp)
{
  DbtagPtr new_tag;
  
  if (sfp == NULL || avp == NULL)
  {
    return;
  }
  
  if (StringHasNoText (avp->text_to_replace))
  {
    /* this is an apply */
    if (avp->etp != NULL)
    {
      if (avp->etp->existing_text_choice == eExistingTextChoiceLeaveOld
          && sfp->dbxref != NULL)
      {
        return;
      }
      else if (avp->etp->existing_text_choice == eExistingTextChoiceReplaceOld)
      {
        sfp->dbxref = RemoveDbxrefList (sfp->dbxref);
      }
    }
    
    new_tag = DbtagFromString (avp->new_text);
    if (new_tag != NULL)
    {
      ValNodeAddPointer (&(sfp->dbxref), 0, new_tag);
    }
  }
  else
  {
    EditDbxref (sfp, avp, fsp);
  }
  
}

static void SetRNAProduct (SeqFeatPtr sfp, ApplyValuePtr avp);

static void SetGBQualString (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  GBQualPtr     gbqual, gbqual_last = NULL;
  ApplyValuePtr avp;
  Boolean       found = FALSE;
  GeneRefPtr    grp;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL 
      || avp->field_list->data.intvalue < 1 
      || avp->field_list->data.intvalue >= NumEditQualifiers + 1)
  {
    return;
  }

  if (StringICmp (EditQualifierList[avp->field_list->data.intvalue - 1].name, "note") == 0)
  {
    sfp->comment = HandleApplyValue (sfp->comment, avp);
    return;
  }
  else if (StringICmp (EditQualifierList[avp->field_list->data.intvalue - 1].name, "exception") == 0)
  {
    sfp->except_text = HandleApplyValue (sfp->except_text, avp);
    return;
  }
  else if (StringICmp (EditQualifierList[avp->field_list->data.intvalue - 1].name, "product") == 0
           && sfp->data.choice == SEQFEAT_RNA)
  {
    SetRNAProduct (sfp, avp);
    return;
  }
  else if (StringICmp (EditQualifierList [avp->field_list->data.intvalue - 1].name, "map") == 0
           && sfp->data.choice == SEQFEAT_GENE)
  {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
    if (grp != NULL)
    {
      grp->maploc = HandleApplyValue (grp->maploc, avp);
      return;
    }
  }
  else if (StringICmp (EditQualifierList [avp->field_list->data.intvalue - 1].name, "db_xref") == 0)
  {
    SetDbxrefFromString (sfp, avp, fsp);
    return;
  }


  if (!StringHasNoText (avp->text_to_replace))
  {
    found = TRUE;
  }
    
  gbqual = sfp->qual;
  while (gbqual != NULL)
  {
    gbqual_last = gbqual;
    if (StringCmp (gbqual->qual, EditQualifierList[avp->field_list->data.intvalue - 1].name) == 0)
    {
      gbqual->val = HandleApplyValue (gbqual->val, avp);
      found = TRUE;
    }
    gbqual = gbqual->next;
  }
  if (!found)
  {
    gbqual = GBQualNew ();
    if (gbqual != NULL)
    {
      gbqual->qual = StringSave (EditQualifierList[avp->field_list->data.intvalue - 1].name);
      gbqual->val = StringSave (avp->new_text);
      if (gbqual_last == NULL)
      {
        sfp->qual = gbqual;
      }
      else
      {
        gbqual_last->next = gbqual;
      }
    }
  }
}

static void RemoveGBQualField (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  GBQualPtr     gbqual, gbqual_last = NULL, gbqual_next;
  ApplyValuePtr avp;
  RnaRefPtr     rrp;
  GeneRefPtr    grp;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL 
      || avp->field_list->data.intvalue < 1 
      || avp->field_list->data.intvalue >= NumEditQualifiers + 1)
  {
    return;
  }
  
  if (StringICmp (EditQualifierList[avp->field_list->data.intvalue - 1].name, "note") == 0)
  {
    sfp->comment = MemFree (sfp->comment);
    return;
  }
  else if (StringICmp (EditQualifierList[avp->field_list->data.intvalue - 1].name, "exception") == 0)
  {
    sfp->except_text = MemFree (sfp->except_text);
    return;
  }
  else if (StringICmp (EditQualifierList[avp->field_list->data.intvalue - 1].name, "product") == 0
           && sfp->data.choice == SEQFEAT_RNA)
  {
    rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
    if (rrp != NULL && rrp->ext.choice == 1)
    {
      rrp->ext.choice = 0;
      rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
    }
    return;
  }
  else if (StringICmp (EditQualifierList [avp->field_list->data.intvalue - 1].name, "map") == 0
           && sfp->data.choice == SEQFEAT_GENE)
  {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
    if (grp != NULL)
    {
      grp->maploc = MemFree (grp->maploc);
    }
  }
  else if (StringICmp (EditQualifierList [avp->field_list->data.intvalue - 1].name, "db_xref") == 0)
  {
    sfp->dbxref = RemoveDbxrefList (sfp->dbxref);
  }


  gbqual = sfp->qual;
  while (gbqual != NULL)
  {
    gbqual_next = gbqual->next;
    if (StringCmp (gbqual->qual, EditQualifierList[avp->field_list->data.intvalue - 1].name) == 0)
    {
      if (gbqual_last == NULL)
      {
        sfp->qual = gbqual->next;
      }
      else
      {
        gbqual_last->next = gbqual->next;
      }
      gbqual->next = NULL;
      GBQualFree (gbqual);
    }
    else
    {
      gbqual_last = gbqual;
    }
    gbqual = gbqual_next;
  }
}


static void RemoveGoTerm (SeqFeatPtr sfp, CharPtr term_name)
{
  UserObjectPtr       uop;
  ObjectIdPtr         oip;
  UserFieldPtr  curr, curr_next, prev = NULL;
  
  if (sfp == NULL || sfp->ext == NULL || StringHasNoText (term_name))
  {
    return;
  }

  uop = (UserObjectPtr) sfp->ext;
  oip = uop->type;
  if (oip == NULL || StringICmp (oip->str, "GeneOntology") != 0) return;
  
  for (curr = uop->data; curr != NULL; curr = curr_next)
  {
    curr_next = curr->next;
    oip = curr->label;
    if (oip != NULL && StringICmp (oip->str, term_name) == 0)
    {
      if (prev == NULL)
      {
        uop->data = curr->next;
      }
      else
      {
        prev->next = curr->next;
      }
      curr->next = NULL;
      UserFieldFree (curr);
    }
    else
    {
      prev = curr;
    }
  }
 
  if (uop->data == NULL)
  {
    sfp->ext = UserObjectFree (sfp->ext);
  }
 
}


static void RemoveGBQualByNameConstraint (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  GBQualPtr           gbqual, gbqual_last = NULL, gbqual_next;
  RnaRefPtr           rrp;
  GeneRefPtr          grp;
  StringConstraintXPtr scp;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  scp = (StringConstraintXPtr) userdata;
  if (scp == NULL)
  {
    return;
  }
  
  if (DoesStringMatchConstraintX ("note", scp))
  {
    sfp->comment = MemFree (sfp->comment);
  }
  
  if (DoesStringMatchConstraintX ("exception", scp))
  {
    sfp->except_text = MemFree (sfp->except_text);
  }

  if (DoesStringMatchConstraintX ("product", scp) && sfp->data.choice == SEQFEAT_RNA)
  {
    rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
    if (rrp != NULL && rrp->ext.choice == 1)
    {
      rrp->ext.choice = 0;
      rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
    }
  }
  
  if (DoesStringMatchConstraintX ("map", scp) && sfp->data.choice == SEQFEAT_GENE)
  {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
    if (grp != NULL)
    {
      grp->maploc = MemFree (grp->maploc);
    }
  }
  
  if (DoesStringMatchConstraintX ("db_xref", scp))
  {
    sfp->dbxref = RemoveDbxrefList (sfp->dbxref);
  }
  
  if (DoesStringMatchConstraintX ("go_process", scp))
  {
    RemoveGoTerm (sfp, "process");
  }
  
  if (DoesStringMatchConstraintX ("go_function", scp))
  {
    RemoveGoTerm (sfp, "function");
  }

  if (DoesStringMatchConstraintX ("go_component", scp))
  {
    RemoveGoTerm (sfp, "component");
  }

  gbqual = sfp->qual;
  while (gbqual != NULL)
  {
    gbqual_next = gbqual->next;
    if (DoesStringMatchConstraintX (gbqual->qual, scp))
    {
      if (gbqual_last == NULL)
      {
        sfp->qual = gbqual->next;
      }
      else
      {
        gbqual_last->next = gbqual->next;
      }
      gbqual->next = NULL;
      GBQualFree (gbqual);
    }
    else
    {
      gbqual_last = gbqual;
    }
    gbqual = gbqual_next;
  }
}


static CharPtr GetFeatureNote (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  if (sfp == NULL || StringHasNoText (sfp->comment))
  {
    return NULL;
  }
  else
  {
    return StringSave (sfp->comment);
  }
}


static void StripFieldNameFromText(CharPtr text,
                                   NameFromValNodeProc field_name_func, 
                                   ValNodePtr          field_vnp)
{
  CharPtr src, dst;
  CharPtr field_name = NULL;
  Uint4    field_name_len;
  
  if (StringHasNoText (text) || field_name_func == NULL)
  {
    return;
  }
  
  field_name = field_name_func(field_vnp);
  field_name_len = StringLen (field_name);
    
  if (!StringHasNoText (field_name) && StringNICmp(text, field_name, field_name_len) == 0
        && StringLen (text) > field_name_len 
        && text[field_name_len] == ' ')
  {
    src = text + field_name_len + 1;
    while (*src == ' ')
    {
      src++;
    }
    dst = text;
    while (*src != 0)
    {
      *dst = *src;
      dst++;
      src++;
    }
    *dst = 0;
  }
  field_name = MemFree (field_name);
}


static CharPtr ReplaceStringForParse(CharPtr src_text, TextPortionXPtr text_portion)
{
  CharPtr         found_loc;
  Int4            found_len;
  CharPtr         dst_txt = NULL, cp;
  
  if (src_text == NULL || text_portion == NULL) {
    return NULL;
  }
  /* for parse, replace the source text with the portion we want */
  found_loc = NULL;
  found_len = 0;
  FindTextPortionXInString (src_text, text_portion, &found_loc, &found_len);
  if (found_loc != NULL) 
  {
    dst_txt = (CharPtr)MemNew (found_len + 1);
    StringNCpy (dst_txt, found_loc, found_len);
    dst_txt[found_len] = 0;
    cp = found_loc + found_len;
    while (*cp != 0) {
      *found_loc = *cp;
      found_loc++;
      cp++;
    }
    *found_loc = 0;
  }
  return dst_txt;
}


static void ConvertFeatureFieldCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ConvertFieldPtr cfp;
  CharPtr         src_text;
  CharPtr         dst_text = NULL;
  ApplyValueData  avd;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  cfp = (ConvertFieldPtr) userdata;
  
  if (cfp->src_field_list == NULL || cfp->dst_field_list == NULL
      || cfp->get_str_func == NULL || cfp->set_str_func == NULL
      || cfp->remove_str_func == NULL)
  {
    return;
  }
  
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;
  src_text = (cfp->get_str_func)(sfp, cfp->src_field_list, cfp->fsp);
  
  if (cfp->strip_name_from_text)
  {
    StripFieldNameFromText(src_text, cfp->name_field_func, cfp->dst_field_list);
  }
  
  if (StringHasNoText (src_text))
  {
    src_text = MemFree (src_text);
    return;
  }
  
  if (cfp->convert_type == CONVERT_TYPE_SWAP)
  {
    /* for swap, we already know what we're doing with existing text - 
     * we're putting it where the new text used to be. */
    avd.etp = NULL;
    /* and we need to get the destination text before it's replaced */
    dst_text = (cfp->get_str_func)(sfp, cfp->dst_field_list, cfp->fsp); 
    if (cfp->strip_name_from_text)
    {
      StripFieldNameFromText(dst_text, cfp->name_field_func, cfp->src_field_list);
    }
  }
  else if (cfp->convert_type == CONVERT_TYPE_PARSE)
  {
    /* for parse, replace the source text with the portion we want */
    dst_text = src_text;
    src_text = ReplaceStringForParse(src_text, cfp->text_portion);
    if (src_text == NULL) 
    {
      dst_text = MemFree (dst_text);
      return;
    }
    if (!cfp->remove_parsed) {
      dst_text = MemFree (dst_text);
    }
    avd.etp = cfp->etp;
  }
  else
  {
    avd.etp = cfp->etp;
  }
  
    
  /* put src_text in the dst_field */
  avd.field_list = cfp->dst_field_list;
  avd.new_text = src_text;
  (cfp->set_str_func) (sfp, &avd, cfp->fsp);
  
  if (cfp->convert_type == CONVERT_TYPE_SWAP
      || (cfp->convert_type == CONVERT_TYPE_PARSE && cfp->remove_parsed))
  {
    /* put dst_text in the src_field */
    avd.field_list = cfp->src_field_list;
    avd.new_text = dst_text;
    (cfp->set_str_func) (sfp, &avd, cfp->fsp);
  }
  else if (cfp->convert_type == CONVERT_TYPE_MOVE)
  {
    /* remove old src_field */
    avd.etp = NULL;
    avd.field_list = cfp->src_field_list;
    avd.new_text = NULL;
    (cfp->remove_str_func) (sfp, &avd, cfp->fsp);
  }
}

static void ConvertDescriptorFieldCallback (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  ConvertFieldPtr cfp;
  CharPtr         src_text;
  CharPtr         dst_text;
  ApplyValueData  avd;
  
  if (sdp == NULL || userdata == NULL)
  {
    return;
  }
  
  cfp = (ConvertFieldPtr) userdata;
  
  if (cfp->src_field_list == NULL || cfp->dst_field_list == NULL
      || cfp->get_d_str_func == NULL || cfp->set_d_str_func == NULL
      || cfp->remove_d_str_func == NULL)
  {
    return;
  }
  
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;
  src_text = (cfp->get_d_str_func)(sdp, cfp->src_field_list, cfp->fsp);
  if (cfp->strip_name_from_text)
  {
    StripFieldNameFromText(src_text, cfp->name_field_func, cfp->dst_field_list);
  }

  if (StringHasNoText (src_text))
  {
    src_text = MemFree (src_text);
    return;
  }
  
  if (cfp->convert_type == CONVERT_TYPE_SWAP)
  {
    /* for swap, we already know what we're doing with existing text - 
     * we're putting it where the new text used to be. */
    avd.etp = NULL;
    /* and we need to get the destination text before it's replaced */
    dst_text = (cfp->get_d_str_func)(sdp, cfp->dst_field_list, cfp->fsp);    
    if (cfp->strip_name_from_text)
    {
      StripFieldNameFromText(dst_text, cfp->name_field_func, cfp->src_field_list);
    }
    
  }
  else if (cfp->convert_type == CONVERT_TYPE_PARSE)
  {
    /* for parse, replace the source text with the portion we want */
    dst_text = src_text;
    src_text = ReplaceStringForParse(src_text, cfp->text_portion);
    if (src_text == NULL) 
    {
      dst_text = MemFree (dst_text);
      return;
    }
    if (!cfp->remove_parsed) {
      dst_text = MemFree (dst_text);
    }
    avd.etp = cfp->etp;
  }
  else
  {
    avd.etp = cfp->etp;
  }
    
  /* put src_text in the dst_field */
  avd.field_list = cfp->dst_field_list;
  avd.new_text = src_text;
  (cfp->set_d_str_func) (sdp, &avd, cfp->fsp);
  
  if (cfp->convert_type == CONVERT_TYPE_SWAP
      || (cfp->convert_type == CONVERT_TYPE_PARSE && cfp->remove_parsed))
  {
    /* put dst_text in the src_field */
    avd.etp = NULL;
    avd.field_list = cfp->src_field_list;
    avd.new_text = dst_text;
    (cfp->set_d_str_func) (sdp, &avd, cfp->fsp);
  }
  else if (cfp->convert_type == CONVERT_TYPE_MOVE)
  {
    /* remove old src_field */
    avd.etp = NULL;
    avd.field_list = cfp->src_field_list;
    avd.new_text = NULL;
    (cfp->remove_d_str_func) (sdp, &avd, cfp->fsp);
  }
}

static void ConvertNonTextFeatureFieldCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ConvertFieldPtr cfp;
  CharPtr         curr_val = NULL;
  CharPtr         src_val;
  ApplyValueData  avd;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  cfp = (ConvertFieldPtr) userdata;
  
  if (cfp->src_field_list == NULL || cfp->dst_field_list == NULL
      || cfp->get_str_func == NULL || cfp->set_str_func == NULL
      || cfp->name_field_func == NULL)
  {
    return;
  }
  
  curr_val = (cfp->get_str_func)(sfp, NULL, NULL);
  src_val = (cfp->name_field_func)(cfp->src_field_list);
  if (StringCmp (curr_val, src_val) == 0)
  {
    avd.where_to_replace = EditApplyFindLocation_anywhere;
    avd.field_list = cfp->dst_field_list;
    avd.etp = NULL;
    (cfp->set_str_func) (sfp, &avd, cfp->fsp);
  }
  curr_val = MemFree (curr_val);
  src_val = MemFree (src_val);
}

static void ConvertNonTextDescriptorFieldCallback (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  ConvertFieldPtr cfp;
  CharPtr         curr_val = NULL;
  CharPtr         src_val;
  ApplyValueData  avd;
  
  if (sdp == NULL || userdata == NULL)
  {
    return;
  }
  
  cfp = (ConvertFieldPtr) userdata;
  
  if (cfp->src_field_list == NULL || cfp->dst_field_list == NULL
      || cfp->get_d_str_func == NULL || cfp->set_d_str_func == NULL
      || cfp->name_field_func == NULL)
  {
    return;
  }
  
  curr_val = (cfp->get_d_str_func)(sdp, NULL, NULL);
  src_val = (cfp->name_field_func)(cfp->src_field_list);
  if (StringCmp (curr_val, src_val) == 0)
  {
    avd.where_to_replace = EditApplyFindLocation_anywhere;
    avd.field_list = cfp->dst_field_list;
    avd.etp = NULL;
    (cfp->set_d_str_func) (sdp, &avd, cfp->fsp);
  }
  curr_val = MemFree (curr_val);
  src_val = MemFree (src_val);
}

#define FEATUREFIELD_NONE        0

typedef struct featurefieldselection 
{
  DIALOG_MESSAGE_BLOCK
  PopuP                    feature_field;

  Boolean                  allow_none;
  Int4                     num_fields;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} FeatureFieldSelectionData, PNTR FeatureFieldSelectionPtr;


static void FeatureFieldSelectionChange (PopuP p)
{
  FeatureFieldSelectionPtr  dlg;

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

static void ResetFeatureFieldSelection (FeatureFieldSelectionPtr dlg)
{
  if (dlg == NULL)
  {
    return;
  }
  SetValue (dlg->feature_field, 1);
  FeatureFieldSelectionChange (dlg->feature_field);
}

static void FeatureFieldToDialog (DialoG d, Pointer data)
{
  FeatureFieldSelectionPtr  dlg;
  ValNodePtr             vnp;
  Int4                   feature_field;

  dlg = (FeatureFieldSelectionPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  vnp = (ValNodePtr) data;
  if (vnp == NULL)
  {
    ResetFeatureFieldSelection (dlg);
  }
  else
  {
    feature_field = vnp->data.intvalue;
    if (feature_field < FEATUREFIELD_NONE || feature_field > dlg->num_fields + 1)
    {
      feature_field = FEATUREFIELD_NONE;
    }
    if (dlg->allow_none)
    {
      feature_field ++;
    }
    SetValue (dlg->feature_field, feature_field);
  }  
}

static Pointer DialogToFeatureField (DialoG d)
{
  FeatureFieldSelectionPtr  dlg;
  ValNodePtr             vnp = NULL;
  Int4                   feature_field;

  dlg = (FeatureFieldSelectionPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  feature_field = GetValue (dlg->feature_field);
  if (dlg->allow_none)
  {
    feature_field --;
  }
  if (feature_field > FEATUREFIELD_NONE)
  {
    vnp = ValNodeNew (NULL);
    if (vnp != NULL)
    {
      vnp->data.intvalue = feature_field;
    }
  }
  return vnp;
}

static void FeatureFieldMessage (DialoG d, Int2 mssg)

{
  FeatureFieldSelectionPtr  dlg;

  dlg = (FeatureFieldSelectionPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        ResetFeatureFieldSelection (dlg);
        break;
      case VIB_MSG_ENTER :
        Select (dlg->feature_field);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestFeatureFieldDialog (DialoG d)

{
  return NULL;
}

static DialoG FeatureFieldSelectionDialogEx 
(GrouP                    h,
 Boolean                  allow_none,
 CharPtr                  none_txt,
 Int4                     num_fields,
 CharPtr PNTR             field_names,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  FeatureFieldSelectionPtr  dlg;
  GrouP                     p;
  Int4                      k;
  
  dlg = (FeatureFieldSelectionPtr) MemNew (sizeof (FeatureFieldSelectionData));
  if (dlg == NULL)
  {
    return NULL;
  }

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

  dlg->dialog = (DialoG) p;
  dlg->todialog = FeatureFieldToDialog;
  dlg->fromdialog = DialogToFeatureField;
  dlg->dialogmessage = FeatureFieldMessage;
  dlg->testdialog = TestFeatureFieldDialog;
  dlg->allow_none = allow_none;
  dlg->num_fields = num_fields;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->feature_field = PopupList (p, TRUE, FeatureFieldSelectionChange);
  if (dlg->allow_none)
  {
    PopupItem (dlg->feature_field, none_txt);
  }
  for (k = 0; k < dlg->num_fields; k++)
  {
    PopupItem (dlg->feature_field, field_names[k]);
  }
  SetValue (dlg->feature_field, 1);
  SetObjectExtra (dlg->feature_field, dlg, NULL);
  return (DialoG) p;  
}

extern DialoG 
FeatureFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Int4                     num_fields,
 CharPtr PNTR             field_names,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialogEx (h, allow_none, "None", 
                                        num_fields, field_names, 
                                        change_notify, change_userdata);
}

static DialoG FeatureFieldSelectionDialogAny
(GrouP                    h,
 Boolean                  allow_none,
 Int4                     num_fields,
 CharPtr PNTR             field_names,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialogEx (h, allow_none, "Any Field", 
                                        num_fields, field_names, 
                                        change_notify, change_userdata);
}


static CharPtr gene_field_list [] = 
{
  "locus", "description", "comment", "allele", "maploc", "locus_tag", "synonym", "old_locus_tag"  
};

#define GENEFIELD_LOCUS         1
#define GENEFIELD_DESCRIPTION   2
#define GENEFIELD_COMMENT       3
#define GENEFIELD_ALLELE        4
#define GENEFIELD_MAPLOC        5
#define GENEFIELD_LOCUS_TAG     6
#define GENEFIELD_SYNONYM       7
#define GENEFIELD_OLD_LOCUS_TAG 8

static int num_gene_fields = sizeof (gene_field_list) / sizeof (CharPtr);

extern DialoG 
GeneFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialog (h, allow_none,
                                      num_gene_fields, gene_field_list, 
                                      change_notify, change_userdata);
}

extern CharPtr 
GetGeneFieldString 
(SeqFeatPtr   sfp,
 ValNodePtr   gene_field,
 FilterSetPtr fsp)
{
  ValNodePtr vnp;
  CharPtr    str = NULL;
  GeneRefPtr grp;
  GBQualPtr  qual;
  
  if (sfp == NULL || gene_field == NULL)
  {
    return NULL;
  }
  
  if (sfp->data.choice == SEQFEAT_GENE)
  {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  }
  else
  {
    grp = SeqMgrGetGeneXref (sfp);
  }
  
  if (sfp->data.choice != SEQFEAT_GENE && grp != NULL)
  {
    vnp = NULL;
  }
  
  vnp = gene_field;
  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && StringHasNoText (str))
  {
    str = NULL;
    switch (vnp->data.intvalue)
    {
      case GENEFIELD_LOCUS:
        if (grp != NULL)
        {
          str = grp->locus;
        }
        break;
      case GENEFIELD_DESCRIPTION:
        if (grp != NULL)
        {
          str = grp->desc;
        }
        break;
      case GENEFIELD_COMMENT:
        if (sfp->data.choice == SEQFEAT_GENE)
        {
          str = sfp->comment;
        }
        break;
      case GENEFIELD_ALLELE:
        if (grp != NULL)
        {
          str = grp->allele;
        }
        break;
      case GENEFIELD_MAPLOC:
        if (grp != NULL)
        {
          str = grp->maploc;
        }
        break;
      case GENEFIELD_SYNONYM:
        if (grp != NULL && grp->syn != NULL)
        {
          str = grp->syn->data.ptrvalue;
        }
        break;
      case GENEFIELD_LOCUS_TAG:
        if (grp != NULL)
        {
          str = grp->locus_tag;
        }
        break;
      case GENEFIELD_OLD_LOCUS_TAG:
        qual = sfp->qual;
        while (qual != NULL && StringICmp (qual->qual, "old_locus_tag") != 0)
        {
          qual = qual->next;
        }
        if (qual != NULL)
        {
          str = qual->val;
        }
        break;
    }
    vnp = vnp->next;
  }
  if (StringHasNoText (str))
  {
    str = NULL;
  }
  else
  {
    str = StringSave (str);
  }
  return str;
}

extern void RemoveGeneFieldString (SeqFeatPtr sfp, ValNodePtr gene_field)
{
  ValNodePtr vnp;
  Boolean    found_nonempty = FALSE;
  GeneRefPtr grp;
  ValNodePtr syn_remove;
  GBQualPtr  qual, prevqual;
  
  if (sfp == NULL || gene_field == NULL)
  {
    return;
  }
  
  if (sfp->data.choice == SEQFEAT_GENE)
  {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  }
  else
  {
    grp = SeqMgrGetGeneXref (sfp);
  }
  
  if (grp == NULL)
  {
    return;
  }
  
  vnp = gene_field;
  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && !found_nonempty)
  {
    switch (vnp->data.intvalue)
    {
      case GENEFIELD_LOCUS:
        if (grp != NULL && !StringHasNoText (grp->locus))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            grp->locus = MemFree (grp->locus);
          }
        }
        break;
      case GENEFIELD_DESCRIPTION:
        if (grp != NULL && !StringHasNoText (grp->desc))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            grp->desc = MemFree (grp->desc);
          }
        }
        break;
      case GENEFIELD_COMMENT:
        if (!StringHasNoText (sfp->comment))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            sfp->comment = MemFree (sfp->comment);
          }
        }
        break;
      case GENEFIELD_ALLELE:
        if (grp != NULL && !StringHasNoText (grp->allele))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            grp->allele = MemFree (grp->allele);  
          }
        }
        break;
      case GENEFIELD_MAPLOC:
        if (grp != NULL && !StringHasNoText (grp->maploc))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            grp->maploc = MemFree (grp->maploc);            
          }
        }
        break;
      case GENEFIELD_SYNONYM:
        if (grp != NULL && grp->syn != NULL && !StringHasNoText (grp->syn->data.ptrvalue))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            syn_remove = grp->syn;
            grp->syn = grp->syn->next;
            syn_remove->next = NULL;
            ValNodeFreeData (syn_remove);
          }
        }
        break;
      case GENEFIELD_LOCUS_TAG:
        if (grp != NULL && !StringHasNoText (grp->locus_tag))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            grp->locus_tag = MemFree (grp->locus_tag);
          }
        }
        break;
      case GENEFIELD_OLD_LOCUS_TAG:
        prevqual = NULL;
        qual = sfp->qual;
        while (qual != NULL)
        {
          if (StringICmp (qual->qual, "old_locus_tag") == 0)
          {
            if (prevqual == NULL)
            {
              sfp->qual = qual->next;
              qual->next = NULL;
              qual = GBQualFree (qual);
              qual = sfp->qual;
            }
            else
            {
              prevqual->next = qual->next;
              qual->next = NULL;
              qual = GBQualFree (qual);
              qual = prevqual->next;
            }
          }
          else
          {
            prevqual = qual;
            qual = qual->next;
          }
        }
        break;
    }
    vnp = vnp->next;
  }
}

static void SetGeneFieldString (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  GeneRefPtr    grp;
  ApplyValuePtr avp;
  Boolean       found;
  GBQualPtr     qual, qual_last;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL)
  {
    return;
  }
  
  if (sfp->data.choice == SEQFEAT_GENE)
  {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  }
  else
  {
    grp = SeqMgrGetGeneXref (sfp);
  }
  
  if (grp == NULL && sfp->data.choice == SEQFEAT_GENE)
  {
    grp = GeneRefNew ();
    sfp->data.value.ptrvalue = grp;
  }
  
  if (grp == NULL)
  {
    return;
  }
  
  switch (avp->field_list->data.intvalue)
  {
    case GENEFIELD_LOCUS:
      grp->locus = HandleApplyValue (grp->locus, avp);
      break;
    case GENEFIELD_DESCRIPTION:
      grp->desc = HandleApplyValue (grp->desc, avp);
      break;
    case GENEFIELD_COMMENT:
      sfp->comment = HandleApplyValue (sfp->comment, avp);
      break;
    case GENEFIELD_ALLELE:
      grp->allele = HandleApplyValue (grp->allele, avp);  
      break;
    case GENEFIELD_MAPLOC:
      grp->maploc = HandleApplyValue (grp->maploc, avp);            
      break;
    case GENEFIELD_SYNONYM:
      if (grp->syn == NULL || !StringHasNoText (avp->text_to_replace)) {
        grp->syn = ApplyValueToValNodeStringList (grp->syn, 0, avp);
      } else {
        grp->syn->data.ptrvalue = HandleApplyValue (grp->syn->data.ptrvalue, avp);
      }
      break;
    case GENEFIELD_LOCUS_TAG:
      grp->locus_tag = HandleApplyValue (grp->locus_tag, avp);
      break;
    case GENEFIELD_OLD_LOCUS_TAG:
      qual = sfp->qual;
      qual_last = NULL;
      found = FALSE;
      while (qual != NULL && !found)
      {
        qual_last = qual;
        if (StringCmp (qual->qual, "old_locus_tag") == 0)
        {
          qual->val = HandleApplyValue (qual->val, avp);
          found = TRUE;
        }
        qual = qual->next;
      }
      if (!found)
      {
        qual = GBQualNew ();
        if (qual != NULL)
        {
          qual->qual = StringSave ("old_locus_tag");
          qual->val = StringSave (avp->new_text);
          if (qual_last == NULL)
          {
            sfp->qual = qual;
          }
          else
          {
            qual_last->next = qual;
          }
        }
      }    
      break;
  }
}

static CharPtr mrna_field_list [] = 
{
  "product", "comment"  
};

#define MRNAFIELD_PRODUCT     1
#define MRNAFIELD_COMMENT     2

static int num_mrna_fields = sizeof (mrna_field_list) / sizeof (CharPtr);

extern DialoG 
MRNAFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialog (h, allow_none,
                                      num_mrna_fields, mrna_field_list, 
                                      change_notify, change_userdata);
}

extern CharPtr 
GetmRNAFieldString 
(SeqFeatPtr   sfp,
 ValNodePtr   mrna_field,
 FilterSetPtr fsp)
{
  ValNodePtr vnp;
  CharPtr    str = NULL;
  RnaRefPtr  rrp;
  
  if (sfp == NULL || mrna_field == NULL || sfp->idx.subtype != FEATDEF_mRNA)
  {
    return NULL;
  }
  
  vnp = mrna_field;
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;

  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && StringHasNoText (str))
  {
    str = NULL;
    switch (vnp->data.intvalue)
    {
      case MRNAFIELD_PRODUCT:
        if (rrp != NULL)
        {
          str = rrp->ext.value.ptrvalue;
        }
        break;
      case MRNAFIELD_COMMENT:
        str = sfp->comment;
        break;
    }
    vnp = vnp->next;
  }
  if (StringHasNoText (str))
  {
    str = NULL;
  }
  else
  {
    str = StringSave (str);
  }
  return str;
}

extern void RemovemRNAFieldString (SeqFeatPtr sfp, ValNodePtr mrna_field)
{
  ValNodePtr vnp;
  Boolean    found_nonempty = FALSE;
  RnaRefPtr  rrp;
  
  if (sfp == NULL || mrna_field == NULL || sfp->idx.subtype != FEATDEF_mRNA)
  {
    return;
  }
  
  vnp = mrna_field;
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && !found_nonempty)
  {
    switch (vnp->data.intvalue)
    {
      case MRNAFIELD_PRODUCT:
        if (rrp != NULL && !StringHasNoText (rrp->ext.value.ptrvalue))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
          }
        }
        break;
      case MRNAFIELD_COMMENT:
        if (!StringHasNoText (sfp->comment))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            sfp->comment = MemFree (sfp->comment);
          }
        }
        break;
    }
    vnp = vnp->next;
  }
}

static void SetmRNAFieldString (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  RnaRefPtr     rrp;
  ApplyValuePtr avp;
  Int4          field_choice;
  
  if (sfp == NULL || userdata == NULL || sfp->idx.subtype != FEATDEF_mRNA)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL)
  {
    return;
  }
  
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (rrp == NULL)
  {
    rrp = RnaRefNew ();
    if (rrp == NULL)
    {
      return;
    }
    sfp->data.value.ptrvalue = rrp;
  }
  field_choice = avp->field_list->data.intvalue;
  switch (avp->field_list->data.intvalue)
  {
    case MRNAFIELD_PRODUCT:
      if (rrp->ext.choice == 0)
      {
        rrp->ext.choice = 1;
        rrp->ext.value.ptrvalue = NULL;
      }    
      rrp->ext.value.ptrvalue = HandleApplyValue (rrp->ext.value.ptrvalue, avp);
      break;
    case MRNAFIELD_COMMENT:
      sfp->comment = HandleApplyValue (sfp->comment,avp);
      break;
  }
}

static CharPtr GetCDSComment (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION || StringHasNoText (sfp->comment))
  {
    return NULL;
  }
  else
  {
    return StringSave (sfp->comment);
  }
}

static void SetCDSComment (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ApplyValuePtr avp;

  if (sfp == NULL || userdata == NULL || sfp->data.choice != SEQFEAT_CDREGION)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;

  sfp->comment = HandleApplyValue (sfp->comment, avp);  
}

static void RemoveCDSComment (SeqFeatPtr sfp, ValNodePtr vnp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION)
  {
    return;
  }
  sfp->comment = MemFree (sfp->comment);
}

extern CharPtr 
GetCDSFieldString 
(SeqFeatPtr   sfp,
 ValNodePtr   cds_field,
 FilterSetPtr fsp)
{
  ValNodePtr vnp;
  CharPtr    str = NULL;
  BioseqPtr  prot_bsp;
  SeqFeatPtr prot_sfp;
  SeqMgrFeatContext prot_context;
  ProtRefPtr        prp;
  
  if (sfp == NULL || cds_field == NULL || sfp->idx.subtype != FEATDEF_CDS)
  {
    return NULL;
  }
  
  vnp = cds_field;

  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && StringHasNoText (str))
  {
    str = NULL;
    switch (vnp->data.intvalue)
    {
      case MRNAFIELD_PRODUCT:
        prot_bsp = BioseqFind (SeqLocId (sfp->product));
        prot_sfp = SeqMgrGetNextFeature (prot_bsp, NULL, SEQFEAT_PROT, 0, &prot_context);
        if (prot_sfp != NULL && prot_sfp->data.value.ptrvalue != NULL) {
          prp = (ProtRefPtr) prot_sfp->data.value.ptrvalue;
          if (prp->name != NULL && !StringHasNoText (prp->name->data.ptrvalue)) {
            str = prp->name->data.ptrvalue;
          }
        }
        break;
      case MRNAFIELD_COMMENT:
        str = sfp->comment;
        break;
    }
    vnp = vnp->next;
  }
  if (StringHasNoText (str))
  {
    str = NULL;
  }
  else
  {
    str = StringSave (str);
  }
  return str;
}

extern void RemoveCDSFieldString (SeqFeatPtr sfp, ValNodePtr cds_field)
{
  ValNodePtr vnp;
  Boolean    found_nonempty = FALSE;
  BioseqPtr  prot_bsp;
  SeqFeatPtr prot_sfp;
  SeqMgrFeatContext prot_context;
  ProtRefPtr        prp;
  
  if (sfp == NULL || cds_field == NULL || sfp->idx.subtype != FEATDEF_CDS)
  {
    return;
  }
  
  vnp = cds_field;
  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && !found_nonempty)
  {
    switch (vnp->data.intvalue)
    {
      case MRNAFIELD_PRODUCT:
        prot_bsp = BioseqFind (SeqLocId (sfp->product));
        prot_sfp = SeqMgrGetNextFeature (prot_bsp, NULL, SEQFEAT_PROT, 0, &prot_context);
        if (prot_sfp != NULL && prot_sfp->data.value.ptrvalue != NULL) {
          prp = (ProtRefPtr) prot_sfp->data.value.ptrvalue;
          if (prp->name != NULL && !StringHasNoText (prp->name->data.ptrvalue)) {
            found_nonempty = TRUE;
            if (vnp->choice != 0) {
              prp->name = ValNodeFreeData (prp->name);
            }
          }
        }
        break;
      case MRNAFIELD_COMMENT:
        if (!StringHasNoText (sfp->comment))
        {
          found_nonempty = TRUE;
          if (vnp->choice != 0)
          {
            sfp->comment = MemFree (sfp->comment);
          }
        }
        break;
    }
    vnp = vnp->next;
  }
}

#define PROTEINFIELD_NAME               1
#define PROTEINFIELD_DESC               2
#define PROTEINFIELD_EC_NUM             3
#define PROTEINFIELD_ACTIVITY           4
#define PROTEINFIELD_COMMENT            5
#define PROTEINFIELD_MATPEPTIDE_NAME    6
#define PROTEINFIELD_MATPEPTIDE_DESC    7
#define PROTEINFIELD_MATPEPTIDE_COMMENT 8

static CharPtr protein_field_list [] =
{
  "name", "description", "E.C. number", "activity", "comment", "mat_peptide name", "mat_peptide description", "mat_peptide comment"
};

static int num_protein_fields = sizeof (protein_field_list) / sizeof (CharPtr);

extern DialoG 
ProteinFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialog (h, allow_none,
                                      num_protein_fields, protein_field_list, 
                                      change_notify, change_userdata);
}

extern CharPtr GetProteinFieldString (SeqFeatPtr sfp, ValNodePtr protein_field, FilterSetPtr fsp)
{
  ValNodePtr     vnp, val_vnp;
  CharPtr        str = NULL;
  ProtRefPtr     prp = NULL;
  Int4           field_choice;
  SeqFeatXrefPtr xref;
  
  if (sfp == NULL || protein_field == NULL || (sfp->data.choice != SEQFEAT_PROT && sfp->data.choice != SEQFEAT_CDREGION))
  {
    return NULL;
  }
  
  vnp = protein_field;
  if (sfp->data.choice == SEQFEAT_PROT) {
      prp = (ProtRefPtr) sfp->data.value.ptrvalue;
  } else if (sfp->data.choice == SEQFEAT_CDREGION) {
      xref = sfp->xref;
      while (xref != NULL && xref->data.choice != SEQFEAT_PROT) {
          xref = xref->next;
      }
      if (xref != NULL) {
          prp = xref->data.value.ptrvalue;
      }
  }


  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE && StringHasNoText (str))
  {
    str = NULL;
    field_choice = vnp->data.intvalue;
    switch (field_choice)
    {
      case PROTEINFIELD_NAME:
        if (prp != NULL && prp->name != NULL && (sfp->idx.subtype == FEATDEF_PROT || sfp->idx.subtype == FEATDEF_CDS))
        {
          val_vnp = prp->name;
          str = val_vnp->data.ptrvalue;
        }
        break;
      case PROTEINFIELD_DESC:
        if (prp != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          str = prp->desc;
        }
        break;
      case PROTEINFIELD_EC_NUM:
        if (prp != NULL && prp->ec != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          val_vnp = prp->ec;
          str = val_vnp->data.ptrvalue;
        }
        break;
      case PROTEINFIELD_ACTIVITY:
        if (prp != NULL && prp->activity != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          val_vnp = prp->activity;
          str = val_vnp->data.ptrvalue;
        }
        break;
      case PROTEINFIELD_COMMENT:
        if (sfp->idx.subtype == FEATDEF_PROT)
        {
          str = sfp->comment;
        }
        break;
      case PROTEINFIELD_MATPEPTIDE_NAME:
        if (prp != NULL && prp->name != NULL && sfp->idx.subtype == FEATDEF_mat_peptide_aa)
        {
          val_vnp = prp->name;
          str = val_vnp->data.ptrvalue;
        }
        break;
      case PROTEINFIELD_MATPEPTIDE_DESC:
        if (sfp->idx.subtype == FEATDEF_mat_peptide_aa && prp != NULL)
        {
           str = prp->desc;
        }
        break;
      case PROTEINFIELD_MATPEPTIDE_COMMENT:
        if (sfp->idx.subtype == FEATDEF_mat_peptide_aa)
        {
          str = sfp->comment;
        }
        break;
    }
    vnp = vnp->next;
  }
  if (StringHasNoText (str))
  {
    str = NULL;
  }
  else
  {
    str = StringSave (str);
  }
  return str;
}

static void RemoveProteinFieldString (SeqFeatPtr sfp, ValNodePtr protein_field)
{
  ValNodePtr vnp;
  ProtRefPtr prp = NULL;
  Int4       field_choice;
  SeqFeatXrefPtr xref = NULL, prev;
  
  if (sfp == NULL || protein_field == NULL)
  {
    return;
  }
  
  vnp = protein_field;
  if (sfp->idx.subtype == FEATDEF_PROT
      || sfp->idx.subtype == FEATDEF_mat_peptide_aa) {
      prp = (ProtRefPtr) sfp->data.value.ptrvalue;
  } else if (sfp->idx.subtype == FEATDEF_CDS) {
      xref = sfp->xref;
      while (xref != NULL && xref->data.choice != SEQFEAT_PROT) {
          xref = xref->next;
      }
      if (xref != NULL) {
          prp = xref->data.value.ptrvalue;
      }
  }      

  while (vnp != NULL && vnp->data.intvalue != FEATUREFIELD_NONE)
  {
    field_choice = vnp->data.intvalue;
    switch (field_choice)
    {
      case PROTEINFIELD_NAME:
        if (prp != NULL && prp->name != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          prp->name = ValNodeFreeData (prp->name);
        }
        break;
      case PROTEINFIELD_DESC:
        if (prp != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          prp->desc = MemFree (prp->desc);
        }
        break;
      case PROTEINFIELD_EC_NUM:
        if (prp != NULL && prp->ec != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          prp->ec = ValNodeFreeData (prp->ec);
        }
        break;
      case PROTEINFIELD_ACTIVITY:
        if (prp != NULL && prp->activity != NULL && sfp->idx.subtype == FEATDEF_PROT)
        {
          prp->activity = ValNodeFreeData (prp->activity);
        }
        break;
      case PROTEINFIELD_COMMENT:
        if (sfp->idx.subtype == FEATDEF_PROT)
        {
          sfp->comment = MemFree (sfp->comment);
        }
        break;
      case PROTEINFIELD_MATPEPTIDE_NAME:
        if (prp != NULL && prp->name != NULL && sfp->idx.subtype == FEATDEF_mat_peptide_aa)
        {
          prp->name = ValNodeFreeData (prp->name);
        }
        break;
      case PROTEINFIELD_MATPEPTIDE_DESC:
        if (prp != NULL && sfp->idx.subtype == FEATDEF_mat_peptide_aa)
        {
          prp->desc = MemFree (prp->desc);
        }
        break;
      case PROTEINFIELD_MATPEPTIDE_COMMENT:
        if (sfp->idx.subtype == FEATDEF_mat_peptide_aa)
        {
          sfp->comment = MemFree (sfp->comment);
        }
        break;
    }
    vnp = vnp->next;
  }
  
  /* remove protein xref if empty */
  if (prp != NULL && xref != NULL
      && prp->name == NULL
      && StringHasNoText (prp->desc)
      && prp->ec == NULL
      && prp->activity == NULL
      && prp->db == NULL) {
    prp = ProtRefFree(prp);
    xref->data.value.ptrvalue = NULL;
    if (sfp->xref == xref) {
        sfp->xref = xref->next;
    } else {
        prev = sfp->xref;
        while (prev != NULL && prev->next != xref) {
            prev = prev->next;
        }
        if (prev != NULL) {
            prev->next = xref->next;
        }
    }
    xref = SeqFeatXrefFree(xref);
  }      
}

extern ValNodePtr ApplyValueToValNodeStringListAsText (ValNodePtr list, Int2 choice, ApplyValuePtr avp);

static void SetProteinFieldString (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ProtRefPtr    prp;
  ApplyValuePtr avp;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  if (sfp->idx.subtype != FEATDEF_PROT && sfp->idx.subtype != FEATDEF_mat_peptide_aa)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL)
  {
    return;
  }

  if (sfp->idx.subtype == FEATDEF_PROT)
  {
    if (avp->field_list->data.intvalue == PROTEINFIELD_MATPEPTIDE_NAME
        || avp->field_list->data.intvalue == PROTEINFIELD_MATPEPTIDE_DESC
        || avp->field_list->data.intvalue == PROTEINFIELD_MATPEPTIDE_COMMENT)
    {
      return;
    }
  }
  else if (sfp->idx.subtype == FEATDEF_mat_peptide_aa)
  {
    if (avp->field_list->data.intvalue != PROTEINFIELD_MATPEPTIDE_NAME
        && avp->field_list->data.intvalue != PROTEINFIELD_MATPEPTIDE_DESC
        && avp->field_list->data.intvalue != PROTEINFIELD_MATPEPTIDE_COMMENT)
    {
      return;
    }
  }
  else    
  {
    return; 
  }
  
  prp = (ProtRefPtr) sfp->data.value.ptrvalue;
  if (prp == NULL)
  {
    prp = ProtRefNew ();
    if (prp == NULL)
    {
      return;
    }
    sfp->data.value.ptrvalue = prp;
  }
  
  switch (avp->field_list->data.intvalue)
  {
    case PROTEINFIELD_NAME:
    case PROTEINFIELD_MATPEPTIDE_NAME:
      if (prp->name == NULL || !StringHasNoText(avp->text_to_replace))
      {
        prp->name = ApplyValueToValNodeStringList (prp->name, 0, avp);
      }
      else
      {
        prp->name->data.ptrvalue = HandleApplyValue (prp->name->data.ptrvalue, avp);
      }
      break;
    case PROTEINFIELD_DESC:
    case PROTEINFIELD_MATPEPTIDE_DESC:
      prp->desc = HandleApplyValue (prp->desc, avp);
      break;
    case PROTEINFIELD_EC_NUM:
      prp->ec = ApplyValueToValNodeStringListAsText (prp->ec, 0, avp);
      break;
    case PROTEINFIELD_ACTIVITY:
      if (prp->activity == NULL || !StringHasNoText(avp->text_to_replace))
      {
        prp->activity = ApplyValueToValNodeStringList (prp->activity, 0, avp);
      }
      else
      {
        prp->activity->data.ptrvalue = HandleApplyValue (prp->activity->data.ptrvalue, avp);
      }
      break;
    case PROTEINFIELD_COMMENT:
    case PROTEINFIELD_MATPEPTIDE_COMMENT:
      sfp->comment = HandleApplyValue (sfp->comment, avp);
      break;
  }
}

static CharPtr PNTR BuildCDSGeneFieldList (Int4Ptr num_fields)
{
  CharPtr PNTR field_name_list;
  Int4         i, k;
  Char         tmp[100];

  if (num_fields == NULL) 
  {
    return NULL;
  }
  *num_fields = 1 + num_gene_fields + num_mrna_fields + num_protein_fields;
  field_name_list = (CharPtr PNTR) MemNew (*num_fields * sizeof (CharPtr));
  if (field_name_list == NULL)
  {
    return NULL;
  }
  
  field_name_list [0] = StringSave ("CDS comment");
  k = 1;
  for (i = 0; i < num_gene_fields; i++)
  {
    sprintf (tmp, "Gene %s", gene_field_list [i]);
    field_name_list [k++] = StringSave (tmp);
  }
  for (i = 0; i < num_mrna_fields; i++)
  {
    sprintf (tmp, "mRNA %s", mrna_field_list [i]);
    field_name_list [k++] = StringSave (tmp);
  }
  for (i = 0; i < num_protein_fields; i++)
  {
    if (StringNCmp (protein_field_list [i], "mat_peptide", 11) == 0)
    {
      sprintf (tmp, "%s", protein_field_list [i]);
      tmp [0] = toupper (tmp [0]);
    }
    else
    {
      sprintf (tmp, "Protein %s", protein_field_list [i]);
    }
    field_name_list [k++] = StringSave (tmp);
  }
  return field_name_list;  
}

static void FreeCDSGeneFieldList (CharPtr PNTR field_name_list, Int4 num_fields)
{
  Int4 i;
  
  if (field_name_list == NULL || num_fields == 0)
  {
    return;
  }
  
  for (i = 0; i < num_fields; i++)
  {
    field_name_list [i] = MemFree (field_name_list [i]);
  }
  field_name_list = MemFree (field_name_list);
}

extern DialoG 
CDSGeneProtFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  CharPtr PNTR field_name_list;
  Int4         num_fields = 0;
  DialoG       d;

  field_name_list = BuildCDSGeneFieldList (&num_fields);
  
  d = FeatureFieldSelectionDialog (h, allow_none,
                                      num_fields, field_name_list, 
                                      change_notify, change_userdata);
  FreeCDSGeneFieldList (field_name_list, num_fields);
  return d;
}

static DialoG
CDSGeneProtFieldConstraintSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  CharPtr PNTR field_name_list;
  Int4         num_fields = 0;
  DialoG       d;

  field_name_list = BuildCDSGeneFieldList (&num_fields);
  
  d = FeatureFieldSelectionDialogAny (h, allow_none,
                                      num_fields, field_name_list, 
                                      change_notify, change_userdata);
  FreeCDSGeneFieldList (field_name_list, num_fields);
  return d; 
}


extern Boolean IsCDSetProteinProductChoice (ValNodePtr vnp)
{
  if (vnp != NULL && vnp->data.intvalue == 2 + num_gene_fields + num_mrna_fields) {
    return TRUE;
  } else {
    return FALSE;
  }
}


static Boolean IsCDSetProteinQualChoice (ValNodePtr vnp)
{
  if (vnp != NULL 
      && vnp->data.intvalue > 1 + num_gene_fields + num_mrna_fields
      && vnp->data.intvalue <= 1 + num_gene_fields + num_mrna_fields + num_protein_fields) 
  {
    return TRUE;
  } 
  else 
  {
    return FALSE;
  }
}

static Boolean IsCDSetMatPeptideQualChoice (ValNodePtr vnp)
{
  if (IsCDSetProteinQualChoice (vnp)
      && StringNICmp (protein_field_list[vnp->data.intvalue - num_gene_fields - num_mrna_fields - 2],
                      "mat_peptide", 11) == 0) 
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  } 
}

static Boolean IsCDSetMRNAQualChoice (ValNodePtr vnp)
{
  if (vnp != NULL 
      && vnp->data.intvalue > 1 + num_gene_fields
      && vnp->data.intvalue <= 1 + num_gene_fields + num_mrna_fields) 
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

static Boolean IsCDSetGeneQualChoice (ValNodePtr vnp)
{
  if (vnp != NULL 
      && vnp->data.intvalue > 1
      && vnp->data.intvalue <= 1 + num_gene_fields)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

static Boolean IsCDSetCDSQualChoice (ValNodePtr vnp)
{
  if (vnp != NULL && vnp->data.intvalue == 1) 
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

extern CharPtr GetCDSGeneProtField (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  CharPtr str = NULL;
  ValNode vn;
  Int4    field_choice;
  
  if (sfp == NULL || vnp == NULL)
  {
    return NULL;
  }
  
  while (vnp != NULL && str == NULL)
  {
    vn.choice = vnp->choice;
    vn.next = NULL;
    
    field_choice = vnp->data.intvalue;
  
    if (IsCDSetCDSQualChoice(vnp))
    {
      /* CDS Comment */
      str = GetCDSComment (sfp, NULL, NULL);
    }
    else if (IsCDSetGeneQualChoice (vnp))
    {
      vn.data.intvalue = vnp->data.intvalue - 1;
      str = GetGeneFieldString (sfp, &vn, NULL);
    }
    else if (IsCDSetMRNAQualChoice (vnp))
    {
      vn.data.intvalue = vnp->data.intvalue - num_gene_fields - 1;
      str = GetmRNAFieldString (sfp, &vn, NULL);
    }
    else if (IsCDSetProteinQualChoice (vnp))
    {
      vn.data.intvalue = vnp->data.intvalue - num_gene_fields - num_mrna_fields - 1;
      str = GetProteinFieldString (sfp, &vn, NULL);
    }
    vnp = vnp->next;
  }
  return str;
}

/* we could have multiple mat_peptides in a CDSset, some that match the constraint and some that do not */
static Boolean DoesConstraintDisqualifyFeature (SeqFeatPtr sfp, ChoiceConstraintPtr ccp)
{
  Boolean is_disqualified = FALSE;
  Boolean does_match = FALSE;
  CharPtr str;

  if (sfp == NULL || ccp == NULL 
      || ccp->constraint_type == CHOICE_CONSTRAINT_ANY
      || sfp->idx.subtype != FEATDEF_mat_peptide_aa 
      || !IsCDSetMatPeptideQualChoice(ccp->qual_choice))
  {
    is_disqualified = FALSE;
  }
  else if (ccp->constraint_type == CHOICE_CONSTRAINT_QUAL_PRESENT && ccp->qual_choice != NULL)
  {
    str = GetCDSGeneProtField (sfp, ccp->qual_choice, NULL);
    if (str == NULL)
    {
      is_disqualified = TRUE;
    }
    MemFree (str);
  }
  else if (ccp->constraint_type == CHOICE_CONSTRAINT_STRING)
  {
    str = GetCDSGeneProtField (sfp, ccp->qual_choice, NULL);
    does_match = DoesStringMatchConstraintX (str, ccp->string_constraint);
    MemFree (str);
    if (ccp->string_constraint != NULL && ccp->string_constraint->not_present)
    {
      does_match = ! does_match;
    }
    if (!does_match)
    {
      is_disqualified = TRUE;
    }
  }
  return is_disqualified;

}

extern void RemoveCDSGeneProtField (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  ValNode vn;
  
  if (sfp == NULL || vnp == NULL)
  {
    return;
  }

  if (fsp != NULL && fsp->cgp != NULL && DoesConstraintDisqualifyFeature (sfp, fsp->cgp)) 
  {
    return;
  }
  
  while (vnp != NULL)
  {
    vn.choice = vnp->choice;
    vn.next = NULL;
  
    if (vnp->data.intvalue == 1)
    {
      /* CDS Comment */
      RemoveCDSComment (sfp, NULL);
    }
    else if (vnp->data.intvalue <= 1 + num_gene_fields)
    {
      vn.data.intvalue = vnp->data.intvalue - 1;
      RemoveGeneFieldString (sfp, &vn);
    }
    else if (vnp->data.intvalue <= 1 + num_gene_fields + num_mrna_fields)
    {
      vn.data.intvalue = vnp->data.intvalue - num_gene_fields - 1;
      RemovemRNAFieldString (sfp, &vn);
    }
    else if (vnp->data.intvalue <= 1 + num_gene_fields + num_mrna_fields + num_protein_fields)
    {
      vn.data.intvalue = vnp->data.intvalue - num_gene_fields - num_mrna_fields - 1;
      RemoveProteinFieldString (sfp, &vn);
    }
    vnp = vnp->next;
  }
}


extern Uint2 FeatDefTypeFromFieldList (ValNodePtr vnp)
{
  if (vnp == NULL) 
  {
    return 0;
  }
  else if (IsCDSetCDSQualChoice(vnp))
  {
    return FEATDEF_CDS;
  }
  else if (IsCDSetGeneQualChoice (vnp))
  {
    return FEATDEF_GENE;
  }
  else if (IsCDSetMRNAQualChoice(vnp))
  {
    return FEATDEF_mRNA;
  }
  else if (IsCDSetProteinQualChoice (vnp))
  {
    if (IsCDSetMatPeptideQualChoice (vnp))
    {
      return FEATDEF_mat_peptide_aa;
    }
    else
    {
      return FEATDEF_PROT;
    }
  }
  else
  {
    return 0;
  }
}


/* return TRUE if actually set field, FALSE otherwise */
extern Boolean 
SetCDSGeneProtField 
(SeqFeatPtr      sfp,
 ValNodePtr      vnp, 
 ApplyValuePtr   avp,
 FilterSetPtr    fsp)
{
  ValNode        vn;
  ApplyValueData local_avd;
  Uint2          choice;
  
  if (sfp == NULL || vnp == NULL || avp == NULL)
  {
    return FALSE;
  }

  choice = FeatDefTypeFromFieldList (vnp);
  if (FindFeatFromFeatDefType(choice) != sfp->data.choice)
  {
    return FALSE;
  }

  if (fsp != NULL && fsp->cgp != NULL && DoesConstraintDisqualifyFeature (sfp, fsp->cgp)) 
  {
    return FALSE;
  }
  
  vn.choice = vnp->choice;
  vn.next = NULL;
  local_avd.field_list = &vn;
  local_avd.new_text = avp->new_text;
  local_avd.etp = avp->etp;
  local_avd.text_to_replace = avp->text_to_replace;
  local_avd.where_to_replace = avp->where_to_replace;

  if (vnp->data.intvalue == 1)
  {
    /* CDS Comment */
    SetCDSComment (sfp, &local_avd, NULL);
  }
  else if (vnp->data.intvalue <= 1 + num_gene_fields)
  {
    vn.data.intvalue = vnp->data.intvalue - 1;
    SetGeneFieldString (sfp, &local_avd, NULL);
  }
  else if (vnp->data.intvalue <= 1 + num_gene_fields + num_mrna_fields)
  {
    vn.data.intvalue = vnp->data.intvalue - num_gene_fields - 1;
    SetmRNAFieldString (sfp, &local_avd, NULL);
  }
  else if (vnp->data.intvalue <= 1 + num_gene_fields + num_mrna_fields + num_protein_fields)
  {
    vn.data.intvalue = vnp->data.intvalue - num_gene_fields - num_mrna_fields - 1;
    SetProteinFieldString (sfp, &local_avd, NULL);
  }
  
  /* no need to free any part of local_avd */

  return TRUE;
}

#define RNAFIELD_PRODUCT           1
#define RNAFIELD_COMMENT           2
#define RNAFIELD_CODONS_RECOGNIZED 3
#define RNAFIELD_NCRNA_CLASS       4
#define RNAFIELD_ANTICODON         5
#define RNAFIELD_TRANSCRIPT_ID     6

static CharPtr rna_field_list [] = 
{
  "Product", "Comment", "Codons Recognized", "ncRNA class", "Anticodon", "Transcript ID"
};

static Int4 num_rna_fields = sizeof (rna_field_list) / sizeof (CharPtr);

static CharPtr GetRNAFieldName (ValNodePtr vnp)
{
  CharPtr label = NULL;
  
  if (vnp != NULL && vnp->data.intvalue >= 1)
  {
    if (vnp->data.intvalue <= num_rna_fields)
    {
      label = StringSave (rna_field_list [vnp->data.intvalue - 1]);
    }
    else if (vnp->data.intvalue <= num_rna_fields + num_gene_fields)
    {
      label = (CharPtr) MemNew ((StringLen (gene_field_list [vnp->data.intvalue - 1 - num_rna_fields]) + 6) * sizeof (Char));
      if (label != NULL)
      {
        sprintf (label, "Gene %s", gene_field_list [vnp->data.intvalue - 1 - num_rna_fields]);
      }
    }
  }
  return label;
}

extern DialoG
RNAAddFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  ValNodePtr choice_list = NULL;
  DialoG     dlg;
  Int4       i;
  
  for (i = 0; i < 4; i++)
  {
    ValNodeAddInt (&choice_list, 0, i + 1);
  }
  for (i = 0; i < num_gene_fields; i++)
  {
    ValNodeAddInt (&choice_list, 0, i + num_rna_fields + 1);
  }

  dlg = ValNodeSelectionDialog (h, choice_list, TALL_SELECTION_LIST,
                                GetRNAFieldName, NULL, 
                                IntValNodeCopy, IntValNodeMatch,
                                "RNA field", change_notify, change_userdata, 
                                FALSE);
                                
  return dlg;
}

extern DialoG
RNARemoveFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  ValNodePtr choice_list = NULL;
  DialoG     dlg;
  Int4       i;
  
  for (i = 0; i < num_rna_fields; i++)
  {
    ValNodeAddInt (&choice_list, 0, i + 1);
  }
  for (i = 0; i < num_gene_fields; i++)
  {
    ValNodeAddInt (&choice_list, 0, i + num_rna_fields + 1);
  }

  dlg = ValNodeSelectionDialog (h, choice_list, TALL_SELECTION_LIST,
                                GetRNAFieldName, NULL, 
                                IntValNodeCopy, IntValNodeMatch,
                                "RNA field", change_notify, change_userdata, 
                                FALSE);

  return dlg;
}

extern DialoG
RNAFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  ValNodePtr choice_list = NULL;
  DialoG     dlg;
  Int4       i;
  
  for (i = 0; i < 4; i++)
  {
    ValNodeAddInt (&choice_list, 0, i + 1);
  }
  for (i = 0; i < num_gene_fields; i++)
  {
    ValNodeAddInt (&choice_list, 0, i + num_rna_fields + 1);
  }

  dlg = ValNodeSelectionDialog (h, choice_list, TALL_SELECTION_LIST,
                                GetRNAFieldName, NULL, 
                                IntValNodeCopy, IntValNodeMatch,
                                "RNA field", change_notify, change_userdata, 
                                FALSE);
                                
  return dlg;
}

static CharPtr rna_subtype_list [] = { "misc_RNA", "preRna", "mRNA", "tRNA",
                                        "rRNA", "ncRNA", "tmRNA"};
static Int2    num_rna_subtypes = sizeof (rna_subtype_list) / sizeof (CharPtr); 

static CharPtr GetRNASubtypeName (ValNodePtr vnp)
{
  CharPtr label = NULL;
  
  if (vnp != NULL && vnp->choice < num_rna_subtypes)
  {
    label = StringSave (rna_subtype_list[vnp->choice]);
  }
  return label;
}

static DialoG RNASubtypeSelectionDialog
(GrouP                    h,
 Boolean                  allow_multi,
 SeqEntryPtr              sep,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  ValNodePtr choice_list = NULL;
  DialoG     dlg;
  
  ValNodeAddInt (&choice_list, 0, FEATDEF_otherRNA);
  ValNodeAddInt (&choice_list, 1, FEATDEF_preRNA);
  ValNodeAddInt (&choice_list, 2, FEATDEF_mRNA);
  ValNodeAddInt (&choice_list, 3, FEATDEF_tRNA);
  ValNodeAddInt (&choice_list, 4, FEATDEF_rRNA);
  ValNodeAddInt (&choice_list, 5, FEATDEF_ncRNA);
  ValNodeAddInt (&choice_list, 6, FEATDEF_tmRNA);

  dlg = ValNodeSelectionDialog (h, choice_list, TALL_SELECTION_LIST,
                                GetRNASubtypeName, NULL, 
                                IntValNodeCopy, IntValNodeMatch,
                                "RNA subtype", change_notify, change_userdata, 
                                allow_multi);
  return dlg;
}


extern CharPtr GetRNAFieldString (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  RnaRefPtr  rrp;
  CharPtr    str = NULL;
  ValNode    vn;
  GeneRefPtr grp;
  SeqFeatPtr gene;
  SeqMgrFeatContext context;
  Int4              field_choice;
  GBQualPtr         qual;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || vnp == NULL)
  {
    return NULL;
  }
  
  rrp = sfp->data.value.ptrvalue;
  
  while (vnp != NULL && str == NULL)
  {
    field_choice = vnp->data.intvalue;
    switch (vnp->data.intvalue)
    {
      case RNAFIELD_PRODUCT :        
        str = GetRNAProductString (sfp, NULL);
        break;
      case RNAFIELD_COMMENT :
        if (!StringHasNoText (sfp->comment))
        {
          str = StringSave (sfp->comment);
        }
        break;
      case RNAFIELD_NCRNA_CLASS :
        for (qual = sfp->qual; qual != NULL && str == NULL; qual = qual->next)
        {
          if (StringCmp (qual->qual, "ncRNA_class") == 0
              && !StringHasNoText (qual->val))
          {
            str = StringSave (qual->val);
          }
        }
        break;
      default:
        if (vnp->data.intvalue >= num_rna_fields + 1)
        {
          vn.choice = 0;
          vn.next = NULL;
          vn.data.intvalue = vnp->data.intvalue - num_rna_fields;
          grp = SeqMgrGetGeneXref (sfp);
          if (grp == NULL)
          {
            gene = SeqMgrGetOverlappingGene (sfp->location, &context);
            str = GetGeneFieldString (gene, &vn, NULL);
          }
          else
          {
            str = GetGeneFieldString (sfp, &vn, NULL);
          }
        }
        break;
          
    }
    vnp = vnp->next;
  }
  return str;
}


static Boolean IsParseabletRNAName (CharPtr name_string)
{
  if (StringHasNoText(name_string)) 
  {
    return TRUE;
  }
  else if (StringNICmp (name_string, "trna-", 5) != 0)
  {
    return FALSE;
  }
  else if (StringLen (name_string) != 8)
  {
    return FALSE;
  }
  else if (ParseTRnaString (name_string, NULL, NULL, TRUE) == 0)
  {
    return FALSE;
  }
  else
  {
    return TRUE;
  }
}


static void SettRNAName (SeqFeatPtr sfp, ApplyValuePtr avp)
{
  RnaRefPtr     rrp;
  tRNAPtr           trp;
  Uint1             new_aa;
  CharPtr           name_string;
  Boolean           justTrnaText = FALSE;
  Uint1             codon [6];
  GBQualPtr         gbqual, gbqual_last = NULL;

  if (sfp == NULL || sfp->data.value.ptrvalue == NULL 
      || sfp->idx.subtype != FEATDEF_tRNA
      || avp == NULL)
  {
    return;
  }
  
  rrp = sfp->data.value.ptrvalue;
  if (rrp->ext.choice == 0)
  {
    trp = MemNew (sizeof (tRNA));
    trp->aatype = 0;
    MemSet (trp->codon, 255, sizeof (trp->codon));
    trp->anticodon = NULL;
    rrp->ext.value.ptrvalue = trp;
    rrp->ext.choice = 2;
  }
  if (rrp->ext.choice != 2)
  {
    return;
  }
  trp = (tRNAPtr) rrp->ext.value.ptrvalue;
  
  name_string = GetRNAProductString (sfp, NULL);
  name_string = HandleApplyValue (name_string, avp);
  if (!IsParseabletRNAName(name_string))
  {
    if (trp->anticodon == NULL
        && trp->codon[0] == 255
        && trp->codon[1] == 255
        && trp->codon[2] == 255
        && trp->codon[3] == 255
        && trp->codon[4] == 255
        && trp->codon[5] == 255)
    {
      trp = MemFree (trp);
      rrp->ext.choice = 1;
      rrp->ext.value.ptrvalue = name_string;
    }
    else
    {
      trp->aa = 0;
      gbqual = sfp->qual;
      while (gbqual != NULL)
      {
        gbqual_last = gbqual;
        gbqual = gbqual->next;
      }
      gbqual = GBQualNew ();
      gbqual->qual = StringSave ("product");
      gbqual->val = name_string;
      if (gbqual_last == NULL)
      {
        sfp->qual = gbqual;
      }
      else
      {
        gbqual->next = gbqual_last->next;
        gbqual_last->next = gbqual;
      }
    }
  }
  else
  {
    new_aa = ParseTRnaString (name_string, &justTrnaText, codon, TRUE);
    trp->aa = new_aa;
    trp->aatype = 2;
    name_string = MemFree (name_string);
  }
}


static void SetRNAProduct (SeqFeatPtr sfp, ApplyValuePtr avp)
{
  RnaRefPtr rrp;
  GBQualPtr gbq, prev_gbq = NULL;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || sfp->data.value.ptrvalue == NULL || avp == NULL)
  {
    return;
  }

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;

  gbq = sfp->qual;
  while (gbq != NULL && StringCmp (gbq->qual, "product") != 0)
  {
    prev_gbq = gbq;
    gbq = gbq->next;
  }
  if (gbq != NULL)
  {
    gbq->val = HandleApplyValue (gbq->val, avp);
    if (StringHasNoText (gbq->val))
    {
      if (prev_gbq == NULL)
      {
        sfp->qual = gbq->next;
      }
      else
      {
        prev_gbq->next = gbq->next;
      }
      gbq->next = NULL;
      gbq = GBQualFree (gbq);
    }
  }
  else if (sfp->idx.subtype == FEATDEF_tRNA || rrp->ext.choice == 2)
  {
    SettRNAName (sfp, avp);
  }
  else if (rrp->ext.choice == 1
           && (StringCmp (rrp->ext.value.ptrvalue, "ncRNA") == 0
               || StringCmp (rrp->ext.value.ptrvalue, "tmRNA") == 0
               || StringCmp (rrp->ext.value.ptrvalue, "misc_RNA") == 0))
  {
    gbq = GBQualNew();
    gbq->qual = StringSave ("product");
    gbq->val = HandleApplyValue (gbq->val, avp);
    gbq->next = sfp->qual;
    sfp->qual = gbq;
  }
  else if (rrp->ext.choice == 1 || rrp->ext.choice == 0)
  {
    rrp->ext.value.ptrvalue = HandleApplyValue (rrp->ext.value.ptrvalue, avp);
    if (StringHasNoText (rrp->ext.value.ptrvalue))
    {
      rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
      rrp->ext.choice = 0;
    }
    else
    {
      rrp->ext.choice = 1;
    }
  }
}


static void SetRNAFieldString (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  RnaRefPtr     rrp;
  ApplyValuePtr avp;
  SeqFeatPtr    gene;
  GeneRefPtr    grp;
  SeqMgrFeatContext context;
  GBQualPtr         qual;
  Boolean           found = FALSE;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || userdata == NULL)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL)
  {
    return;
  }

  rrp = sfp->data.value.ptrvalue;
    
  switch (avp->field_list->data.intvalue)
  {
    case RNAFIELD_PRODUCT :
      SetRNAProduct (sfp, avp);
      break;
    case RNAFIELD_COMMENT :
      sfp->comment = HandleApplyValue (sfp->comment, avp);
      break;
    case RNAFIELD_NCRNA_CLASS :
      for (qual = sfp->qual; qual != NULL; qual = qual->next)
      {
        if (StringCmp (qual->qual, "ncRNA_class") == 0)
        {
          qual->val = HandleApplyValue (qual->val, avp);
          found = TRUE;
        }
      }
      if (!found)
      {
        qual = GBQualNew ();
        qual->qual = StringSave ("ncRNA_class");
        qual->val = StringSave (avp->new_text);
        qual->next = sfp->qual;
        sfp->qual = qual;
      }
      break;
    default:
      if (avp->field_list->data.intvalue >= num_rna_fields + 1)
      {
        avp->field_list->data.intvalue -= num_rna_fields;
        grp = SeqMgrGetGeneXref (sfp);
        if (grp == NULL)
        {
          gene = SeqMgrGetOverlappingGene (sfp->location, &context);
          SetGeneFieldString (gene, avp, fsp);
        }
        else
        {
          SetGeneFieldString (sfp, avp, fsp);
        }
        avp->field_list->data.intvalue += num_rna_fields;
      }
      break;
  }
}


static void RemoveRNAProduct (SeqFeatPtr sfp)
{
  RnaRefPtr rrp;
  GBQualPtr gbq, prev_gbq = NULL;
  tRNAPtr     trp;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || sfp->data.value.ptrvalue == NULL)
  {
    return;
  }

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;

  gbq = sfp->qual;
  while (gbq != NULL && StringCmp (gbq->qual, "product") != 0)
  {
    prev_gbq = gbq;
    gbq = gbq->next;
  }
  if (gbq != NULL)
  {
    if (prev_gbq == NULL)
    {
      sfp->qual = gbq->next;
    }
    else
    {
      prev_gbq->next = gbq->next;
    }
    gbq->next = NULL;
    gbq = GBQualFree (gbq);
  }
  else if (rrp->ext.choice == 1
           && (StringCmp (rrp->ext.value.ptrvalue, "ncRNA") == 0
               || StringCmp (rrp->ext.value.ptrvalue, "tmRNA") == 0
               || StringCmp (rrp->ext.value.ptrvalue, "misc_RNA") == 0))
  {
    /* do nothing - already have no product */
  }
  else if (rrp->ext.choice == 0)
  {
    /* do nothing - already have no product */
  }
  else if (rrp->ext.choice == 1)
  {
    rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
    rrp->ext.choice = 0;
  }
  else if (rrp->ext.choice == 2)
  {
    trp = (tRNAPtr) rrp->ext.value.ptrvalue;
    if (trp != NULL)
    {
      trp->aatype = 0;
    }
  }
}


static void RemoveRNAField (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  RnaRefPtr     rrp;
  tRNAPtr       trp;
  ApplyValuePtr avp;
  SeqFeatPtr    gene;
  GeneRefPtr    grp;
  ValNode       vn;
  SeqMgrFeatContext context;
  GBQualPtr         qual, qual_prev = NULL, qual_next;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || userdata == NULL)
  {
    return;
  }
  
  avp = (ApplyValuePtr) userdata;
  if (avp->field_list == NULL)
  {
    return;
  }
  
  rrp = sfp->data.value.ptrvalue;

  switch (avp->field_list->data.intvalue)
  {
    case RNAFIELD_PRODUCT :
      RemoveRNAProduct (sfp);
      break;
    case RNAFIELD_COMMENT :
      sfp->comment = MemFree (sfp->comment);
      break;
    case RNAFIELD_NCRNA_CLASS :
      qual = sfp->qual;
      while (qual != NULL)
      {
        qual_next = qual->next;
        if (StringCmp (qual->qual, "ncRNA_class") == 0)
        {
          if (qual_prev == NULL)
          {
            sfp->qual = qual_next;
          }
          else
          {
            qual_prev->next = qual_next;
          }
          qual->next = NULL;
          qual = GBQualFree (qual);
        }
        else
        {
          qual_prev = qual;
        }
        qual = qual_next;
      }
      break;
    case RNAFIELD_ANTICODON :
      if (rrp->ext.choice == 2) {
        trp = (tRNAPtr) rrp->ext.value.ptrvalue;
        if (trp != NULL && trp->anticodon != NULL) {
          trp->anticodon = SeqLocFree (trp->anticodon);
        }
      }
      break;
    case RNAFIELD_CODONS_RECOGNIZED :
      if (rrp->ext.choice == 2) {
        trp = (tRNAPtr) rrp->ext.value.ptrvalue;
        if (trp != NULL) {
          trp->codon [0] = 255;
          trp->codon [1] = 255;
          trp->codon [2] = 255;
          trp->codon [3] = 255;
          trp->codon [4] = 255;
          trp->codon [5] = 255;
        }
      }
      break;
    case RNAFIELD_TRANSCRIPT_ID :
      sfp->product = SeqLocFree (sfp->product);
      break;
    default:
      if (avp->field_list->data.intvalue >= num_rna_fields + 1)
      {
        vn.choice = 1;
        vn.next = NULL;
        vn.data.intvalue = avp->field_list->data.intvalue - num_rna_fields;
        grp = SeqMgrGetGeneXref (sfp);
        if (grp == NULL)
        {
          gene = SeqMgrGetOverlappingGene (sfp->location, &context);
          RemoveGeneFieldString (gene, &vn);
        }
        else
        {
          RemoveGeneFieldString (sfp, &vn);
        }
      }
      break;
  }
}

typedef struct exonfieldselection 
{
  DIALOG_MESSAGE_BLOCK
  PopuP                    exon_field;

  Boolean                  allow_none;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;  
} ExonFieldSelectionData, PNTR ExonFieldSelectionPtr;

#define EXONFIELD_ALLELE            1
#define EXONFIELD_COMMENT           2
#define EXONFIELD_EC_NUMBER         3
#define EXONFIELD_FUNCTION          4
#define EXONFIELD_OLD_LOCUS_TAG     5
#define EXONFIELD_NUMBER            6
#define EXONFIELD_PRODUCT           7

static CharPtr exon_field_list [] = 
{
  "allele", "comment", "EC_number", "function", "old_locus_tag", "number", "product"  
};
#define NUM_EXON_FIELDS 7

extern DialoG
ExonFieldSelectionDialog
(GrouP                    h,
 Boolean                  allow_none,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialog (h, allow_none,
                                      NUM_EXON_FIELDS, exon_field_list, 
                                      change_notify, change_userdata);

}

extern CharPtr GetExonFieldString (SeqFeatPtr sfp, ValNodePtr exon_field)
{
  ValNodePtr vnp;
  CharPtr    str = NULL;
  GBQualPtr  gbqual;
  
  if (sfp == NULL || exon_field == NULL || sfp->idx.subtype != FEATDEF_exon)
  {
    return NULL;
  }
  
  vnp = exon_field;
  while (vnp != NULL && StringHasNoText (str))
  {
    str = NULL;
    if (vnp->data.intvalue == EXONFIELD_COMMENT)
    {
      str = sfp->comment;
    }
    else
    {
      gbqual = sfp->qual;
      while (gbqual != NULL && StringHasNoText (str))
      {
        if (vnp->data.intvalue < NUM_EXON_FIELDS + 1
            && vnp->data.intvalue >= 1
            && StringCmp (gbqual->qual, exon_field_list[vnp->data.intvalue - 1]) == 0)
        {
          str = gbqual->val;
        }
        gbqual = gbqual->next;
      }    
    }
    vnp = vnp->next;
  }
  if (StringHasNoText (str))
  {
    str = NULL;
  }
  else
  {
    str = StringSave (str);
  }
  return str;
}

extern void RemoveExonFieldString (SeqFeatPtr sfp, ValNodePtr exon_field)
{
  ValNodePtr vnp;
  Boolean    found_nonempty = FALSE;
  GBQualPtr  gbqual, prev_qual;
  
  if (sfp == NULL || exon_field == NULL || sfp->idx.subtype != FEATDEF_exon)
  {
    return;
  }
  
  vnp = exon_field;
  while (vnp != NULL && !found_nonempty)
  {
    if (vnp->data.intvalue == EXONFIELD_COMMENT)
    {
      if (!StringHasNoText (sfp->comment))
      {
        found_nonempty = TRUE;
        if (vnp->choice != 0)
        {
          sfp->comment = MemFree (sfp->comment);
        }
      }
    }
    else
    {     
      gbqual = sfp->qual;
      prev_qual = NULL;
      while (gbqual != NULL)
      {
        if (vnp->data.intvalue <= NUM_EXON_FIELDS + 1
            && vnp->data.intvalue >= 1
            && StringCmp (gbqual->qual, exon_field_list[vnp->data.intvalue - 1]) == 0)
        {
           if (!StringHasNoText (gbqual->val))
           {
            found_nonempty = TRUE;
            if (vnp->choice != 0)
            {
              if (prev_qual == NULL)
              {
                sfp->qual = gbqual->next;
              } else {
                prev_qual->next = gbqual->next;
              }
              gbqual->next = NULL;
              GBQualFree (gbqual);
              return;
            }
          }
        }
        prev_qual = gbqual;
        gbqual = gbqual->next;
      }
    }
    vnp = vnp->next;
  }
}


#define PARSE_FIELD_BIOSRC_TAXNAME  1
#define PARSE_FIELD_BIOSRC_LINEAGE  2
#define PARSE_FIELD_BIOSRC_DIVISION 3

static CharPtr biosrc_string_list [] =
{
  "Organism Name", "Lineage", "Division"
};

static int num_biosrc_strings = sizeof (biosrc_string_list) / sizeof (CharPtr);

extern DialoG BioSourceStringDialog 
(GrouP                    h,
 Boolean                  allow_multi,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return FeatureFieldSelectionDialog (h, allow_multi,
                                      num_biosrc_strings, biosrc_string_list, 
                                      change_notify, change_userdata);
}

static CharPtr GetSourceStringFromBioSource (BioSourcePtr biop, ValNodePtr vnp)
{
  if (biop == NULL || vnp == NULL || biop->org == NULL)
  {
    return NULL;
  }
  
  if (vnp->data.intvalue == PARSE_FIELD_BIOSRC_TAXNAME
      && !StringHasNoText (biop->org->taxname))
  {
    return StringSave (biop->org->taxname);
  }
  else if (vnp->data.intvalue == PARSE_FIELD_BIOSRC_LINEAGE
           && biop->org->orgname != NULL
           && !StringHasNoText (biop->org->orgname->lineage))
  {
    return StringSave (biop->org->orgname->lineage);
  }
  else if (vnp->data.intvalue == PARSE_FIELD_BIOSRC_DIVISION
           && biop->org->orgname != NULL
           && !StringHasNoText (biop->org->orgname->div))
  {
    return StringSave (biop->org->orgname->div);
  }
  else
  {
    return NULL;
  }
}

static CharPtr 
GetSourceFeatureString 
(SeqFeatPtr   sfp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || vnp == NULL)
  {
    return NULL;
  }
  
  return GetSourceStringFromBioSource (sfp->data.value.ptrvalue, vnp);
}

static CharPtr 
GetSourceDescriptorString 
(SeqDescrPtr  sdp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sdp == NULL || sdp->choice != Seq_descr_source || vnp == NULL)
  {
    return NULL;
  }
  return GetSourceStringFromBioSource (sdp->data.ptrvalue, vnp);
}


static CharPtr GetDbxrefFromBioSource (BioSourcePtr biop, ValNodePtr vnp)
{
  ValNodePtr dbx;
  DbtagPtr   dbtag;
  Char       buf[20];

  if (biop == NULL || vnp == NULL || vnp->data.ptrvalue == NULL || biop->org == NULL)
  {
    return NULL;
  }
  
  dbx = biop->org->db;
  while (dbx != NULL)
  {
    dbtag = (DbtagPtr) dbx->data.ptrvalue;
    if (dbtag != NULL && dbtag->tag != NULL
        && StringCmp (dbtag->db, vnp->data.ptrvalue) == 0)
    {
      if (dbtag->tag->str == NULL)
      {
        sprintf (buf, "%d", dbtag->tag->id);
        return StringSave (buf);
      }
      else
      {
        return StringSave (dbtag->tag->str);
      }
    }
    dbx = dbx->next;
  }
  return NULL;
}

static CharPtr 
GetBioSourceFeatureDbxrefString 
(SeqFeatPtr   sfp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || vnp == NULL)
  {
    return NULL;
  }
  
  return GetDbxrefFromBioSource (sfp->data.value.ptrvalue, vnp);
}

static CharPtr 
GetBioSourceDescriptorDbxrefString 
(SeqDescrPtr  sdp,
 ValNodePtr   vnp,
 FilterSetPtr fsp)
{
  if (sdp == NULL || sdp->choice != Seq_descr_source || vnp == NULL)
  {
    return NULL;
  }
  return GetDbxrefFromBioSource (sdp->data.ptrvalue, vnp);
}


#define TEXT_PORTION_START_AFTER 1
#define TEXT_PORTION_START_WITH  2
#define TEXT_PORTION_END_AFTER   1
#define TEXT_PORTION_END_WITH    2

typedef struct textportiondialog
{
  DIALOG_MESSAGE_BLOCK

  GrouP  start_choice;
  TexT   start_text;
  GrouP  end_choice;
  TexT   end_text;
  ButtoN rem_before;
  ButtoN also_rem_before;
  ButtoN rem_after;
  ButtoN also_rem_after;
  ButtoN insensitive;
  ButtoN whole_word;

  Boolean inside;

  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} TextPortionXDialogData, PNTR TextPortionXDialogPtr;


static void OutsideEnableDisable (TextPortionXDialogPtr tp)
{
  if (tp == NULL || tp->inside)
  {
    return;
  }
  if (GetStatus (tp->rem_before)) 
  {
    Enable (tp->start_text);
    Enable (tp->also_rem_before);
  }
  else
  {
    Disable (tp->start_text);
    Disable (tp->also_rem_before);
  }
  if (GetStatus (tp->rem_after)) 
  {
    Enable (tp->end_text);
    Enable (tp->also_rem_after);
  }
  else
  {
    Disable (tp->end_text);
    Disable (tp->also_rem_after);
  }
}

static void ResetTextPortionXDialog (TextPortionXDialogPtr tp)
{
  if (tp == NULL)
  {
    return;
  }
  if (tp->inside)
  {
    SetValue (tp->start_choice, TEXT_PORTION_START_AFTER);
    SetValue (tp->end_choice, TEXT_PORTION_END_AFTER);
  }
  else
  {
    SetStatus (tp->rem_before, FALSE);
    SetStatus (tp->also_rem_before, FALSE);
    SetStatus (tp->rem_after, FALSE);
    SetStatus (tp->also_rem_after, FALSE);
  }
  SetTitle (tp->start_text, "");
  SetTitle (tp->end_text, "");
  SetStatus (tp->insensitive, FALSE);
  SetStatus (tp->whole_word, FALSE);
  OutsideEnableDisable (tp);
}

static void TextPortionXToDialog (DialoG d, Pointer data)
{
  TextPortionXDialogPtr tdlg;
  TextPortionXPtr       tdata;
  Int4                 start_choice, end_choice;
  
  tdlg = (TextPortionXDialogPtr) GetObjectExtra (d);
  if (tdlg == NULL)
  {
    return;
  }
  tdata = (TextPortionXPtr) data;
  ResetTextPortionXDialog (tdlg);  
  if (tdata != NULL)
  {
    start_choice = tdata->end_choice;
    end_choice = tdata->end_choice;
    if (start_choice < TEXT_PORTION_START_AFTER || start_choice > TEXT_PORTION_START_WITH)
    {
      start_choice = TEXT_PORTION_START_AFTER;
    }
    if (end_choice < TEXT_PORTION_END_AFTER || end_choice > TEXT_PORTION_END_WITH)
    {
      end_choice = TEXT_PORTION_END_AFTER;
    }
    if (tdlg->inside)
    {
      SetValue (tdlg->start_choice, start_choice);
      SetValue (tdlg->end_choice, end_choice);
    }
    else
    {
      SetStatus (tdlg->rem_before, StringHasNoText (tdata->start_text));
      SetStatus (tdlg->rem_after, StringHasNoText (tdata->end_text));
      if (start_choice == TEXT_PORTION_START_AFTER)
      {
        SetStatus (tdlg->also_rem_before, TRUE);
      }
      else
      {
        SetStatus (tdlg->also_rem_before, FALSE);
      }
      if (end_choice == TEXT_PORTION_END_AFTER)
      {
        SetStatus (tdlg->also_rem_after, TRUE);
      }
      else
      {
        SetStatus (tdlg->also_rem_after, FALSE);
      }
    }
    if (tdata->start_text != NULL)
    {
      SetTitle (tdlg->start_text, tdata->start_text);
    }
    if (tdata->end_text != NULL)
    {
      SetTitle (tdlg->end_text, tdata->end_text);
    }
    SetStatus (tdlg->insensitive, tdata->insensitive);
    SetStatus (tdlg->whole_word, tdata->whole_word);
  }
  OutsideEnableDisable (tdlg);
}

static Pointer DialogToTextPortionX (DialoG d)
{
  TextPortionXDialogPtr tdlg;
  TextPortionXPtr       tdata;

  tdlg = (TextPortionXDialogPtr) GetObjectExtra (d);
  if (tdlg == NULL)
  {
    return NULL;
  }

  tdata = (TextPortionXPtr) MemNew (sizeof (TextPortionXData));
  if (tdata != NULL)
  {
    if (tdlg->inside)
    {
      tdata->start_choice = GetValue (tdlg->start_choice);
      tdata->end_choice = GetValue (tdlg->end_choice);
      tdata->start_text = JustSaveStringFromText (tdlg->start_text);
      tdata->end_text = JustSaveStringFromText (tdlg->end_text);
    }
    else
    {
      if (GetStatus (tdlg->rem_before))
      {
        tdata->start_text = JustSaveStringFromText (tdlg->start_text);
      }
      else
      {
        tdata->start_text = NULL;
      }
      if (GetStatus (tdlg->rem_after))
      {
        tdata->end_text = JustSaveStringFromText (tdlg->end_text);
      }
      else
      {
        tdata->end_text = NULL;
      }

      if (GetStatus (tdlg->also_rem_before))
      {
        tdata->start_choice = TEXT_PORTION_START_AFTER;
      }
      else
      {
        tdata->start_choice = TEXT_PORTION_START_WITH;
      }
      if (GetStatus (tdlg->also_rem_after))
      {
        tdata->end_choice = TEXT_PORTION_END_AFTER;
      }
      else
      {
        tdata->end_choice = TEXT_PORTION_END_WITH;
      }

    }
        
    tdata->insensitive = GetStatus (tdlg->insensitive);
    tdata->whole_word = GetStatus (tdlg->whole_word);
  }
  return tdata;
}

static void TextPortionXMessage (DialoG d, Int2 mssg)

{
  TextPortionXDialogPtr tp;

  tp = (TextPortionXDialogPtr) GetObjectExtra (d);
  if (tp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        ResetTextPortionXDialog (tp);        
        break;
      case VIB_MSG_ENTER :
        Select (tp->start_text);
        break;
      case NUM_VIB_MSG + 1 :
        SetTitle (tp->start_text, "");
        SetTitle (tp->end_text, "");
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestTextPortionXDialog (DialoG d)

{
  return NULL;
}

static void ChangeTextPortionXGroup (GrouP g)
{
  TextPortionXDialogPtr tp;

  tp = (TextPortionXDialogPtr) GetObjectExtra (g);
  if (tp == NULL) return;

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

static void ChangeTextPortionXText (TexT t)
{
  TextPortionXDialogPtr tp;

  tp = (TextPortionXDialogPtr) GetObjectExtra (t);
  if (tp == NULL) return;

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


static void ChangeTextPortionXBtn (ButtoN b)
{
  TextPortionXDialogPtr tp;

  tp = (TextPortionXDialogPtr) GetObjectExtra (b);
  if (tp == NULL) return;
  OutsideEnableDisable (tp);

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



extern DialoG TextPortionXDialogEx (GrouP h, Boolean inside, Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  TextPortionXDialogPtr tp;
  GrouP                p, g1, g2;
  
  tp = (TextPortionXDialogPtr) MemNew (sizeof (TextPortionXDialogData));
  if (tp == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, tp, StdCleanupExtraProc);

  tp->dialog = (DialoG) p;
  tp->todialog = TextPortionXToDialog;
  tp->fromdialog = DialogToTextPortionX;
  tp->dialogmessage = TextPortionXMessage;
  tp->testdialog = TestTextPortionXDialog;

  tp->change_notify = change_notify;
  tp->change_userdata = change_userdata;
  tp->inside = inside;

  g1 = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (g1, 10, 10);

  if (inside) 
  {
    StaticPrompt (g1, "Between", 0, popupMenuHeight, programFont, 'r');
    tp->start_choice = HiddenGroup (g1, 2, 0, ChangeTextPortionXGroup);
    RadioButton (tp->start_choice, "just after");
    RadioButton (tp->start_choice, "starting at");
    SetValue (tp->start_choice, TEXT_PORTION_START_AFTER);
    SetObjectExtra (tp->start_choice, tp, NULL);

    tp->start_text = DialogText (g1, "", 10, ChangeTextPortionXText);
    SetObjectExtra (tp->start_text, tp, NULL);
    
    StaticPrompt (g1, "And", 0, popupMenuHeight, programFont, 'r');
    tp->end_choice = HiddenGroup (g1, 2, 0, ChangeTextPortionXGroup);
    RadioButton (tp->end_choice, "up to");
    RadioButton (tp->end_choice, "including");
    SetValue (tp->end_choice, TEXT_PORTION_END_AFTER);
    SetObjectExtra (tp->end_choice, tp, NULL);
      
    tp->end_text = DialogText (g1, "", 10, ChangeTextPortionXText);
    SetObjectExtra (tp->end_text, tp, NULL);
  }
  else
  {
    tp->rem_before = CheckBox (g1, "Before", ChangeTextPortionXBtn);
    SetObjectExtra (tp->rem_before, tp, NULL);
    tp->start_text = DialogText (g1, "", 10, ChangeTextPortionXText);
    SetObjectExtra (tp->start_text, tp, NULL);
    tp->also_rem_before = CheckBox (g1, "Also Remove Entered Text", ChangeTextPortionXBtn);

    tp->rem_after = CheckBox (g1, "After", ChangeTextPortionXBtn);
    SetObjectExtra (tp->rem_after, tp, NULL);
    tp->end_text = DialogText (g1, "", 10, ChangeTextPortionXText);
    SetObjectExtra (tp->end_text, tp, NULL);
    tp->also_rem_after = CheckBox (g1, "Also Remove Entered Text", ChangeTextPortionXBtn);    
  }
  
  g2 = HiddenGroup (p, 2, 0, NULL);
  tp->insensitive = CheckBox (g2, "Case insensitive", ChangeTextPortionXBtn);
  SetObjectExtra (tp->insensitive, tp, NULL);
  tp->whole_word = CheckBox (g2, "Whole word", ChangeTextPortionXBtn);
  SetObjectExtra (tp->whole_word, tp, NULL);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) g2, NULL);
  
  ResetTextPortionXDialog (tp);
  return (DialoG) p;
}

extern DialoG TextPortionXDialog (GrouP h)
{
  return TextPortionXDialogEx (h, TRUE, NULL, NULL);
}

extern TextPortionXPtr TextPortionXFree (TextPortionXPtr tp)
{
  if (tp == NULL) return NULL;
  tp->start_text = MemFree (tp->start_text);
  tp->end_text = MemFree (tp->end_text);
  tp = MemFree (tp->end_text);
  return tp;
}


static TextPortionXPtr TextPortionXNew (void)
{
  TextPortionXPtr tp;

  tp = (TextPortionXPtr) MemNew (sizeof (TextPortionXData));
  return tp;
}


static Boolean IsWholeWordMatch (CharPtr start, CharPtr found, Int4 match_len)
{
  Boolean rval = TRUE;
  Char    char_after;
  Char    char_before;
  
  if (match_len == 0)
  {
    rval = TRUE;
  }
  else if (start == NULL || found == NULL)
  {
    rval = FALSE;
  }
  else
  {
      char_after = *(found + match_len);
    if (found != start)
      {
        char_before = *(found - 1);
        if (isalpha ((Int4) char_before) || isdigit ((Int4) char_before))
        {
          rval = FALSE;
        }
      }
      if (char_after != 0 && (isalpha ((Int4) char_after) || isdigit ((Int4)char_after)))
      {
        rval = FALSE;
      }   
  }
  return rval;
}

extern void 
FindTextPortionXInString 
(CharPtr        str, 
 TextPortionXPtr tp, 
 CharPtr PNTR   ploc, 
 Int4Ptr        plen)
{
  CharPtr found_start, found_end;
  Int4    found_len;
  
  if (ploc == NULL || plen == NULL || tp == NULL)
  {
    return;
  }
  *ploc = NULL;
  *plen = 0;

  if (str == NULL)
  {
    return;
  }
  
  if (tp->start_text == NULL || tp->start_text [0] == 0)
  {
    found_start = str;
  }
  else
  {
    if (tp->insensitive)
    {
      found_start = StringISearch (str, tp->start_text);
    }
    else
    {
      found_start = StringSearch (str, tp->start_text);
    }
    
    if (tp->whole_word && ! IsWholeWordMatch (str, found_start, StringLen (tp->start_text)))
    {
      found_start = NULL;
    }
  }
  
  if (found_start == NULL)
  {
    return;
  }
  

  
  if (tp->start_choice == TEXT_PORTION_START_AFTER)
  {
    found_start += StringLen (tp->start_text);
  }
  
  if (tp->end_text == NULL || tp->end_text [0] == 0)
  {
    found_len = StringLen (found_start);
  }
  else
  {
    if (tp->insensitive)
    {
      found_end = StringISearch (found_start, tp->end_text);
    }
    else
    {
      found_end = StringSearch (found_start, tp->end_text);
    }
    if (tp->whole_word && ! IsWholeWordMatch (str, found_end, StringLen (tp->end_text)))
    {
      found_end = NULL;
    }    
    
    if (found_end == NULL)
    {
      found_len = 0;
    }
    else if (tp->end_choice == TEXT_PORTION_END_WITH)
    {
      found_len = (Int4)(found_end - found_start) + StringLen (tp->end_text);
    }
    else
    {
      found_len = found_end - found_start;
    }
  }

  if (found_len > 0)
  {
    *ploc = found_start;
    *plen = found_len;
  }
}

static void ClearDialogBtn (ButtoN b)
{
  DialoG d;
  
  d = (DialoG) GetObjectExtra (b);
  
  PointerToDialog (d, NULL);
}

typedef struct changecasedlg {
  DIALOG_MESSAGE_BLOCK
  GrouP change_case;
  GrouP options;
  ButtoN keep_cap_before_punct;
  ButtoN keep_cap_before_cap;
  ButtoN keep_cap_before_digit;
  ButtoN remove_comma;
} ChangeCaseDlgData, PNTR ChangeCaseDlgPtr;

typedef enum {
  eChangeCaseNone = 1,
  eChangeCaseAllLower,
  eChangeCaseCapFirst,
  eChangeCaseLowerFirstRestNoChange,
  eChangeCaseOptions
} EChangeCaseRule;

typedef struct changecase {
  EChangeCaseRule change;
  FixProductCapData fc;
} ChangeCaseData, PNTR ChangeCasePtr;


static void ChangeCapitalizationChoice (GrouP g)
{
  ChangeCaseDlgPtr dlg;

  dlg = (ChangeCaseDlgPtr) GetObjectExtra (g);
  if (dlg == NULL) {
    return;
  }

  if (GetValue (dlg->change_case) == eChangeCaseOptions) {
    Enable (dlg->options);
  } else {
    Disable (dlg->options);
  }
}


static void DataToChangeCaseDialog (DialoG d, Pointer data)
{
  ChangeCasePtr    ccp = (ChangeCasePtr) data;
  ChangeCaseDlgPtr dlg = (ChangeCaseDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;

  if (ccp == NULL || ccp->change == eChangeCaseNone) {
    SetValue (dlg->change_case, eChangeCaseNone);
  } else {
    SetValue (dlg->change_case, ccp->change);
    if (ccp->change == eChangeCaseOptions) {
      SetStatus (dlg->keep_cap_before_cap, ccp->fc.keep_cap_before_cap);
      SetStatus (dlg->keep_cap_before_punct, ccp->fc.keep_cap_before_punct);
      SetStatus (dlg->keep_cap_before_digit, ccp->fc.keep_cap_before_digit);
      if (StringChr (ccp->fc.remove_list, ',') == NULL) {
        SetStatus (dlg->remove_comma, FALSE);
      } else {
        SetStatus (dlg->remove_comma, TRUE);
      }
    }
  }
  ChangeCapitalizationChoice (dlg->options);
}

static Pointer ChangeCaseDialogToData (DialoG d)
{
  ChangeCaseDlgPtr dlg = (ChangeCaseDlgPtr) GetObjectExtra (d);
  ChangeCasePtr    ccp = (ChangeCasePtr) MemNew (sizeof (ChangeCaseData));

  if (dlg == NULL) {
    ccp->change = eChangeCaseNone;
    MemSet (&(ccp->fc), 0, sizeof (FixProductCapData));
  } else {
    ccp->change = GetValue (dlg->change_case);
    if (ccp->change == eChangeCaseOptions) {
      ccp->fc.keep_cap_before_cap = GetStatus (dlg->keep_cap_before_cap);
      ccp->fc.keep_cap_before_punct = GetStatus (dlg->keep_cap_before_punct);
      ccp->fc.keep_cap_before_digit = GetStatus (dlg->keep_cap_before_digit);
      if (GetStatus (dlg->remove_comma)) {
        ccp->fc.remove_list = ",";
      } else {
        ccp->fc.remove_list = NULL;
      }
    } else {
      MemSet (&(ccp->fc), 0, sizeof (FixProductCapData));
    }
  }
  return ccp;
}


static DialoG ChangeCaseDialog (GrouP h)
{
  ChangeCaseDlgPtr dlg;
  GrouP            p;
  
  dlg = (ChangeCaseDlgPtr) MemNew (sizeof (ChangeCaseDlgData));
  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 = DataToChangeCaseDialog;
  dlg->fromdialog = ChangeCaseDialogToData;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;

  dlg->change_case = NormalGroup (p, 3, 0, "Capitalization", programFont, ChangeCapitalizationChoice);
  SetObjectExtra (dlg->change_case, dlg, NULL);
  SetGroupSpacing (dlg->change_case, 10, 10);

  RadioButton (dlg->change_case, "No change");
  RadioButton (dlg->change_case, "To lower");
  RadioButton (dlg->change_case, "First cap, rest lower");
  RadioButton (dlg->change_case, "First lower, rest no change");
  RadioButton (dlg->change_case, "Special options");
  
  dlg->options = HiddenGroup (p, 0, 4, NULL);
  dlg->keep_cap_before_punct = CheckBox (dlg->options, "Keep caps before punctuation", NULL);
  dlg->keep_cap_before_cap = CheckBox (dlg->options, "Keep caps before caps", NULL);
  dlg->keep_cap_before_digit = CheckBox (dlg->options, "Keep caps before numbers", NULL);
  dlg->remove_comma = CheckBox (dlg->options, "Remove comma", NULL);

  SetValue (dlg->change_case, eChangeCaseNone);
  Disable (dlg->options);

  return (DialoG) p;
}


static void ChangeCase (CharPtr PNTR pText, ChangeCasePtr ccp, ValNodePtr orgnames)
{
  if (ccp != NULL) {
    switch (ccp->change) {
      case eChangeCaseNone:
        break;
      case eChangeCaseAllLower:
        FixCapitalizationInTitle (pText, FALSE, orgnames);
        break;
      case eChangeCaseCapFirst:
        FixCapitalizationInTitle (pText, TRUE, orgnames);
        break;
      case eChangeCaseLowerFirstRestNoChange:
        if (pText != NULL && *pText != NULL && isalpha (**pText)) {
          **pText = tolower (**pText);
        }
        break;
      case eChangeCaseOptions:
        FixProductCapitalizationInString (pText, &(ccp->fc));
        break;
    }
  }
}

typedef struct stringconstraintdialog 
{
  DIALOG_MESSAGE_BLOCK
  PopuP  match_choice;
  TexT   match_text;
  ButtoN insensitive;
  ButtoN whole_word;   
} StringConstraintDialogXData, PNTR StringConstraintDialogXPtr;

static void ResetStringConstraintDialogX (StringConstraintDialogXPtr scdp)
{
  if (scdp == NULL) return;
  
  SetValue (scdp->match_choice, 1);
  SetTitle (scdp->match_text, "");
  SetStatus (scdp->insensitive, FALSE);
  SetStatus (scdp->whole_word, FALSE);
}


static Int4 GetPopupPosForStringConstraint (StringConstraintXPtr scp)
{
  Int4 rval = 1;

  if (scp == NULL) return 1;

  switch (scp->match_location)
  {
    case eStringConstraintContains:
      if (scp->not_present) 
      {
        rval = 2;
      } else {
        rval = 1;
      }
      break;
    case eStringConstraintEquals:
      if (scp->not_present) {
        rval = 4;
      } else {
        rval = 3;
      }
      break;
    case eStringConstraintStarts:
      if (scp->not_present) {
        rval = 9;
      } else {
        rval = 5;
      }
      break;
    case eStringConstraintEnds:
      if (scp->not_present) {
        rval = 10;
      } else {
        rval = 6;
      }
      break;
    case eStringConstraintInList:
      if (scp->not_present) {
        rval = 8;
      } else {
        rval = 7;
      }
      break;
  }
  return rval;
}


static void StringConstraintToDialog (DialoG d, Pointer data)

{
  StringConstraintDialogXPtr scdp;
  StringConstraintXPtr       scp;

  scdp = (StringConstraintDialogXPtr) GetObjectExtra (d);
  scp = (StringConstraintXPtr) data;
  if (scdp == NULL)
  {
    return;
  }

  if (scp == NULL)
  {
    ResetStringConstraintDialogX (scdp);
  }
  else
  {
    SetValue (scdp->match_choice, GetPopupPosForStringConstraint (scp));
    
    if (StringHasNoText (scp->match_text))
    {
      SetTitle (scdp->match_text, " ");
    }
    else
    {
      SetTitle (scdp->match_text, scp->match_text);
    }
    
    SetStatus (scdp->insensitive, scp->insensitive);
    SetStatus (scdp->whole_word, scp->whole_word);
  }
}

static Pointer DialogToStringConstraint (DialoG d)

{
  StringConstraintDialogXPtr scdp;
  StringConstraintXPtr       scp;
  Int4                      match_choice;

  scdp = (StringConstraintDialogXPtr) GetObjectExtra (d);
  if (scdp == NULL)
  {
    return NULL;
  }
  scp = (StringConstraintXPtr) MemNew (sizeof (StringConstraintData));
  if (scp != NULL)
  {
    scp->match_text = SaveStringFromText (scdp->match_text);
    scp->insensitive = GetStatus (scdp->insensitive);
    scp->whole_word = GetStatus (scdp->whole_word);
    match_choice = GetValue (scdp->match_choice);
    switch (match_choice)
    {
      case 1:
        scp->match_location = eStringConstraintContains;
        scp->not_present = FALSE;
        break;
      case 2:
        scp->match_location = eStringConstraintContains;
        scp->not_present = TRUE;
        break;
      case 3:
        scp->match_location = eStringConstraintEquals;
        scp->not_present = FALSE;
        break;
      case 4:
        scp->match_location = eStringConstraintEquals;
        scp->not_present = TRUE;
        break;
      case 5:
        scp->match_location = eStringConstraintStarts;
        scp->not_present = FALSE;
        break;
      case 6:
        scp->match_location = eStringConstraintEnds;
        scp->not_present = FALSE;
        break;
      case 7:
        scp->match_location = eStringConstraintInList;
        scp->not_present = FALSE;
        break;
      case 8:
        scp->match_location = eStringConstraintInList;
        scp->not_present = TRUE;
        break;
      case 9:
        scp->match_location = eStringConstraintStarts;
        scp->not_present = TRUE;
        break;
      case 10:
        scp->match_location = eStringConstraintEnds;
        scp->not_present = TRUE;
        break;
      default:
        scp->match_location = eStringConstraintContains;
        scp->not_present = FALSE;
        break;
    }
  }
  return scp;
}

static void StringConstraintMessage (DialoG d, Int2 mssg)

{
  StringConstraintDialogXPtr scdp;

  scdp = (StringConstraintDialogXPtr) GetObjectExtra (d);
  if (scdp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        ResetStringConstraintDialogX (scdp);        
        break;
      case VIB_MSG_ENTER :
        Select (scdp->match_text);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestStringConstraintDialogX (DialoG d)

{
  return NULL;
}

extern DialoG StringConstraintDialogX (GrouP h, CharPtr label, Boolean clear_btn)

{
  StringConstraintDialogXPtr scdp;
  GrouP                     p, g, k;
  ButtoN                    b = NULL;
  
  scdp = (StringConstraintDialogXPtr) MemNew (sizeof (StringConstraintDialogXData));
  if (scdp == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, scdp, StdCleanupExtraProc);

  scdp->dialog = (DialoG) p;
  scdp->todialog = StringConstraintToDialog;
  scdp->fromdialog = DialogToStringConstraint;
  scdp->dialogmessage = StringConstraintMessage;
  scdp->testdialog = TestStringConstraintDialogX;

  g = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  
  if (!StringHasNoText (label))
  {
    StaticPrompt (g, label, 0, dialogTextHeight, systemFont, 'l');
  }
  
  scdp->match_choice = PopupList (g, TRUE, NULL);
  PopupItem (scdp->match_choice, "Contains");
  PopupItem (scdp->match_choice, "Does not contain");
  PopupItem (scdp->match_choice, "Equals");
  PopupItem (scdp->match_choice, "Does not equal");
  PopupItem (scdp->match_choice, "Starts with");
  PopupItem (scdp->match_choice, "Ends with");
  PopupItem (scdp->match_choice, "Is one of");
  PopupItem (scdp->match_choice, "Is not one of");
  PopupItem (scdp->match_choice, "Does not start with");
  PopupItem (scdp->match_choice, "Does not end with");
  SetValue (scdp->match_choice, 1);
  scdp->match_text = DialogText (g, "", 15, NULL);
  
  k = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (k, 10, 10);
  scdp->insensitive = CheckBox (k, "Case Insensitive", NULL);
  scdp->whole_word = CheckBox (k, "Whole Word", NULL);
  
  if (clear_btn)
  {
    b = PushButton (p, "Clear Constraint", ClearDialogBtn);
    SetObjectExtra (b, p, NULL);
  }

  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) k, (HANDLE) b, NULL);
    
  return (DialoG) p;
}


typedef struct pseudoconstraintdialog 
{
  DIALOG_MESSAGE_BLOCK
  GrouP  pseudo_choice;  
  DialoG feature_choice;
} PseudoConstraintDialogData, PNTR PseudoConstraintDialogPtr;

static ENUM_ALIST(pseudoconstraint_alist)
{"Any Feature",   FEATDEF_ANY  },
{"CDS",   FEATDEF_CDS  },
{"Gene",  FEATDEF_GENE },
{"mRNA",  FEATDEF_mRNA },
END_ENUM_ALIST

static void ResetPseudoConstraintDialog (PseudoConstraintDialogPtr pcdp)
{
  if (pcdp != NULL)
  {
    SetValue (pcdp->pseudo_choice, 1);
    SendMessageToDialog (pcdp->feature_choice, VIB_MSG_INIT);
  }
}

static void PseudoConstraintToDialog (DialoG d, Pointer data)
{
  PseudoConstraintDialogPtr pcdp;
  PseudoConstraintPtr       pcp;
  ValNode                   vn;

  pcdp = (PseudoConstraintDialogPtr) GetObjectExtra (d);
  
  if (pcdp != NULL)
  {
    ResetPseudoConstraintDialog (pcdp);
    pcp = (PseudoConstraintPtr) data;
    if (pcp != NULL)
    {
      if (pcp->is_pseudo)
      {
        SetValue (pcdp->pseudo_choice, 1);
      }
      else
      {
        SetValue (pcdp->pseudo_choice, 2);
      }
      vn.choice = pcp->featdef_type;
      vn.data.ptrvalue = NULL;
      vn.next = NULL;
      PointerToDialog (pcdp->feature_choice, &vn);
    }
  }
}

static Pointer DialogToPseudoConstraint (DialoG d)
{
  PseudoConstraintDialogPtr pcdp;
  PseudoConstraintPtr       pcp;  
  ValNodePtr                vnp;

  pcdp = (PseudoConstraintDialogPtr) GetObjectExtra (d);
  if (pcdp == NULL) return NULL;
  pcp = (PseudoConstraintPtr) MemNew (sizeof (PseudoConstraintData));
  if (pcp != NULL)
  {
    if (GetValue (pcdp->pseudo_choice) == 1)
    {
      pcp->is_pseudo = TRUE;
    }
    else
    {
      pcp->is_pseudo = FALSE;
    }
    vnp = DialogToPointer (pcdp->feature_choice);
    if (vnp == NULL)
    {
      pcp->featdef_type = FEATDEF_ANY;
    }
    else
    {
      pcp->featdef_type = vnp->choice;
    }
    ValNodeFreeData (vnp);
  }
  return pcp;
}

static void PseudoConstraintMessage (DialoG d, Int2 mssg)

{
  PseudoConstraintDialogPtr pcdp;

  pcdp = (PseudoConstraintDialogPtr) GetObjectExtra (d);
  if (pcdp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        ResetPseudoConstraintDialog (pcdp);        
        break;
      case VIB_MSG_ENTER :
        Select (pcdp->feature_choice);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestPseudoConstraintDialog (DialoG d)

{
  return NULL;
}

static DialoG PseudoConstraintDialog (GrouP h)
{
  PseudoConstraintDialogPtr pcdp;
  GrouP                     p;
  
  pcdp = (PseudoConstraintDialogPtr) MemNew (sizeof (PseudoConstraintDialogData));
  if (pcdp == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, 2, 0, NULL);
  SetObjectExtra (p, pcdp, StdCleanupExtraProc);
  SetGroupSpacing (p, 10, 10);

  pcdp->dialog = (DialoG) p;
  pcdp->todialog = PseudoConstraintToDialog;
  pcdp->fromdialog = DialogToPseudoConstraint;
  pcdp->dialogmessage = PseudoConstraintMessage;
  pcdp->testdialog = TestPseudoConstraintDialog;

  pcdp->feature_choice = EnumAssocSelectionDialog (p, pseudoconstraint_alist,
                                                   "feature", FALSE, NULL, NULL);

  pcdp->pseudo_choice = HiddenGroup (p, 2, 0, NULL);
  RadioButton (pcdp->pseudo_choice, "Is Pseudo");
  RadioButton (pcdp->pseudo_choice, "Is Not Pseudo");
  SetValue (pcdp->pseudo_choice, 1);
  
  return (DialoG) p;  
}

typedef struct choiceconstraintdialog 
{
  DIALOG_MESSAGE_BLOCK
  GrouP               constraint_type;
  DialoG              present_qual_choice;
  DialoG              string_qual_choice;
  DialoG              string_constraint;
  DialoG              pseudo_constraint;
  DialoG              match_1;
  DialoG              match_2;
  FreeValNodeProc     free_vn_proc;
  CopyValNodeDataProc copy_vn_proc;
} ChoiceConstraintDialogData, PNTR ChoiceConstraintDialogPtr;

static void ChangeChoiceConstraintType (GrouP p)
{
  ChoiceConstraintDialogPtr scdp;
  Int4                      constraint_type;

  scdp = (ChoiceConstraintDialogPtr) GetObjectExtra (p);
  if (scdp == NULL)
  {
    return;
  }
  
  constraint_type = GetValue (scdp->constraint_type);
  if (constraint_type == CHOICE_CONSTRAINT_QUAL_PRESENT)
  {
    Enable (scdp->present_qual_choice);
    Disable (scdp->string_qual_choice);
    Disable (scdp->string_constraint);
    Disable (scdp->match_1);
    Disable (scdp->match_2);
    SafeDisable (scdp->pseudo_constraint);
  }
  else if (constraint_type == CHOICE_CONSTRAINT_STRING)
  {
    Enable (scdp->string_qual_choice);
    Enable (scdp->string_constraint);
    Disable (scdp->present_qual_choice);
    Disable (scdp->match_1);
    Disable (scdp->match_2);
    SafeDisable (scdp->pseudo_constraint);
  }
  else if (constraint_type == CHOICE_CONSTRAINT_MATCH)
  {
    Enable (scdp->match_1);
    Enable (scdp->match_2);
    Disable (scdp->string_qual_choice);
    Disable (scdp->string_constraint);
    Disable (scdp->present_qual_choice);
    SafeDisable (scdp->pseudo_constraint);
  }
  else if (constraint_type == CHOICE_CONSTRAINT_PSEUDO)
  {
    Disable (scdp->present_qual_choice);
    Disable (scdp->string_qual_choice);
    Disable (scdp->string_constraint);
    Disable (scdp->match_1);
    Disable (scdp->match_2);
    Enable (scdp->pseudo_constraint);
  }
  else
  {
    Disable (scdp->present_qual_choice);
    Disable (scdp->string_qual_choice);
    Disable (scdp->string_constraint);
    Disable (scdp->match_1);
    Disable (scdp->match_2);
    SafeDisable (scdp->pseudo_constraint);
  }
}

static void ResetChoiceConstraintDialog (ChoiceConstraintDialogPtr scdp)
{
  if (scdp != NULL)  
  {
    SetValue (scdp->constraint_type, CHOICE_CONSTRAINT_ANY);
    PointerToDialog (scdp->present_qual_choice, NULL);
    PointerToDialog (scdp->string_qual_choice, NULL);
    PointerToDialog (scdp->string_constraint, NULL);
    PointerToDialog (scdp->pseudo_constraint, NULL);
    ChangeChoiceConstraintType (scdp->constraint_type);
  }
}

static void ChoiceConstraintToDialog (DialoG d, Pointer data)
{
  ChoiceConstraintDialogPtr src_dlg;
  ChoiceConstraintPtr       src_data;
  Int4 constraint_type;
  
  src_dlg = (ChoiceConstraintDialogPtr) GetObjectExtra (d);
  if (src_dlg == NULL)
  {
    return;
  }
  src_data = (ChoiceConstraintPtr) data;
  if (src_data == NULL)
  {
    ResetChoiceConstraintDialog (src_dlg);
  }
  else
  {
    constraint_type = src_data->constraint_type;
    if (constraint_type < CHOICE_CONSTRAINT_ANY || constraint_type > CHOICE_CONSTRAINT_PSEUDO)
    {
      constraint_type = CHOICE_CONSTRAINT_ANY;
    }
    SetValue (src_dlg->constraint_type, constraint_type);
    if (constraint_type == CHOICE_CONSTRAINT_QUAL_PRESENT)
    {
      PointerToDialog (src_dlg->present_qual_choice, src_data->qual_choice);
    }
    else if (constraint_type == CHOICE_CONSTRAINT_STRING)
    {
      PointerToDialog (src_dlg->string_qual_choice, src_data->qual_choice);
      PointerToDialog (src_dlg->string_constraint, src_data->string_constraint);
    }
    else if (constraint_type == CHOICE_CONSTRAINT_MATCH)
    {
      PointerToDialog (src_dlg->match_1, src_data->qual_choice);
      PointerToDialog (src_dlg->match_2, src_data->qual_choice_match);
    }
    else if (constraint_type == CHOICE_CONSTRAINT_PSEUDO)
    {
      PointerToDialog (src_dlg->pseudo_constraint, src_data->pseudo_constraint);
    }
    ChangeChoiceConstraintType (src_dlg->constraint_type);
  }
}

static Pointer DialogToChoiceConstraint (DialoG d)
{
  ChoiceConstraintDialogPtr src_dlg;
  ChoiceConstraintPtr       src_data;

  src_dlg = (ChoiceConstraintDialogPtr) GetObjectExtra (d);
  if (src_dlg == NULL) 
  {
    return NULL;
  }
  
  src_data = (ChoiceConstraintPtr) MemNew (sizeof (ChoiceConstraintData));
  if (src_data != NULL)
  {
    src_data->constraint_type = GetValue (src_dlg->constraint_type);
    if (src_data->constraint_type == CHOICE_CONSTRAINT_QUAL_PRESENT)
    {
      src_data->qual_choice = DialogToPointer (src_dlg->present_qual_choice);
    }
    else if (src_data->constraint_type == CHOICE_CONSTRAINT_STRING)
    {
      src_data->qual_choice = DialogToPointer (src_dlg->string_qual_choice);
      src_data->string_constraint = DialogToPointer (src_dlg->string_constraint);
    }
    else if (src_data->constraint_type == CHOICE_CONSTRAINT_MATCH)
    {
      src_data->qual_choice = DialogToPointer (src_dlg->match_1);
      src_data->qual_choice_match = DialogToPointer (src_dlg->match_2);
    }
    else if (src_data->constraint_type == CHOICE_CONSTRAINT_PSEUDO)
    {
      src_data->pseudo_constraint = DialogToPointer (src_dlg->pseudo_constraint);
    }
    src_data->free_vn_proc = src_dlg->free_vn_proc;
    src_data->copy_vn_proc = src_dlg->copy_vn_proc;
  }
  return src_data;
}

static void ChoiceConstraintMessage (DialoG d, Int2 mssg)

{
  ChoiceConstraintDialogPtr scdp;

  scdp = (ChoiceConstraintDialogPtr) GetObjectExtra (d);
  if (scdp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        ResetChoiceConstraintDialog (scdp);        
        break;
      case VIB_MSG_ENTER :
        Select (scdp->constraint_type);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestChoiceConstraintDialog (DialoG d)

{
  return NULL;
}

extern DialoG 
ConstraintChoiceDialog 
(GrouP                     h, 
 FeatureFieldSelectionProc func_present,
 FeatureFieldSelectionProc func_string,
 FreeValNodeProc           free_vn_proc,
 CopyValNodeDataProc       copy_vn_proc,
 CharPtr                   any_name,
 CharPtr                   present_name,
 Boolean                   clear_btn,
 Boolean                   use_pseudo)
{
  ChoiceConstraintDialogPtr scdp;
  GrouP                     p, q;
  ButtoN                    b = NULL;
  
  scdp = (ChoiceConstraintDialogPtr) MemNew (sizeof (ChoiceConstraintDialogData));
  if (scdp == NULL)
  {
    return NULL;
  }

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

  scdp->dialog = (DialoG) p;
  scdp->todialog = ChoiceConstraintToDialog;
  scdp->fromdialog = DialogToChoiceConstraint;
  scdp->dialogmessage = ChoiceConstraintMessage;
  scdp->testdialog = TestChoiceConstraintDialog;

  scdp->constraint_type = HiddenGroup (p, 2, 0, ChangeChoiceConstraintType);
  RadioButton (scdp->constraint_type, any_name);
  StaticPrompt (scdp->constraint_type, "", 0, dialogTextHeight, systemFont, 'l');  
  
  RadioButton (scdp->constraint_type, present_name);
  scdp->present_qual_choice = func_present (scdp->constraint_type, FALSE, NULL, NULL);
    
  RadioButton (scdp->constraint_type, "When");
  q = HiddenGroup (scdp->constraint_type, 2, 0, NULL);
  scdp->string_qual_choice = func_string (q, TRUE, NULL, NULL);
  scdp->string_constraint = StringConstraintDialogX (q, NULL, FALSE);
  
  RadioButton (scdp->constraint_type, "When");
  q = HiddenGroup (scdp->constraint_type, 3, 0, NULL);
  scdp->match_1 = func_string (q, TRUE, NULL, NULL);
  StaticPrompt (q, "Equals", 0, dialogTextHeight, systemFont, 'l');
  scdp->match_2 = func_string (q, TRUE, NULL, NULL);

  if (use_pseudo)
  {
    RadioButton (scdp->constraint_type, "When");
    scdp->pseudo_constraint = PseudoConstraintDialog (scdp->constraint_type);
  }
  else
  {
    scdp->pseudo_constraint = NULL;
  }
  
  SetValue (scdp->constraint_type, CHOICE_CONSTRAINT_ANY);
  SetObjectExtra (scdp->constraint_type, scdp, NULL);

  Disable (scdp->present_qual_choice);
  Disable (scdp->string_qual_choice);
  Disable (scdp->string_constraint);
  Disable (scdp->match_1);
  Disable (scdp->match_2);
  
  scdp->free_vn_proc = free_vn_proc;
  scdp->copy_vn_proc = copy_vn_proc;
  
  if (clear_btn)
  {
    b = PushButton (p, "Clear Constraint", ClearDialogBtn);
    SetObjectExtra (b, p, NULL);    
  }
  
  AlignObjects (ALIGN_CENTER, (HANDLE) scdp->constraint_type, (HANDLE) b, NULL);
   
  return (DialoG) p;
}

extern DialoG SourceConstraintDialogX (GrouP h, Boolean clear_btn)
{
  return ConstraintChoiceDialog (h, SourceQualTypeConstraintSelectionDialog, 
                                 SourceStringConstraintSelectionDialog,
                                 ValNodeSimpleDataFree,
                                 SourceQualValNodeDataCopy,
                                 "For any source", "When qualifier present:",
                                 clear_btn,
                                 FALSE);
}


extern DialoG CDSGeneProtConstraintDialog (GrouP h, Boolean clear_btn)
{
  return ConstraintChoiceDialog (h, CDSGeneProtFieldSelectionDialog,
                                 CDSGeneProtFieldConstraintSelectionDialog,
                                 NULL, IntValNodeCopy,
                                 "For any CDS-Gene-Prot-mRNA set", 
                                 "When field present:", 
                                 clear_btn, TRUE);
}

typedef struct LocationConstraintXdialog 
{
  DIALOG_MESSAGE_BLOCK
  PopuP  interval_end_choice;
  PopuP  interval_match_choice;
  PopuP  endpoint_match_choice;
  TexT   only_val;
  TexT   first_val;
  TexT   second_val;
  PrompT second_val_prompt;
  PopuP  strand;
  PopuP  sequence_type;
  Boolean show_interval_controls;
} LocationConstraintXDialogData, PNTR LocationConstraintXDialogPtr;

static void ShowLocationChoiceControls (PopuP p)

{
  LocationConstraintXDialogPtr lcdp;
  Int4                        match_choice;

  lcdp = (LocationConstraintXDialogPtr) GetObjectExtra (p);
  if (lcdp == NULL || lcdp->interval_end_choice == NULL) return;
  
  if (GetValue (lcdp->interval_end_choice) == LOCATION_CONSTRAINT_WHOLE_INTERVAL)
  {
    Show (lcdp->interval_match_choice);
    Hide (lcdp->endpoint_match_choice);
    if (p == lcdp->interval_end_choice)
    {
      SetValue (lcdp->interval_match_choice, GetValue (lcdp->endpoint_match_choice)); 
    }
    match_choice = GetValue (lcdp->interval_match_choice);
  }
  else
  {
    Hide (lcdp->interval_match_choice);
    Show (lcdp->endpoint_match_choice);
    if (p == lcdp->interval_end_choice)
    {
      SetValue (lcdp->endpoint_match_choice, GetValue (lcdp->interval_match_choice)); 
    }
    match_choice = GetValue (lcdp->endpoint_match_choice);
  }
  
  switch (match_choice)
  {
    case LOCATION_CONSTRAINT_ANY :
      Hide (lcdp->only_val);
      Hide (lcdp->first_val);
      Hide (lcdp->second_val_prompt);
      Hide (lcdp->second_val);
      break;
    case LOCATION_CONSTRAINT_UPSTREAM :
    case LOCATION_CONSTRAINT_DOWNSTREAM :
      Show (lcdp->only_val);
      Hide (lcdp->first_val);
      Hide (lcdp->second_val_prompt);
      Hide (lcdp->second_val);
      break;
    case LOCATION_CONSTRAINT_CONTAINED :
    case LOCATION_CONSTRAINT_NOT_IN :
    case LOCATION_CONSTRAINT_OVERLAP :
    case LOCATION_CONSTRAINT_EQUAL :
      Hide (lcdp->only_val);
      Show (lcdp->first_val);
      Show (lcdp->second_val_prompt);
      Show (lcdp->second_val);
      break;
  }
}

static void ResetLocationConstraintXDialog (LocationConstraintXDialogPtr lcdp)
{
  if (lcdp == NULL) return;
  
  SafeSetValue (lcdp->interval_end_choice, LOCATION_CONSTRAINT_WHOLE_INTERVAL);
  SafeSetValue (lcdp->interval_match_choice, LOCATION_CONSTRAINT_ANY);
  SafeSetTitle (lcdp->only_val, "");
  SafeSetTitle (lcdp->first_val, "");
  SafeSetTitle (lcdp->second_val, "");
  SetValue (lcdp->strand, LOCATION_CONSTRAINT_ANY_STRAND);
  SetValue (lcdp->sequence_type, LOCATION_CONSTRAINT_ANY_SEQ);
  ShowLocationChoiceControls (lcdp->interval_end_choice);
}

static void LocationConstraintXToDialog (DialoG d, Pointer data)

{
  LocationConstraintXDialogPtr lcdp;
  LocationConstraintXPtr       lcp;
  Int4                        interval_end_choice;
  Int4                        match_choice;
  Char                        tmp [15];
  Int4                        strand;
  Int4                        sequence_type;

  lcdp = (LocationConstraintXDialogPtr) GetObjectExtra (d);
  if (lcdp == NULL)
  {
    return;
  }
  lcp = (LocationConstraintXPtr) data;
  
  ResetLocationConstraintXDialog (lcdp);
  if (lcp != NULL)
  {
    interval_end_choice = lcp->interval_end_choice;
    if (interval_end_choice < LOCATION_CONSTRAINT_WHOLE_INTERVAL
        || interval_end_choice > LOCATION_CONSTRAINT_STOP_ENDPOINT)
    {
      interval_end_choice = LOCATION_CONSTRAINT_WHOLE_INTERVAL;
    }
    match_choice = lcp->match_choice;
    if (match_choice < LOCATION_CONSTRAINT_ANY)
    {
      match_choice = LOCATION_CONSTRAINT_ANY;
    }
    else if (match_choice > LOCATION_CONSTRAINT_EQUAL)
    {
      match_choice = LOCATION_CONSTRAINT_EQUAL;
    }
    if (match_choice > LOCATION_CONSTRAINT_NOT_IN && interval_end_choice != LOCATION_CONSTRAINT_WHOLE_INTERVAL)
    {
      interval_end_choice = LOCATION_CONSTRAINT_WHOLE_INTERVAL;
    }
    
    SetValue (lcdp->interval_end_choice, interval_end_choice);
    if (interval_end_choice == LOCATION_CONSTRAINT_WHOLE_INTERVAL)
    {
      SetValue (lcdp->interval_match_choice, match_choice);
    }
    else
    {
      SetValue (lcdp->endpoint_match_choice, match_choice);
    }
    switch (match_choice)
    {
      case LOCATION_CONSTRAINT_ANY :
        break;
      case LOCATION_CONSTRAINT_UPSTREAM :
      case LOCATION_CONSTRAINT_DOWNSTREAM :
        if (lcp->left >= 0)
        {
          sprintf (tmp, "%d", lcp->left + 1);
          SetTitle (lcdp->only_val, tmp);
        }
        break;
      case LOCATION_CONSTRAINT_CONTAINED :
      case LOCATION_CONSTRAINT_NOT_IN :
      case LOCATION_CONSTRAINT_OVERLAP :
      case LOCATION_CONSTRAINT_EQUAL :
        if (lcp->left >= 0)
        {
          sprintf (tmp, "%d", lcp->left + 1);
          SetTitle (lcdp->first_val, tmp);
        }
        if (lcp->right >= 0)
        {
          sprintf (tmp, "%d", lcp->right + 1);
          SetTitle (lcdp->second_val, tmp);
        }
        break;
    }
    strand = lcp->strand;
    if (strand < LOCATION_CONSTRAINT_ANY_STRAND || strand > LOCATION_CONSTRAINT_MINUS_STRAND)
    {
      strand = LOCATION_CONSTRAINT_ANY_STRAND;
    }
    SetValue (lcdp->strand, strand);
    sequence_type = lcp->sequence_type;
    if (sequence_type < LOCATION_CONSTRAINT_ANY_SEQ || sequence_type > LOCATION_CONSTRAINT_PROT_SEQ)
    {
      sequence_type = LOCATION_CONSTRAINT_ANY_SEQ;
    }
    SetValue (lcdp->sequence_type, LOCATION_CONSTRAINT_ANY_SEQ);
  }
  ShowLocationChoiceControls (lcdp->interval_match_choice);
}

static Pointer DialogToLocationConstraintX (DialoG d)

{
  LocationConstraintXDialogPtr lcdp;
  LocationConstraintXPtr       lcp;
  CharPtr                     tmp;

  lcdp = (LocationConstraintXDialogPtr) GetObjectExtra (d);
  if (lcdp == NULL)
  {
    return NULL;
  }
  lcp = (LocationConstraintXPtr) MemNew (sizeof (LocationConstraintXData));
  if (lcp != NULL)
  {
    if (lcdp->show_interval_controls)
    {
      lcp->interval_end_choice = GetValue (lcdp->interval_end_choice);
      if (lcp->interval_end_choice == LOCATION_CONSTRAINT_WHOLE_INTERVAL)
      {
        lcp->match_choice = GetValue (lcdp->interval_match_choice);
      }
      else
      {
        lcp->match_choice = GetValue (lcdp->endpoint_match_choice);
      }
      switch (lcp->match_choice)
      {
        case LOCATION_CONSTRAINT_ANY :
          break;
        case LOCATION_CONSTRAINT_UPSTREAM :
        case LOCATION_CONSTRAINT_DOWNSTREAM :
          tmp = SaveStringFromText (lcdp->only_val);
          if (StringHasNoText (tmp))
          {
            lcp->left = 0;
          }
          else
          {
            lcp->left = atoi (tmp) - 1;
          }
          tmp = MemFree (tmp);
          break;
        case LOCATION_CONSTRAINT_CONTAINED :
        case LOCATION_CONSTRAINT_NOT_IN :
        case LOCATION_CONSTRAINT_OVERLAP :
        case LOCATION_CONSTRAINT_EQUAL :
          tmp = SaveStringFromText (lcdp->first_val);
          if (StringHasNoText (tmp))
          {
            lcp->left = 0;
          }
          else
          {
            lcp->left = atoi (tmp) - 1;
          }
          tmp = MemFree (tmp);
          tmp = SaveStringFromText (lcdp->second_val);
          if (StringHasNoText (tmp))
          {
            lcp->right = 0;
          }
          else
          {
            lcp->right = atoi (tmp) - 1;
          }
          tmp = MemFree (tmp);
          break;
      }
    }
    else
    {
      lcp->match_choice = LOCATION_CONSTRAINT_ANY;
      lcp->left = 0;
      lcp->right = 0;
    }
    lcp->strand = GetValue (lcdp->strand);
    lcp->sequence_type = GetValue (lcdp->sequence_type);
  }
  return lcp;
}

static void LocationConstraintXMessage (DialoG d, Int2 mssg)

{
  LocationConstraintXDialogPtr lcdp;

  lcdp = (LocationConstraintXDialogPtr) GetObjectExtra (d);
  if (lcdp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        ResetLocationConstraintXDialog (lcdp);        
        break;
      case VIB_MSG_ENTER :
        if (lcdp->show_interval_controls)
        {
          Select (lcdp->interval_end_choice);
        }
        else
        {
          Select (lcdp->strand);
        }
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestLocationConstraintXDialog (DialoG d)

{
  return NULL;
}


extern DialoG 
LocationConstraintXDialog 
(GrouP   h,
 Boolean show_interval_controls, 
 Boolean clear_btn)

{
  LocationConstraintXDialogPtr lcdp;
  GrouP                       p, g, k, val_grp, strand_grp, g2, g3, g4;
  ButtoN                      b = NULL;
  
  lcdp = (LocationConstraintXDialogPtr) MemNew (sizeof (LocationConstraintXDialogData));
  if (lcdp == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, lcdp, StdCleanupExtraProc);

  lcdp->dialog = (DialoG) p;
  lcdp->todialog = LocationConstraintXToDialog;
  lcdp->fromdialog = DialogToLocationConstraintX;
  lcdp->dialogmessage = LocationConstraintXMessage;
  lcdp->testdialog = TestLocationConstraintXDialog;
  
  lcdp->show_interval_controls = show_interval_controls;

  if (lcdp->show_interval_controls)
  {
    g = HiddenGroup (p, 6, 0, NULL);
    SetGroupSpacing (g, 10, 10);
    lcdp->interval_end_choice = PopupList (g, TRUE, ShowLocationChoiceControls);
    PopupItem (lcdp->interval_end_choice, "Entire location");
    PopupItem (lcdp->interval_end_choice, "Start");
    PopupItem (lcdp->interval_end_choice, "Stop");
    SetValue (lcdp->interval_end_choice, 1);
    SetObjectExtra (lcdp->interval_end_choice, lcdp, NULL);
  
    StaticPrompt (g, "is", 0, dialogTextHeight, systemFont, 'l');
  
    k = HiddenGroup (g, 0, 0, NULL);
    lcdp->interval_match_choice = PopupList (k, TRUE, ShowLocationChoiceControls);
    PopupItem (lcdp->interval_match_choice, "Any location");
    PopupItem (lcdp->interval_match_choice, "Upstream from");
    PopupItem (lcdp->interval_match_choice, "Downstream from");
    PopupItem (lcdp->interval_match_choice, "Contained in");
    PopupItem (lcdp->interval_match_choice, "Not in");
    PopupItem (lcdp->interval_match_choice, "Overlaps");
    PopupItem (lcdp->interval_match_choice, "Equal to");
    SetValue (lcdp->interval_match_choice, 1);
    SetObjectExtra (lcdp->interval_match_choice, lcdp, NULL);
  
    lcdp->endpoint_match_choice = PopupList (k, TRUE, ShowLocationChoiceControls);
    PopupItem (lcdp->endpoint_match_choice, "Any location");
    PopupItem (lcdp->endpoint_match_choice, "Upstream from");
    PopupItem (lcdp->endpoint_match_choice, "Downstream from");
    PopupItem (lcdp->endpoint_match_choice, "Contained in");
    PopupItem (lcdp->endpoint_match_choice, "Not in");
    SetValue (lcdp->endpoint_match_choice, 1);
    SetObjectExtra (lcdp->endpoint_match_choice, lcdp, NULL);

    g2 = HiddenGroup (g, 2, 0, NULL);
    SetGroupSpacing (g2, 10, 10);
    val_grp = HiddenGroup (g2, 0, 0, NULL);
    lcdp->only_val = DialogText (val_grp, "", 5, NULL);
    g3 = HiddenGroup (val_grp, 3, 0, NULL);
    lcdp->first_val = DialogText (g3, "", 5, NULL);
    lcdp->second_val_prompt = StaticPrompt (g3, "to", 0, dialogTextHeight, systemFont, 'l');
    lcdp->second_val = DialogText (g3, "", 5, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) lcdp->only_val, (HANDLE) g3, NULL);
  }
  else
  {
    g = HiddenGroup (p, 2, 0, NULL);
  }
  
  strand_grp = HiddenGroup (g, 2, 0, NULL);
  StaticPrompt (strand_grp, "on", 0, dialogTextHeight, systemFont, 'l');
  lcdp->strand = PopupList (strand_grp, TRUE, NULL);
  PopupItem (lcdp->strand, "Any strand");
  PopupItem (lcdp->strand, "Plus strand");
  PopupItem (lcdp->strand, "Minus strand");
  SetValue (lcdp->strand, LOCATION_CONSTRAINT_ANY_STRAND);
  
  g4 = HiddenGroup (g, 3, 0, NULL);
  SetGroupSpacing (g4, 10, 10);
  StaticPrompt (g4, "on", 0, dialogTextHeight, systemFont, 'l');
  lcdp->sequence_type = PopupList (g4, TRUE, NULL);
  PopupItem (lcdp->sequence_type, "nucleotide and protein sequences");
  PopupItem (lcdp->sequence_type, "nucleotide sequences only");
  PopupItem (lcdp->sequence_type, "protein sequences only");
  SetValue (lcdp->sequence_type, LOCATION_CONSTRAINT_ANY_SEQ);

  if (clear_btn)
  {
    b = PushButton (p, "Clear Constraint", ClearDialogBtn);
    SetObjectExtra (b, p, NULL);
  }

  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) b, NULL);
  
  ShowLocationChoiceControls (lcdp->interval_match_choice);
  return (DialoG) p;
}

static Boolean DoesStrandMatchConstraint (SeqLocPtr slp, LocationConstraintXPtr lcp)
{
  Uint2 strand;
  Boolean rval = FALSE;
  
  if (slp == NULL)
  {
    rval = FALSE;
  }
  else if (lcp == NULL || lcp->strand == LOCATION_CONSTRAINT_ANY_STRAND)
  {
    rval = TRUE;
  }
  else
  {
    strand = SeqLocStrand (slp);
    if (strand == Seq_strand_minus)
    {
      if (lcp->strand == LOCATION_CONSTRAINT_MINUS_STRAND)
      {
        rval = TRUE;
      }
      else
      {
        rval = FALSE;
      }
    }
    else
    {
      if (lcp->strand == LOCATION_CONSTRAINT_PLUS_STRAND)
      {
        rval = TRUE;
      }
      else
      {
        rval = FALSE;
      }
    }
  }
  return rval;
}

static Boolean DoesSequenceTypeMatchContraint (SeqLocPtr slp, LocationConstraintXPtr lcp)
{
  Boolean   rval = FALSE;
  BioseqPtr bsp;
  
  if (slp == NULL)
  {
    rval = FALSE;
  }
  else if (lcp == NULL || lcp->sequence_type == LOCATION_CONSTRAINT_ANY_SEQ)
  {
    rval = TRUE;
  }
  else
  {
    bsp = BioseqFindFromSeqLoc (slp);
    if (bsp != NULL)
    {
      if (ISA_na (bsp->mol) && lcp->sequence_type == LOCATION_CONSTRAINT_NUC_SEQ)
      {
        rval = TRUE;
      }
      else if (ISA_aa (bsp->mol) && lcp->sequence_type == LOCATION_CONSTRAINT_PROT_SEQ)
      {
        rval = TRUE;
      }
    }
  }
  return rval;
}

extern Boolean DoesLocationMatchConstraint (SeqLocPtr slp, LocationConstraintXPtr lcp)

{
  Boolean rval = FALSE;
  Int4    loc_start, loc_stop, endpoint = 0;
  
  if (slp == NULL)
  {
    return FALSE;
  }
  
  if (lcp == NULL)
  {
    rval = TRUE;
  }
  else if (! DoesStrandMatchConstraint (slp, lcp))
  {
    rval = FALSE;
  }
  else if (! DoesSequenceTypeMatchContraint (slp, lcp))
  {
    rval = FALSE;
  }
  else
  {
    switch (lcp->match_choice)
    {
      case LOCATION_CONSTRAINT_ANY :
        rval = TRUE;
        break;
      case LOCATION_CONSTRAINT_UPSTREAM :
        if (lcp->interval_end_choice == LOCATION_CONSTRAINT_WHOLE_INTERVAL
            || lcp->interval_end_choice == LOCATION_CONSTRAINT_STOP_ENDPOINT)
        {
          endpoint = SeqLocStop (slp);
        }
        else
        {
          endpoint = SeqLocStart (slp);
        }
        if (endpoint < lcp->left)
        {
          rval = TRUE;
        }
        break;
      case LOCATION_CONSTRAINT_DOWNSTREAM :
        if (lcp->interval_end_choice == LOCATION_CONSTRAINT_WHOLE_INTERVAL
            || lcp->interval_end_choice == LOCATION_CONSTRAINT_START_ENDPOINT)
        {
          endpoint = SeqLocStart (slp);
        }
        else
        {
          endpoint = SeqLocStop (slp);
        }
        if (endpoint > lcp->left)
        {
          rval = TRUE;
        }
        break;
      case LOCATION_CONSTRAINT_CONTAINED :
        if (lcp->interval_end_choice == LOCATION_CONSTRAINT_WHOLE_INTERVAL)
        {
          loc_start = SeqLocStart (slp);
          loc_stop = SeqLocStop (slp);
        }
        else
        {
          if (lcp->interval_end_choice == LOCATION_CONSTRAINT_START_ENDPOINT)
          {
            endpoint = SeqLocStart (slp);
          }
          else
          {
            endpoint = SeqLocStop (slp);
          }
          loc_start = endpoint;
          loc_stop = endpoint;
        }
        if (loc_start >= lcp->left && loc_stop <= lcp->right)
        {
          rval = TRUE;
        }
        break;
      case LOCATION_CONSTRAINT_NOT_IN :
        if (lcp->interval_end_choice == LOCATION_CONSTRAINT_WHOLE_INTERVAL)
        {
          loc_start = SeqLocStart (slp);
          loc_stop = SeqLocStop (slp);
        }
        else
        {
          if (lcp->interval_end_choice == LOCATION_CONSTRAINT_START_ENDPOINT)
          {
            endpoint = SeqLocStart (slp);
          }
          else
          {
            endpoint = SeqLocStop (slp);
          }
          loc_start = endpoint;
          loc_stop = endpoint;
        }
        if (loc_stop <= lcp->left || loc_start >= lcp->right)
        {
          rval = TRUE;
        }
        break;
      case LOCATION_CONSTRAINT_OVERLAP :
        loc_start = SeqLocStart (slp);
        loc_stop = SeqLocStop (slp);
        if (loc_start <= lcp->left && loc_stop >= lcp->left)
        {
          rval = TRUE;
        }
        else if (loc_start <= lcp->right && loc_stop >= lcp->right)
        {
          rval = TRUE;
        }
        else if (loc_start >= lcp->left && loc_stop <= lcp->right)
        {
          rval = TRUE;
        }
        break;
      case LOCATION_CONSTRAINT_EQUAL :
        loc_start = SeqLocStart (slp);
        loc_stop = SeqLocStop (slp);
        if (loc_start == lcp->left && loc_stop == lcp->right)
        {
          rval = TRUE;
        }
        break;
    }
  }
  return rval; 
}

typedef struct filterform
{
  DIALOG_MESSAGE_BLOCK
  
  DialoG string_constraint;
  DialoG source_constraint;
  DialoG location_constraint;
  DialoG cds_gene_prot_constraint;
  DialoG id_list_constraint;
  
  DialoG tbs;
  DialoG pages[6];
  Int4   current_page;
  
} FilterFormData, PNTR FilterFormPtr;

static void FilterToDialog (DialoG d, Pointer userdata)
{
  FilterFormPtr dlg;
  FilterSetPtr dlg_data;
  
  dlg = (FilterFormPtr) GetObjectExtra (d);
  
  if (dlg == NULL)
  {
    return;
  }
  dlg_data = (FilterSetPtr) userdata;
  PointerToDialog (dlg->string_constraint, NULL);
  PointerToDialog (dlg->source_constraint, NULL);
  PointerToDialog (dlg->location_constraint, NULL);
  PointerToDialog (dlg->cds_gene_prot_constraint, NULL);
  PointerToDialog (dlg->id_list_constraint, NULL);
  
  if (dlg_data != NULL)
  {
    PointerToDialog (dlg->string_constraint, dlg_data->scp);
    PointerToDialog (dlg->source_constraint, dlg_data->ccp);
    PointerToDialog (dlg->location_constraint, dlg_data->lcp);
    PointerToDialog (dlg->cds_gene_prot_constraint, dlg_data->cgp);
    PointerToDialog (dlg->id_list_constraint, dlg_data->id_list);
  }
}

static void ApplyNthPage (FilterFormPtr dlg, FilterSetPtr dlg_data, Int4 page)
{
  if (page < 0) return;

  /* adjust page value to reflect missing pages */
  if (dlg->source_constraint == NULL)
  {
    page++;
  }
  if (dlg->string_constraint == NULL && page >= 1)
  {
    page++;
  }
  if (dlg->location_constraint == NULL && page >= 2)
  {
    page++;
  }
  if (dlg->cds_gene_prot_constraint == NULL && page >= 3)
  {
    page++;
  }
  if (dlg->id_list_constraint == NULL && page >= 4)
  {
    page++;
  }
  
  switch (page)
  {
    case 0:
      dlg_data->ccp = DialogToPointer (dlg->source_constraint);
      break;
    case 1:
      dlg_data->scp = DialogToPointer (dlg->string_constraint);
      break;
    case 2:
      dlg_data->lcp = DialogToPointer (dlg->location_constraint);
      break;
    case 3:
      dlg_data->cgp = DialogToPointer (dlg->cds_gene_prot_constraint);
      break;
    case 4:
      dlg_data->id_list = DialogToPointer (dlg->id_list_constraint);
      break;
  }
  
}


static Pointer DialogToFilter (DialoG d)
{
  FilterFormPtr dlg;
  FilterSetPtr dlg_data;
  
  dlg = (FilterFormPtr) GetObjectExtra (d);
  
  if (dlg == NULL)
  {
    return NULL;
  }
  dlg_data = (FilterSetPtr) MemNew (sizeof (FilterSetData));
  
  if (dlg_data != NULL)
  {
    if (dlg->tbs == NULL) 
    {
      dlg_data->scp = DialogToPointer (dlg->string_constraint);
      dlg_data->ccp = DialogToPointer (dlg->source_constraint);
      dlg_data->lcp = DialogToPointer (dlg->location_constraint);
      dlg_data->cgp = DialogToPointer (dlg->cds_gene_prot_constraint);
      dlg_data->id_list = DialogToPointer (dlg->id_list_constraint);
    }
    else
    {
      /* only take one value */
      ApplyNthPage (dlg, dlg_data, dlg->current_page);
    }
  }
  return dlg_data;
}

static void FilterFormMessage (DialoG d, Int2 mssg)

{
}

static ValNodePtr TestFilterFormDialog (DialoG d)

{
  return NULL;
}

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

{
  FilterFormPtr ffp;

  ffp = (FilterFormPtr) data;
  if (ffp != NULL) {
    ffp->current_page = newval;
    SafeHide (ffp->pages [oldval]);
    Update ();
    SafeShow (ffp->pages [newval]);
    Update ();
  }
}

static void ClearAllConstraints (ButtoN b)
{
  FilterFormPtr ffp;

  ffp = (FilterFormPtr) GetObjectExtra (b);
  if (ffp != NULL)
  {
    PointerToDialog (ffp->dialog, NULL);
  }
}

extern DialoG 
FilterGroup 
(GrouP h,
 Boolean has_string_constraint,
 Boolean has_source_constraint,
 Boolean has_location_constraint,
 Boolean has_cds_gene_prot_constraint,
 Boolean has_id_list_constraint,
 CharPtr string_constraint_label)
{
  GrouP         g, k;
  FilterFormPtr ffp;
  Int4          num_pages = 0;
  CharPtr       filterTabs[5];
  Int4          i;
  ButtoN        clear_constraints = NULL;
  
  if (! has_string_constraint
      && ! has_source_constraint
      && ! has_location_constraint
      && ! has_cds_gene_prot_constraint
      && ! has_id_list_constraint)
  {
    return NULL;
  }
  
  ffp = (FilterFormPtr) MemNew (sizeof (FilterFormData));
  if (ffp == NULL)
  {
    return NULL;
  }

  g = NormalGroup (h, -1, 0, NULL, programFont, NULL);
  SetObjectExtra (g, ffp, StdCleanupExtraProc);

  ffp->dialog = (DialoG) g;
  ffp->todialog = FilterToDialog;
  ffp->fromdialog = DialogToFilter;
  ffp->dialogmessage = FilterFormMessage;
  ffp->testdialog = TestFilterFormDialog;
  
  ffp->current_page = 0;

  if (has_source_constraint)
  {
    filterTabs [num_pages++] = "Source Constraint";
  }
  if (has_string_constraint)
  {
    filterTabs [num_pages++] = "String Constraint";
  }
  if (has_location_constraint)
  {
    filterTabs [num_pages++] = "Location Constraint";
  }
  if (has_cds_gene_prot_constraint)
  {
    filterTabs [num_pages++] = "CDS-Gene-Prot-mRNA Set Constraint";
  }
  if (has_id_list_constraint)
  {
    filterTabs [num_pages++] = "Sequence ID Constraint";
  }
  filterTabs [num_pages] = NULL;
  
  if (num_pages > 1)
  {
  
    ffp->tbs = CreateFolderTabs (g, filterTabs, 0,
                                 0, 0, PROGRAM_FOLDER_TAB,
                                 ChangeFilterPage, (Pointer) ffp);
    k = HiddenGroup (g, 0, 0, NULL);
    num_pages = 0;
    if (has_source_constraint)
    {
      ffp->source_constraint = SourceConstraintDialogX (k, FALSE);
      ffp->pages [num_pages++] = ffp->source_constraint;
    }
    if (has_string_constraint)
    {
      ffp->string_constraint = StringConstraintDialogX (k, string_constraint_label, FALSE);
      ffp->pages [num_pages++] = ffp->string_constraint;
    }
    if (has_location_constraint)
    {
      ffp->location_constraint = LocationConstraintXDialog (k, FALSE, FALSE);
      ffp->pages [num_pages++] = ffp->location_constraint;
    }
    if (has_cds_gene_prot_constraint)
    {
      ffp->cds_gene_prot_constraint = CDSGeneProtConstraintDialog (k, FALSE);
      ffp->pages [num_pages++] = ffp->cds_gene_prot_constraint;
    }
    if (has_id_list_constraint)
    {
      ffp->id_list_constraint = StringConstraintDialogX (k, "Where sequence ID", FALSE);
      ffp->pages[num_pages++] = ffp->id_list_constraint;
    }
    for (i = 1; i < num_pages; i++)
    {
      Hide (ffp->pages [i]);
    }
    AlignObjects (ALIGN_CENTER, (HANDLE) ffp->pages [0],
                                (HANDLE) ffp->pages [1],
                                (HANDLE) ffp->pages [2],
                                (HANDLE) ffp->pages [3],
                                NULL);
    clear_constraints = PushButton (g, "Clear Constraints", ClearAllConstraints);
    SetObjectExtra (clear_constraints, ffp, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) ffp->tbs, (HANDLE) k, (HANDLE) clear_constraints, NULL);
  }
  else
  {
    if (has_source_constraint)
    {
      ffp->source_constraint = SourceConstraintDialogX (g, TRUE);
    }
    else if (has_string_constraint)
    {
      ffp->string_constraint = StringConstraintDialogX (g, string_constraint_label, TRUE);
    }
    else if (has_location_constraint)
    {
      ffp->location_constraint = LocationConstraintXDialog (g, FALSE, TRUE);
    }
    else if (has_cds_gene_prot_constraint)
    {
      ffp->cds_gene_prot_constraint = CDSGeneProtConstraintDialog (g, TRUE);
    }
    else if (has_id_list_constraint)
    {
      ffp->id_list_constraint = StringConstraintDialogX (g, "Where sequence ID", TRUE);
    }
  }
  return (DialoG) g;
}

/* Operations on constrained features and descriptors */
typedef struct objecthasstring
{
  StringConstraintXPtr scp;
  Boolean             found;
} ObjectHasStringData, PNTR ObjectHasStringPtr;

typedef struct constraintop 
{
  FilterSetPtr           fsp;
  ObjMgrPtr              omp;
  ObjMgrTypePtr          omtp;
  AsnIoPtr               aip;
  Uint2                  entityID;
  ObjectHasStringData    ohsd;
  Pointer                userdata;
  FeatureActionProc      feature_action;
  Uint1                  seqFeatChoice;
  Uint1                  featDefChoice;
  DescriptorActionProc   descriptor_action;
  Uint1                  descriptorChoice;
} ConstraintOpData, PNTR ConstraintOpPtr;

extern Boolean DoesStringMatchConstraintX (CharPtr pchSource, StringConstraintXPtr scp)
{
  CharPtr pFound;
  Boolean rval = FALSE;
  Char    char_after = 0;
  
  if (pchSource == NULL) return FALSE;
  
  if (scp == NULL || StringHasNoText (scp->match_text)) return TRUE;

  switch (scp->match_location) 
  {
    case eStringConstraintContains:
        if (scp->insensitive)
        {
          pFound = StringISearch (pchSource, scp->match_text);
        }
        else
        {
          pFound = StringSearch (pchSource, scp->match_text);
        }
      if (pFound == NULL) 
      {
        rval = FALSE;
      }
      else if (scp->whole_word) 
      {
        rval = IsWholeWordMatch (pchSource, pFound, StringLen (scp->match_text));
        while (!rval && pFound != NULL) 
        {
            if (scp->insensitive)
            {
              pFound = StringISearch (pFound + 1, scp->match_text);
            }
            else
            {
              pFound = StringSearch (pFound + 1, scp->match_text);
            }
          if (pFound != NULL)
          {
            rval = IsWholeWordMatch (pchSource, pFound, StringLen (scp->match_text));
          }
        }
      }
      else
      {
        rval = TRUE;
      }
      break;
    case eStringConstraintStarts:
        if (scp->insensitive)
        {
          pFound = StringISearch (pchSource, scp->match_text);
        }
        else
        {
          pFound = StringSearch (pchSource, scp->match_text);
        }
      if (pFound == pchSource)
      {
        if (scp->whole_word) 
        {
          rval = IsWholeWordMatch (pchSource, pFound, StringLen (scp->match_text));
        }
        else
        {
          rval = TRUE;
        }
      }
      break;
    case eStringConstraintEnds:
        if (scp->insensitive)
        {
          pFound = StringISearch (pchSource, scp->match_text);
        }
        else
        {
          pFound = StringSearch (pchSource, scp->match_text);
        }
      while (pFound != NULL && !rval) {
          char_after = *(pFound + StringLen (scp->match_text));
        if (char_after == 0)
        {
          if (scp->whole_word) 
          {
            rval = IsWholeWordMatch (pchSource, pFound, StringLen (scp->match_text));
          }
          else
          {
            rval = TRUE;
          }
          /* stop the search, we're at the end of the string */
          pFound = NULL;
        }
        else
        {
            if (scp->insensitive)
            {
              pFound = StringISearch (pFound + 1, scp->match_text);
            }
            else
            {
              pFound = StringSearch (pFound + 1, scp->match_text);
            }
        }
      }
      break;
    case eStringConstraintEquals:
      if (scp->insensitive) 
      {
        if (StringICmp (pchSource, scp->match_text) == 0) 
        {
          rval = TRUE;
        }
      }
      else
      {
        if (StringCmp (pchSource, scp->match_text) == 0) 
        {
          rval = TRUE;
        }
      }
      break;
    case eStringConstraintInList:
        if (scp->insensitive)
        {
          pFound = StringISearch (scp->match_text, pchSource);
        }
        else
        {
          pFound = StringSearch (scp->match_text, pchSource);
        }
      if (pFound == NULL) 
      {
        rval = FALSE;
      }
      else
      {
        rval = IsWholeWordMatch (scp->match_text, pFound, StringLen (pchSource));
        while (!rval && pFound != NULL) 
        {
            if (scp->insensitive)
            {
              pFound = StringISearch (pFound + 1, pchSource);
            }
            else
            {
              pFound = StringSearch (pFound + 1, pchSource);
            }
          if (pFound != NULL)
          {
            rval = IsWholeWordMatch (scp->match_text, pFound, StringLen (pchSource));
          }
        }
      }
      if (!rval) {
        /* look for spans */
        rval = IsStringInSpanInList (pchSource, scp->match_text);
      }
      break;
    }
    return rval;
}

static void LIBCALLBACK AsnWriteConstraintCallBack (AsnExpOptStructPtr pAEOS)

{
  CharPtr            pchSource;
  ObjectHasStringPtr ohsp;

  ohsp = (ObjectHasStringPtr) pAEOS->data;
  if (ISA_STRINGTYPE (AsnFindBaseIsa (pAEOS->atp))) 
  {
      pchSource = (CharPtr) pAEOS->dvp->ptrvalue;
      ohsp->found |= DoesStringMatchConstraintX (pchSource, ohsp->scp);
  }
}

static Boolean 
DoesObjectMatchStringConstraint 
(ObjMgrTypePtr      omtp,
 AsnIoPtr           aip, 
 Pointer            ptr, 
 ObjectHasStringPtr ohsp)

{
  SeqFeatPtr        sfp;
  SeqMgrFeatContext fcontext;
  CharPtr           search_txt = NULL;
  
  if (omtp == NULL || ohsp == NULL)
  {
    return FALSE;
  }
  ohsp->found = FALSE;
  (omtp->asnwrite) (ptr, aip, NULL);
  
  if (!ohsp->found && omtp->datatype == OBJ_SEQFEAT)
  {
    sfp = (SeqFeatPtr) ptr;
    if (SeqMgrFeaturesAreIndexed(sfp->idx.entityID) == 0) {
      SeqMgrIndexFeatures (sfp->idx.entityID, NULL);
    }

    sfp = SeqMgrGetDesiredFeature (sfp->idx.entityID, NULL, sfp->idx.itemID, 0, sfp, &fcontext);
    ohsp->found = DoesStringMatchConstraintX (fcontext.label, ohsp->scp);
    if (!ohsp->found && sfp != NULL && sfp->idx.subtype == FEATDEF_tRNA)
    {
      search_txt = (CharPtr) MemNew ((StringLen (fcontext.label) + 6) * sizeof (Char));
      if (search_txt != NULL)
      {
        sprintf (search_txt, "tRNA-%s", fcontext.label);
        ohsp->found = DoesStringMatchConstraintX (search_txt, ohsp->scp);
        search_txt = MemFree (search_txt);
      }
    }
  }
  return ohsp->found;
}


static Boolean DoesSourceHaveOneQualPresent (BioSourcePtr biop, SourceQualDescPtr sqdp)
{
  OrgModPtr    mod;
  SubSourcePtr ssp;
  Boolean      is_present = FALSE;
  
  if (biop == NULL || sqdp == NULL)
  {
    return FALSE;
  }
  
  if (sqdp->isOrgMod)
  {
    if (biop->org != NULL && biop->org->orgname != NULL)
    {
      mod = biop->org->orgname->mod;
      while (mod != NULL && mod->subtype != sqdp->subtype)
      {
        mod = mod->next;
      }
      if (mod != NULL && mod->subtype == sqdp->subtype)
      {
        is_present = TRUE;
      }
    }
  }
  else
  {
    ssp = biop->subtype;
    while (ssp != NULL && ssp->subtype != sqdp->subtype)
    {
      ssp = ssp->next;
    }
    if (ssp != NULL && ssp->subtype == sqdp->subtype)
    {
      is_present = TRUE;
    }
  }
  return is_present;
}

static Boolean DoesSourceHaveQualPresent (BioSourcePtr biop, ValNodePtr qual_list)
{
  ValNodePtr        vnp;
  Boolean           qual_found = FALSE;
  
  if (biop == NULL)
  {
    return FALSE;
  }
  if (qual_list == NULL)
  {
    return TRUE;
  }
  
  for (vnp = qual_list; vnp != NULL && !qual_found; vnp = vnp->next)
  {
    qual_found = DoesSourceHaveOneQualPresent (biop, (SourceQualDescPtr) vnp->data.ptrvalue);
  }
  
  return qual_found;
}

static Boolean IsMatchAny (ValNodePtr vnp)
{
  if (vnp == NULL
      || vnp->data.ptrvalue == NULL
      || (vnp->choice > 0 && StringICmp (vnp->data.ptrvalue, "Organism or Any Qual") == 0))
  {
    return TRUE;
  } else {
    return FALSE;
  }
}

static Boolean DoFieldsMatch (BioSourcePtr biop, ChoiceConstraintPtr ccp)
{
  ValNodePtr swap, swap2;
  ValNodePtr qual_choice_list = NULL, vnp1, vnp2;
  Boolean    rval = FALSE;
  ChoiceConstraintData tmp_const;
  StringConstraintData scd;
  SourceQualDescPtr sqdp;
  CharPtr    location;
  OrgModPtr              mod = NULL;
  SubSourcePtr           ssp;

  if (biop == NULL) return FALSE;
  if (ccp == NULL) return TRUE;

  /* Init temporary choice constraint */
  MemSet (&tmp_const, 0, sizeof (ChoiceConstraintData));
  MemSet (&scd, 0, sizeof (StringConstraintData));
  scd.match_location = eStringConstraintEquals;
  tmp_const.string_constraint = &scd;
  tmp_const.constraint_type = CHOICE_CONSTRAINT_STRING;

  if (IsMatchAny (ccp->qual_choice) && IsMatchAny (ccp->qual_choice_match)) {
    /* both are wildcards */
    ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Organism"));
    qual_choice_list->next = GetSourceQualDescList (TRUE, TRUE, TRUE, FALSE);
    ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Lineage"));
    ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Location"));

    /* save original wildcards */
    swap = ccp->qual_choice;
    swap2 = ccp->qual_choice_match;
    for (vnp1 = qual_choice_list; vnp1 != NULL && vnp1->next != NULL && !rval; vnp1 = vnp1->next) {
      ccp->qual_choice = vnp1;
      for (vnp2 = vnp1->next; vnp2 != NULL && !rval; vnp2 = vnp2->next) {
        ccp->qual_choice_match = vnp2;
        if (DoFieldsMatch (biop, ccp)) {
          rval = TRUE;
        }
      }
    }
    /* put original wildcards back */
    ccp->qual_choice = swap;
    ccp->qual_choice_match = swap2;
    qual_choice_list = ValNodeFreeData (qual_choice_list);
  } else if (IsMatchAny (ccp->qual_choice) || IsMatchAny (ccp->qual_choice_match)) {
    /* one is a wild card */
    ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Organism"));
    qual_choice_list->next = GetSourceQualDescList (TRUE, TRUE, TRUE, FALSE);
    ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Lineage"));
    ValNodeAddPointer (&qual_choice_list, 1, StringSave ("Location"));

    /* if one wild card, make it the first one */    
    if (IsMatchAny (ccp->qual_choice_match)) {
      swap = ccp->qual_choice;
      ccp->qual_choice = ccp->qual_choice_match;
      ccp->qual_choice_match = swap;
    }
    /* save original wildcard */
    swap = ccp->qual_choice;
    for (vnp1 = qual_choice_list; vnp1 != NULL && !rval; vnp1 = vnp1->next) {
      if (SourceQualValNodeMatch (vnp1, ccp->qual_choice_match)) {
        /* don't compare something to itself */
        continue;
      } else {
        ccp->qual_choice = vnp1;
        rval = DoFieldsMatch (biop, ccp);
      }
    }
    /* put original wildcard back */
    ccp->qual_choice = swap;
    qual_choice_list = ValNodeFreeData (qual_choice_list);
  } 
  else if (ccp->qual_choice->choice > 0) 
  {  
    tmp_const.qual_choice = ccp->qual_choice_match;
    if (StringICmp (ccp->qual_choice->data.ptrvalue, "Organism") == 0)
    {
      if (biop->org != NULL && !StringHasNoText (biop->org->taxname))
      {
        tmp_const.string_constraint->match_text = biop->org->taxname;
        rval = DoesOneSourceMatchConstraint (biop, &tmp_const);
      }
    }
    else if (StringICmp (ccp->qual_choice->data.ptrvalue, "Lineage") == 0)
    {
      if (biop->org != NULL && biop->org->orgname != NULL && !StringHasNoText (biop->org->orgname->lineage))
      {
        tmp_const.string_constraint->match_text = biop->org->orgname->lineage;
        rval = DoesOneSourceMatchConstraint (biop, &tmp_const);
      }
    }
    else if (StringICmp (ccp->qual_choice->data.ptrvalue, "Location") == 0)
    {
      location = GetLocationFromSource (biop, NULL);
      tmp_const.string_constraint->match_text = location;
      if (!StringHasNoText (location)) {
        rval = DoesOneSourceMatchConstraint (biop, &tmp_const);
      }
      location = MemFree (location);
    }
  }
  else
  {
    tmp_const.qual_choice = ccp->qual_choice_match;
    sqdp = (SourceQualDescPtr) ccp->qual_choice->data.ptrvalue;
    if (sqdp->isOrgMod)
    {
      if (biop->org != NULL && biop->org->orgname != NULL)
      {
        mod = biop->org->orgname->mod;
      }
      while (! rval && mod != NULL)
      {
        if (mod->subtype == sqdp->subtype && !StringHasNoText (mod->subname))
        {
          tmp_const.string_constraint->match_text = mod->subname;
          rval = DoesOneSourceMatchConstraint (biop, &tmp_const);
        }
        mod = mod->next;
      }
    }
    else
    {
      ssp = biop->subtype;
      while (!rval && ssp != NULL)
      {
        if (ssp->subtype == sqdp->subtype && !StringHasNoText (ssp->name))
        {
          tmp_const.string_constraint->match_text = ssp->name;
          rval = DoesOneSourceMatchConstraint (biop, &tmp_const);
        }
        ssp = ssp->next;
      }
    }
  }
  return rval;
}

extern Boolean 
DoesOneSourceMatchConstraint 
(BioSourcePtr biop, ChoiceConstraintPtr scp)
{
  Boolean                does_match = FALSE;
  OrgModPtr              mod = NULL;
  SubSourcePtr           ssp;
  SourceQualDescPtr      sqdp;
  CharPtr                location;
  
  if (scp == NULL || scp->constraint_type == CHOICE_CONSTRAINT_ANY)
  {
    return TRUE;
  }
  if (biop == NULL)
  {
    return FALSE;
  }
  
  if (scp->constraint_type == CHOICE_CONSTRAINT_STRING)
  {
    if (IsMatchAny (scp->qual_choice))
    {
      if (biop->org != NULL)
      {
        does_match = DoesStringMatchConstraintX (biop->org->taxname, scp->string_constraint);
        if (biop->org->orgname != NULL)
        {
          mod = biop->org->orgname->mod;
        }
        while (! does_match && mod != NULL)
        {
          does_match = DoesStringMatchConstraintX (mod->subname, scp->string_constraint);
          mod = mod->next;
        }
      }
      ssp = biop->subtype;
      while (!does_match && ssp != NULL)
      {
        does_match = DoesStringMatchConstraintX (ssp->name, scp->string_constraint);
        ssp = ssp->next;
      }
    }
    else if (scp->qual_choice->choice > 0 && StringICmp (scp->qual_choice->data.ptrvalue, "Organism") == 0)
    {
      if (biop->org != NULL)
      {
        does_match = DoesStringMatchConstraintX (biop->org->taxname, scp->string_constraint);
      }
    }
    else if (scp->qual_choice->choice > 0 && StringICmp (scp->qual_choice->data.ptrvalue, "Lineage") == 0)
    {
      if (biop->org != NULL && biop->org->orgname != NULL)
      {
        does_match = DoesStringMatchConstraintX (biop->org->orgname->lineage,
                                                scp->string_constraint);
      }
    }
    else if (scp->qual_choice->choice > 0 && StringICmp (scp->qual_choice->data.ptrvalue, "Location") == 0)
    {
      location = GetLocationFromSource (biop, NULL);
      does_match = DoesStringMatchConstraintX (location, scp->string_constraint);
      location = MemFree (location);
    }
    else
    {
      sqdp = (SourceQualDescPtr) scp->qual_choice->data.ptrvalue;
      if (sqdp->isOrgMod)
      {
        if (biop->org != NULL && biop->org->orgname != NULL)
        {
          mod = biop->org->orgname->mod;
        }
        while (! does_match && mod != NULL)
        {
          if (mod->subtype == sqdp->subtype)
          {
            does_match = DoesStringMatchConstraintX (mod->subname, scp->string_constraint);
          }
          mod = mod->next;
        }
      }
      else
      {
        ssp = biop->subtype;
        while (!does_match && ssp != NULL)
        {
          if (ssp->subtype == sqdp->subtype)
          {
            does_match = DoesStringMatchConstraintX (ssp->name, scp->string_constraint);
          }
          ssp = ssp->next;
        }
      }
    }
    if (scp->string_constraint->not_present)
    {
      does_match = ! does_match;
    }
  }
  else if (scp->constraint_type == CHOICE_CONSTRAINT_MATCH)
  {
    does_match = DoFieldsMatch (biop, scp);
  }
  else
  {
    does_match = DoesSourceHaveQualPresent (biop, scp->qual_choice);
  }
  return does_match;
}

static void SeqEntryConstrainedFeaturesCallback (SeqFeatPtr sfp, Pointer userdata)
{
  SeqMgrFeatContext fcontext;
  ConstraintOpPtr   cop;
  Boolean           feature_matches = TRUE;
  
  if (sfp == NULL || userdata == NULL) return;
  cop = (ConstraintOpPtr) userdata;
  sfp = SeqMgrGetDesiredFeature (sfp->idx.entityID, NULL, sfp->idx.itemID, 0, sfp, &fcontext);
  if (sfp == NULL) return;
  
  if (cop->seqFeatChoice != 0 && cop->seqFeatChoice != sfp->data.choice) return;
  if (cop->featDefChoice != 0 && cop->featDefChoice != sfp->idx.subtype) return;
  
  if (cop->fsp != NULL && cop->fsp->scp != NULL)
  {
    if (cop->fsp->scp->not_present)
    {
      if (DoesObjectMatchStringConstraint (cop->omtp, cop->aip, (Pointer) sfp, &(cop->ohsd))
          || DoesStringMatchConstraintX (fcontext.label, cop->fsp->scp))
      {
        feature_matches = FALSE; 
      }
    }
    else
    {
      if (! DoesObjectMatchStringConstraint (cop->omtp, cop->aip, (Pointer) sfp, &(cop->ohsd))
          && ! DoesStringMatchConstraintX (fcontext.label, cop->fsp->scp))
      {
        feature_matches = FALSE;
      }
    }
  }
  if (cop->fsp != NULL && cop->fsp->lcp != NULL)
  {
    if (! DoesLocationMatchConstraint (sfp->location, cop->fsp->lcp))
    {
      feature_matches = FALSE;
    }
  }
  
  if (feature_matches)
  {
    (cop->feature_action) (sfp, cop->userdata, cop->fsp);
  }
}


static void BioseqConstrainedFeaturesCallback (BioseqPtr bsp, Pointer userdata)
{
  ConstraintOpPtr   cop;
  SeqFeatPtr        sfp;
  ObjMgrDataPtr     omdp;
  BioseqExtraPtr    bspextra;
  Int4              index;
  
  if (bsp == NULL || userdata == NULL) return;
  cop = (ConstraintOpPtr) userdata;

  omdp = SeqMgrGetOmdpForBioseq (bsp);
  if (omdp == NULL || omdp->datatype != OBJ_BIOSEQ) return;

  bspextra = (BioseqExtraPtr) omdp->extradata;
  if (bspextra == NULL) {
    SeqMgrIndexFeatures (bsp->idx.entityID, NULL);
    omdp = SeqMgrGetOmdpForBioseq (bsp);
    if (omdp == NULL || omdp->datatype != OBJ_BIOSEQ) return;

    bspextra = (BioseqExtraPtr) omdp->extradata;
  }
  if (bspextra == NULL) return;

  for (index = 0; index < bspextra->numfeats; index++)
  {
    sfp = bspextra->featsByPos[index]->sfp;
    SeqEntryConstrainedFeaturesCallback (sfp, userdata);
  }
}


static void SeqEntryConstrainedDescriptorsCallback (SeqDescrPtr sdp, Pointer userdata)
{
  ConstraintOpPtr cop;
  Boolean         descriptor_matches = TRUE;
  
  if (sdp == NULL || userdata == NULL) return;
  cop = (ConstraintOpPtr) userdata;
  
  if (cop != NULL && cop->descriptorChoice != 0 && cop->descriptorChoice != sdp->choice)
  {
    return;
  }
  
  if (cop != NULL && cop->fsp != NULL && cop->fsp->scp != NULL)
  {
    if (cop->fsp->scp->not_present)
    {
      if (DoesObjectMatchStringConstraint (cop->omtp, cop->aip, (Pointer) sdp, &(cop->ohsd)))
      {
        descriptor_matches = FALSE;
      }
    }
    else
    {
      if (! DoesObjectMatchStringConstraint (cop->omtp, cop->aip, (Pointer) sdp, &(cop->ohsd)))
      {
        descriptor_matches = FALSE;
      }
    }
  }
  if (descriptor_matches)
  {
    (cop->descriptor_action) (sdp, cop->userdata, cop->fsp);
  }
}


static void ConstrainedSourceCallback (SeqEntryPtr sep, ConstraintOpPtr cop)
{
  BioseqPtr       bsp;
  BioseqSetPtr    bssp = NULL;
  SeqAnnotPtr     sap = NULL;
  SeqDescrPtr     sdp = NULL;
  SeqFeatPtr      sfp;
  Boolean         found = FALSE;
  
  if (sep == NULL || sep->data.ptrvalue == NULL || cop == NULL || cop->fsp == NULL)
  {
    return;
  }
   
  if (IS_Bioseq (sep))
  {
    bsp = (BioseqPtr) sep->data.ptrvalue;
    sap = bsp->annot;
    sdp = bsp->descr;
  }
  else if (IS_Bioseq_set (sep))
  {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    sap = bssp->annot;
    sdp = bssp->descr;
  }
  while (sap != NULL && !found)
  {
    if (sap->type == 1)
    {
      sfp = (SeqFeatPtr) sap->data;
      while (sfp != NULL && ! found)
      {
        if (sfp->data.choice == SEQFEAT_BIOSRC 
            && DoesOneSourceMatchConstraint (sfp->data.value.ptrvalue, cop->fsp->ccp))
        {
          found = TRUE;
        }
        sfp = sfp->next;
      }
    }
    sap = sap->next;
  }
  
  while (sdp != NULL && !found)
  {
    if (sdp->choice == Seq_descr_source
        && DoesOneSourceMatchConstraint (sdp->data.ptrvalue, cop->fsp->ccp))
    {
      found = TRUE;
    }
    sdp = sdp->next;
  }
  if (found)
  {
    if (cop->feature_action != NULL)
    {
      cop->omtp = ObjMgrTypeFind (cop->omp, OBJ_SEQFEAT, NULL, NULL);
      if (cop->omtp != NULL)
      {
        VisitBioseqsInSep (sep, cop, BioseqConstrainedFeaturesCallback);
      }
    }
  
    if (cop->descriptor_action != NULL)
    {
      cop->omtp = ObjMgrTypeFind (cop->omp, OBJ_SEQDESC, NULL, NULL);
      if (cop->omtp != NULL)
      {
        VisitDescriptorsInSep (sep, cop, SeqEntryConstrainedDescriptorsCallback);
      }
    }
  }
  else if (bssp != NULL)
  {
    ConstrainedSourceCallback (bssp->seq_set, cop);
  }
  ConstrainedSourceCallback (sep->next, cop);
}


static void ConstrainedBioseqCallback (SeqEntryPtr sep, ConstraintOpPtr cop)
{
  BioseqPtr       bsp = NULL;
  BioseqSetPtr    bssp = NULL;
  SeqFeatPtr      sfp;
  SeqDescPtr      sdp;
  SeqMgrFeatContext fcontext;
  SeqMgrDescContext dcontext;
  
  if (sep == NULL || sep->data.ptrvalue == NULL || cop == NULL || cop->fsp == NULL)
  {
    return;
  }
   
  if (IS_Bioseq (sep))
  {
    bsp = (BioseqPtr) sep->data.ptrvalue;
  }
  else if (IS_Bioseq_set (sep))
  {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
  }

  if (bsp != NULL && DoesIDListMeetStringConstraint (bsp->id, cop->fsp->id_list))
  {
    if (cop->feature_action != NULL)
    {
      cop->omtp = ObjMgrTypeFind (cop->omp, OBJ_SEQFEAT, NULL, NULL);
      if (cop->omtp != NULL)
      {
        for (sfp = SeqMgrGetNextFeature (bsp, NULL, 0, 0, &fcontext);
             sfp != NULL;
             sfp = SeqMgrGetNextFeature (bsp, sfp, 0, 0, &fcontext))
        {
          SeqEntryConstrainedFeaturesCallback (sfp, cop);
        }
      }
    }
  
    if (cop->descriptor_action != NULL)
    {
      cop->omtp = ObjMgrTypeFind (cop->omp, OBJ_SEQDESC, NULL, NULL);
      if (cop->omtp != NULL)
      {
        for (sdp = SeqMgrGetNextDescriptor (bsp, NULL, 0, &dcontext);
             sdp != NULL;
             sdp = SeqMgrGetNextDescriptor (bsp, sdp, 0, &dcontext))
        {
          SeqEntryConstrainedDescriptorsCallback (sdp, cop);
        }
      }
    }
  }
  else if (bssp != NULL)
  {
    ConstrainedBioseqCallback (bssp->seq_set, cop);
  }
  ConstrainedBioseqCallback (sep->next, cop);
}


extern void 
OperateOnSeqEntryConstrainedObjects 
(SeqEntryPtr           sep,
 FilterSetPtr          fsp,
 FeatureActionProc     feature_action,
 DescriptorActionProc  descriptor_action,
 Uint1                 seqFeatChoice,
 Uint1                 featDefChoice,
 Uint1                 descriptorChoice,
 Pointer               userdata)
{
  ConstraintOpData  cod;
  AsnExpOptPtr      aeop;
  SeqEntryPtr       old_scope;
  BioseqPtr         bsp = NULL;
  SeqFeatPtr        sfp;
  SeqDescrPtr       sdp;
  SeqMgrFeatContext fcontext;
  SeqMgrDescContext dcontext;
  
  if (sep == NULL) return;
  if (feature_action == NULL && descriptor_action == NULL) return;
  
  cod.omp = ObjMgrGet ();
  if (cod.omp == NULL) return;
  
  cod.fsp               = fsp;
  cod.feature_action    = feature_action;
  cod.descriptor_action = descriptor_action;
  cod.userdata          = userdata;
  cod.entityID          = SeqMgrGetEntityIDForSeqEntry (sep);
  cod.seqFeatChoice     = seqFeatChoice;
  cod.featDefChoice     = featDefChoice;
  cod.descriptorChoice  = descriptorChoice;
  
  old_scope = SeqEntrySetScope (sep);

  cod.aip = AsnIoNullOpen ();
  if (fsp == NULL)
  {
    cod.ohsd.scp = NULL;
  }
  else
  {
    cod.ohsd.scp = fsp->scp;
  }
  
  aeop = AsnExpOptNew (cod.aip, NULL, NULL, AsnWriteConstraintCallBack);
  if (aeop != NULL) {
    aeop->user_data = (Pointer) &(cod.ohsd);
  }
  
  if (IS_Bioseq (sep))
  {
    bsp = (BioseqPtr) sep->data.ptrvalue;
  }
  
  if (fsp == NULL || (fsp->ccp == NULL && fsp->id_list == NULL)
      || (fsp->ccp != NULL && fsp->ccp->constraint_type == CHOICE_CONSTRAINT_ANY)
      || (fsp->id_list != NULL && bsp != NULL && DoesIDListMeetStringConstraint (bsp->id, fsp->id_list)))
  {
    if (feature_action != NULL)
    {
      cod.omtp = ObjMgrTypeFind (cod.omp, OBJ_SEQFEAT, NULL, NULL);
      if (cod.omtp != NULL)
      {
        if (bsp == NULL)
        {
          VisitBioseqsInSep (sep, &cod, BioseqConstrainedFeaturesCallback);
        }
        else
        {
          sfp = SeqMgrGetNextFeature (bsp, NULL, 0, 0, &fcontext);
          while (sfp != NULL)
          {
            SeqEntryConstrainedFeaturesCallback (sfp, &cod);
            sfp = SeqMgrGetNextFeature (bsp, sfp, 0, 0, &fcontext);
          }
        }
      }
    }
  
    if (descriptor_action != NULL)
    {
      cod.omtp = ObjMgrTypeFind (cod.omp, OBJ_SEQDESC, NULL, NULL);
      if (cod.omtp != NULL)
      {
        if (bsp == NULL)
        {
          VisitDescriptorsInSep (sep, &cod, SeqEntryConstrainedDescriptorsCallback);
        }
        else
        {
          sdp = SeqMgrGetNextDescriptor (bsp, NULL, 0, &dcontext);
          while (sdp != NULL)
          {
            SeqEntryConstrainedDescriptorsCallback (sdp, &cod);
            sdp = SeqMgrGetNextDescriptor (bsp, sdp, 0, &dcontext);
          }
        }
      }
    }
  }
  else if (fsp->id_list != NULL)
  {
    ConstrainedBioseqCallback (sep, &cod);
  }
  else
  {
    ConstrainedSourceCallback (sep, &cod);
  }

  AsnIoClose (cod.aip);
  SeqEntrySetScope (old_scope);
}

extern GetSamplePtr GetSampleNew (void)
{
  GetSamplePtr gsp;
  
  gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
  if (gsp != NULL)
  {
    gsp->all_same = TRUE;
    gsp->sample_text = NULL;
    gsp->num_found = 0;
    gsp->feat_dest_list = NULL;
    gsp->descr_dest_list = NULL;
    
    gsp->fieldstring_func = NULL;
    gsp->descrstring_func = NULL;
    gsp->requested_field = NULL;
    gsp->free_vn_proc = NULL;
    gsp->copy_vn_proc = NULL;
  }
  return gsp;
}

extern GetSamplePtr GetSampleFree (GetSamplePtr gsp)
{
  if (gsp != NULL)
  {
    gsp->sample_text = MemFree (gsp->sample_text);
    if (gsp->free_vn_proc != NULL)
    {
      (gsp->free_vn_proc)(gsp->requested_field);
    }
    gsp->requested_field = ValNodeFree (gsp->requested_field);
    gsp->feat_dest_list = ValNodeFree (gsp->feat_dest_list);
    gsp->descr_dest_list = ValNodeFree (gsp->descr_dest_list);
    gsp = MemFree (gsp);
  }
  return gsp;
}

static GetSamplePtr GetSampleCopy (GetSamplePtr gsp)
{
  GetSamplePtr copy_gsp;
  ValNodePtr   copy_vnp;
  
  if (gsp == NULL)
  {
    return NULL;
  }
  
  copy_gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
  if (copy_gsp != NULL)
  {
    copy_gsp->fieldstring_func = gsp->fieldstring_func;
    copy_gsp->descrstring_func = gsp->descrstring_func;
    copy_gsp->free_vn_proc = gsp->free_vn_proc;
    copy_gsp->copy_vn_proc = gsp->copy_vn_proc;
    if (copy_gsp->copy_vn_proc != NULL)
    {
      copy_gsp->requested_field = (gsp->copy_vn_proc) (gsp->requested_field);
    }
    copy_gsp->sample_text = StringSave (gsp->sample_text);
    copy_gsp->num_found = gsp->num_found;
    copy_gsp->all_same = gsp->all_same;
    
    copy_vnp = gsp->feat_dest_list;
    copy_gsp->feat_dest_list = NULL;
    while (copy_vnp != NULL)
    {
      ValNodeAddPointer (&(copy_gsp->feat_dest_list), copy_vnp->choice, copy_vnp->data.ptrvalue);
      copy_vnp = copy_vnp->next;
    }
    
    copy_vnp = gsp->descr_dest_list;
    copy_gsp->descr_dest_list = NULL;
    while (copy_vnp != NULL)
    {
      ValNodeAddPointer (&(copy_gsp->descr_dest_list), copy_vnp->choice, copy_vnp->data.ptrvalue);
      copy_vnp = copy_vnp->next;
    }
    
  }
  return copy_gsp;
}

static ValNodePtr AddValNodeLists (ValNodePtr vnp1, ValNodePtr vnp2)
{
  ValNodePtr new_list = NULL, copy_vnp, check_vnp;
  
  /* copy first list */
  copy_vnp = vnp1;
  while (copy_vnp != NULL)
  {
    ValNodeAddPointer (&(new_list), copy_vnp->choice, copy_vnp->data.ptrvalue);
    copy_vnp = copy_vnp->next;
  }
      
  copy_vnp = vnp2;
  while (copy_vnp != NULL)
  {
    /* check to see if destination is already in the list */
      check_vnp = new_list;
       while (check_vnp != NULL && check_vnp->data.ptrvalue != copy_vnp->data.ptrvalue)
       {
      check_vnp = check_vnp->next;
    }
      
    if (check_vnp == NULL)
    {
      /* add to list if not present */
      ValNodeAddPointer (&new_list, copy_vnp->choice, copy_vnp->data.ptrvalue);
    }
    else if (check_vnp->choice < 250)
    {
      /* do not increase choice beyond 250 */
      check_vnp->choice ++;
    }
          
    copy_vnp = copy_vnp->next;
  }
  return new_list;
}

static GetSamplePtr GetSampleAdd (GetSamplePtr gsp1, GetSamplePtr gsp2)
{
  GetSamplePtr sum_gsp;
  
  if (gsp1 == NULL && gsp2 == NULL)
  {
    sum_gsp =  NULL;
  }
  else if (gsp1 == NULL)
  {
    sum_gsp =  GetSampleCopy (gsp2);
  }
  else if (gsp2 == NULL)
  {
    sum_gsp =  GetSampleCopy (gsp1);
  }
  else
  {
    sum_gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
    if (sum_gsp != NULL)
    {
      sum_gsp->fieldstring_func = gsp1->fieldstring_func;
      sum_gsp->descrstring_func = gsp1->descrstring_func;
      sum_gsp->requested_field = gsp1->requested_field;
      gsp1->requested_field = NULL;
      sum_gsp->sample_text = StringSave (gsp1->sample_text);
      sum_gsp->num_found = gsp1->num_found + gsp2->num_found;
      if (gsp1->num_found == 0)
      {
        sum_gsp->all_same = gsp2->all_same;
      }
      else if (gsp2->num_found == 0)
      {
        sum_gsp->all_same = gsp1->all_same;
      }
      else if (!gsp1->all_same || ! gsp2->all_same)
      {
        sum_gsp->all_same = FALSE;
      }
      else if (StringCmp (gsp1->sample_text, gsp2->sample_text) == 0)
      {
        sum_gsp->all_same = TRUE;
      }
      else
      {
        sum_gsp->all_same = FALSE;
      }
      
      /* combine destination lists */
      sum_gsp->feat_dest_list = AddValNodeLists (gsp1->feat_dest_list, gsp2->feat_dest_list);
            
    }
  }
  return sum_gsp;
}


static void GetSampleFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  GetSamplePtr cp;
  CharPtr      str;
  ValNodePtr   check_vnp;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  cp = (GetSamplePtr) userdata;
  if (cp->fieldstring_func == NULL)
  {
    return;
  }
  
  check_vnp = cp->feat_dest_list;
  while (check_vnp != NULL && check_vnp->data.ptrvalue != sfp)
  {
    check_vnp = check_vnp->next;
  }
  if (check_vnp == NULL)
  {
    ValNodeAddPointer (&(cp->feat_dest_list), 1, sfp);
  }
  else
  {
    if (check_vnp->choice < 250)
    {
      check_vnp->choice ++;
    }
    return;
  }
  
  str = cp->fieldstring_func (sfp, cp->requested_field, NULL);
  if (!StringHasNoText (str) || (fsp != NULL && fsp->scp != NULL && fsp->scp->not_present))
  {
    cp->num_found ++;
    if (cp->sample_text == NULL)
    {
      cp->sample_text = str;
    }
    else
    {
      if (StringCmp (str, cp->sample_text) != 0)
      {
        cp->all_same = FALSE;
      }
      str = MemFree (str);
    }
  }
}

static void GetSampleDescriptorCallback (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  GetSamplePtr cp;
  CharPtr      str;
  ValNodePtr   check_vnp;
  
  if (sdp == NULL || userdata == NULL)
  {
    return;
  }
  
  cp = (GetSamplePtr) userdata;
  if (cp->descrstring_func == NULL)
  {
    return;
  }
  
  check_vnp = cp->descr_dest_list;
  while (check_vnp != NULL && check_vnp->data.ptrvalue != sdp)
  {
    check_vnp = check_vnp->next;
  }
  if (check_vnp == NULL)
  {
    ValNodeAddPointer (&(cp->descr_dest_list), 1, sdp);
  }
  else
  {
    if (check_vnp->choice < 250)
    {
      check_vnp->choice ++;
    }
    return;
  }

  str = cp->descrstring_func (sdp, cp->requested_field, NULL);
  if (!StringHasNoText (str))
  {
    cp->num_found ++;
    if (cp->sample_text == NULL)
    {
      cp->sample_text = str;
    }
    else
    {
      if (StringCmp (str, cp->sample_text) != 0)
      {
        cp->all_same = FALSE;
      }
      str = MemFree (str);
    }
  }
}

static GetSamplePtr CheckForExistingTextInSeqEntry
(SeqEntryPtr              sep,
 ValNodePtr               requested_field,
 GetFeatureFieldString    fieldstring_func,
 GetDescriptorFieldString descrstring_func,
 FreeValNodeProc          free_vn_proc,
 CopyValNodeDataProc      copy_vn_proc,
 FilterSetPtr             fsp,
 Uint1                    seqfeat_choice,
 Uint1                    featdef_choice,
 Uint1                    descr_choice)
{
  GetSamplePtr gsp;
  
  gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
  if (gsp != NULL)
  {
    gsp->sample_text = NULL;
    gsp->fieldstring_func = fieldstring_func;
    gsp->descrstring_func = descrstring_func;
    gsp->free_vn_proc = free_vn_proc;
    gsp->copy_vn_proc = copy_vn_proc;
    gsp->requested_field = (gsp->copy_vn_proc) (requested_field);
    gsp->num_found = 0;
    gsp->all_same = TRUE;
    OperateOnSeqEntryConstrainedObjects (sep, fsp, GetSampleFeatureCallback, 
                                         GetSampleDescriptorCallback,
                                         seqfeat_choice, featdef_choice,
                                         descr_choice, gsp);
  }
  return gsp;  
}

static GetSamplePtr CheckForExistingText
(Uint2                    entityID,
 ValNodePtr               requested_field,
 GetFeatureFieldString    fieldstring_func,
 GetDescriptorFieldString descrstring_func,
 FreeValNodeProc          free_vn_proc,
 CopyValNodeDataProc      copy_vn_proc,
 FilterSetPtr             fsp,
 Uint1                    seqfeat_choice,
 Uint1                    featdef_choice,
 Uint1                    descr_choice)
{
  SeqEntryPtr  sep;

  sep = GetTopSeqEntryForEntityID (entityID);
  return CheckForExistingTextInSeqEntry (sep, requested_field, 
                                         fieldstring_func, descrstring_func,
                                         free_vn_proc, copy_vn_proc, fsp,
                                         seqfeat_choice, featdef_choice, descr_choice);
}

extern CharPtr HandleApplyValue (CharPtr orig_text, ApplyValuePtr avp)
{
  CharPtr new_str, cp_found;
  Int4    found_len, replace_len, new_len;
  
  if (avp == NULL)
  {
    return orig_text;
  }
  else if (StringHasNoText (orig_text))
  {
    if (StringHasNoText (avp->text_to_replace))
    {
      MemFree (orig_text);
      return StringSave (avp->new_text);
    }
    else
    {
      return orig_text;
    }
  }
  else if (StringHasNoText (avp->text_to_replace))
  {
    return HandleExistingText (orig_text, StringSave (avp->new_text), avp->etp);
  }
  else
  {
    found_len = StringLen (avp->text_to_replace);
    replace_len = StringLen (avp->new_text);
    cp_found = StringISearch (orig_text, avp->text_to_replace);
    if (avp->where_to_replace == EditApplyFindLocation_beginning
        && cp_found != orig_text) {
      cp_found = NULL;
    } 
    while (cp_found != NULL)
    {
      if (avp->where_to_replace == EditApplyFindLocation_end
          && cp_found != orig_text + StringLen (orig_text) - found_len) {
        cp_found = StringISearch (cp_found + found_len, avp->text_to_replace);
      } else {
        new_len = StringLen (orig_text) + 1 - found_len + replace_len;
        new_str = (CharPtr) MemNew (new_len * sizeof (Char));
        if (new_str != NULL)
        {
          if (cp_found != orig_text)
          {
            StringNCpy (new_str, orig_text, cp_found - orig_text);
          }
          StringCat (new_str, avp->new_text);
          StringCat (new_str, cp_found + found_len);
          cp_found = new_str + (cp_found - orig_text) + replace_len;
          orig_text = MemFree (orig_text);
          orig_text = new_str;
        }
        cp_found = StringISearch (cp_found, avp->text_to_replace);
      }
    }
    return orig_text;
  }
}

extern ValNodePtr ApplyValueToValNodeStringList (ValNodePtr list, Int2 choice, ApplyValuePtr avp)
{
  ValNodePtr vnp;
  
  if (avp == NULL)
  {
    return NULL;
  }
  
  if (!StringHasNoText (avp->text_to_replace))
  {
    for (vnp = list; vnp != NULL; vnp = vnp->next)
    {
      vnp->data.ptrvalue = HandleApplyValue (vnp->data.ptrvalue, avp);
    }
  }
  else
  {
    ValNodeAddPointer (&list, choice, StringSave (avp->new_text));
  }
  return list;
}



static void MakeSemicolonStringsIntoSeparateItems (ValNodePtr PNTR list)
{
  ValNodePtr prev = NULL, vnp_next, vnp, vnp_new;
  CharPtr    cp;

  if (list == NULL || *list == NULL) return;

  vnp = *list;
  while (vnp != NULL)
  {
    vnp_next = vnp->next;
    if (StringHasNoText (vnp->data.ptrvalue))
    {
      if (prev == NULL)
      {
        *list = vnp_next;
      }
      else
      {
        prev->next = vnp_next;
      }
      vnp->next = NULL;
      vnp = ValNodeFreeData (vnp);
    }
    else if ((cp = StringChr (vnp->data.ptrvalue, ';')) != NULL)
    {
      vnp_new = ValNodeNew (NULL);
      vnp_new->choice = vnp->choice;
      vnp_new->data.ptrvalue = StringSave (cp + 1);
      vnp_new->next = vnp_next;
      *cp = 0;
      vnp->next = vnp_new;
      vnp_next = vnp_new;
      prev = vnp;
    }
    else
    {
      prev = vnp;
    }
    vnp = vnp_next;
  }
}


extern ValNodePtr ApplyValueToValNodeStringListAsText (ValNodePtr list, Int2 choice, ApplyValuePtr avp)
{
  ValNodePtr vnp;
  
  if (avp == NULL)
  {
    return NULL;
  }
  
  if (!StringHasNoText (avp->text_to_replace))
  {
    for (vnp = list; vnp != NULL; vnp = vnp->next)
    {
      vnp->data.ptrvalue = HandleApplyValue (vnp->data.ptrvalue, avp);
    }
  }
  else
  {
    if (avp->etp == NULL
        || avp->etp->existing_text_choice == eExistingTextChoiceReplaceOld)
    {
      list = ValNodeFree (list);
      ValNodeAddPointer (&list, choice, StringSave (avp->new_text));
    }
    else if (avp->etp->existing_text_choice == eExistingTextChoiceAppendSemi)
    {
      ValNodeAddPointer (&list, choice, StringSave (avp->new_text));
    }
    else if (avp->etp->existing_text_choice == eExistingTextChoicePrefixSemi)
    {
      vnp = ValNodeNew(NULL);
      vnp->choice = (Uint1) choice;
      vnp->data.ptrvalue = StringSave (avp->new_text);
      vnp->next = list;
      list = vnp;
    }
    else if (avp->etp->existing_text_choice == eExistingTextChoiceLeaveOld)
    {
      if (list == NULL)
      {
        ValNodeAddPointer (&list, choice, StringSave (avp->new_text));
      }
    } else {
      for (vnp = list; vnp != NULL; vnp = vnp->next)
      {
        vnp->data.ptrvalue = HandleApplyValue (vnp->data.ptrvalue, avp);
      }
    }
  }

  if (avp->etp == NULL || avp->etp->existing_text_choice != eExistingTextChoiceLeaveOld) 
  {
    MakeSemicolonStringsIntoSeparateItems (&list);
  }
  return list;
}


typedef struct parsefielddialog
{
  DIALOG_MESSAGE_BLOCK
  DialoG                   parse_field_type;
  DialoG                   biosrc_string_choice;
  DialoG                   source_qual_choice;
  DialoG                   gene_field;
  DialoG                   rna_subtype;
  DialoG                   rna_field;
  DialoG                   protein_field;
  DialoG                   import_feature;
  DialoG                   import_qual;
  DialoG                   feature;
  GrouP                    dbxref_grp;
  TexT                     dbxref_db;
  GrouP                    feat_or_desc;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
  Boolean                  is_search_field;
} ParseFieldDialogData, PNTR ParseFieldDialogPtr;

static void ChangeParseFieldType (Pointer data)
{
  ParseFieldDialogPtr dlg;
  ValNodePtr          vnp;

  dlg = (ParseFieldDialogPtr) data;
  if (dlg == NULL)
  {
    return;
  }
  vnp = DialogToPointer (dlg->parse_field_type);
  Hide (dlg->biosrc_string_choice);
  Hide (dlg->feat_or_desc);
  Hide (dlg->source_qual_choice);
  Hide (dlg->gene_field);
  Hide (dlg->rna_subtype);
  Hide (dlg->rna_field);
  Hide (dlg->protein_field);
  Hide (dlg->import_feature);
  Hide (dlg->import_qual);
  Hide (dlg->feature);
  Hide (dlg->dbxref_grp);
  if (vnp != NULL)
  {
    switch (vnp->choice)
    {
      case PARSE_FIELD_BIOSRC_STRING:
        Show (dlg->biosrc_string_choice);
        Show (dlg->feat_or_desc);
        break;
      case PARSE_FIELD_SOURCE_QUAL:
        Show (dlg->source_qual_choice);
        Show (dlg->feat_or_desc);
        break;
      case PARSE_FIELD_GENE_FIELD:
        Show (dlg->gene_field);
        break;
      case PARSE_FIELD_RNA_FIELD:
        Show (dlg->rna_subtype);
        Show (dlg->rna_field);
        break;
      case PARSE_FIELD_PROTEIN_FIELD:
        Show (dlg->protein_field);
        break;
      case PARSE_FIELD_IMPORT_QUAL:
        Show (dlg->import_feature);
        Show (dlg->import_qual);
        break;
      case PARSE_FIELD_FEATURE_NOTE:
        Show (dlg->feature);
        break;
      case PARSE_FIELD_DBXREF:
        Show (dlg->dbxref_grp);
        break;
    }
  }
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }
}

static void ResetParseFieldDialog (ParseFieldDialogPtr dlg)
{
  ValNode vn;

  if (dlg == NULL)
  {
    return;
  }
  vn.choice = PARSE_FIELD_DEFLINE;
  vn.data.ptrvalue = NULL;
  vn.next = NULL;
  PointerToDialog (dlg->parse_field_type, &vn);
  ChangeParseFieldType (dlg);
}

static void ParseFieldToDialog (DialoG d, Pointer data)
{
  ParseFieldDialogPtr dlg;
  ParseFieldPtr       parse_field;
  Int4                parse_field_type;
  ValNode             vn;

  dlg = (ParseFieldDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  ResetParseFieldDialog (dlg);
  parse_field = (ParseFieldPtr) data;
  if (parse_field != NULL)
  {
    parse_field_type = parse_field->parse_field_type;
    if (parse_field_type < PARSE_FIELD_DEFLINE 
        || (dlg->is_search_field && parse_field_type > SEARCH_FIELD_PUBLICATION)
        || (!dlg->is_search_field && parse_field_type > MAX_PARSE_FIELD_TYPE))
    {
      parse_field_type = PARSE_FIELD_DEFLINE;
    }
    vn.choice = parse_field_type;
    vn.data.ptrvalue = NULL;
    vn.next = NULL;
    PointerToDialog (dlg->parse_field_type, &vn);
    switch (parse_field_type)
    {
      case PARSE_FIELD_BIOSRC_STRING:
        PointerToDialog (dlg->biosrc_string_choice, parse_field->feature_field);
        if (parse_field->do_desc && parse_field->do_feat)
        {
          SetValue (dlg->feat_or_desc, 1);
        }
        else if (parse_field->do_desc)
        {
          SetValue (dlg->feat_or_desc, 2);
        }
        else if (parse_field->do_feat)
        {
          SetValue (dlg->feat_or_desc, 3);
        }
        else
        {
          SetValue (dlg->feat_or_desc, 1);
        }
        break;
      case PARSE_FIELD_SOURCE_QUAL:
        PointerToDialog (dlg->source_qual_choice, parse_field->feature_field);
        if (parse_field->do_desc && parse_field->do_feat)
        {
          SetValue (dlg->feat_or_desc, 1);
        }
        else if (parse_field->do_desc)
        {
          SetValue (dlg->feat_or_desc, 2);
        }
        else if (parse_field->do_feat)
        {
          SetValue (dlg->feat_or_desc, 3);
        }
        else
        {
          SetValue (dlg->feat_or_desc, 1);
        }
        break;
      case PARSE_FIELD_DBXREF:
        if (parse_field->feature_field == NULL || parse_field->feature_field->data.ptrvalue == NULL)
        {
          SetTitle (dlg->dbxref_db, "");
        }
        else
        {
          SetTitle (dlg->dbxref_db, parse_field->feature_field->data.ptrvalue);
        }
        if (parse_field->do_desc && parse_field->do_feat)
        {
          SetValue (dlg->feat_or_desc, 1);
        }
        else if (parse_field->do_desc)
        {
          SetValue (dlg->feat_or_desc, 2);
        }
        else if (parse_field->do_feat)
        {
          SetValue (dlg->feat_or_desc, 3);
        }
        else
        {
          SetValue (dlg->feat_or_desc, 1);
        }
        break;
      case PARSE_FIELD_GENE_FIELD:
        PointerToDialog (dlg->gene_field, parse_field->feature_field);
        break;
      case PARSE_FIELD_RNA_FIELD:
        PointerToDialog (dlg->rna_subtype, parse_field->feature_subtype);
        PointerToDialog (dlg->rna_field, parse_field->feature_field);
        break;
      case PARSE_FIELD_PROTEIN_FIELD:
        PointerToDialog (dlg->protein_field, parse_field->feature_field);
        break;
      case PARSE_FIELD_IMPORT_QUAL:
        PointerToDialog (dlg->import_feature, parse_field->feature_subtype);
        PointerToDialog (dlg->import_qual, parse_field->feature_field);
        break;
      case PARSE_FIELD_FEATURE_NOTE:
        PointerToDialog (dlg->feature, parse_field->feature_field);
        break;
      case SEARCH_FIELD_PUBLICATION:
        /* nothing to do here */
        break;
    }
  }
  ChangeParseFieldType (dlg);
}

static Pointer DialogToParseField (DialoG d)
{
  ParseFieldDialogPtr dlg;
  ParseFieldPtr       parse_field;
  Int4                val;
  ValNodePtr          vnp;

  dlg = (ParseFieldDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  vnp = DialogToPointer (dlg->parse_field_type);
  if (vnp == NULL)
  {
    return NULL;
  }

  parse_field = (ParseFieldPtr) MemNew (sizeof (ParseFieldData));
  if (parse_field != NULL)
  {
    parse_field->parse_field_type = vnp->choice;
    parse_field->feature_field = NULL;
    switch (parse_field->parse_field_type)
    {
      case PARSE_FIELD_BIOSRC_STRING:
        parse_field->feature_field = DialogToPointer (dlg->biosrc_string_choice);
        val = GetValue (dlg->feat_or_desc);
        switch (val)
        {
          case 2:
            parse_field->do_desc = TRUE;
            parse_field->do_feat = FALSE;
            break;
          case 3:
            parse_field->do_desc = FALSE;
            parse_field->do_feat = TRUE;
            break;
          case 1:
          default:
            parse_field->do_desc = TRUE;
            parse_field->do_feat = TRUE;
            break;
        }
        break;
      case PARSE_FIELD_SOURCE_QUAL:
        parse_field->feature_field = DialogToPointer (dlg->source_qual_choice);
        val = GetValue (dlg->feat_or_desc);
        switch (val)
        {
          case 2:
            parse_field->do_desc = TRUE;
            parse_field->do_feat = FALSE;
            break;
          case 3:
            parse_field->do_desc = FALSE;
            parse_field->do_feat = TRUE;
            break;
          case 1:
          default:
            parse_field->do_desc = TRUE;
            parse_field->do_feat = TRUE;
            break;
        }
        break;
      case PARSE_FIELD_DBXREF:        
        if (!TextHasNoText (dlg->dbxref_db))
        {
          ValNodeAddPointer (&(parse_field->feature_field), 0, SaveStringFromText (dlg->dbxref_db));
        }
        val = GetValue (dlg->feat_or_desc);
        switch (val)
        {
          case 2:
            parse_field->do_desc = TRUE;
            parse_field->do_feat = FALSE;
            break;
          case 3:
            parse_field->do_desc = FALSE;
            parse_field->do_feat = TRUE;
            break;
          case 1:
          default:
            parse_field->do_desc = TRUE;
            parse_field->do_feat = TRUE;
            break;
        }
        break;
      case PARSE_FIELD_GENE_FIELD:
        parse_field->feature_field = DialogToPointer (dlg->gene_field);
        break;
      case PARSE_FIELD_RNA_FIELD:
        parse_field->feature_subtype = DialogToPointer (dlg->rna_subtype);
        parse_field->feature_field = DialogToPointer (dlg->rna_field);
        break;
      case PARSE_FIELD_PROTEIN_FIELD:
        parse_field->feature_field = DialogToPointer (dlg->protein_field);
        break;
      case PARSE_FIELD_IMPORT_QUAL:
        parse_field->feature_subtype = DialogToPointer (dlg->import_feature);
        parse_field->feature_field = DialogToPointer (dlg->import_qual);
        break;
      case PARSE_FIELD_FEATURE_NOTE:
        parse_field->feature_field = DialogToPointer (dlg->feature);
        break;
      case SEARCH_FIELD_PUBLICATION:
        /* nothing to do here */
        break;
    }
  }
  vnp = ValNodeFreeData (vnp);
  return parse_field;
}

static void ParseFieldMessage (DialoG d, Int2 mssg)

{
  ParseFieldDialogPtr dlg;

  dlg = (ParseFieldDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        ResetParseFieldDialog (dlg);        
        break;
      case VIB_MSG_ENTER :
        Select (dlg->parse_field_type);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestParseFieldDialog (DialoG d)

{
  ValNodePtr          head = NULL;
  ParseFieldDialogPtr dlg;
  Int4                parse_field_type;
  ValNodePtr          vnp;

  dlg = (ParseFieldDialogPtr) GetObjectExtra (d);
  
  if (dlg != NULL)
  {
    vnp = DialogToPointer (dlg->parse_field_type);
    if (vnp == NULL) 
    {
      head = AddStringToValNodeChain (head, "destination type", 1);
    }
    else
    {
      parse_field_type = vnp->choice;
      vnp = ValNodeFreeData (vnp);
      switch (parse_field_type)
      {
        case PARSE_FIELD_DEFLINE:
        case PARSE_FIELD_CDS_COMMENT:
        case PARSE_FIELD_COMMENT_DESC:
          /* don't need any extra information */
          break;
        case PARSE_FIELD_BIOSRC_STRING:
          vnp = DialogToPointer (dlg->biosrc_string_choice);
          if (vnp == NULL)
          {
            head = AddStringToValNodeChain (head, "biosrc string type", 1);
          }
          ValNodeFree (vnp);
          break;
        case PARSE_FIELD_SOURCE_QUAL:
          vnp = DialogToPointer (dlg->source_qual_choice);
          if (vnp == NULL)
          {
            head = AddStringToValNodeChain (head, "source qual type", 1);
          }
          ValNodeFree (vnp);
          break;
        case PARSE_FIELD_GENE_FIELD:
          vnp = DialogToPointer (dlg->gene_field);
          if (vnp == NULL || vnp->data.intvalue == FEATUREFIELD_NONE)
          {
            head = AddStringToValNodeChain (head, "gene field", 1);
          }
          ValNodeFree (vnp);
          break;
        case PARSE_FIELD_RNA_FIELD:
          vnp = DialogToPointer (dlg->rna_subtype);
          if (vnp == NULL)
          {
            head = AddStringToValNodeChain (head, "RNA subtype", 1);
          }
          ValNodeFree (vnp);
          vnp = DialogToPointer (dlg->rna_field);
          if (vnp == NULL || vnp->data.intvalue == FEATUREFIELD_NONE)
          {
            head = AddStringToValNodeChain (head, "RNA field", 1);
          }
          ValNodeFree (vnp);          
          break;
        case PARSE_FIELD_PROTEIN_FIELD:
          vnp = DialogToPointer (dlg->protein_field);
          if (vnp == NULL || vnp->data.intvalue == FEATUREFIELD_NONE)
          {
            head = AddStringToValNodeChain (head, "protein field", 1);
          }
          ValNodeFree (vnp);
          break;
        case PARSE_FIELD_IMPORT_QUAL:
          vnp = DialogToPointer (dlg->import_qual);
          if (vnp == NULL)
          {
            ValNodeAddPointer (&head, 1, "import qualifier");
          }
          ValNodeFree (vnp);
          vnp = DialogToPointer (dlg->import_feature);
          if (vnp == NULL)
          {
            ValNodeAddPointer (&head, 1, "import feature");
          }
          ValNodeFree (vnp);
          break;
        case PARSE_FIELD_FEATURE_NOTE:
          vnp = DialogToPointer (dlg->feature);
          if (vnp == NULL)
          {
            ValNodeAddPointer (&head, 1, "feature");
            ValNodeFree (vnp);
          }
          break;
        case PARSE_FIELD_DBXREF:
          if (TextHasNoText (dlg->dbxref_db))
          {
            ValNodeAddPointer (&head, 1, "specify database");
          }
          break;
        case SEARCH_FIELD_PUBLICATION:
          if (!dlg->is_search_field) 
          {
            head = AddStringToValNodeChain (head, "field type", 1);
          }
          break;
        default:
          head = AddStringToValNodeChain (head, "field type", 1);
          break;
      }
    }
  }      

  return head;
}


static ValNodePtr GetParseFieldDestList (Boolean include_pub, Boolean include_dbxref)
{
  ValNodePtr list = NULL;

  ValNodeAddPointer (&list, PARSE_FIELD_DEFLINE, StringSave ("Definition Line"));
  ValNodeAddPointer (&list, PARSE_FIELD_BIOSRC_STRING, StringSave ("Biosource"));
  ValNodeAddPointer (&list, PARSE_FIELD_SOURCE_QUAL, StringSave ("Source Qualifier"));
  ValNodeAddPointer (&list, PARSE_FIELD_GENE_FIELD, StringSave ("Gene Field"));
  ValNodeAddPointer (&list, PARSE_FIELD_RNA_FIELD, StringSave ("RNA Field"));
  ValNodeAddPointer (&list, PARSE_FIELD_CDS_COMMENT, StringSave ("CDS Comment"));
  ValNodeAddPointer (&list, PARSE_FIELD_PROTEIN_FIELD, StringSave ("Protein Field"));
  ValNodeAddPointer (&list, PARSE_FIELD_IMPORT_QUAL, StringSave ("Import Feature"));
  ValNodeAddPointer (&list, PARSE_FIELD_FEATURE_NOTE, StringSave ("Feature Note"));
  ValNodeAddPointer (&list, PARSE_FIELD_COMMENT_DESC, StringSave ("Comment Descriptor"));
  if (include_dbxref)
  {
    ValNodeAddPointer (&list, PARSE_FIELD_DBXREF, StringSave ("Dbxref"));
  }
  if (include_pub)
  {
    ValNodeAddPointer (&list, SEARCH_FIELD_PUBLICATION, StringSave ("Publication"));
  }
  return list;  
}


static void ChangeParseFieldDestText (TexT t)
{
  ParseFieldDialogPtr dlg;

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


extern DialoG ParseFieldDestDialogEx 
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Boolean                  is_search_field,
 Boolean                  include_dbxref)
{
  ParseFieldDialogPtr dlg;
  GrouP               p, m, g;
  GrouP               subgrp;
  ValNodePtr          choice_list;
  ValNode             vn;
  
  dlg = (ParseFieldDialogPtr) MemNew (sizeof (ParseFieldDialogData));
  if (dlg == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (h, -1, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);

  dlg->dialog = (DialoG) p;
  dlg->todialog = ParseFieldToDialog;
  dlg->fromdialog = DialogToParseField;
  dlg->dialogmessage = ParseFieldMessage;
  dlg->testdialog = TestParseFieldDialog;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->is_search_field = is_search_field;
  
  m = HiddenGroup (p, 2, 0, NULL);

  choice_list = GetParseFieldDestList (is_search_field, include_dbxref);
  dlg->parse_field_type = ValNodeSelectionDialog (m, choice_list, TALL_SELECTION_LIST, ValNodeStringName,
                                ValNodeSimpleDataFree, ValNodeStringCopy,
                                ValNodeChoiceMatch, "parse destination", 
                                ChangeParseFieldType, dlg, FALSE);

  subgrp = HiddenGroup (m, 0, 0, NULL);
  dlg->biosrc_string_choice = BioSourceStringDialog (subgrp, FALSE, change_notify, change_userdata);
  Hide (dlg->biosrc_string_choice);
  dlg->source_qual_choice = SourceQualTypeSelectionDialog (subgrp, FALSE, change_notify, change_userdata);
  Hide (dlg->source_qual_choice);
  dlg->gene_field = GeneFieldSelectionDialog (subgrp, FALSE, change_notify, change_userdata);
  Hide (dlg->gene_field);
  g = HiddenGroup (subgrp, 2, 0, NULL);
  dlg->rna_subtype = RNASubtypeSelectionDialog (g, FALSE, NULL, change_notify, change_userdata);
  Hide (dlg->rna_subtype);
  dlg->rna_field = RNAFieldSelectionDialog (g, FALSE, 
                                            change_notify, 
                                            change_userdata);
  Hide (dlg->rna_field);
  dlg->protein_field = ProteinFieldSelectionDialog (subgrp, FALSE, change_notify, change_userdata);
  Hide (dlg->protein_field);
  g = HiddenGroup (subgrp, 2, 0, NULL);
  dlg->import_feature = FeatureSelectionDialog (g, TRUE, change_notify, change_userdata);
  Hide (dlg->import_feature);
  dlg->import_qual = GBQualSelectionDialog (g, FALSE, change_notify, change_userdata);
  Hide (dlg->import_qual);
  dlg->feature = FeatureSelectionDialog (subgrp, TRUE, change_notify, change_userdata);
  Hide (dlg->feature);
  if (include_dbxref)
  {
    dlg->dbxref_grp = HiddenGroup (subgrp, 2, 0, NULL);
    StaticPrompt (dlg->dbxref_grp, "Database:", 0, 0, programFont, 'r');
    dlg->dbxref_db = DialogText (dlg->dbxref_grp, "", 5, ChangeParseFieldDestText);
    SetObjectExtra (dlg->dbxref_db, dlg, NULL);
    Hide (dlg->dbxref_grp);
  }
  else
  {
    dlg->dbxref_db = NULL;
  }

  vn.choice = PARSE_FIELD_DEFLINE;
  vn.data.ptrvalue = NULL;
  vn.next = NULL;
  PointerToDialog (dlg->parse_field_type, &vn);

  dlg->feat_or_desc = HiddenGroup (p, 3, 0, NULL);
  RadioButton (dlg->feat_or_desc, "Descriptors and Features");
  RadioButton (dlg->feat_or_desc, "Descriptors Only");
  RadioButton (dlg->feat_or_desc, "Features Only");
  SetValue (dlg->feat_or_desc, 1);
  Hide (dlg->feat_or_desc);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) m, (HANDLE) dlg->feat_or_desc, NULL);

  return (DialoG) p;  
}

extern DialoG ParseFieldDestDialog 
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return ParseFieldDestDialogEx (h, change_notify, change_userdata, FALSE, FALSE);
}
/* Search Field dialog is identical to ParseFieldDestDialog except that it allows
 * an additional option, Publication.
 */
extern DialoG SearchFieldDialog 
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  return ParseFieldDestDialogEx (h, change_notify, change_userdata, TRUE, FALSE);
}


typedef enum
{
 eParseFieldSrcDefLine = 1,
 eParseFieldSrcGenBankFlatFile,
 eParseFieldSrcLocalID,
 eParseFieldSrcTaxName,
 eParseFieldSrcSourceQual,
 eParseFieldSrcComment,
 eParseFieldSrcBankitComment,
 eParseFieldSrcStructuredComment,
 eParseFieldSrcTaxNameAfterNomial,
 eParseFieldSrcFileID
} EParseFieldSrc;

const Int4 MAX_PARSE_FIELD = eParseFieldSrcFileID;

static CharPtr parse_src_field_list [] = 
{
  "Definition Line", "GenBank Flatfile", "Local ID", "Organism Name", "Source Qualifier", "Comment", "Bankit Comment", 
  "Structured Comment", "Taxname after Binomial/Trinomial", "File ID"
};

static int num_parse_src_fields = sizeof (parse_src_field_list) / sizeof (CharPtr);

typedef struct parsefieldsrc 
{
  Int4              parse_field;
  SourceQualDescPtr sqdp;
  CharPtr           comment_field;
} ParseFieldSrcData, PNTR ParseFieldSrcPtr;

static ParseFieldSrcPtr ParseFieldSrcFree (ParseFieldSrcPtr pfsp)
{
  if (pfsp != NULL) {
    pfsp->comment_field = MemFree (pfsp->comment_field);
    pfsp->sqdp = MemFree (pfsp->sqdp);
    pfsp = MemFree (pfsp);
  }
  return pfsp;
}

typedef struct parsefieldsrcdlg
{
  DIALOG_MESSAGE_BLOCK
  DialoG     parse_field;
  PopuP      comment_field;
  ValNodePtr comment_field_list;
  DialoG     src_qual;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;      
} ParseFieldSrcDlgData, PNTR ParseFieldSrcDlgPtr;

static void CleanupParseFieldSrcDialog (GraphiC g, VoidPtr data)
{
  ParseFieldSrcDlgPtr dlg;

  dlg = (ParseFieldSrcDlgPtr) data;
  if (dlg != NULL) {
    dlg->comment_field_list = ValNodeFreeData(dlg->comment_field_list);
  }
  StdCleanupExtraProc (g, data);
}

static void ResetParseFieldSourceDialog (ParseFieldSrcDlgPtr dlg)
{
  if (dlg == NULL) {
      return;
  }
  SendMessageToDialog (dlg->parse_field, VIB_MSG_INIT);
  SetValue (dlg->comment_field, 0);
  Hide (dlg->comment_field);
}

static void ParseFieldSourceDialogMessage (DialoG d, Int2 mssg)

{
  ParseFieldSrcDlgPtr dlg;

  dlg = (ParseFieldSrcDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        ResetParseFieldSourceDialog (dlg);
        break;
      default :
        break;
    }
  }
}

static void ParseFieldChange (Pointer userdata) 
{
  ParseFieldSrcDlgPtr dlg;
  ValNodePtr          vnp;
  
  dlg = (ParseFieldSrcDlgPtr) userdata;
  if (dlg == NULL) return;
   
  vnp = DialogToPointer (dlg->parse_field);
  if (vnp == NULL || vnp->data.intvalue != eParseFieldSrcStructuredComment) {
    Hide (dlg->comment_field);
  } else {
    Show (dlg->comment_field);
  }
  if (vnp == NULL || vnp->data.intvalue != eParseFieldSrcSourceQual) {
    Hide (dlg->src_qual);
  } else {
    Show (dlg->src_qual);
  }
  
  if (dlg->change_notify != NULL) {
    (dlg->change_notify)(dlg->change_userdata);
  }  
}

static Int4 FindCommentFieldPosition (CharPtr comment_field, ValNodePtr comment_list)
{
  Int4       pos = 1;
  ValNodePtr vnp;
  
  if (StringHasNoText (comment_field) || comment_list == NULL) {
    return 0;
  }
  
  vnp = comment_list;
  while (vnp != NULL && StringCmp (comment_field, vnp->data.ptrvalue) != 0) {
    vnp = vnp->next;
    pos++;
  }
  if (vnp == NULL) {
    pos = 0;
  }
  return pos;
}

static void DataToParseFieldSrcDialog(DialoG d, Pointer data)
{
  ParseFieldSrcDlgPtr dlg;
  ParseFieldSrcPtr    pfsp;
  ValNode             vn;

  dlg = (ParseFieldSrcDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    pfsp = (ParseFieldSrcPtr) data;
    if (pfsp == NULL) {
      ResetParseFieldSourceDialog (dlg);
    } else {
      vn.data.intvalue = pfsp->parse_field;
      vn.next = NULL;
      vn.choice = 0;
      PointerToDialog (dlg->parse_field, &vn);
      SetValue (dlg->comment_field, FindCommentFieldPosition (pfsp->comment_field, dlg->comment_field_list));
      vn.data.ptrvalue = pfsp->sqdp;
      PointerToDialog (dlg->src_qual, &vn);
    }
  }
  ParseFieldChange (dlg);     
}

static Pointer ParseFieldSrcDialogToData(DialoG d)
{
  ParseFieldSrcDlgPtr dlg;
  ParseFieldSrcPtr    pfsp;
  ValNodePtr          vnp;
  Int4                field_pos;

  dlg = (ParseFieldSrcDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) {
    return NULL;
  }
  
  pfsp = (ParseFieldSrcPtr) MemNew (sizeof (ParseFieldSrcData));
  vnp = DialogToPointer (dlg->parse_field);
  if (vnp != NULL) {
    pfsp->parse_field = vnp->data.intvalue;
    vnp = ValNodeFree (vnp);
  }
  pfsp->comment_field = NULL;
  pfsp->sqdp = NULL;
  if (pfsp->parse_field == eParseFieldSrcStructuredComment) {
    field_pos = GetValue (dlg->comment_field);
    if (field_pos > 0) {
      vnp = dlg->comment_field_list;
      field_pos --;
      while (field_pos > 0 && vnp != NULL) {
        vnp = vnp->next;
        field_pos --;
      }
      if (vnp != NULL) {
        pfsp->comment_field = StringSave (vnp->data.ptrvalue);
      }
    }
  } else if (pfsp->parse_field == eParseFieldSrcSourceQual) {
    vnp = DialogToPointer (dlg->src_qual);
    if (vnp != NULL) {
      pfsp->sqdp = vnp->data.ptrvalue;
      vnp->data.ptrvalue = NULL;
      vnp = ValNodeFreeData (vnp);
    }
  }
  
  return pfsp;
}

static void AddCommentFieldName (SeqDescrPtr sdp, Pointer userdata)
{
  UserObjectPtr uop;
  ObjectIdPtr   oip;
  UserFieldPtr  ufp;
  ValNodePtr    vnp;
  Boolean       found;
  ValNodePtr PNTR comment_field_list = (ValNodePtr PNTR) userdata;
  
  if (comment_field_list == NULL 
      || sdp->choice != Seq_descr_user 
      || sdp->extended == 0
      || sdp->data.ptrvalue == NULL) {
    return;
  }

  uop = (UserObjectPtr) sdp->data.ptrvalue;
  oip = uop->type;
  if (oip != NULL && StringCmp (oip->str, "StructuredComment") == 0)
  {
    for (ufp = uop->data; ufp != NULL; ufp = ufp->next)
    {
      oip = ufp->label;
      if (oip != NULL && !StringHasNoText (oip->str))
      {
        found = FALSE;
        vnp = *comment_field_list;
        while (vnp != NULL && !found) {
          if (StringCmp (vnp->data.ptrvalue, oip->str) == 0) {
            found = TRUE;
          }
          vnp = vnp->next;
        }
        if (!found) {
          ValNodeAddPointer (comment_field_list, 0, StringSave (oip->str));
        }
      }
    }
  }
}


static ValNodePtr TestParseFieldSource (DialoG d)

{
  ParseFieldSrcDlgPtr dlg;
  ValNodePtr          head = NULL, vnp, vnp2;

  dlg = (ParseFieldSrcDlgPtr) GetObjectExtra (d);
  
  vnp = DialogToPointer (dlg->parse_field);
  if (vnp == NULL)
  {
    head = AddStringToValNodeChain (head, "field choice", 1);
  }
  else if (vnp->data.intvalue == eParseFieldSrcSourceQual)
  {
    vnp2 = DialogToPointer (dlg->src_qual);
    if (vnp2 == NULL)
    {
      head = AddStringToValNodeChain (head, "source qual", 1);
    }
    vnp2 = ValNodeFree (vnp2);
  }

  vnp = ValNodeFree (vnp);
  
  return head;  
}


extern DialoG ParseFieldSourceDialog
(GrouP                    h,
 SeqEntryPtr              sep,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  ParseFieldSrcDlgPtr dlg;
  GrouP               p, k;
  ValNodePtr          vnp;
  
  dlg = (ParseFieldSrcDlgPtr) MemNew (sizeof (ParseFieldSrcDlgData));
  if (dlg == NULL) 
  {
    return NULL;
  }
  
  p = HiddenGroup (h, 2, 0, NULL);

  SetObjectExtra (p, dlg, CleanupParseFieldSrcDialog);

  dlg->dialog = (DialoG) p;
  dlg->todialog = DataToParseFieldSrcDialog;
  dlg->fromdialog = ParseFieldSrcDialogToData;
  dlg->dialogmessage = ParseFieldSourceDialogMessage;
  dlg->testdialog = TestParseFieldSource;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->parse_field = FeatureFieldSelectionDialog (p, FALSE,
                                                  num_parse_src_fields, parse_src_field_list, 
                                                  ParseFieldChange, dlg);

  k = HiddenGroup (p, 0, 0, NULL);
  dlg->comment_field = PopupList (k, TRUE, NULL);
  dlg->src_qual = SourceQualTypeSelectionDialogEx (k, FALSE, change_notify,
                                                   change_userdata, 6, FALSE, FALSE);
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->comment_field, (HANDLE) dlg->src_qual, NULL);
  
  /* Add to comment_field list all user fields found in SeqEntry sep */
  VisitDescriptorsInSep (sep, &(dlg->comment_field_list), AddCommentFieldName);
  for (vnp = dlg->comment_field_list; vnp != NULL; vnp = vnp->next) {
    PopupItem (dlg->comment_field, vnp->data.ptrvalue);
  }
    
  return (DialoG) p;  
}

typedef struct sampledialog
{
  DIALOG_MESSAGE_BLOCK
  DoC                doc;
  Int2               start_item;
  Int2               start_row;
  Int2               start_col;
  Int2               end_item;
  Int2               end_row;
  Int2               end_col;
  SetSamplePtr       ssp;
} SampleDialogData, PNTR SampleDialogPtr;

static void TruncateStringAfterTwoLines (CharPtr str, Uint4 char_per_line)
{
  CharPtr cp;
  Uint4    string_len;
  
  if (StringHasNoText (str))
  {
    return;
  }
  
  string_len = StringLen (str);
  if (string_len < char_per_line)
  {
    return;
  }
  
  cp = str + char_per_line;
  while (! isspace ((Int4)(*cp)) && cp > str)
  {
    cp--;
  }
  if (cp == str)
  {
    if (string_len > 2 * char_per_line)
    {
      str [2 * char_per_line] = 0;
    }
  }
  else
  {
    if (StringLen (cp) > char_per_line)
    {
      cp[char_per_line] = 0;
    }
  }
  
}

typedef struct exportsample
{
  ValNodePtr   row_list;
  SetSamplePtr ssp;
} ExportSampleData, PNTR ExportSamplePtr;

static void ExportSampleDialogCallback (BioseqPtr bsp, Pointer userdata)
{
  ExportSamplePtr   esp;
  SeqFeatPtr        sfp;
  SeqMgrFeatContext fcontext;
  SeqDescrPtr       sdp;
  SeqMgrDescContext dcontext;
  Char              tmp[128];
  CharPtr           val_str = NULL, new_val_str, tmp_val_str;
  ValNodePtr        sample_field, sample_field_next;
  ValNodePtr        new_row = NULL;
  SeqIdPtr          sip;
  
  if (bsp == NULL || userdata == NULL || ! ISA_na (bsp->mol))
  {
    return;
  }
  
  esp = (ExportSamplePtr) userdata;
  
  sip = SeqIdFindBest (bsp->id, 0);
  SeqIdWrite (sip, tmp, PRINTID_REPORT, sizeof (tmp));
  
  ValNodeAddPointer (&new_row, 0, StringSave (tmp));
  
  for (sample_field = esp->ssp->field_list;
       sample_field != NULL; 
       sample_field = sample_field->next)
  {
    sample_field_next = sample_field->next;
    sample_field->next = NULL;
    val_str = NULL;
  
    if (esp->ssp->descrstring_func != NULL)
    {
      sdp = SeqMgrGetNextDescriptor (bsp, NULL, 0, &dcontext);
      while (sdp != NULL)
      {
        new_val_str = (esp->ssp->descrstring_func) (sdp, sample_field, NULL);
        tmp_val_str = StringAppend (val_str, new_val_str);
        val_str = MemFree (val_str);
        new_val_str = MemFree (new_val_str);
        val_str = tmp_val_str;
        tmp_val_str = NULL;
        sdp = SeqMgrGetNextDescriptor (bsp, sdp, 0, &dcontext);
      }
    }
    if (esp->ssp->fieldstring_func != NULL)
    {
      sfp = SeqMgrGetNextFeature (bsp, NULL, 0, 0, &fcontext);
      while (sfp != NULL)
      {
        new_val_str = (esp->ssp->fieldstring_func)(sfp, sample_field, NULL);
        tmp_val_str = StringAppend (val_str, new_val_str);
        val_str = MemFree (val_str);
        new_val_str = MemFree (new_val_str);
        val_str = tmp_val_str;
        tmp_val_str = NULL;
        sfp = SeqMgrGetNextFeature (bsp, sfp, 0, 0, &fcontext);
      }
    }
    ValNodeAddPointer (&new_row, 0, val_str);
    sample_field->next = sample_field_next;
  }
  ValNodeAddPointer (&(esp->row_list), 0, new_row);
}

static void GetTableDataColumnWidths (ValNodePtr row_list)
{
  ValNodePtr row_vnp, col_vnp, header_vnp;

  if (row_list == NULL || row_list->next == NULL)
  {
    return;
  }
  
  /* only collect widths for data columns, not header, so we can detect empty rows */
  for (row_vnp = row_list->next; row_vnp != NULL; row_vnp = row_vnp->next)
  {
    for (col_vnp = row_vnp->data.ptrvalue, header_vnp = row_list->data.ptrvalue;
         col_vnp != NULL && header_vnp != NULL;
         col_vnp = col_vnp->next, header_vnp = header_vnp->next)
    {
      if (!StringHasNoText (col_vnp->data.ptrvalue))
      {
        header_vnp->choice = MAX (header_vnp->choice, StringLen (col_vnp->data.ptrvalue));
      }
    }
  }
}

static void RemoveTableEmptyColumns (ValNodePtr row_list)
{
  ValNodePtr row_vnp, col_vnp, header_vnp, col_vnp_next, col_vnp_prev;
  ValNodePtr column_list;

  if (row_list == NULL)
  {
    return;
  }
  
  /* remove data columns */
  for (row_vnp = row_list->next; row_vnp != NULL; row_vnp = row_vnp->next)
  {
    column_list = row_vnp->data.ptrvalue;
    col_vnp_prev = NULL;
    for (col_vnp = column_list, header_vnp = row_list->data.ptrvalue;
         col_vnp != NULL && header_vnp != NULL;
         col_vnp = col_vnp_next, header_vnp = header_vnp->next)
    {
      col_vnp_next = col_vnp->next;
      if (header_vnp->choice == 0)
      {
        if (col_vnp_prev == NULL)
        {
          row_vnp->data.ptrvalue = col_vnp_next;
          column_list = row_vnp->data.ptrvalue;
        }
        else
        {
          col_vnp_prev->next = col_vnp_next;
        }
        col_vnp->next = NULL;
        ValNodeFreeData (col_vnp);
      }
      else
      {
        col_vnp_prev = col_vnp;
      }
    }
  }
  
  /* remove headers for empty columns */
  col_vnp_prev = NULL;
  for (col_vnp = row_list->data.ptrvalue;
       col_vnp != NULL;
       col_vnp = col_vnp_next)
  {
    col_vnp_next = col_vnp->next;
    if (col_vnp->choice == 0)
    {
      if (col_vnp_prev == NULL)
      {
        row_list->data.ptrvalue = col_vnp_next;
      }
      else
      {
        col_vnp_prev->next = col_vnp_next;
      }
      col_vnp->next = NULL;
      ValNodeFreeData (col_vnp);
    }
    else
    {
      col_vnp_prev = col_vnp;
    }
  }
}

static void ExportSampleDialogData (SampleDialogPtr dlg)
{
  Char             path [PATH_MAX];
  ExportSampleData esd;
  SeqEntryPtr      sep;
  CharPtr          sample_label;
  ValNodePtr       sample_field;
  FILE             *fp;
  ValNodePtr       new_row = NULL;
  
  if (dlg == NULL || dlg->ssp == NULL || dlg->ssp->label_vn_proc == NULL)
  {
    return;
  }
  
  sep = GetTopSeqEntryForEntityID (dlg->ssp->entityID);
  if (sep == NULL)
  {
    return;
  }
  if (! GetOutputFileName (path, sizeof (path), NULL)) return;
  fp = FileOpen (path, "w");
  if (fp == NULL)
  {
    Message (MSG_ERROR, "Unable to open %s", path);
    return;
  }
  WatchCursor ();
  Update ();
  esd.ssp = dlg->ssp;
  ValNodeAddPointer (&new_row, 0, StringSave ("Sequence"));
    
  for (sample_field = dlg->ssp->field_list;
       sample_field != NULL; 
       sample_field = sample_field->next)
  {
    sample_label = (dlg->ssp->label_vn_proc) (sample_field);
    ValNodeAddPointer (&new_row, 0, StringSave (sample_label));
  }
  esd.row_list = NULL;
  ValNodeAddPointer (&(esd.row_list), 0, new_row);
  
  VisitBioseqsInSep (sep, &esd, ExportSampleDialogCallback);

  GetTableDataColumnWidths (esd.row_list);
  RemoveTableEmptyColumns (esd.row_list);

  PrintTableDisplayRowListToFile (esd.row_list, fp);
  esd.row_list = FreeTableDisplayRowList (esd.row_list);
  
  FileClose (fp);
  ArrowCursor();
  Update();
}

static void RefreshSampleDialog (SampleDialogPtr dlg)
{
  SeqEntryPtr     sep;
  RecT            r;
  ParData         ParFmt = {FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0};
  ColData         ColFmt[] = 
  {
    {0, 0, 80, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, 80, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, 80, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, TRUE}
  };
  GetSampleData   gsd;
  CharPtr         new_line;
  ValNodePtr      sample_field, sample_field_next;
  CharPtr         sample_label = NULL;
  Int4            max_char_per_line;

  if (dlg == NULL)
  {
    return;
  }
  Reset (dlg->doc);
  if (dlg->ssp == NULL || dlg->ssp->field_list == NULL
      || (dlg->ssp->fieldstring_func == NULL && dlg->ssp->descrstring_func == NULL))
  {
    return;
  }
  
  sep = GetTopSeqEntryForEntityID (dlg->ssp->entityID);
  if (sep == NULL)
  {
    return;
  }
  gsd.sample_text = NULL;
  gsd.fieldstring_func = dlg->ssp->fieldstring_func;
  gsd.descrstring_func = dlg->ssp->descrstring_func;
    
  ObjectRect (dlg->doc, &r);
  InsetRect (&r, 4, 4);
  ColFmt[0].pixWidth = (r.right - r.left) / 3;
  ColFmt[1].pixWidth = (r.right - r.left) / 4;
  ColFmt[2].pixWidth = (r.right - r.left)  - ColFmt[0].pixWidth - ColFmt[1].pixWidth;

  SelectFont (programFont);
  max_char_per_line = ColFmt[2].pixWidth / CharWidth ('0');
  
  
  for (sample_field = dlg->ssp->field_list; sample_field != NULL; sample_field = sample_field->next)
  {
    sample_field_next = sample_field->next;
    sample_field->next = NULL;
    gsd.requested_field = sample_field;
    gsd.num_found = 0;
    gsd.all_same = TRUE;
    gsd.feat_dest_list = NULL;
    gsd.descr_dest_list = NULL;
    
    OperateOnSeqEntryConstrainedObjects (sep, dlg->ssp->fsp, GetSampleFeatureCallback, 
                                         GetSampleDescriptorCallback,
                                         0, 0, 0, &gsd);
    if (gsd.num_found > 0)
    {
      sample_label = (dlg->ssp->label_vn_proc) (sample_field);
      new_line = (CharPtr) MemNew (
           (StringLen (sample_label) 
            + StringLen (gsd.sample_text) + 20)
            * sizeof (Char));
      if (new_line != NULL)
      {
        if (gsd.all_same)
        {
          sprintf (new_line, "%s\t(%d same)\t", sample_label, gsd.num_found);
        }
        else
        {
          sprintf (new_line, "%s\t(%d mixed)\t", sample_label, gsd.num_found);
        }
        if (!StringHasNoText (gsd.sample_text))
        {
          TruncateStringAfterTwoLines (gsd.sample_text, max_char_per_line);
          StringCat (new_line, gsd.sample_text);
        }
        StringCat (new_line, "\n");
          AppendText (dlg->doc, new_line, &ParFmt, ColFmt, programFont);
          MemFree (new_line);
      }
      sample_label = MemFree (sample_label);
    }
    gsd.sample_text = MemFree (gsd.sample_text);
    sample_field->next = sample_field_next;
  }
  InvalDocument (dlg->doc);
}

static void PopulateSampleDialog (DialoG d, Pointer userdata)
{
  SampleDialogPtr dlg;
  SetSamplePtr    ssp;
  
  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg == NULL || userdata == NULL)
  {
    return;
  }
  ssp = (SetSamplePtr) userdata;
  dlg->ssp = SetSampleFree (dlg->ssp);
  dlg->ssp = SetSampleCopy (ssp);
}

static void 
GetSampleDialogStartAndEnd 
(SampleDialogPtr dlg,
 Int2Ptr         start_item,
 Int2Ptr         start_row,
 Int2Ptr         start_col,
 Int2Ptr         end_item,
 Int2Ptr         end_row,
 Int2Ptr         end_col)
{
  if (dlg == NULL || start_item == NULL || start_row == NULL
      || start_col == NULL || end_item == NULL
      || end_row == NULL || end_col == NULL)
  {
    return;
  }
  
  if (dlg->start_item < 0)
  {
    *start_item = -1;
    *start_row = -1;
    *start_col = -1;
    *end_item = -1;
    *end_row = -1;
    *end_col = -1;
  }
  else if (dlg->end_item < 0)
  {
    *start_item = dlg->start_item;
    *start_row = dlg->start_row;
    *start_col = dlg->start_col;
    *end_item = *start_item;
    *end_row = *start_row;
    *end_col = *start_col;
  }
  else if (dlg->start_item <= dlg->end_item)
  {
    *start_item = dlg->start_item;
    *end_item = dlg->end_item;
    if (dlg->start_row <= dlg->end_row)
    {
      *start_row = dlg->start_row;
      *end_row = dlg->end_row;
      if (dlg->start_col <= dlg->end_col)
      {
        *start_col = dlg->start_col;
        *end_col = dlg->end_col;
      }
      else
      {
        *start_col = dlg->end_col;
        *end_col = dlg->start_col;
      }
    }
    else
    {
      *start_row = dlg->end_row;
      *start_col = dlg->end_col;
      *end_row = dlg->start_row;
      *end_col = dlg->start_col;
    }
  }
  else
  {
    *start_item = dlg->end_item;
    *start_row = dlg->end_row;
    *start_col = dlg->end_col;
    *end_item = dlg->start_item;
    *end_row = dlg->start_row;
    *end_col = dlg->start_col;
  }
  
}

static void SampleDialogCopy (SampleDialogPtr dlg)
{
  Int2       start_row = 0, end_row = 0, tmp_row, first_row, last_row;
  Int2       start_col = 0, end_col = 0, first_col, last_col;
  Int2       start_item = 0, end_item = 0, tmp_item;
  CharPtr    str;
  ValNodePtr strings = NULL;
  Int2       num_rows, num_cols;
  
  if (dlg->start_row < 0)
  {
    return;
  }
  
  GetSampleDialogStartAndEnd (dlg, &start_item, &start_row, &start_col,
                              &end_item, &end_row, &end_col);

  first_row = start_row;
  first_col = start_col;
  for (tmp_item = start_item; tmp_item <= end_item; tmp_item++)
  {
    GetItemParams (dlg->doc, tmp_item, NULL, &num_rows, &num_cols, NULL, NULL);
    if (tmp_item == end_item)
    {
      last_row = end_row;
    }
    else
    {
      last_row = num_rows;
    }
    for (tmp_row = first_row; tmp_row <= last_row; tmp_row++)
    {
      if (tmp_row == last_row && tmp_item == end_item)
      {
        last_col = end_col;
      }
      else
      {
        last_col = num_cols;
      }
      str = GetDocText (dlg->doc, tmp_item, tmp_row, 3);
      ValNodeAddPointer (&strings, 0, str);
    }
    first_row = 1;
  }
  str = MergeValNodeStrings (strings, FALSE);
  
  StringToClipboard (str);
  MemFree (str);
  strings = ValNodeFreeData (strings);
}

static void InvalidateSampleDialogRows (DoC d, Int2 start_item, Int2 end_item)
{
  Int2 num_rows;
  if (d == NULL)
  {
    return;
  }
  if (start_item < 1)
  {
    start_item = 1;
  }
  if (end_item < 1)
  {
    end_item = start_item;
  }
  while (start_item <= end_item)
  {
    GetItemParams (d, start_item, NULL, &num_rows, NULL, NULL, NULL);
    InvalDocRows (d, start_item, 1, num_rows); 
    start_item++;       
  }
}

static void SampleDialogOnClick (DoC d, PoinT pt)
{
  SampleDialogPtr   dlg;
  Int2              pos_item, pos_row, pos_col;
  Int2              start_item = 0, start_row, start_col;
  Int2              end_item = -1, end_row, end_col;
  
  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  MapDocPoint (d, pt, &(pos_item), &(pos_row), &(pos_col), NULL);
  if (dlg->start_item == pos_item
      && dlg->start_row == pos_row 
      && dlg->start_col == pos_col)
  {
    dlg->start_row = -1;
    dlg->start_col = -1;
    
  }
  else
  {
    if (dlg->start_item > -1)
    {
      GetSampleDialogStartAndEnd (dlg, &start_item, &start_row, &start_col,
                                  &end_item, &end_row, &end_col);
    }
    dlg->start_item = pos_item;
    dlg->start_row = pos_row;
    dlg->start_col = pos_col;
  }
  dlg->end_item = -1;
  dlg->end_row = -1;
  dlg->end_col = -1;
  InvalidateSampleDialogRows (d, start_item, end_item);
  InvalidateSampleDialogRows (d, pos_item, pos_item);
}

static void SampleDialogOnDrag (DoC d, PoinT pt)
{
  SampleDialogPtr   dlg;
  
  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  MapDocPoint (d, pt, &(dlg->end_item), &(dlg->end_row), &(dlg->end_col), NULL);
  InvalDocument (d);
}

static void SampleDialogOnRelease (DoC d, PoinT pt)
{
  SampleDialogPtr   dlg;
  
  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  MapDocPoint (d, pt, &(dlg->end_item), &(dlg->end_row), &(dlg->end_col), NULL);
  InvalDocument (d);
}

static Boolean SampleDialogInvert (DoC d, Int2 item, Int2 row, Int2 col)
{
  SampleDialogPtr   dlg;
  Int2              start_item = 0, start_row = 0, start_col = 0;
  Int2              end_item = 0, end_row = 0, end_col = 0;
  Boolean           rval = FALSE;
  
  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return FALSE;
  
  if (dlg->start_row < 0)
  {
    return FALSE;
  }
  
  if (dlg->end_item == -1)
  {
    if(dlg->start_item == item
       && dlg->start_row == row
       && dlg->start_col == col)
    {
      return TRUE;
    }
    else
    {
      return FALSE;
    }
  }
  GetSampleDialogStartAndEnd (dlg, &start_item, &start_row, &start_col,
                              &end_item, &end_row, &end_col);
  if (start_item <= item && end_item >= item)
  {
    rval = TRUE;
    if (start_item == item)
    {
      if (start_row == row)
      {
        if (start_col > col)
        {
          rval = FALSE;
        }
      }
      else if (start_row > row)
      {
        rval = FALSE;
      }
    }
    
    if (end_item == item)
    {
      if (end_row == row)
      {
        if (end_col < col)
        {
          rval = FALSE;
        }
      }
      else if (end_row < row)
      {
        rval = FALSE;
      }
    }
  }
  if (col == 3)
  {
    return rval;
  }
  else
  {
    return FALSE;
  }
}

static void SampleDialogOnKey (SlatE s, Char ch)
{
  DoC             d;
  SampleDialogPtr dlg;

  d = (DoC) s;
  Select (d);
  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  CaptureSlateFocus ((SlatE) s);
  
#ifdef WIN_MSWIN
  if (ch == 3)
  {
    SampleDialogCopy (dlg);
  }
#else
  if (ctrlKey)
  {
    if (ch == 'c')
    {
      SampleDialogCopy (dlg);
    }
  }
#endif
}

static void SampleDialogMessage (DialoG d, Int2 mssg)

{
  SampleDialogPtr dlg;

  dlg = (SampleDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        RefreshSampleDialog (dlg);
        break;
      default :
        break;
    }
  }
}

static void CleanupSampleDialog (GraphiC g, VoidPtr data)
{
  SampleDialogPtr dlg;

  dlg = (SampleDialogPtr) data;
  if (dlg != NULL) {
    dlg->ssp = SetSampleFree (dlg->ssp);
  }
  StdCleanupExtraProc (g, data);
}

static void RefreshSampleBtn (ButtoN b)
{
  SampleDialogPtr dlg;
  
  dlg = (SampleDialogPtr) GetObjectExtra (b);
  RefreshSampleDialog (dlg);  
}

static void ExportSampleBtn (ButtoN b)
{
  SampleDialogPtr dlg;
  
  dlg = (SampleDialogPtr) GetObjectExtra (b);
  ExportSampleDialogData (dlg);  
}

extern DialoG SampleDialog (GrouP h)
{
  SampleDialogPtr dlg;
  GrouP           p, g;
  ButtoN          b;
  
  dlg = (SampleDialogPtr) MemNew (sizeof (SampleDialogData));
  if (dlg == NULL) 
  {
    return NULL;
  }
  
  p = HiddenGroup (h, -1, 0, NULL);

  SetObjectExtra (p, dlg, CleanupSampleDialog);

  dlg->dialog = (DialoG) p;
  dlg->todialog = PopulateSampleDialog;
  dlg->fromdialog = NULL;
  dlg->dialogmessage = SampleDialogMessage;
  dlg->testdialog = NULL;

  dlg->doc = DocumentPanel (p, stdCharWidth * 27, stdLineHeight * 8);
  SetObjectExtra (dlg->doc, dlg, NULL);
  SetDocProcs (dlg->doc, SampleDialogOnClick, SampleDialogOnDrag, SampleDialogOnRelease, NULL);  
  SetDocShade (dlg->doc, NULL, NULL, SampleDialogInvert, NULL);
  SetDocAutoAdjust (dlg->doc, TRUE);
  SetSlateChar ((SlatE) dlg->doc, SampleDialogOnKey);
  
  g = HiddenGroup (p, 2, 0, NULL);
  b = PushButton (g, "Refresh Qualifiers", RefreshSampleBtn);
  SetObjectExtra (b, dlg, NULL);
  b = PushButton (g, "Export Qualifiers", ExportSampleBtn);
  SetObjectExtra (b, dlg, NULL);  
  
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->doc, (HANDLE) g, NULL);
  
  return (DialoG) p;  
}

typedef struct featurefieldchoicedialog 
{
  DIALOG_MESSAGE_BLOCK
  DialoG first_choice;
  DialoG second_choice;
  DialoG third_choice;
  ButtoN remove_first;
  ButtoN remove_second;
  ButtoN remove_third;

  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;      
} FeatureFieldChoiceDialogData, PNTR FeatureFieldChoiceDialogPtr;

static void FeatureFieldChoiceChange (Pointer data)
{
  FeatureFieldChoiceDialogPtr dlg;
  ValNodePtr                  vnp;

  dlg = (FeatureFieldChoiceDialogPtr) data;
  if (dlg == NULL)
  {
    return;
  }
  
  Disable (dlg->remove_first);
  Disable (dlg->remove_second);
  Disable (dlg->remove_third);
  
  Disable (dlg->second_choice);
  Disable (dlg->third_choice);
  
  vnp = DialogToPointer (dlg->first_choice);
  if (vnp != NULL)
  {
    ValNodeFree (vnp);
    Enable (dlg->second_choice);
    Enable (dlg->remove_first);
    vnp = DialogToPointer (dlg->second_choice);
    if (vnp != NULL)
    {
      ValNodeFree (vnp);
      Enable (dlg->third_choice);
      Enable (dlg->remove_second);
      vnp = DialogToPointer (dlg->third_choice);
      if (vnp != NULL)
      {
        ValNodeFree (vnp);
        Enable (dlg->remove_third);
      }
    }
  }
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }
}

static void ResetFeatureFieldChoiceDialog (FeatureFieldChoiceDialogPtr dlg)
{
  if (dlg == NULL) 
  {
    return;
  }
  PointerToDialog (dlg->first_choice, NULL);
  PointerToDialog (dlg->second_choice, NULL);
  PointerToDialog (dlg->third_choice, NULL);
  if (dlg->remove_first != NULL)
  {
    SetStatus (dlg->remove_first, FALSE);
  }
  if (dlg->remove_second != NULL)
  {
    SetStatus (dlg->remove_second, FALSE);
  }
  if (dlg->remove_third != NULL)
  {
    SetStatus (dlg->remove_third, FALSE);
  }
  FeatureFieldChoiceChange (dlg);
}

static void FeatureFieldChoiceToDialog (DialoG d, Pointer data)
{
  FeatureFieldChoiceDialogPtr dlg;
  ValNodePtr                  feature_field;
  
  dlg = (FeatureFieldChoiceDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetFeatureFieldChoiceDialog (dlg);
  feature_field = (ValNodePtr) data;
  if (feature_field != NULL)
  {
    PointerToDialog (dlg->first_choice, feature_field);
    if (feature_field->choice != 0 && dlg->remove_first != NULL)
    {
      SetStatus (dlg->remove_first, TRUE);
    }
    feature_field = feature_field->next;
    if (feature_field != NULL)
    {
      PointerToDialog (dlg->second_choice, feature_field);
      if (feature_field->choice != 0 && dlg->remove_second != NULL)
      {
        SetStatus (dlg->remove_second, TRUE);
      }
      feature_field = feature_field->next;
      if (feature_field != NULL)
      {
        if (feature_field->choice != 0 && dlg->remove_third != NULL)
        {
          SetStatus (dlg->remove_third, TRUE);
        }
        PointerToDialog (dlg->third_choice, feature_field);
      }
    }
  }
  FeatureFieldChoiceChange (dlg);
}

static Pointer DialogToFeatureFieldChoice (DialoG d)
{
  FeatureFieldChoiceDialogPtr dlg;
  ValNodePtr                  choice_list = NULL;
  ValNodePtr                  feature_field, vnp_prev;

  dlg = (FeatureFieldChoiceDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  feature_field = DialogToPointer (dlg->first_choice);
  if (feature_field != NULL)
  {
    if (dlg->remove_first != NULL && GetStatus (dlg->remove_first))
    {
      feature_field->choice = 1;
    }
    choice_list = feature_field;
    
    feature_field = DialogToPointer (dlg->second_choice);
    if (feature_field != NULL)
    {
      if (dlg->remove_second != NULL && GetStatus (dlg->remove_second))
      {
        feature_field->choice = 1;
      }
      choice_list->next = feature_field;
      vnp_prev = feature_field;
      
      feature_field = DialogToPointer (dlg->third_choice);
      if (feature_field != NULL)
      {
        if (dlg->remove_third != NULL && GetStatus (dlg->remove_third))
        {
          feature_field->choice = 1;
        }
        vnp_prev->next = feature_field;
      }
      else
      {
        ValNodeFree (feature_field);
      }
    }
    else
    {
      ValNodeFree (feature_field);
    }
  }
  else
  {
    ValNodeFree (feature_field);
  }
  return choice_list;
}

static void FeatureFieldChoiceMessage (DialoG d, Int2 mssg)

{
  FeatureFieldChoiceDialogPtr  dlg;

  dlg = (FeatureFieldChoiceDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT :
        /* reset list */
        ResetFeatureFieldChoiceDialog (dlg);
        break;
      case VIB_MSG_ENTER :
        Select (dlg->first_choice);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestFeatureFieldChoice (DialoG d)

{
  FeatureFieldChoiceDialogPtr dlg;
  ValNodePtr                  head = NULL;
  ValNodePtr                  vnp;

  dlg = (FeatureFieldChoiceDialogPtr) GetObjectExtra (d);
  vnp = DialogToPointer (d);
  if (vnp == NULL)
  {
    head = AddStringToValNodeChain (head, "feature field choice", 1);
  }
  else
  {
    ValNodeFree (vnp);
  }
  return head;  
}

extern DialoG FeatureFieldChoiceDialog 
(GrouP h,
 FeatureFieldSelectionProc make_fieldlist_dlg,
 Boolean                   offer_to_remove,
 Nlm_ChangeNotifyProc      change_notify,
 Pointer                   change_userdata)
{
  FeatureFieldChoiceDialogPtr  dlg;
  GrouP                     p, g;

  if (make_fieldlist_dlg == NULL)
  {
    return NULL;
  }
  
  dlg = (FeatureFieldChoiceDialogPtr) MemNew (sizeof (FeatureFieldChoiceDialogData));
  if (dlg == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, 0, 6, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);

  dlg->dialog = (DialoG) p;
  dlg->todialog = FeatureFieldChoiceToDialog;
  dlg->fromdialog = DialogToFeatureFieldChoice;
  dlg->dialogmessage = FeatureFieldChoiceMessage;
  dlg->testdialog = TestFeatureFieldChoice;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  g = HiddenGroup (p, 3, 0, NULL);
  StaticPrompt (g, "1st Choice", 0, 0, programFont, 'c');
  dlg->first_choice = (make_fieldlist_dlg) (g, TRUE, FeatureFieldChoiceChange, dlg);
  if (offer_to_remove)
  {
    dlg->remove_first = CheckBox (g, "Remove if used", NULL);
    Disable (dlg->remove_first);
  }
  else
  {
    dlg->remove_first = NULL;
  }
  
  g = HiddenGroup (p, 3, 0, NULL);
  StaticPrompt (g, "2nd Choice", 0, 0, programFont, 'c');
  dlg->second_choice = (make_fieldlist_dlg) (g, TRUE, FeatureFieldChoiceChange, dlg);
  if (offer_to_remove)
  {
    dlg->remove_second = CheckBox (g, "Remove if used", NULL);
    Disable (dlg->remove_second);
  }
  else
  {
    dlg->remove_second = NULL;
  }
  Disable (dlg->second_choice);
  
  g = HiddenGroup (p, 3, 0, NULL);
  StaticPrompt (g, "3rd Choice", 0, 0, programFont, 'c');
  dlg->third_choice = (make_fieldlist_dlg) (g, TRUE, FeatureFieldChoiceChange, dlg);
  if (offer_to_remove)
  {
    dlg->remove_third = CheckBox (g, "Remove if used", NULL);
    Disable (dlg->remove_third);
  }
  else
  {
    dlg->remove_third = NULL;
  }
  Disable (dlg->third_choice);

  return (DialoG) p;
}


typedef ValNodePtr (*AdjustConvertFeatureListFunc) PROTO ((ValNodePtr, Pointer, BaseFormPtr, BoolPtr cancel));
typedef Pointer (*CollectConvertFeatureOptionsFunc) PROTO ((ValNodePtr, BaseFormPtr, BoolPtr cancel));
typedef Pointer (*FreeConvertFeatureOptionsFunc) PROTO ((Pointer));
typedef Boolean (*ConvertFeatureTypeFunc) PROTO ((SeqFeatPtr, Uint2, Pointer));
typedef void (*ConversionCleanupFunc) PROTO ((Uint2, Pointer));

typedef struct convertfeatureprocs {
  Uint2 seqfeat_from;
  Uint2 featdef_from;
  Uint2 seqfeat_to;
  Uint2 featdef_to;
  AdjustConvertFeatureListFunc adjust_features;
  CollectConvertFeatureOptionsFunc collect_options;
  FreeConvertFeatureOptionsFunc    free_options;
  ConvertFeatureTypeFunc           convert_func;
  ConversionCleanupFunc            cleanup_func;
  CharPtr                          help_text;
} ConvertFeatureProcsData, PNTR ConvertFeatureProcsPtr;

static Pointer SimpleOptsFree (Pointer);
static Boolean ConvertCDSToRNAEx (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertGeneToRNAEx (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static ValNodePtr AdjustConvertFeaturesForBioSrcToRepeatRegion (ValNodePtr feat_list, Pointer extradata, BaseFormPtr bfp, BoolPtr cancel);
static Boolean ConvertBioSrcToRepeatRegionEx (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static ValNodePtr AdjustCDSToMiscFeatList (ValNodePtr feat_list, Pointer extradata, BaseFormPtr bfp, BoolPtr cancel);
static Pointer GetCDSConversionOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel);
static Boolean CDSToMiscFeatConvertFunc (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static void CDSToMiscFeatConversionCleanup (Uint2 entityID, Pointer extradata);
static Boolean ConvertImpToProt (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertProtToImp (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertImpToSpecialRNA (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertRegionToImp (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertRegionToRNA (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertImpToRNA (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertCommentToMiscFeat (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertGeneToImpFeat (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertRNAToMiscFeat (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertSiteToMiscFeat (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertProtToRegion (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertRegionToProt (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Pointer GetBondOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel);
static Pointer GetSiteOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel);
static Boolean ConvertToSiteOrBond (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Pointer GetRegionOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel);
static ValNodePtr AdjustRegionConversionFeatures (ValNodePtr feat_list, Pointer extradata, BaseFormPtr bfp, BoolPtr cancel);
static Boolean ConvertAnyToRegion (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertImpToImp (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertRNAToRNA (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertProtToProt (SeqFeatPtr, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertCDSToMatPeptide (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertMiscFeatureToCDSFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertMiscFeatureToGeneFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata);
static Boolean ConvertmRNAToCodingRegionFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata);
static Boolean ConverttRNAToGeneFunction (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata);

static ConvertFeatureProcsData ConvertFeaturesTable[] = {
  { SEQFEAT_CDREGION, FEATDEF_CDS,                SEQFEAT_RNA,    FEATDEF_ANY,
        NULL,  GetCDSConversionOpts, SimpleOptsFree, ConvertCDSToRNAEx, NULL, 
        "Delete protein product sequence.\nClear product field if transcript ID removal was requested.\nIf converting to tRNA and anticodon value can be parsed from label, set aa value, and add any text that could not be parsed into an anticodon value to the feature note.\nIf converting to other RNA, put label in RNA product." },
  { SEQFEAT_GENE,     FEATDEF_GENE,               SEQFEAT_RNA,    FEATDEF_ANY,
        NULL,  NULL,                   NULL,           ConvertGeneToRNAEx, NULL,
        "If converting to tRNA and anticodon value can be parsed from label, set aa value, and add any text that could not be parsed into an anticodon value to the feature note.  If converting to other RNA, put label in RNA product.  Also append gene locus, allele, description, map location, and locus tag to comment (as long as these values are not already in the label and therefore in the RNA product)." },
  { SEQFEAT_BIOSRC,   FEATDEF_BIOSRC,             SEQFEAT_IMP,    FEATDEF_repeat_region, 
       AdjustConvertFeaturesForBioSrcToRepeatRegion, NULL, NULL, ConvertBioSrcToRepeatRegionEx, NULL,
        "Creates a repeat_region with mobile_element qualifiers for the transposon and/or insertion sequence qualifiers on the BioSource.  All other BioSource information is discarded." },
  { SEQFEAT_CDREGION, FEATDEF_CDS,                SEQFEAT_IMP,    FEATDEF_misc_feature, 
       AdjustCDSToMiscFeatList, GetCDSConversionOpts, SimpleOptsFree, CDSToMiscFeatConvertFunc, CDSToMiscFeatConversionCleanup,
       "Copy comment from coding region to new misc_feature and remove product field.  If not pseudo coding region, add product name from protein feature to new misc_feature comment and delete product sequence." },
  { SEQFEAT_IMP,      FEATDEF_ANY,                SEQFEAT_PROT,   FEATDEF_ANY, 
       NULL, NULL, NULL, ConvertImpToProt, NULL,
       "Original feature must be on nucleotide sequence and be contained in coding region location.  Coding region must have product protein sequence.  New feature is created on product protein sequence so that the translated location will be as close as possible to the original nucleotide location (may not be exact because of codon boundaries)." },
  { SEQFEAT_PROT,     FEATDEF_mat_peptide_aa,     SEQFEAT_IMP,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertProtToImp, NULL,
       "Original feature must be on a protein sequence that is a product of a coding region.\nNew feature will be created on same sequence as coding region.\n"
       "If protein feature has name, this will be saved as /product qualifier on new feature.\nIf protein feature does not have name but does have description, this will be saved as /product qualifier on new feature.\n"
       "EC_number values from the protein feature will be saved as /EC_number qualifiers on the new feature.\nActivity values will be saved as /function qualifiers on the new feature.\n"
       "Db_xref values from the protein feature will be saved as /db_xref qualifers on the new feature." },
  { SEQFEAT_PROT,     FEATDEF_sig_peptide_aa,     SEQFEAT_IMP,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertProtToImp, NULL,
       "Original feature must be on a protein sequence that is a product of a coding region.\nNew feature will be created on same sequence as coding region.\n"
       "If protein feature has name, this will be saved as /product qualifier on new feature.\nIf protein feature does not have name but does have description, this will be saved as /product qualifier on new feature.\n"
       "EC_number values from the protein feature will be saved as /EC_number qualifiers on the new feature.\nActivity values will be saved as /function qualifiers on the new feature.\n"
       "Db_xref values from the protein feature will be saved as /db_xref qualifers on the new feature." },
  { SEQFEAT_PROT,     FEATDEF_transit_peptide_aa, SEQFEAT_IMP,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertProtToImp, NULL,
       "Original feature must be on a protein sequence that is a product of a coding region.\nNew feature will be created on same sequence as coding region.\n"
       "If protein feature has name, this will be saved as /product qualifier on new feature.\nIf protein feature does not have name but does have description, this will be saved as /product qualifier on new feature.\n"
       "EC_number values from the protein feature will be saved as /EC_number qualifiers on the new feature.\nActivity values will be saved as /function qualifiers on the new feature.\n"
       "Db_xref values from the protein feature will be saved as /db_xref qualifers on the new feature." },
  { SEQFEAT_IMP,      FEATDEF_misc_feature,                SEQFEAT_CDREGION,    FEATDEF_CDS,
       NULL, NULL, NULL, ConvertMiscFeatureToCDSFunction, NULL,
       "Use misc_feature comment for coding region product name." },
  { SEQFEAT_RNA,      FEATDEF_mRNA,                        SEQFEAT_CDREGION,    FEATDEF_CDS,
       NULL, NULL, NULL, ConvertmRNAToCodingRegionFunction, NULL,
       "Convert mRNA to coding region, use mRNA product for protein feature" },
  { SEQFEAT_RNA,      FEATDEF_tRNA,                        SEQFEAT_GENE,    FEATDEF_GENE,
       NULL, NULL, NULL, ConverttRNAToGeneFunction, NULL,
       "Convert tRNA to gene, use tRNA product for gene description" },
  { SEQFEAT_IMP,      FEATDEF_misc_feature,                SEQFEAT_GENE,    FEATDEF_GENE,
       NULL, NULL, NULL, ConvertMiscFeatureToGeneFunction, NULL,
       "Use misc_feature comment for gene locus." },
  { SEQFEAT_IMP,      FEATDEF_ANY,                SEQFEAT_RNA,    FEATDEF_misc_RNA,
       NULL, NULL, NULL, ConvertImpToSpecialRNA, NULL,
       "Creates a misc_RNA.  Import feature key is discarded." },
  { SEQFEAT_IMP,      FEATDEF_ANY,                SEQFEAT_RNA,    FEATDEF_precursor_RNA,
       NULL, NULL, NULL, ConvertImpToSpecialRNA, NULL,
       "Creates a precursor RNA.  Import feature key is discarded." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_IMP,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertRegionToImp, NULL,
       "Creates a misc_feature with the region name saved as a /note qualifier." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_RNA,    FEATDEF_misc_RNA,
       NULL, NULL, NULL, ConvertRegionToRNA, NULL,
       "Creates a misc_RNA feature with the region name as the product name." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_RNA,    FEATDEF_precursor_RNA,
       NULL, NULL, NULL, ConvertRegionToRNA, NULL,
       "Creates a precursor RNA feature with the region name as the product name." },
  { SEQFEAT_IMP,      FEATDEF_ANY,                SEQFEAT_RNA,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertImpToRNA, NULL,
       "Creates an RNA feature of the specified type." },
  { SEQFEAT_COMMENT,  FEATDEF_ANY,                SEQFEAT_IMP,    FEATDEF_misc_feature,
       NULL, NULL, NULL, ConvertCommentToMiscFeat, NULL,
       "Creates a misc_feature with the same note as the original.  Note the flatfile display for the feature is the same." },
  { SEQFEAT_GENE,     FEATDEF_GENE,               SEQFEAT_IMP,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertGeneToImpFeat, NULL,
       "Creates an import feature with the gene description and locus prepended to the original comment, separated by semicolons." },
  { SEQFEAT_RNA,      FEATDEF_ANY,                SEQFEAT_IMP,    FEATDEF_misc_feature,
       NULL, NULL, NULL, ConvertRNAToMiscFeat, NULL,
       "Creates a misc_feature and adds the RNA product name to the comment." } ,
  { SEQFEAT_SITE,     FEATDEF_ANY,                SEQFEAT_IMP,    FEATDEF_misc_feature,
       NULL, NULL, NULL, ConvertSiteToMiscFeat, NULL,
       "Creates a misc_feature with the site type name as a /note qualifier." } ,
  { SEQFEAT_PROT,     FEATDEF_mat_peptide_aa,     SEQFEAT_REGION, FEATDEF_REGION,
       NULL, NULL, NULL, ConvertProtToRegion, NULL,
       "Creates a Region feature with the protein name as the region name." },
  { SEQFEAT_PROT,     FEATDEF_sig_peptide_aa,     SEQFEAT_REGION, FEATDEF_REGION,
       NULL, NULL, NULL, ConvertProtToRegion, NULL,
       "Creates a Region feature with the protein name as the region name." },
  { SEQFEAT_PROT,     FEATDEF_transit_peptide_aa, SEQFEAT_REGION, FEATDEF_REGION,
       NULL, NULL, NULL, ConvertProtToRegion, NULL,
       "Creates a Region feature with the protein name as the region name." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_PROT,   FEATDEF_mat_peptide_aa,
       NULL, NULL, NULL, ConvertRegionToProt, NULL,
       "If feature is on nucleotide sequence, will create feature on protein product sequence for overlapping coding region.  Protein name will be region name." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_PROT,   FEATDEF_sig_peptide_aa,
       NULL, NULL, NULL, ConvertRegionToProt, NULL,
       "If feature is on nucleotide sequence, will create feature on protein product sequence for overlapping coding region.  Protein name will be region name." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_PROT,   FEATDEF_transit_peptide_aa,
       NULL, NULL, NULL, ConvertRegionToProt, NULL,
       "If feature is on nucleotide sequence, will create feature on protein product sequence for overlapping coding region.  Protein name will be region name." },
  { SEQFEAT_REGION,   FEATDEF_REGION,             SEQFEAT_PROT,   FEATDEF_preprotein,
       NULL, NULL, NULL, ConvertRegionToProt, NULL,
       "If feature is on nucleotide sequence, will create feature on protein product sequence for overlapping coding region.  Protein name will be region name." },
  { 0,                FEATDEF_ANY,                SEQFEAT_BOND,    FEATDEF_BOND,
       NULL, GetBondOpts, SimpleOptsFree, ConvertToSiteOrBond, NULL,
       "Create Bond feature with specified bond type.  Location is a SeqLocBond with a point at the start of the original location and a point at the end of the original location.  All feature ID, partialness, except, comment, product, location, genbank qualifiers, title, citation, experimental evidence, gene xrefs, db xrefs, and pseudo-ness information is discarded." },
  { 0,                FEATDEF_ANY,                SEQFEAT_SITE,    FEATDEF_SITE,
       NULL, GetSiteOpts, SimpleOptsFree, ConvertToSiteOrBond, NULL,
       "Create Site feature with specified site type.  All feature ID, partialness, except, comment, product, location, genbank qualifiers, title, citation, experimental evidence, gene xrefs, db xrefs, and pseudo-ness information is discarded." },
  { 0,                FEATDEF_ANY,                SEQFEAT_REGION,    FEATDEF_REGION,
       AdjustRegionConversionFeatures, GetRegionOpts, SimpleOptsFree, ConvertAnyToRegion, NULL,
       "Create Region feature on nucleotide sequence or protein product sequence of overlapping coding region as specified.  Use comment on feature for region name.\n"
       "All feature ID, partialness, except, comment, product, location, genbank qualifiers, title, citation, experimental evidence, gene xrefs, db xrefs, and pseudo-ness information is discarded." },
  { SEQFEAT_IMP,      FEATDEF_ANY,                SEQFEAT_IMP,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertImpToImp, NULL,
       "Changes type of import feature." },
  { SEQFEAT_RNA,      FEATDEF_ANY,                SEQFEAT_RNA,    FEATDEF_ANY,
       NULL, NULL, NULL, ConvertRNAToRNA, NULL,
       "Changes type of RNA feature." },
  { SEQFEAT_RNA,      FEATDEF_ncRNA,              SEQFEAT_IMP,    FEATDEF_misc_binding,
       NULL, NULL, NULL, ConvertncRNAToMiscBinding, NULL,
       "Changes ncRNA to misc_binding." },
  { SEQFEAT_PROT,     FEATDEF_ANY,                SEQFEAT_PROT,   FEATDEF_ANY,
       NULL, NULL, NULL, ConvertProtToProt, NULL,
       "Changes type of protein feature." },
  { SEQFEAT_CDREGION, FEATDEF_CDS,                SEQFEAT_PROT,   FEATDEF_mat_peptide_aa,
       NULL, NULL, NULL, ConvertCDSToMatPeptide, NULL,
       "If coding region is overlapped by another coding region, will convert the coding region to a mat-peptide on the overlapping coding region's protein sequence, otherwise if you have checked \"Leave Original Feature\" it will create a mat-peptide with the same protein names and description on the protein sequence for the coding region." }
};

static Int4 num_convert_feature_table_lines = sizeof (ConvertFeaturesTable) / sizeof (ConvertFeatureProcsData);

static Int4 GetConvertFeatureTableLine (Uint2 seqfeat_from, Uint2 featdef_from, Uint2 seqfeat_to, Uint2 featdef_to)
{
  Int4 i, table_line_num = -1;

  for (i = 0; i < num_convert_feature_table_lines && table_line_num == -1; i++)
  {
    if ((ConvertFeaturesTable[i].seqfeat_from == 0 || ConvertFeaturesTable[i].seqfeat_from == seqfeat_from)
        && (ConvertFeaturesTable[i].featdef_from == FEATDEF_ANY || ConvertFeaturesTable[i].featdef_from == featdef_from)
        && (ConvertFeaturesTable[i].seqfeat_to == 0 || ConvertFeaturesTable[i].seqfeat_to == seqfeat_to)
        && (ConvertFeaturesTable[i].featdef_to == FEATDEF_ANY || ConvertFeaturesTable[i].featdef_to == featdef_to))
    {
      table_line_num = i;
    }
  }
  return table_line_num;
}


typedef struct featureselremconform
{
  FORM_MESSAGE_BLOCK
  DialoG  feature_select;
  DialoG  constraints;
  DialoG  accept_cancel;
  PopuP   action_choice;
  DialoG  feature_select_to;
  PrompT  from_prompt;
  PrompT  to_prompt;
  DialoG  feature_select_from;
  GrouP   remove_grp;
  GrouP   convert_grp;
  ButtoN  clear_constraints_on_action_change;
  ButtoN  leave_original_feature_btn;  /* checkbox for conversion only */

  DoC     help_text; /* for conversion only - describes conversion */  

  /* used for convert callback */
  Uint1      featdef_to;
  Boolean    renormalize_nucprot;
  Boolean    leave_original_feature;
  ValNodePtr feat_list;
  BaseFormPtr bfp;
} FeatureSelRemConvFormData, PNTR FeatureSelRemConvFormPtr;

static void CleanupFeatureSelRemConvForm (GraphiC g, VoidPtr data)

{
  FeatureSelRemConvFormPtr  mrfp;

  mrfp = (FeatureSelRemConvFormPtr) data;
  if (mrfp != NULL) {
  }
  StdCleanupFormProc (g, data);
}

static void FeatureRemoveClearText (Pointer data)
{
  FeatureSelRemConvFormPtr   mrfp;
  FilterSetPtr          fsp;

  mrfp = (FeatureSelRemConvFormPtr) data;
  if (mrfp == NULL) return;
 
  fsp = DialogToPointer (mrfp->constraints);
  FilterSetClearText (fsp);
  PointerToDialog (mrfp->constraints, fsp);
  FilterSetFree (fsp);
}

static void FeatureRemoveClear (Pointer data)
{
  FeatureSelRemConvFormPtr mrfp;

  mrfp = (FeatureSelRemConvFormPtr) data;
  if (mrfp == NULL) return;
 
  PointerToDialog (mrfp->feature_select, NULL);
  PointerToDialog (mrfp->constraints, NULL);
  PointerToDialog (mrfp->feature_select_to, NULL);
  PointerToDialog (mrfp->feature_select_from, NULL);
  SetStatus (mrfp->leave_original_feature_btn, FALSE);
}

static void FeatureRemoveChangeNotify (Pointer userdata)
{
  FeatureSelRemConvFormPtr mrfp;
  ValNodePtr          err_list = NULL, vnp_from, vnp_to;
  Int4                table_line;
  RecT                r;
  Boolean             need_original = FALSE;

  mrfp = (FeatureSelRemConvFormPtr) userdata;
  if (mrfp == NULL) return;

  if (mrfp->action_choice == NULL)
  {
    /* just the remove dialog */
    err_list = TestDialog (mrfp->feature_select);
  }
  else if (GetValue (mrfp->action_choice) == FEATURE_CONVERT)
  {
    /* convert dialog in choice */
    if (mrfp->feature_select_from != NULL)
    {
      err_list = TestDialog (mrfp->feature_select_from);
    }
    else
    {
      err_list = TestDialog (mrfp->feature_select);
    }
    if (err_list == NULL && mrfp->feature_select_to != NULL)
    {
      err_list = TestDialog (mrfp->feature_select_to); 
    }
    /* set help text */
    Reset (mrfp->help_text);
    vnp_from = (ValNodePtr) DialogToPointer (mrfp->feature_select_from);
    vnp_to = (ValNodePtr) DialogToPointer (mrfp->feature_select_to);
    if (vnp_from == NULL || vnp_to == NULL) 
    {
      AppendText (mrfp->help_text, "No conversion selected", NULL, NULL, systemFont);
    }
    else if (vnp_from->choice == vnp_to->choice)
    {
      AppendText (mrfp->help_text, "Can't convert from and to the same type", NULL, NULL, systemFont);
      ValNodeAddPointer (&err_list, 0, "Can't convert from and to the same type");
    }
    else
    {
      /* set help text */
      table_line = GetConvertFeatureTableLine (FindFeatFromFeatDefType (vnp_from->choice),
                                               vnp_from->choice,
                                               FindFeatFromFeatDefType (vnp_to->choice),
                                               vnp_to->choice);
      if (table_line < 0)
      {
        AppendText (mrfp->help_text, "Conversion not supported", NULL, NULL, systemFont);
      }
      else if (ConvertFeaturesTable[table_line].help_text == NULL)
      {
        AppendText (mrfp->help_text, "No description available", NULL, NULL, systemFont);
      }
      else
      {
        AppendText (mrfp->help_text, ConvertFeaturesTable[table_line].help_text, NULL, NULL, systemFont);
      }

      /* if converting from CDS to site, bond, or region, "leave original" must be checked */
      if ((vnp_from->choice == FEATDEF_CDS
           || vnp_from->choice == FEATDEF_ANY)
          && (vnp_to->choice == FEATDEF_SITE 
              || vnp_to->choice == FEATDEF_BOND
              || vnp_to->choice == FEATDEF_REGION))
      {
        need_original = TRUE;
        AppendText (mrfp->help_text, "If converting from CDS to site or bond, or region, you must check Leave Original Feature.", NULL, NULL, systemFont);
      }
    }
    vnp_from = ValNodeFreeData (vnp_from);
    vnp_to = ValNodeFreeData (vnp_to);
    ObjectRect (mrfp->help_text, &r);
    InvalRect (&r);  

  }
  else
  {
    /* single feature dialog in choice */
    if (mrfp->feature_select != NULL)
    {
      err_list = TestDialog (mrfp->feature_select);
    }
  }
  
  if (err_list == NULL && (!need_original || GetStatus (mrfp->leave_original_feature_btn)))
  {
    EnableAcceptCancelDialogAccept (mrfp->accept_cancel);
  }
  else
  {
    DisableAcceptCancelDialogAccept (mrfp->accept_cancel);
  }
  ValNodeFree (err_list);
}


static void FeatureRemoveChangeButton (ButtoN b)
{
  FeatureSelRemConvFormPtr mrfp;
  
  mrfp = (FeatureSelRemConvFormPtr) GetObjectExtra (b);
  if (mrfp == NULL) return;
  FeatureRemoveChangeNotify (mrfp);
}


static void FeatureRemoveOrConvertCenterAction (PopuP p)
{
  FeatureSelRemConvFormPtr mrfp;
  
  mrfp = (FeatureSelRemConvFormPtr) GetObjectExtra (p);
  if (mrfp == NULL) return;
  
  if (GetValue (p) == 2)
  {
    Hide (mrfp->remove_grp);
    Show (mrfp->convert_grp);
  }
  else
  {
    Show (mrfp->remove_grp);
    Hide (mrfp->convert_grp);
  }
  
  if (GetStatus (mrfp->clear_constraints_on_action_change))
  {
    PointerToDialog (mrfp->feature_select, NULL);
    PointerToDialog (mrfp->feature_select_from, NULL);
    PointerToDialog (mrfp->feature_select_to, NULL);
    PointerToDialog (mrfp->constraints, NULL);
  }
  FeatureRemoveChangeNotify ((Pointer) mrfp);
}



static void SelectFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)

{
  if (sfp == NULL) return;
  ObjMgrAlsoSelect (sfp->idx.entityID, sfp->idx.itemID, OBJ_SEQFEAT, 0, NULL);
}

static void DeselectFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)

{
  if (sfp == NULL) return;
  ObjMgrDeSelect (sfp->idx.entityID, sfp->idx.itemID, OBJ_SEQFEAT, 0, NULL);
}

typedef struct removefeature
{
  ValNodePtr bsplist;
  ValNodePtr bssplist;  
} RemoveFeatureData, PNTR RemoveFeaturePtr;

static void RemoveFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  RemoveFeaturePtr rfp;
  SeqIdPtr         sip;
  BioseqPtr        productbsp, productcdna;
  BioseqSetPtr     productnps;
  
  if (sfp == NULL || userdata == NULL) return;
  rfp = (RemoveFeaturePtr) userdata;
  
  sfp->idx.deleteme = TRUE;
  if (sfp->data.choice == SEQFEAT_CDREGION)
  {
    if (sfp->product != NULL)
    {
      sip = SeqLocId (sfp->product);
      if (sip != NULL)
      {
        productbsp = BioseqFind (sip);
        if (productbsp != NULL)
        {
          ValNodeAddPointer (&(rfp->bsplist), 0, (Pointer) productbsp);
        }
      }
    }
  }
  else if (sfp->data.choice == SEQFEAT_RNA)
  {
    if (sfp->product != NULL)
    {
      sip = SeqLocId (sfp->product);
      if (sip != NULL)
      {
        productcdna = BioseqFind (sip);
        if (productcdna != NULL && productcdna->idx.parenttype == OBJ_BIOSEQSET)
        {
          productnps = (BioseqSetPtr) productcdna->idx.parentptr;
          if (productnps != NULL && productnps->_class == BioseqseqSet_class_nuc_prot)
          {
            ValNodeAddPointer (&(rfp->bssplist), 0, (Pointer) productnps);
          }
        }
      }
    }
  }
}


static Boolean ConvertRegionToImp (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertRegionToImpFunc (sfp, featdef_to);
}


static Boolean 
ConvertToBondSiteOrRegion 
(SeqFeatPtr sfp, 
 Int4       site_or_bond_type,
 Uint2      featdef_to,
 Boolean    create_prot_feats)
{
  BioseqPtr          bsp;
  SeqLocPtr          slp;
  SeqEntryPtr        sep;
  SeqMgrFeatContext  context;
  SeqFeatPtr         newsfp;
  SeqIdPtr           sip;
  CharPtr            comment;
  SeqBondPtr         sbp;
  SeqPntPtr          spp;
  Boolean            no_cds = FALSE;
  
  if (sfp == NULL)
  {
    return FALSE;
  }
  
  if (site_or_bond_type == -1 && featdef_to != FEATDEF_REGION)
  {
    return FALSE;
  }
  
  bsp = BioseqFindFromSeqLoc (sfp->location);
  sfp = SeqMgrGetDesiredFeature (0, bsp, 0, 0, sfp, &context);
  if (sfp == NULL || bsp == NULL) return FALSE;

  if (ISA_aa (bsp->mol))
  {
    if (create_prot_feats)
    {
      slp = (SeqLocPtr) AsnIoMemCopy (sfp->location, (AsnReadFunc) SeqLocAsnRead, (AsnWriteFunc) SeqLocAsnWrite);
    }
    else
    {
      slp = FindNucleotideLocationForProteinFeatureConversion (sfp->location);
    }
  }
  else if (create_prot_feats) 
  {
    slp = GetProteinLocationForNucleotideFeatureConversion (sfp->location, &no_cds);
    if (no_cds) {
      Message (MSG_ERROR, "Unable to find CDS covering %s", context.label);
      return FALSE;
    }
  } else {
    slp = (SeqLocPtr) AsnIoMemCopy (sfp->location, (AsnReadFunc) SeqLocAsnRead, (AsnWriteFunc) SeqLocAsnWrite);
  }

  bsp = GetBioseqGivenSeqLoc (slp, context.entityID);
  if (bsp == NULL) {
    Message (MSG_ERROR, "Unable to find target %s bioseq for %s", create_prot_feats ? "protein" : "nucleotide", context.label);
    slp = SeqLocFree (slp);
    return FALSE;
  } 

  sep = SeqMgrGetSeqEntryForData (bsp);
  if (sep == NULL) {
    Message (MSG_ERROR, "Unable to find target %s seq-entry for %s", create_prot_feats ? "protein" : "nucleotide", context.label);
    return FALSE;
  }
  
  comment = sfp->comment;

  newsfp = NULL;
  if (featdef_to == FEATDEF_BOND || featdef_to == FEATDEF_SITE) {
    newsfp = SeqFeatNew ();
    if (newsfp != NULL) 
    {
      newsfp->data.choice = FindFeatFromFeatDefType(featdef_to);
      newsfp->data.value.intvalue = site_or_bond_type;
        sfp->comment = NULL;
    }
  } else if (featdef_to == FEATDEF_REGION) {
    newsfp = SeqFeatNew ();
    if (newsfp != NULL) 
    {
      newsfp->data.choice = FindFeatFromFeatDefType(featdef_to);
      newsfp->data.value.ptrvalue = sfp->comment;
      sfp->comment = NULL;
        comment = NULL;
    }
  }
  if (newsfp != NULL) {
    sfp->idx.deleteme = TRUE;
    CreateNewFeature (sep, NULL, FindFeatFromFeatDefType(featdef_to), newsfp);
    newsfp->location = slp;
    newsfp->comment = comment;
    if (featdef_to == FEATDEF_BOND) {
      if (slp->choice != SEQLOC_BOND) {
        sip = SeqLocId (slp);
        if (sip != NULL) {
          sbp = SeqBondNew ();
          if (sbp != NULL) {
            slp = ValNodeNew (NULL);
            if (slp != NULL) {
              slp->choice = SEQLOC_BOND;
              slp->data.ptrvalue = (Pointer) sbp;
              spp = SeqPntNew ();
              if (spp != NULL) {
                spp->strand = SeqLocStrand (newsfp->location);
                spp->id = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (sip, 0)));
                spp->point = SeqLocStart (newsfp->location);
                sbp->a = spp;
              }
              spp = SeqPntNew ();
              if (spp != NULL) {
                spp->strand = SeqLocStrand (newsfp->location);
                spp->id = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (sip, 0)));
                spp->point = SeqLocStop (newsfp->location);
                sbp->b = spp;
              }
              newsfp->location = SeqLocFree (newsfp->location);
              newsfp->location = slp;
            }
          }
        }
      }
    }
  }
  if (newsfp == NULL)
  {
    return FALSE;
  } 
  else
  {
    return TRUE;
  }
}


typedef struct origfeat 
{
  SeqEntryPtr sep;
  SeqFeatPtr  sfp;
} OrigFeatData, PNTR OrigFeatPtr;


static ValNodePtr AddDuplicateFeature (SeqFeatPtr sfp_orig, ValNodePtr PNTR feat_list)
{
  SeqEntryPtr sep;
  BioseqPtr   bsp;
  OrigFeatPtr ofp;
  
  if (sfp_orig == NULL || feat_list == NULL) return NULL;
  bsp = BioseqFindFromSeqLoc (sfp_orig->location);
  if (bsp == NULL) return NULL;
  sep = SeqMgrGetSeqEntryForData (bsp);
  if (sep == NULL) return NULL;
  
  ofp = (OrigFeatPtr) MemNew (sizeof (OrigFeatData));
  if (ofp == NULL) return NULL;
  
  ofp->sep = sep;
  ofp->sfp = (SeqFeatPtr) AsnIoMemCopy (sfp_orig, (AsnReadFunc) SeqFeatAsnRead,
                                                 (AsnWriteFunc) SeqFeatAsnWrite);
  
  return ValNodeAddPointer (feat_list, 0, ofp);
}

static Pointer SimpleOptsFree (Pointer data)
{
  return MemFree (data);
}

static Boolean 
ConvertCDSToRNAEx 
(SeqFeatPtr  sfp,
 Uint2       toFeatSubType,
 Pointer     extradata)
{
  CDSConversionOptsPtr   opts;
  Boolean     remove_mRNA = FALSE;
  Boolean     remove_gene = FALSE;
  Boolean     remove_transcript_id = FALSE;
  Boolean     remove_only_pseudo = FALSE;


  if (sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION) return FALSE;

  opts = (CDSConversionOptsPtr) extradata;
  if (opts != NULL) {
    remove_mRNA = opts->remove_mrna;
    remove_gene = opts->remove_gene;
    remove_transcript_id = opts->remove_transcript_id;
    remove_only_pseudo = opts->only_pseudo;
  }
  if (remove_only_pseudo && !sfp->pseudo) {
    return FALSE;
  }
  ApplyCDSOptionsToFeature (sfp, remove_mRNA, remove_gene, remove_transcript_id, FALSE);
  return ConvertCDSToRNA (sfp, toFeatSubType);
}


static Boolean ConvertGeneToRNAEx (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertGeneToRNA (sfp, featdef_to);
}


static ValNodePtr CreateClickableListForSourceFeats (ValNodePtr feat_list)
{
  ValNodePtr transposon_only_list = NULL;
  ValNodePtr insertion_seq_only_list = NULL;
  ValNodePtr transposon_and_insertion_seq_list = NULL;
  ValNodePtr note_only_list = NULL;
  ValNodePtr no_data_list = NULL;
  ValNodePtr vnp;
  CharPtr transposon_txt, insertion_seq_txt, note_txt;
  SeqFeatPtr sfp;
  BioSourcePtr biop;
  Boolean is_transposon;
  Boolean is_insertion_seq;
  ClickableItemPtr cip;
  ValNodePtr clickable_list = NULL;
  
  for (vnp = feat_list; vnp != NULL; vnp = vnp->next) {   
    sfp = (SeqFeatPtr) vnp->data.ptrvalue;
    biop = (BioSourcePtr) sfp->data.value.ptrvalue;
    is_transposon = FALSE;
    is_insertion_seq = FALSE;
    transposon_txt = SubSourceText (biop, SUBSRC_transposon_name, &is_transposon);
    insertion_seq_txt = SubSourceText (biop, SUBSRC_insertion_seq_name, &is_insertion_seq);
    note_txt = NoteText (biop, sfp->comment);
    
    if (is_transposon && is_insertion_seq) {
      ValNodeAddPointer (&transposon_and_insertion_seq_list, OBJ_SEQFEAT, vnp->data.ptrvalue);
    } else if (is_transposon) {
      ValNodeAddPointer (&transposon_only_list, OBJ_SEQFEAT, vnp->data.ptrvalue);
    } else if (is_insertion_seq) {
      ValNodeAddPointer (&insertion_seq_only_list, OBJ_SEQFEAT, vnp->data.ptrvalue);
    } else if (!StringHasNoText (note_txt)) {
      ValNodeAddPointer (&note_only_list, OBJ_SEQFEAT, vnp->data.ptrvalue);
    } else {
      ValNodeAddPointer (&no_data_list, OBJ_SEQFEAT, vnp->data.ptrvalue);
    }
  }

  if (transposon_and_insertion_seq_list != NULL) {
    cip = NewClickableItem (0, "%d source features have both transposon-name and insertion-seq-name.", transposon_and_insertion_seq_list);
    ValNodeAddPointer (&clickable_list, 0, cip);
  }
  if (transposon_only_list != NULL) {
    cip = NewClickableItem (0, "%d source features have transposon-name.", transposon_only_list);
    cip->chosen = TRUE;
    ValNodeAddPointer (&clickable_list, 0, cip);
  }
  if (insertion_seq_only_list != NULL) {
    cip = NewClickableItem (0, "%d source features have insertion-seq-name.", insertion_seq_only_list);
    cip->chosen = TRUE;
    ValNodeAddPointer (&clickable_list, 0, cip);
  }
  if (note_only_list != NULL) {
    cip = NewClickableItem (0, "%d source features have notes, but neither transposon-name nor insertion-seq-name.", note_only_list);
    ValNodeAddPointer (&clickable_list, 0, cip);
  }
  if (no_data_list != NULL) {
    cip = NewClickableItem (0, "%d source features have no notes, no transposon-name, and no insertion-seq-name.", no_data_list);
    ValNodeAddPointer (&clickable_list, 0, cip);
  }

  return clickable_list;
}


static ValNodePtr AdjustConvertFeaturesForBioSrcToRepeatRegion (ValNodePtr feat_list, Pointer extradata, BaseFormPtr bfp, BoolPtr cancel)
{
  ValNodePtr clickable_list;

  *cancel = FALSE;
  clickable_list = CreateClickableListForSourceFeats (feat_list);
  feat_list = ValNodeFree (feat_list);
  feat_list = ChooseFeaturesForConversion (clickable_list, bfp, "Available Data", "Features");
  /* NOTE - the clickable_list will be freed by the dialog  - do not free it again here */
  if (feat_list == NULL)
  {
    *cancel = TRUE;
  }
  return feat_list;
}


static void RemoveNonPseudoFeatures (ValNodePtr PNTR feat_list)
{
  ValNodePtr prev = NULL, next_vnp, vnp;
  SeqFeatPtr sfp;

  vnp = *feat_list;
  while (vnp != NULL) {
    next_vnp = vnp->next;
    sfp = (SeqFeatPtr) vnp->data.ptrvalue;
    if (sfp != NULL && !sfp->pseudo) {
      if (prev == NULL) {
        *feat_list = vnp->next;
      } else {
        prev->next = vnp->next;
      }
      vnp->next = NULL;
      vnp = ValNodeFree (vnp);
    } else {
      prev = vnp;
    }
    vnp = next_vnp;
  }    
}


static Pointer GetCDSConversionOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel)
{
  Boolean all_are_pseudo = TRUE, any_are_pseudo = FALSE, any_gps = FALSE;
  CDSConversionOptsPtr opts;
  ValNodePtr vnp;
  SeqFeatPtr sfp;
  
  for (vnp = feat_list; vnp != NULL; vnp = vnp->next)
  {
    sfp = (SeqFeatPtr) vnp->data.ptrvalue;
    if (sfp != NULL)
    {
      if (sfp->pseudo)
      {
        any_are_pseudo = TRUE;
      }
      else
      {
        all_are_pseudo = FALSE;
      }
      if (!any_gps)
      {
        any_gps = IsFeatInGPS (sfp);
      }
    }
  }

  opts = GetCDSConversionOptions (all_are_pseudo, any_are_pseudo, any_gps, cancel);

  return opts;
}


static ValNodePtr AdjustCDSToMiscFeatList (ValNodePtr feat_list, Pointer extradata, BaseFormPtr bfp, BoolPtr cancel)
{
  CDSConversionOptsPtr opts;

  if (feat_list == NULL || extradata == NULL || cancel == NULL) return feat_list;
  opts = (CDSConversionOptsPtr) extradata;
  *cancel = FALSE;
  if (opts->only_pseudo) {
    /* remove features that are not pseudo */
    RemoveNonPseudoFeatures (&feat_list);
  }
  return feat_list;
}


static void CDSToMiscFeatConversionCleanup (Uint2 entityID, Pointer extradata)
{
  SeqEntryPtr sep;

  DeleteMarkedObjects (entityID, 0, NULL);
  sep = GetTopSeqEntryForEntityID (entityID);
  RenormalizeNucProtSets (sep, TRUE);  
}


static Boolean CDSToMiscFeatConvertFunc (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  CDSConversionOptsPtr opts;
  Boolean              rval = FALSE;

  opts = (CDSConversionOptsPtr) extradata;
  if (opts == NULL || sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION)
  {
    return FALSE;
  }
  else if (sfp->pseudo) 
  {
    rval = ConvertOnePseudoCDSToMiscFeat (sfp);
  }
  else
  {
    /* do other here */
  
    rval = ConvertOneCDSToMiscFeat (sfp, FALSE, FALSE, opts);
  }
  return TRUE;
}


typedef struct siteorbond {
  Int4 site_or_bond_type;
} SiteOrBondData, PNTR SiteOrBondPtr;

static Pointer GetSiteOrBondOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel, Uint2 toFeat)
{
  EnumFieldAssocPtr  al;
  CharPtr            mssg;
  UIEnum             val;
  SiteOrBondPtr      opts = NULL;

  *cancel = FALSE;
  al = NULL;
  mssg = NULL;
  if (toFeat == FEATDEF_BOND) {
    al = enum_bond_alist;
    mssg = "Select type of bond";
  } else if (toFeat == FEATDEF_SITE) {
    al = enum_site_alist;
    mssg = "Select type of site";
  } else {
    *cancel = TRUE;
    return NULL;
  }

  if (al == NULL || ! AlistMessage (al, &val, 1, mssg)) 
  {
    *cancel = TRUE;
  }
  else
  {
    opts = (SiteOrBondPtr) MemNew (sizeof (SiteOrBondData));
    opts->site_or_bond_type = (Int4) val; 
  }
  return opts;
}


static Pointer GetSiteOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel)
{
  return GetSiteOrBondOpts (feat_list, bfp, cancel, FEATDEF_SITE);
}


static Pointer GetBondOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel)
{
  return GetSiteOrBondOpts (feat_list, bfp, cancel, FEATDEF_BOND);
}


static Boolean ConvertToSiteOrBond (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  SiteOrBondPtr      opts = NULL;

  opts = (SiteOrBondPtr) extradata;
  if (sfp == NULL || opts == NULL) return FALSE;

  return ConvertToBondSiteOrRegion (sfp, opts->site_or_bond_type, featdef_to, TRUE);
}

typedef struct regionopts {
  Boolean create_prot_feats;
} RegionOptsData, PNTR RegionOptsPtr;


static ValNodePtr GetBadProteinConversionFeatures (ValNodePtr feat_list)
{
  ValNodePtr no_cds_list = NULL, bad_coordinates_list = NULL, err_list = NULL, vnp;
  Boolean    no_cds;
  SeqLocPtr  slp;
  SeqFeatPtr sfp;
  BioseqPtr  bsp;

  for (vnp = feat_list; vnp != NULL; vnp = vnp->next) {
    sfp = (SeqFeatPtr) vnp->data.ptrvalue;
    bsp = BioseqFindFromSeqLoc (sfp->location);
    if (bsp != NULL && ISA_aa (bsp->mol))
    {
      slp = FindNucleotideLocationForProteinFeatureConversion (sfp->location);
      if (slp == NULL)
      {
        no_cds = TRUE;
      }
    }
    else
    {
      slp = GetProteinLocationForNucleotideFeatureConversion (sfp->location, &no_cds);
    }
    if (slp == NULL) {
      if (no_cds) {
        ValNodeAddPointer (&no_cds_list, OBJ_SEQFEAT, sfp);
      } else {
        ValNodeAddPointer (&bad_coordinates_list, OBJ_SEQFEAT, sfp);
      }
    } else {
      slp = SeqLocFree (slp);
    }
  }

  if (no_cds_list != NULL) {
    ValNodeAddPointer (&err_list, 0, NewClickableItem (0, "%d features have no overlapping coding region", no_cds_list));
  }
  if (bad_coordinates_list != NULL) {
    ValNodeAddPointer (&err_list, 0, NewClickableItem (0, "%d features do not stop or start on codon boundaries", bad_coordinates_list));
  }
  return err_list;
}


static Pointer GetRegionOpts (ValNodePtr feat_list, BaseFormPtr bfp, BoolPtr cancel)
{
  ValNodePtr            err_list;
  WindoW                w;
  GrouP                 h, nuc_prot_choice, c;
  DialoG                bad_prot_feats = NULL;
  ButtoN                b;
  ModalAcceptCancelData acd;
  Boolean               rval = FALSE;
  RegionOptsPtr         opts = NULL;
  
  *cancel = FALSE;

  if (feat_list == NULL) return NULL;
  
  err_list = GetBadProteinConversionFeatures (feat_list);
    
  w = MovableModalWindow(-20, -13, -10, -10, "Create Nucleotide or Protein Features", NULL);
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  if (err_list != NULL) {
    bad_prot_feats = CreateClickableListDialog (h, "Reasons", "Original Features",
                                                ScrollToDiscrepancyItem, EditDiscrepancyItem, NULL,
                                                GetDiscrepancyItemText);
    PointerToDialog (bad_prot_feats, err_list);
  }

  nuc_prot_choice = HiddenGroup (h, 0, 2, NULL);
  RadioButton (nuc_prot_choice, "Create Nucleotide Features");
  RadioButton (nuc_prot_choice, err_list == NULL ? "Create Protein Features" : "Create Protein Features (features listed above will be skipped)");
  SetValue (nuc_prot_choice, err_list == NULL ? 2 : 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) nuc_prot_choice,
                              (HANDLE) c, 
                              (HANDLE) bad_prot_feats, 
                              NULL);
  Show (w);
  Select (w);
  acd.accepted = FALSE;
  acd.cancelled = FALSE;
  while (!acd.accepted && ! acd.cancelled)
  {
    ProcessExternalEvent ();
    Update ();
  }
  ProcessAnEvent ();
  if (acd.cancelled)
  {
    *cancel = TRUE;
  }
  else
  {
    opts = (RegionOptsPtr) MemNew (sizeof (RegionOptsData));
    if (GetValue (nuc_prot_choice) == 2)
    {
      opts->create_prot_feats = TRUE;
    }
    else
    {
      opts->create_prot_feats = FALSE;
    }
  }
  Remove (w);
  
  return opts;
}


static void RemoveClickableItemFeaturesFromFeatureList (ClickableItemPtr cip, ValNodePtr PNTR feat_list)
{
  ValNodePtr vnp_i, vnp_p, vnp_r;

  if (cip == NULL || feat_list == NULL || *feat_list == NULL) return;
  for (vnp_i = cip->item_list; vnp_i != NULL; vnp_i = vnp_i->next) {
    if (vnp_i->choice != OBJ_SEQFEAT || vnp_i->data.ptrvalue == NULL) continue;
    vnp_p = NULL;
    vnp_r = *feat_list;
    while (vnp_r != NULL && vnp_r->data.ptrvalue != vnp_i->data.ptrvalue) {
      vnp_p = vnp_r;
      vnp_r = vnp_r->next;
    }
    if (vnp_r != NULL) {
      if (vnp_p == NULL) {
        *feat_list = vnp_r->next;
      } else {
        vnp_p->next = vnp_r->next;
      }
      vnp_r->next = NULL;
      vnp_r = ValNodeFree (vnp_r);
    }
  }
  for (vnp_i = cip->subcategories; vnp_i != NULL; vnp_i = vnp_i->next) {
    RemoveClickableItemFeaturesFromFeatureList (vnp_i->data.ptrvalue, feat_list);
  }
}


static ValNodePtr AdjustRegionConversionFeatures (ValNodePtr feat_list, Pointer extradata, BaseFormPtr bfp, BoolPtr cancel)
{
  RegionOptsPtr opts = (RegionOptsPtr) extradata;
  ValNodePtr    err_list, vnp_c;

  *cancel = FALSE;
  if (opts == NULL || !opts->create_prot_feats) return feat_list;
  
  err_list = GetBadProteinConversionFeatures (feat_list);
  
  for (vnp_c = err_list; vnp_c != NULL; vnp_c = vnp_c->next) {
    RemoveClickableItemFeaturesFromFeatureList (vnp_c->data.ptrvalue, &feat_list);
  }
  return feat_list;  
}


static Boolean ConvertAnyToRegion (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  RegionOptsPtr opts = (RegionOptsPtr) extradata;

  if (opts == NULL || sfp == NULL) return FALSE;
  return ConvertToBondSiteOrRegion (sfp, 0, FEATDEF_REGION, opts->create_prot_feats);
}


static Boolean ConvertImpToImp (SeqFeatPtr sfp, Uint2 featdef_to, Pointer extradata)
{
  return ConvertImpToImpFunc (sfp, featdef_to);
}


static void CollectFeaturesToBeConverted (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ValNodePtr PNTR feat_list = (ValNodePtr PNTR) userdata;
  
  ValNodeAddPointer (feat_list, OBJ_SEQFEAT, sfp);
}


static void MoveInferenceToOverlappingGene (SeqFeatPtr sfp)
{
  GBQualPtr qual, qual_prev= NULL, qual_next;
  SeqFeatPtr gene;

  if (sfp == NULL) return;

  gene = GetGeneForFeature (sfp);
  if (gene == NULL || gene->idx.deleteme || gene == sfp) return;

  qual = sfp->qual;
  while (qual != NULL) {
    qual_next = qual->next;
    if (StringCmp (qual->qual, "inference") == 0) {
      if (qual_prev == NULL) {
        sfp->qual = qual->next;
      } else {
        qual_prev->next = qual->next;
      }
      qual->next = NULL;
      qual->next = gene->qual;
      gene->qual = qual;
    } else {
      qual_prev = qual;
    }
    qual = qual_next;
  }
}


static Boolean NewConvertFeaturesByList (SeqEntryPtr sep, FilterSetPtr fsp, Uint1 featdef_from, FeatureSelRemConvFormPtr mrfp)
{
  ValNodePtr feat_list = NULL, sel_feat_list = NULL, vnp;
  Boolean not_match = FALSE;
  Boolean    only_pseudo = FALSE;
  SelStructPtr sel;
  SeqFeatPtr   sfp;
  SeqMgrFeatContext fcontext;
  Int4              table_line_num = -1;
  Uint2             seqfeat_from, seqfeat_to;
  Pointer           extradata = NULL;
  Boolean           cancel = FALSE;
  ValNodePtr        dup_feat_vnp = NULL;
  OrigFeatPtr       ofp;
  Boolean           removed_cds = FALSE;
  BioseqPtr         protBsp;

  OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                       CollectFeaturesToBeConverted,
                                       NULL, 0, featdef_from, 0, &feat_list);
  sel = ObjMgrGetSelected ();
  while (sel != NULL) {
    if (sel->entityID == mrfp->input_entityID && sel->itemtype == OBJ_SEQFEAT) {
      sfp = SeqMgrGetDesiredFeature (sel->entityID, NULL, sel->itemID, 0, NULL, &fcontext);
      if (sfp != NULL) {
        vnp = feat_list; 
        while (vnp != NULL && vnp->data.ptrvalue != sfp) {
          vnp = vnp->next;
        }
        if (vnp == NULL) {
          not_match = TRUE;
        }
        ValNodeAddPointer (&sel_feat_list, OBJ_SEQFEAT, sfp);
      }
    }
    sel = sel->next;
  }
  if (sel_feat_list != NULL) {
    feat_list = ValNodeFree (feat_list);
    if (not_match 
        && Message (MSG_YN, 
                    "Selected feature%s do%s not match options - apply to selected feature anyway?",
                    sel_feat_list->next == NULL ? "" : "s",
                    sel_feat_list->next == NULL ? "es" : "") == ANS_NO) {
      sel_feat_list = ValNodeFree (sel_feat_list);
      return FALSE;
    }
    feat_list = sel_feat_list;
  }

  if (feat_list == NULL) {
    Message (MSG_ERROR, "No features of the specified type exist to be converted!");
    return FALSE;
  }

  /* find line in table that applies */
  seqfeat_from = FindFeatFromFeatDefType (featdef_from);
  seqfeat_to =   FindFeatFromFeatDefType (mrfp->featdef_to);

  table_line_num = GetConvertFeatureTableLine (seqfeat_from, featdef_from, seqfeat_to, mrfp->featdef_to);
  if (table_line_num < 0 || ConvertFeaturesTable[table_line_num].convert_func == NULL)
  {
    Message (MSG_ERROR, "This conversion not supported - contact"
         " sequindev for instructions.");
    return FALSE;
  }

  /* collect extra data if necessary */
  if (ConvertFeaturesTable[table_line_num].collect_options != NULL)
  {
    extradata = (ConvertFeaturesTable[table_line_num].collect_options) (feat_list, mrfp->bfp, &cancel);
    if (cancel)
    {
      feat_list = ValNodeFree (feat_list);
      /* free extradata */
      if (ConvertFeaturesTable[table_line_num].free_options != NULL)
      {
        (ConvertFeaturesTable[table_line_num].free_options) (extradata);
      }
      return FALSE;
    }
  }
  else if (featdef_from == FEATDEF_CDS && mrfp->featdef_to == FEATDEF_mat_peptide_aa) 
  {
    extradata = &(mrfp->leave_original_feature);
  }

  /* adjust feature list if necessary */  
  if (ConvertFeaturesTable[table_line_num].adjust_features != NULL) 
  {
    feat_list = (ConvertFeaturesTable[table_line_num].adjust_features) (feat_list, extradata, mrfp->bfp, &cancel);
    if (cancel || feat_list == NULL)
    {
      feat_list = ValNodeFree (feat_list);
      /* free extradata */
      if (ConvertFeaturesTable[table_line_num].free_options != NULL)
      {
        (ConvertFeaturesTable[table_line_num].free_options) (extradata);
      }
      return FALSE;
    }
  }

  /* process list */
  for (vnp = feat_list; vnp != NULL; vnp = vnp->next)
  {
    sfp = (SeqFeatPtr) vnp->data.ptrvalue;
    if (sfp == NULL) continue;
    protBsp = NULL;
    if (sfp->data.choice == SEQFEAT_CDREGION && !mrfp->leave_original_feature)
    {
      protBsp = BioseqFindFromSeqLoc (sfp->product);
      if (protBsp != NULL)
      {
        protBsp->idx.deleteme = TRUE;
      }
      removed_cds = TRUE;
    }
      
    dup_feat_vnp = NULL;
    if (mrfp->leave_original_feature)
    {
      /* need a list of duplicate features that we can add to the appropriate SeqEntrys
       * when we are done with the conversions.
       */
      dup_feat_vnp = AddDuplicateFeature (sfp, &(mrfp->feat_list)); 
    }

    if (ConvertFeaturesTable[table_line_num].convert_func (sfp, mrfp->featdef_to, extradata))
    {
      /* set the subtype to zero so that it will be reindexed */
      sfp->idx.subtype = 0;
      if (mrfp->featdef_to == FEATDEF_misc_feature)
      {
        MoveInferenceToOverlappingGene (sfp);
      }
    }
    else if (dup_feat_vnp != NULL && dup_feat_vnp->data.ptrvalue != NULL)
    {
      /* feature wasn't removed, for whatever reason */
      /* remove the duplicate feature from the list to be added later */
      ofp = (OrigFeatPtr) dup_feat_vnp->data.ptrvalue;
      ofp->sfp = SeqFeatFree (ofp->sfp);
      ofp->sep = NULL;

      /* don't remove protein product if feature wasn't removed */
      if (protBsp != NULL)
      {
        protBsp->idx.deleteme = FALSE;
      }
    }
    else if (sfp->data.choice == SEQFEAT_CDREGION) 
    {
      /* failed to convert feature, don't remove product */
      if (protBsp != NULL)
      {
        protBsp->idx.deleteme = FALSE;
      }
    }
  }

  feat_list = ValNodeFree (feat_list);

  /* do cleanup tasks */
  if (ConvertFeaturesTable[table_line_num].cleanup_func != NULL)
  {
    (ConvertFeaturesTable[table_line_num].cleanup_func) (mrfp->input_entityID, extradata);
  }

  /* free extradata */
  if (ConvertFeaturesTable[table_line_num].free_options != NULL)
  {
    (ConvertFeaturesTable[table_line_num].free_options) (extradata);
  }

  if (removed_cds)
  {
    DeleteMarkedObjects (mrfp->input_entityID, 0, NULL);
    RenormalizeNucProtSets (sep, TRUE);       
  }

  return TRUE;
}


static void RemoveProteinProducts (ValNodePtr bsplist, Uint2 entityID, SeqEntryPtr sep)
{
  MsgAnswer  ans;
  ValNodePtr tmp;
  BioseqPtr  bsp;
  Uint4      itemID;
  OMProcControl  ompc;
  
  if (bsplist == NULL) 
  {
    return;
  }
  ans = Message (MSG_YN, "Remove protein products?");
  if (ans == ANS_YES) {
    for (tmp = bsplist; tmp != NULL; tmp = tmp->next) {
      bsp = (BioseqPtr) tmp->data.ptrvalue;
      itemID = GetItemIDGivenPointer (entityID, OBJ_BIOSEQ, (Pointer) bsp);
      if (itemID > 0) {
        MemSet ((Pointer) (&ompc), 0, sizeof (OMProcControl));
        ompc.do_not_reload_from_cache = TRUE;
        ompc.input_entityID = entityID;
        ompc.input_itemID = itemID;
        ompc.input_itemtype = OBJ_BIOSEQ;
        if (! DetachDataForProc (&ompc, FALSE)) {
          Message (MSG_POSTERR, "DetachDataForProc failed");
        }
        SeqMgrDeleteFromBioseqIndex (bsp);
      }
    }
    ans = Message (MSG_YN, "Renormalize Nuc-Prot sets?");
    if (ans == ANS_YES)
    {
      RemoveOrphanProteins (entityID, sep);
      RenormalizeNucProtSets (sep, TRUE);
    }
  }
}

static void RemoveCDNANucProtProducts (ValNodePtr bssplist, Uint2 entityID)
{
  MsgAnswer     ans;
  ValNodePtr    tmp;
  BioseqSetPtr  bssp;
  Uint4         itemID;
  OMProcControl ompc;

  if (bssplist == NULL)
  {
    return;
  }
  
  ans = Message (MSG_YN, "Remove cDNA nuc-prot products?");
  if (ans != ANS_YES)
  {
    return;
  }

  for (tmp = bssplist; tmp != NULL; tmp = tmp->next) 
  {
    bssp = (BioseqSetPtr) tmp->data.ptrvalue;
    itemID = GetItemIDGivenPointer (entityID, OBJ_BIOSEQSET, (Pointer) bssp);
    if (itemID > 0) {
      MemSet ((Pointer) (&ompc), 0, sizeof (OMProcControl));
      ompc.do_not_reload_from_cache = TRUE;
      ompc.input_entityID = entityID;
      ompc.input_itemID = itemID;
      ompc.input_itemtype = OBJ_BIOSEQSET;
      if (! DetachDataForProc (&ompc, FALSE)) {
        Message (MSG_POSTERR, "DetachDataForProc failed");
      }
    }
  }
}


static Boolean FeatureRemoveOrConvertAction (Pointer userdata)
{
  FeatureSelRemConvFormPtr   mrfp;
  FilterSetPtr          fsp;
  SeqEntryPtr           sep;
  ValNodePtr            feature_type_list, vnp;
  Int4                  window_action;
  Uint1                 feat_def_choice;
  RemoveFeatureData     rfd;
  OrigFeatPtr           ofp;
  SeqFeatPtr            sfp;
  Boolean               rval = TRUE;
  SeqEntryPtr           create_sep;
  
  if (userdata == NULL) return FALSE;
  
  mrfp = (FeatureSelRemConvFormPtr) userdata;
  window_action = GetValue (mrfp->action_choice);
  
  fsp = (FilterSetPtr) DialogToPointer (mrfp->constraints);
  
  sep = GetTopSeqEntryForEntityID (mrfp->input_entityID);
  if (sep == NULL) return FALSE;
  
  if (window_action == FEATURE_CONVERT)
  {
    vnp = (ValNodePtr) DialogToPointer (mrfp->feature_select_to);
    if (vnp == NULL) 
    {
      FilterSetFree (fsp);
      return FALSE;
    }
    mrfp->featdef_to = vnp->choice;
    /* NOTE - I do not need to use ValNodeFreeData because I'm using
     * the data.ptrvalue in mrfp->featname_to and will free it when I free
     * mrfp.
     */
    vnp = ValNodeFree (vnp);
    feature_type_list = (ValNodePtr) DialogToPointer (mrfp->feature_select_from);
    mrfp->leave_original_feature = GetStatus (mrfp->leave_original_feature_btn);
  }
  else
  {
    feature_type_list = (ValNodePtr) DialogToPointer (mrfp->feature_select);
  }
  if (feature_type_list == NULL)
  {
    FilterSetFree (fsp);
    return FALSE;
  }
    
  for (vnp = feature_type_list; vnp != NULL; vnp = vnp->next)
  {
    feat_def_choice = vnp->choice;
    if (feat_def_choice == 255)
    {
      feat_def_choice = 0;
    }
    switch (window_action)
    {
      case FEATURE_CONVERT:
        rval = NewConvertFeaturesByList (sep, fsp, feat_def_choice, mrfp);
        break;
      case FEATURE_REMOVE:
        rfd.bsplist = NULL;
        rfd.bssplist = NULL;
        OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                             RemoveFeatureCallback,
                                             NULL, 0, feat_def_choice, 0, &rfd);
        RemoveProteinProducts (rfd.bsplist, mrfp->input_entityID, sep);
        RemoveCDNANucProtProducts (rfd.bssplist, mrfp->input_entityID);
        rfd.bsplist = ValNodeFree (rfd.bsplist);
        rfd.bssplist = ValNodeFree (rfd.bssplist);
        break;
      case FEATURE_SELECT:
        OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                             SelectFeatureCallback,
                                             NULL, 0, feat_def_choice, 0, NULL);
        break;
      case FEATURE_DESELECT:
        OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                             DeselectFeatureCallback,
                                             NULL, 0, feat_def_choice, 0, NULL);
        break;
    }
  }
  
  ValNodeFree (feature_type_list);
  FilterSetFree (fsp);
  
  if (window_action == FEATURE_REMOVE || window_action == FEATURE_CONVERT)
  {
    DeleteMarkedObjects (mrfp->input_entityID, 0, NULL);
  }
  
  if (window_action == FEATURE_CONVERT && mrfp->leave_original_feature)
  {
    for (vnp = mrfp->feat_list; vnp != NULL; vnp = vnp->next)
    {
      ofp = (OrigFeatPtr) vnp->data.ptrvalue;
      if (ofp == NULL || ofp->sep == NULL || ofp->sfp == NULL)
      {
        continue;
      }
      if (IS_Bioseq_set (ofp->sep)) {
        create_sep = FindNucSeqEntry (ofp->sep);
      } else {
        create_sep = ofp->sep;
      }
      sfp = CreateNewFeature (create_sep, NULL, ofp->sfp->data.choice, ofp->sfp);
    }
  }
  mrfp->feat_list = ValNodeFreeData (mrfp->feat_list);

  if (mrfp->renormalize_nucprot) {
    RenormalizeNucProtSets (sep, TRUE);       
  }
  
  ObjMgrSetDirtyFlag (mrfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, mrfp->input_entityID, 0, 0);  
  Update ();
  return rval;
}

NLM_EXTERN void FeatureRemoveOrConvertBaseForm (BaseFormPtr bfp, Int4 first_action)
{
  FeatureSelRemConvFormPtr mrfp;
  WindoW              w;
  GrouP               h, k, g, n;
  SeqEntryPtr         sep;

  if (bfp == NULL) return;

  mrfp = (FeatureSelRemConvFormPtr) MemNew (sizeof (FeatureSelRemConvFormData));
  if (mrfp == NULL) return;
  
  mrfp->bfp = bfp;
  
  w = FixedWindow (-50, -33, -10, -10, "Remove or Convert Features", StdCloseWindowProc);
  SetObjectExtra (w, mrfp, CleanupFeatureSelRemConvForm);
  mrfp->form = (ForM) w;
  mrfp->input_entityID = bfp->input_entityID;
  
  sep = GetTopSeqEntryForEntityID(bfp->input_entityID);
  
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  if (first_action < FEATURE_REMOVE || first_action > FEATURE_DESELECT)
  {
    first_action = FEATURE_REMOVE;
  }
  mrfp->action_choice = PopupList (h, TRUE, FeatureRemoveOrConvertCenterAction);
  PopupItem (mrfp->action_choice, "Remove Features");
  PopupItem (mrfp->action_choice, "Convert Features");
  SetValue (mrfp->action_choice, first_action);
  SetObjectExtra (mrfp->action_choice, mrfp, NULL);
  
  mrfp->clear_constraints_on_action_change = CheckBox (h, "Clear when changing actions", NULL);
  SetStatus (mrfp->clear_constraints_on_action_change, TRUE);

  n = HiddenGroup (h, 0, 0, NULL);
  mrfp->remove_grp = HiddenGroup (n, 0, 1, NULL);
  mrfp->feature_select =  FeatureSelectionDialogEx (mrfp->remove_grp, TRUE, sep,
                                                  FeatureRemoveChangeNotify, 
                                                  mrfp);
  
  mrfp->convert_grp = HiddenGroup (n, 3, 0, NULL);
  k = HiddenGroup (mrfp->convert_grp, 0, 2, NULL);
  mrfp->from_prompt = StaticPrompt (k, "From", 0, dialogTextHeight, systemFont, 'l');
  mrfp->feature_select_from =  FeatureSelectionDialogEx (k, FALSE, sep,
                                                  FeatureRemoveChangeNotify, 
                                                  mrfp);
  AlignObjects (ALIGN_CENTER, (HANDLE) mrfp->from_prompt, (HANDLE) mrfp->feature_select_from, NULL);
  k = HiddenGroup (mrfp->convert_grp, 0, 2, NULL);
  mrfp->to_prompt = StaticPrompt (k, "To", 0, dialogTextHeight, systemFont, 'l');
  mrfp->feature_select_to =  FeatureSelectionDialog (k, FALSE,
                                                  FeatureRemoveChangeNotify, 
                                                  mrfp);
  AlignObjects (ALIGN_CENTER, (HANDLE) mrfp->to_prompt, (HANDLE) mrfp->feature_select_to, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) mrfp->remove_grp, (HANDLE) mrfp->convert_grp, NULL);

  g = HiddenGroup (mrfp->convert_grp, 0, 2, NULL);
  StaticPrompt (g, "Conversion Function", 0, dialogTextHeight, systemFont, 'c');
  SelectFont (systemFont);
  mrfp->help_text = DocumentPanel (g, stdCharWidth * 12, LineHeight () * (TALL_SELECTION_LIST + 1));

  mrfp->leave_original_feature_btn = CheckBox (mrfp->convert_grp, "Leave Original Feature", FeatureRemoveChangeButton);
  SetObjectExtra (mrfp->leave_original_feature_btn, mrfp, NULL);
  SetStatus (mrfp->leave_original_feature_btn, FALSE);
  AlignObjects (ALIGN_CENTER, (HANDLE) k, (HANDLE) mrfp->leave_original_feature_btn, NULL);
  
  mrfp->feat_list = NULL;
  mrfp->constraints = FilterGroup (h, TRUE, FALSE, TRUE, FALSE, TRUE, "Where feature text");
  mrfp->accept_cancel = AcceptCancelDialog (h, FeatureRemoveOrConvertAction, NULL, FeatureRemoveClear, FeatureRemoveClearText, (Pointer)mrfp, w);
  AlignObjects (ALIGN_CENTER, (HANDLE) mrfp->action_choice,
                              (HANDLE) mrfp->clear_constraints_on_action_change,
                              (HANDLE) n,
                              (HANDLE) mrfp->constraints,
                              (HANDLE) mrfp->accept_cancel, NULL);
                              
  FeatureRemoveOrConvertCenterAction (mrfp->action_choice);
  
  Show (w);
}


static void FeatureRemoveOrConvert (IteM i, Int4 first_action)
{
  BaseFormPtr         bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  FeatureRemoveOrConvertBaseForm (bfp, first_action);
}


extern void FeatureRemove (IteM i)
{
  FeatureRemoveOrConvert (i, FEATURE_REMOVE);
}

extern void ConvertFeatures (IteM i)
{
  FeatureRemoveOrConvert (i, FEATURE_CONVERT);
}

static Boolean SelectFeatureAction (Pointer userdata)
{
  FeatureSelRemConvFormPtr   mrfp;
  FilterSetPtr          fsp;
  SeqEntryPtr           sep;
  ValNodePtr            feature_type_list, vnp;
  Uint1                 feat_def_choice;
  
  if (userdata == NULL) return FALSE;
  
  mrfp = (FeatureSelRemConvFormPtr) userdata;
  fsp = (FilterSetPtr) DialogToPointer (mrfp->constraints);
  
  sep = GetTopSeqEntryForEntityID (mrfp->input_entityID);
  if (sep == NULL) return FALSE;
  
  feature_type_list = (ValNodePtr) DialogToPointer (mrfp->feature_select);
  if (feature_type_list == NULL)
  {
    FilterSetFree (fsp);
    return FALSE;
  }
    
  for (vnp = feature_type_list; vnp != NULL; vnp = vnp->next)
  {
    feat_def_choice = vnp->choice;
    if (feat_def_choice == 255)
    {
      feat_def_choice = 0;
    }
    OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                         SelectFeatureCallback,
                                         NULL, 0, feat_def_choice, 0, NULL);
  }
  
  ValNodeFree (feature_type_list);
  FilterSetFree (fsp);
  
  ObjMgrSetDirtyFlag (mrfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, mrfp->input_entityID, 0, 0);  
  Update ();
  return TRUE;
}

extern void SelectFeatures (IteM i)
{
  BaseFormPtr              bfp;
  FeatureSelRemConvFormPtr mrfp;
  WindoW                   w;
  GrouP                    h;
  SeqEntryPtr              sep;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  mrfp = (FeatureSelRemConvFormPtr) MemNew (sizeof (FeatureSelRemConvFormData));
  if (mrfp == NULL) return;
  
  w = FixedWindow (-50, -33, -10, -10, "Select Features", StdCloseWindowProc);
  SetObjectExtra (w, mrfp, CleanupFeatureSelRemConvForm);
  mrfp->form = (ForM) w;
  mrfp->input_entityID = bfp->input_entityID;
  sep = GetTopSeqEntryForEntityID(bfp->input_entityID);
  
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  mrfp->feature_select =  FeatureSelectionDialogEx (h, TRUE, sep,
                                                  FeatureRemoveChangeNotify, 
                                                  mrfp);
    
  mrfp->constraints = FilterGroup (h, TRUE, FALSE, TRUE, FALSE, FALSE, "Where feature text");
  mrfp->accept_cancel = AcceptCancelDialog (h, SelectFeatureAction, NULL, FeatureRemoveClear, FeatureRemoveClearText, (Pointer)mrfp, w);
  AlignObjects (ALIGN_CENTER, (HANDLE) mrfp->feature_select,
                              (HANDLE) mrfp->constraints,
                              (HANDLE) mrfp->accept_cancel, NULL);
                              
  Show (w);
}


typedef struct reverseintervalsform
{
  FORM_MESSAGE_BLOCK
  DialoG  feature_select;
  DialoG  constraints;
  DialoG  accept_cancel;

  ValNodePtr feat_list;
  BaseFormPtr bfp;
} ReverseIntervalsFormData, PNTR ReverseIntervalsFormPtr;

static void ReverseIntervalsChangeNotify (Pointer userdata)
{
  ReverseIntervalsFormPtr fp;
  ValNodePtr          err_list;

  fp = (ReverseIntervalsFormPtr) userdata;
  if (fp == NULL) return;

  err_list = TestDialog (fp->feature_select);
  
  if (err_list == NULL)
  {
    EnableAcceptCancelDialogAccept (fp->accept_cancel);
  }
  else
  {
    DisableAcceptCancelDialogAccept (fp->accept_cancel);
  }
  ValNodeFree (err_list);
}

static void ReverseIntervalsClearText (Pointer data)
{
  ReverseIntervalsFormPtr   mrfp;
  FilterSetPtr          fsp;

  mrfp = (ReverseIntervalsFormPtr) data;
  if (mrfp == NULL) return;
 
  fsp = DialogToPointer (mrfp->constraints);
  FilterSetClearText (fsp);
  PointerToDialog (mrfp->constraints, fsp);
  FilterSetFree (fsp);
}

static void ReverseIntervalsClear (Pointer data)
{
  ReverseIntervalsFormPtr mrfp;

  mrfp = (ReverseIntervalsFormPtr) data;
  if (mrfp == NULL) return;
 
  PointerToDialog (mrfp->feature_select, NULL);
  PointerToDialog (mrfp->constraints, NULL);
}

static void ReverseLocationIntervalOrder (SeqLocPtr slp)
{
  SeqLocPtr     subslp_list = NULL, subslp, subslp_next;
  PackSeqPntPtr pspp;
  Int4 pnt_num, swap;

  if (slp == NULL 
      || slp->choice == SEQLOC_NULL
      || slp->choice == SEQLOC_EMPTY
      || slp->choice == SEQLOC_WHOLE
      || slp->choice == SEQLOC_INT
      || slp->choice == SEQLOC_PNT
      || slp->choice == SEQLOC_BOND
      || slp->choice == SEQLOC_FEAT) {
    return;
  }
  if (slp->choice == SEQLOC_MIX
      || slp->choice == SEQLOC_PACKED_INT
      || slp->choice == SEQLOC_EQUIV) {
    for (subslp = slp->data.ptrvalue;
         subslp != NULL;
         subslp = subslp_next) {
      subslp_next = subslp->next;
      subslp->next = subslp_list;
      subslp_list = subslp;
      ReverseLocationIntervalOrder (subslp);
    }
    slp->data.ptrvalue = subslp_list;
  } else if (slp->choice == SEQLOC_PACKED_PNT) {
    pspp = (PackSeqPntPtr) slp->data.ptrvalue;
    if (pspp != NULL) {
      for (pnt_num = 0; pnt_num < pspp->used / 2; pnt_num++) {
        swap = pspp->pnts[pnt_num];
        pspp->pnts[pnt_num] = pspp->pnts[pspp->used - 1 - pnt_num];
        pspp->pnts[pspp->used - 1 - pnt_num] = swap;
      }
    }
  }
}

static void ReverseIntervalsCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)

{
  if (sfp == NULL || sfp->location == NULL) return;

  ReverseLocationIntervalOrder (sfp->location);
}


static Boolean ReverseIntervalsAction (Pointer userdata)
{
  ReverseIntervalsFormPtr   mrfp;
  FilterSetPtr          fsp;
  SeqEntryPtr           sep;
  ValNodePtr            feature_type_list, vnp;
  Uint1                 feat_def_choice;
  
  if (userdata == NULL) return FALSE;
  
  mrfp = (ReverseIntervalsFormPtr) userdata;
  fsp = (FilterSetPtr) DialogToPointer (mrfp->constraints);
  
  sep = GetTopSeqEntryForEntityID (mrfp->input_entityID);
  if (sep == NULL) return FALSE;
  
  feature_type_list = (ValNodePtr) DialogToPointer (mrfp->feature_select);
  if (feature_type_list == NULL)
  {
    FilterSetFree (fsp);
    return FALSE;
  }
    
  for (vnp = feature_type_list; vnp != NULL; vnp = vnp->next)
  {
    feat_def_choice = vnp->choice;
    if (feat_def_choice == 255)
    {
      feat_def_choice = 0;
    }
    OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                         ReverseIntervalsCallback,
                                         NULL, 0, feat_def_choice, 0, NULL);
  }
  
  ValNodeFree (feature_type_list);
  FilterSetFree (fsp);
  
  ObjMgrSetDirtyFlag (mrfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, mrfp->input_entityID, 0, 0);  
  Update ();
  return TRUE;
}

extern void ReverseFeatureIntervals (IteM i)
{
  BaseFormPtr              bfp;
  ReverseIntervalsFormPtr  fp;
  WindoW                   w;
  GrouP                    h;
  SeqEntryPtr              sep;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  fp = (ReverseIntervalsFormPtr) MemNew (sizeof (ReverseIntervalsFormData));
  if (fp == NULL) return;
  
  w = FixedWindow (-50, -33, -10, -10, "Reverse Feature Intervals", StdCloseWindowProc);
  SetObjectExtra (w, fp, StdCleanupFormProc);
  fp->form = (ForM) w;
  fp->input_entityID = bfp->input_entityID;
  sep = GetTopSeqEntryForEntityID(bfp->input_entityID);
  
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  fp->feature_select =  FeatureSelectionDialogEx (h, TRUE, sep,
                                                  ReverseIntervalsChangeNotify, 
                                                  fp);
    
  fp->constraints = FilterGroup (h, TRUE, FALSE, TRUE, FALSE, FALSE, "Where feature text");
  fp->accept_cancel = AcceptCancelDialog (h, ReverseIntervalsAction, NULL, ReverseIntervalsClear, ReverseIntervalsClearText, (Pointer)fp, w);
  AlignObjects (ALIGN_CENTER, (HANDLE) fp->feature_select,
                              (HANDLE) fp->constraints,
                              (HANDLE) fp->accept_cancel, NULL);
                              
  Show (w);
}



extern ParseFieldPtr ParseFieldFree (ParseFieldPtr pfp)
{
  if (pfp != NULL)
  {
    switch (pfp->parse_field_type) {
      case PARSE_FIELD_SOURCE_QUAL :
      case PARSE_FIELD_FEATURE_NOTE:
      case PARSE_FIELD_DBXREF:
        pfp->feature_field = ValNodeFreeData (pfp->feature_field);
        break;
      case PARSE_FIELD_DEFLINE:
      case PARSE_FIELD_BIOSRC_STRING:
      case PARSE_FIELD_GENE_FIELD:
      case PARSE_FIELD_RNA_FIELD:
      case PARSE_FIELD_CDS_COMMENT:
      case PARSE_FIELD_COMMENT_DESC:
      case PARSE_FIELD_IMPORT_QUAL:
      case PARSE_FIELD_PROTEIN_FIELD:
        pfp->feature_field = ValNodeFree (pfp->feature_field);
        break;
      default:
        pfp->feature_field = ValNodeFree (pfp->feature_field);
    }

    if (pfp->parse_field_type == PARSE_FIELD_RNA_FIELD)
    {
      pfp->feature_subtype = ValNodeFree (pfp->feature_subtype);
    }
    else
    {
      pfp->feature_subtype = ValNodeFreeData (pfp->feature_subtype);
    }
    pfp = MemFree (pfp);
  }
  return pfp;
}


#define AECR_APPLY   1
#define AECR_EDIT    2
#define AECR_CONVERT 3
#define AECR_SWAP    4
#define AECR_PARSE   5
#define AECR_REMOVE  6
#define NUM_AECR     6


static void FeatureEditor (IteM i, Int4 first_action)
{
  BaseFormPtr     bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  FeatureEditorBaseForm (bfp, first_action);
}


extern void FeatureEvidenceEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_EVIDENCE);
}

extern void FeatureExceptionEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_EXCEPTION);
}

extern void FeaturePartialEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_PARTIAL);
}

extern void FeatureStrandEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_STRAND);
}

extern void FeatureCitationEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_CITATION);
}

extern void FeatureExperimentEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_EXPERIMENT);
}

extern void FeatureInferenceEditor (IteM i)
{
  FeatureEditor(i, FEAT_ED_INFERENCE);
}

extern void FeaturePseudoEditor (IteM i)
{
  FeatureEditor (i, FEAT_ED_PSEUDO);
}

typedef struct cdset 
{
  ValNodePtr cds_list;
  ValNodePtr gene_list;
  ValNodePtr prot_list;
  ValNodePtr mrna_list;
} CDSetData, PNTR CDSetPtr;

static ValNodePtr FreeCDSetList (ValNodePtr vnp)
{
  CDSetPtr cdsp;
  
  if (vnp != NULL)
  {
    FreeCDSetList (vnp->next);
    vnp->next = NULL;
    cdsp = (CDSetPtr) vnp->data.ptrvalue;
    if (cdsp != NULL)
    {
      cdsp->cds_list = ValNodeFree (cdsp->cds_list);
      cdsp->gene_list = ValNodeFree (cdsp->gene_list);
      cdsp->prot_list = ValNodeFree (cdsp->prot_list);
      cdsp->mrna_list = ValNodeFree (cdsp->mrna_list);
    }
    ValNodeFreeData (vnp);
  }
  return NULL;
}

extern CharPtr GetCDSGeneProtFieldName (ValNodePtr vnp)
{
  CharPtr PNTR      field_name_list = NULL;
  Int4              num_fields = 0;
  CharPtr           label = NULL;

  field_name_list = BuildCDSGeneFieldList (&num_fields);
  if (field_name_list != NULL
      && vnp->data.intvalue > 0 && vnp->data.intvalue <= num_fields)
  {
    label = StringSave (field_name_list [vnp->data.intvalue - 1]);
  }
  FreeCDSGeneFieldList (field_name_list, num_fields);
  return label;
}

static DialoG GetCDSGeneProtSample (GrouP h, Uint2 entityID)
{
  CharPtr PNTR      field_name_list = NULL;
  Int4              num_fields = 0, i;
  DialoG            d = NULL;
  SetSampleData     ssd;
  
  field_name_list = BuildCDSGeneFieldList (&num_fields);
  if (field_name_list != NULL)
  {    
    ssd.entityID = entityID;
    ssd.field_list = NULL;
    for (i = 0; i < num_fields; i++)
    {
      ValNodeAddInt (&ssd.field_list, 0, i + 1);
    }
    ssd.fieldstring_func = GetCDSGeneProtField;
    ssd.descrstring_func = NULL;
    ssd.fsp = NULL;
    ssd.free_vn_proc = NULL;
    ssd.copy_vn_proc = IntValNodeCopy;
    ssd.match_vn_proc = IntValNodeMatch;
    ssd.label_vn_proc = GetCDSGeneProtFieldName;  
    
    
    d = SampleDialog (h);
    PointerToDialog (d, &ssd);
    FreeCDSGeneFieldList (field_name_list, num_fields);
  }
  return d;
}


typedef struct buildcdset2
{
  ValNodePtr cds_list;
  ValNodePtr mrna_list;
  ValNodePtr gene_list;
} BuildCDSet2Data, PNTR BuildCDSet2Ptr;

static void BuildCDSet2Callback (SeqFeatPtr sfp, Pointer userdata)
{
  BuildCDSet2Ptr b;

  if (sfp == NULL || sfp->idx.deleteme || userdata == NULL) return;
  b = (BuildCDSet2Ptr) userdata;
  if (sfp->data.choice == SEQFEAT_CDREGION)
  {
    ValNodeAddPointer (&(b->cds_list), OBJ_SEQFEAT, sfp);
  }
  else if (sfp->data.choice == SEQFEAT_GENE)
  {
    ValNodeAddPointer (&(b->gene_list), OBJ_SEQFEAT, sfp);
  }
  else if (sfp->idx.subtype == FEATDEF_mRNA)
  {
    ValNodeAddPointer (&(b->mrna_list), OBJ_SEQFEAT, sfp);
  }
  else if (SeqMgrGetGeneXref (sfp) != NULL)
  {
    ValNodeAddPointer (&(b->gene_list), OBJ_SEQFEAT, sfp);
  }
}


static void UnmarkFeatureList (ValNodePtr list)
{
  SeqFeatPtr sfp;

  while (list != NULL)
  {
    sfp = list->data.ptrvalue;
    if (sfp != NULL)
    {
      sfp->idx.deleteme = FALSE;
    }
    list = list->next;
  }
}


static SeqFeatPtr GetGeneForCDSetFeature (SeqFeatPtr sfp)
{
  GeneRefPtr        grp;
  SeqFeatPtr        gene = NULL;
  SeqMgrFeatContext fcontext;

  grp = SeqMgrGetGeneXref (sfp);
  if (grp == NULL)
  {
    gene = SeqMgrGetOverlappingGene (sfp->location, &fcontext);
  }
  else if (!SeqMgrGeneIsSuppressed (grp))
  {
    if (!StringHasNoText (grp->locus_tag))
    {
      gene = SeqMgrGetGeneByLocusTag (BioseqFindFromSeqLoc (sfp->location), grp->locus_tag, &fcontext);
    }
  }
  return gene;
}


static CDSetPtr BuildCDSetFromCodingRegion (SeqFeatPtr cds, BoolPtr indexing_needed)
{
  SeqMgrFeatContext fcontext;
  SeqFeatPtr        gene = NULL, mrna, prot;
  BioseqPtr         protbsp;
  CDSetPtr          cdsp;
  ProtRefPtr        prp;

  if (cds == NULL || cds->data.choice != SEQFEAT_CDREGION) return NULL;

  cdsp = (CDSetPtr) MemNew (sizeof (CDSetData));
  ValNodeAddPointer (&(cdsp->cds_list), 0, cds);

  gene = GetGeneForCDSetFeature (cds);
  if (gene != NULL)
  {
    ValNodeAddPointer (&(cdsp->gene_list), 0, gene);
    /* mark gene, so that we'll know it isn't lonely */
    gene->idx.deleteme = TRUE;
  }

  mrna = SeqMgrGetOverlappingmRNA (cds->location, &fcontext);
  if (mrna != NULL)
  {
    ValNodeAddPointer (&(cdsp->mrna_list), 0, mrna);
    /* mark mrna, so that we'll know it's already in a set */
    mrna->idx.deleteme = TRUE;
  }

  if (cds->product != NULL)
  {
    protbsp = BioseqFindFromSeqLoc (cds->product);
    if (protbsp != NULL)
    {
      prot = SeqMgrGetNextFeature (protbsp, NULL, SEQFEAT_PROT, FEATDEF_PROT, &fcontext);
      /* if there is no full-length protein feature, make one */
      if (prot == NULL)
      {
        prp = ProtRefNew ();
        prot = CreateNewFeatureOnBioseq (protbsp, SEQFEAT_PROT, NULL);
        if (prot != NULL)
        {
          prot->data.value.ptrvalue = prp;
          if (indexing_needed != NULL)
          {
            *indexing_needed = TRUE;
          }
        }
      }
      if (prot != NULL)
      {
        ValNodeAddPointer (&(cdsp->prot_list), 0, prot);
      }
      
      /* also add in mat_peptides from protein feature */
      prot = SeqMgrGetNextFeature (protbsp, NULL, SEQFEAT_PROT, FEATDEF_mat_peptide_aa, &fcontext);
      while (prot != NULL)
      {
        ValNodeAddPointer (&(cdsp->prot_list), 0, prot);
        prot = SeqMgrGetNextFeature (protbsp, prot, SEQFEAT_PROT, FEATDEF_mat_peptide_aa, &fcontext);
      }
    }
  }  
  return cdsp;
}


static CDSetPtr BuildCDSetFrommRNA (SeqFeatPtr mrna)
{
  SeqFeatPtr        gene;
  CDSetPtr          cdsp;

  if (mrna == NULL || mrna->idx.deleteme || mrna->idx.subtype != FEATDEF_mRNA) return NULL;

  cdsp = (CDSetPtr) MemNew (sizeof (CDSetData));
  ValNodeAddPointer (&(cdsp->mrna_list), 0, mrna);

  gene = GetGeneForCDSetFeature (mrna);
  if (gene != NULL)
  {
    ValNodeAddPointer (&(cdsp->gene_list), 0, gene);
    /* mark gene, so that we'll know it isn't lonely */
    gene->idx.deleteme = TRUE;
  }

  return cdsp;
}


static Boolean DoesCDSetMatchConstraint (CDSetPtr cdsp, ChoiceConstraintPtr ccp);

static ValNodePtr BuildCDSet2List (Uint2 entityID, ChoiceConstraintPtr  ccp)
{
  SeqEntryPtr    sep;
  BuildCDSet2Data b;
  CDSetPtr       cdsp;
  ValNodePtr     vnp, vnp_next, vnp_prev;
  ValNodePtr     cdset_list = NULL;
  SeqFeatPtr     cds, gene, mrna;
  Boolean        need_indexing = FALSE;
  
  sep = GetTopSeqEntryForEntityID (entityID);

  b.cds_list = NULL;
  b.gene_list = NULL;
  b.mrna_list = NULL;
  
  VisitFeaturesInSep (sep, &b, BuildCDSet2Callback);

  /* build cdsets that have coding regions */
  for (vnp = b.cds_list; vnp != NULL; vnp = vnp->next)
  {
    cds = (SeqFeatPtr) vnp->data.ptrvalue;
    if (cds == NULL) continue;
    cdsp = BuildCDSetFromCodingRegion (cds, &need_indexing);
    if (cdsp != NULL)
    {
      ValNodeAddPointer (&cdset_list, 0, cdsp);
    }
  }
  if (need_indexing)
  {
    /* indexing because we have created full-length protein features */
    SeqMgrIndexFeatures (entityID, NULL);
  }

  /* build cdsets for mrna features that don't have coding regions */
  for (vnp = b.mrna_list; vnp != NULL; vnp = vnp->next)
  {
    mrna = (SeqFeatPtr) vnp->data.ptrvalue;
    if (mrna == NULL || mrna->idx.deleteme) continue;
    cdsp = BuildCDSetFrommRNA (mrna);
    if (cdsp != NULL)
    {
      ValNodeAddPointer (&cdset_list, 0, cdsp);
    }
  }

  /* build cdsets for lonely genes / features with gene xrefs that are not coding regions or mrnas */
  for (vnp = b.gene_list; vnp != NULL; vnp = vnp->next)
  {
    gene = (SeqFeatPtr) vnp->data.ptrvalue;
    if (gene == NULL || gene->idx.deleteme) continue;
    cdsp = (CDSetPtr) MemNew (sizeof (CDSetData));
    ValNodeAddPointer (&(cdsp->gene_list), 0, gene);
    ValNodeAddPointer (&cdset_list, 0, cdsp);
  }

  /* now unmark features */
  UnmarkFeatureList (b.cds_list);
  UnmarkFeatureList (b.mrna_list);
  UnmarkFeatureList (b.gene_list);

  b.cds_list = ValNodeFree (b.cds_list);
  b.mrna_list = ValNodeFree (b.mrna_list);
  b.gene_list = ValNodeFree (b.gene_list);

  /* now remove sets that don't match our choice constraint */
  vnp_prev = NULL;
  for (vnp = cdset_list; vnp != NULL; vnp = vnp_next)
  {
    vnp_next = vnp->next;
    if (!DoesCDSetMatchConstraint (vnp->data.ptrvalue, ccp))
    {
      if (vnp_prev == NULL)
      {
        cdset_list = vnp->next;
      }
      else
      {
        vnp_prev->next = vnp->next;
      }
      vnp->next = NULL;
      FreeCDSetList (vnp);     
    }
    else
    {
      vnp_prev = vnp;
    }
  }
  
  return cdset_list;
}

typedef struct buildcdset
{
  ValNodePtr cdset_list;
  ValNodePtr lonely_gene_list;
} BuildCDSetData, PNTR BuildCDSetPtr;


static CharPtr GetCDSetField (CDSetPtr cdsp, ValNodePtr vnp)
{
  ValNodePtr sfp_vnp;
  CharPtr    str = NULL;
  
  if (cdsp == NULL || vnp == NULL)
  {
    return NULL;
  }
  
  for (sfp_vnp = cdsp->cds_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, NULL);
  }
  for (sfp_vnp = cdsp->gene_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, NULL);
  }
  for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, NULL);
  }
  for (sfp_vnp = cdsp->mrna_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, NULL);
  }
  return str;
}

static Boolean SetCDSetField (CDSetPtr cdsp, ValNodePtr vnp, ApplyValuePtr avp, FilterSetPtr fsp)
{
  ValNodePtr sfp_vnp;
  Boolean    rval = FALSE;
  
  if (cdsp == NULL || vnp == NULL || avp == NULL)
  {
    return FALSE;
  }
  
  for (sfp_vnp = cdsp->cds_list; sfp_vnp != NULL ; sfp_vnp = sfp_vnp->next)
  {
    rval |= SetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, avp, fsp);
  }
  for (sfp_vnp = cdsp->gene_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
  {
    if (sfp_vnp->choice == 0) /* don't set again if already set */
    {
      rval |= SetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, avp, fsp);
    }
  }
  for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
  {
    rval |= SetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, avp, fsp);
  }
  for (sfp_vnp = cdsp->mrna_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
  {
    rval |= SetCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, avp, fsp);
  }
  return rval;
}

static void RemoveCDSetField (CDSetPtr cdsp, ValNodePtr vnp, FilterSetPtr fsp)
{
  ValNodePtr sfp_vnp;
  CharPtr    str = NULL;
  
  if (cdsp == NULL || vnp == NULL)
  {
    return;
  }
  
  for (sfp_vnp = cdsp->cds_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    RemoveCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, fsp);
  }
  for (sfp_vnp = cdsp->gene_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    RemoveCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, fsp);
  }
  for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    RemoveCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, fsp);
  }
  for (sfp_vnp = cdsp->mrna_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
  {
    RemoveCDSGeneProtField (sfp_vnp->data.ptrvalue, vnp, fsp);
  }
}

static Boolean GetCDSetPseudoMatch (CDSetPtr cdsp, Int4 featdef_type, Boolean is_pseudo)
{
  ValNodePtr sfp_vnp;
  CharPtr    str = NULL;
  SeqFeatPtr sfp;
  GeneRefPtr grp;
  Boolean    gene_is_pseudo;
  
  if (cdsp == NULL)
  {
    return FALSE;
  }
  
  if (featdef_type == FEATDEF_CDS || featdef_type == FEATDEF_ANY)
  {
    for (sfp_vnp = cdsp->cds_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
    {
      sfp = sfp_vnp->data.ptrvalue;
      if (sfp != NULL && ((is_pseudo && sfp->pseudo) || (!is_pseudo && ! sfp->pseudo)))
      {
        return TRUE;
      }
    }
  }
  if (featdef_type == FEATDEF_GENE || featdef_type == FEATDEF_ANY)
  {
    for (sfp_vnp = cdsp->gene_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
    {
      sfp = sfp_vnp->data.ptrvalue;
      if (sfp != NULL)
      {
        gene_is_pseudo = FALSE;
        if (sfp->pseudo)
        {
          gene_is_pseudo = TRUE;
        }
        if (sfp->data.choice == SEQFEAT_GENE)
        {
          grp = (GeneRefPtr) sfp->data.value.ptrvalue;
          if (grp != NULL && grp->pseudo)
          {
            gene_is_pseudo = TRUE;
          }
        }
        if ((is_pseudo && gene_is_pseudo) || (!is_pseudo && ! gene_is_pseudo))
        {
          return TRUE;
        }
      }
    }
  }
  if (featdef_type == FEATDEF_mRNA || featdef_type == FEATDEF_ANY)
  {
    for (sfp_vnp = cdsp->mrna_list; sfp_vnp != NULL && str == NULL; sfp_vnp = sfp_vnp->next)
    {
      sfp = sfp_vnp->data.ptrvalue;
      if (sfp != NULL && ((is_pseudo && sfp->pseudo) || (!is_pseudo && ! sfp->pseudo)))
      {
        return TRUE;
      }
    }
  }
  return FALSE;
}

static Boolean DoCDSFieldsMatch (CDSetPtr cdsp, ValNodePtr qual_choice, ValNodePtr qual_choice_match)
{
  CharPtr str1, str2;
  Int4    field_choice, field_choice2;
  ValNode field_vn, field_vn2;
  Boolean does_match = FALSE;

  if (qual_choice == NULL && qual_choice_match == NULL) 
  {
    /* both are wildcards */
    field_vn.next = NULL;
    field_vn.choice = 0;
    field_vn2.next = NULL;
    field_vn2.choice = 0;

    for (field_choice = 1;
         ! does_match && field_choice < num_gene_fields + num_mrna_fields + num_protein_fields;
         field_choice++)
    {
      field_vn.data.intvalue = field_choice;
      str1 = GetCDSetField (cdsp, &field_vn);
      if (!StringHasNoText (str1)) {
        for (field_choice2 = field_choice + 1;
             ! does_match && field_choice2 < 1 + num_gene_fields + num_mrna_fields + num_protein_fields;
             field_choice2++)
        {
          field_vn2.data.intvalue = field_choice2;
          str2 = GetCDSetField (cdsp, &field_vn2);
          if (StringCmp (str1, str2) == 0) 
          {
            does_match = TRUE;
          }
          str2 = MemFree (str2);
        }
      }
      str1 = MemFree (str1);
    }
  }
  else if (qual_choice == NULL || qual_choice_match == NULL) 
  {
    /* one is a wildcard */
    /* make sure it is the first one */
    if (qual_choice_match == NULL) 
    {
      qual_choice_match = qual_choice;
      qual_choice = NULL;
    }
    field_vn.next = NULL;
    field_vn.choice = 0;
    str2 = GetCDSetField (cdsp, qual_choice_match);
    if (!StringHasNoText (str2)) {
      for (field_choice = 1;
           ! does_match && field_choice < num_gene_fields + num_mrna_fields + num_protein_fields;
           field_choice++)
      {
        if (field_choice != qual_choice_match->data.intvalue) {
          /* don't compare something to itself */
          field_vn.data.intvalue = field_choice;
          str1 = GetCDSetField (cdsp, &field_vn);
          if (StringCmp (str1, str2) == 0) 
          {
            does_match = TRUE;
          }
          str1 = MemFree (str1);
        }
      }
    }
    str2 = MemFree (str2);
  } else {
    str1 = GetCDSetField (cdsp, qual_choice);
    str2 = GetCDSetField (cdsp, qual_choice_match);
    if (!StringHasNoText (str1) && StringCmp (str1, str2) == 0)
    {
      does_match = TRUE;
    }
    str1 = MemFree (str1);
    str2 = MemFree (str2);
  }
  return does_match;
}


static Boolean DoesCDSetMatchConstraint (CDSetPtr cdsp, ChoiceConstraintPtr ccp)
{
  Boolean                does_match = FALSE;
  CharPtr                str;
  Int4                   field_choice;
  ValNode                field_vn;
  ValNodePtr             sfp_vnp;
  
  if (cdsp == NULL)
  {
    return FALSE;
  }
  if (ccp == NULL || ccp->constraint_type == CHOICE_CONSTRAINT_ANY)
  {
    does_match = TRUE;
  }
  else if (ccp->constraint_type == CHOICE_CONSTRAINT_QUAL_PRESENT)
  {
    if (ccp->qual_choice == NULL)
    {
      does_match = TRUE;
    }
    else
    {
      str = GetCDSetField (cdsp, ccp->qual_choice);
      if (str != NULL)
      {
        MemFree (str);
        does_match = TRUE;
      }
    }
  }
  else if (ccp->constraint_type == CHOICE_CONSTRAINT_STRING)
  {
    if (ccp->qual_choice == NULL) 
    {
      field_vn.next = NULL;
      field_vn.choice = 0;
      for (field_choice = 1;
           ! does_match && field_choice < 1 + num_gene_fields + num_mrna_fields + num_protein_fields;
           field_choice++)
      {
        field_vn.data.intvalue = field_choice;
        str = GetCDSetField (cdsp, &field_vn);
        does_match = DoesStringMatchConstraintX (str, ccp->string_constraint);
        MemFree (str);
      }
      if (ccp->string_constraint != NULL && ccp->string_constraint->not_present)
      {
        does_match = ! does_match;
      }
    }
    else if (IsCDSetMatPeptideQualChoice(ccp->qual_choice))
    {
      /* if we're matching protein fields, we need to look at all of them */
      for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL && !does_match; sfp_vnp = sfp_vnp->next)
      {
        str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, ccp->qual_choice, NULL);
        does_match = DoesStringMatchConstraintX (str, ccp->string_constraint);
        if (ccp->string_constraint != NULL && ccp->string_constraint->not_present)
        {
          does_match = ! does_match;
        }
        MemFree (str);
      }
    } 
    else
    {
      str = GetCDSetField (cdsp, ccp->qual_choice);
      does_match = DoesStringMatchConstraintX (str, ccp->string_constraint);
      MemFree (str);
      if (ccp->string_constraint != NULL && ccp->string_constraint->not_present)
      {
        does_match = ! does_match;
      }
    }
  }
  else if (ccp->constraint_type == CHOICE_CONSTRAINT_MATCH)
  {
    does_match = DoCDSFieldsMatch (cdsp, ccp->qual_choice, ccp->qual_choice_match);
  }
  else if (ccp->constraint_type == CHOICE_CONSTRAINT_PSEUDO)
  {
    if (ccp->pseudo_constraint == NULL)
    {
      does_match = TRUE;
    }
    else
    {
      does_match = GetCDSetPseudoMatch (cdsp, 
                                        ccp->pseudo_constraint->featdef_type, 
                                        ccp->pseudo_constraint->is_pseudo);
    }
  }
  return does_match;
}

static SeqLocPtr GetNewFeatureLocation(ValNodePtr feat_list, Uint2 choice, Uint2 choice_from)
{
  ValNodePtr vnp;
  SeqFeatPtr sfp;
  BioseqPtr  bsp = NULL;
  SeqLocPtr  slp = NULL;
  
  for (vnp = feat_list; vnp != NULL && slp == NULL; vnp = vnp->next)
  {
    sfp = vnp->data.ptrvalue;
    if (sfp != NULL)
    {
      if (choice == FEATDEF_PROT || choice == FEATDEF_mat_peptide_aa)
      {
        bsp = BioseqFindFromSeqLoc (sfp->product);
        slp = SeqLocWholeNew (bsp);
      }
      else
      {
        if (choice_from == sfp->idx.subtype || (sfp->idx.subtype == FEATDEF_CDS && (choice_from == FEATDEF_PROT || choice_from == FEATDEF_mat_peptide_aa)))
        {
          slp = (SeqLocPtr) AsnIoMemCopy (sfp->location, (AsnReadFunc) SeqLocAsnRead, (AsnWriteFunc) SeqLocAsnWrite);
        }
        else
        { 
          bsp = BioseqFindFromSeqLoc (sfp->location);
          slp = SeqLocWholeNew (bsp);
        }
      }
    }
  }  
  return slp;  
}


static SeqFeatPtr BuildOneNewCDSGeneProtFeature (CDSetPtr cdsp, Uint2 choice, Uint2 choice_from)
{
  BioseqPtr bsp = NULL;
  SeqLocPtr slp = NULL, slp_tmp, slp_sum;
  SeqFeatPtr sfp = NULL;
  RnaRefPtr  rrp;
  ProtRefPtr prp;
  
  if (cdsp == NULL)
  {
    return NULL;
  }

  if (choice == FEATDEF_PROT || choice == FEATDEF_mat_peptide_aa)
  {
    slp = GetNewFeatureLocation(cdsp->cds_list, choice, choice_from);
  }
  else
  {
    /* get location from CDS features */
    if (choice_from == 0 || choice_from == FEATDEF_CDS || choice_from == FEATDEF_PROT || choice_from == FEATDEF_mat_peptide_aa)
    {
      slp = GetNewFeatureLocation(cdsp->cds_list, choice, choice_from);
    }

    /* get location from mRNA features */
    if ((choice_from == 0 && slp == NULL) || choice_from == FEATDEF_mRNA)
    {    
      slp_tmp = GetNewFeatureLocation(cdsp->mrna_list, choice, choice_from);
      if (slp == NULL) 
      {
        slp = slp_tmp;
      }
      else
      {
        bsp = BioseqFindFromSeqLoc (sfp->location);
        if (bsp != NULL)
        {
          slp_sum = SeqLocMerge (bsp, slp_tmp, slp, TRUE, FALSE, FALSE);
          slp = SeqLocFree (slp);
          slp_tmp = SeqLocFree (slp_tmp);
          slp = slp_sum;
          slp_sum = NULL;
        }
      }
    }

    /* get location from gene features */
    if ((choice_from == 0 && slp == NULL) || choice_from == FEATDEF_GENE)
    {    
      slp_tmp = GetNewFeatureLocation(cdsp->gene_list, choice, choice_from);
      if (slp == NULL) 
      {
        slp = slp_tmp;
      }
      else
      {
        bsp = BioseqFindFromSeqLoc (sfp->location);
        if (bsp != NULL)
        {
          slp_sum = SeqLocMerge (bsp, slp_tmp, slp, TRUE, FALSE, FALSE);
          slp = SeqLocFree (slp);
          slp_tmp = SeqLocFree (slp_tmp);
          slp = slp_sum;
          slp_sum = NULL;
        }
      }
    }
  }  
  
  bsp = BioseqFindFromSeqLoc (slp);
  if (bsp != NULL && slp != NULL)
  {
    sfp = CreateNewFeatureOnBioseq (bsp, FindFeatFromFeatDefType(choice), slp);
    switch (choice)
    {
      case FEATDEF_mRNA:
        rrp = RnaRefNew ();
        rrp->type = 2;
        sfp->data.value.ptrvalue = rrp;
        break;
      case FEATDEF_GENE:
        sfp->data.value.ptrvalue = GeneRefNew();
        break;
      case FEATDEF_CDS:
        sfp->data.value.ptrvalue = CdRegionNew();
        break;
      case FEATDEF_PROT:
      case FEATDEF_mat_peptide_aa:
        prp = ProtRefNew();        
        if (choice == FEATDEF_mat_peptide_aa)
        {
          prp->processed = 2;
        }
        sfp->data.value.ptrvalue = prp;
        break;
    }
  }
  return sfp;
}

static SeqFeatPtr BuildNewCDSGeneProtFeature (CDSetPtr cdsp, ValNodePtr vnp, ValNodePtr field_from)
{
  SeqFeatPtr new_sfp = NULL, sfp;
  Uint2      choice_to, choice_from;
  ValNodePtr v;
  Boolean    found;
  
  if (cdsp == NULL || vnp == NULL)
  {
    return NULL;
  }

  choice_to = FeatDefTypeFromFieldList (vnp);
  choice_from = FeatDefTypeFromFieldList (field_from);

  switch (choice_to)
  {
    case FEATDEF_CDS:
      if (choice_from != 0 && cdsp->cds_list == NULL)
      {
        /* add new CDS */
        new_sfp = BuildOneNewCDSGeneProtFeature (cdsp, choice_to, choice_from);
        if (new_sfp != NULL)
        {
          ValNodeAddPointer (&(cdsp->cds_list), 0, new_sfp);
        }
      }
      break;
    case FEATDEF_GENE:
      if ((choice_from != 0 || vnp->data.intvalue - 1 == GENEFIELD_LOCUS) && cdsp->gene_list == NULL)
      {
        /* add new gene */
        new_sfp = BuildOneNewCDSGeneProtFeature (cdsp, choice_to, choice_from);
        if (new_sfp != NULL)
        {
          ValNodeAddPointer (&(cdsp->gene_list), 0, new_sfp);
        }
      }
      break;
    case FEATDEF_mRNA:
      if (choice_from != 0 && cdsp->mrna_list == NULL)
      {
        /* add new mrna */
        new_sfp = BuildOneNewCDSGeneProtFeature (cdsp, choice_to, choice_from);
        if (new_sfp != NULL)
        {
          ValNodeAddPointer (&(cdsp->mrna_list), 0, new_sfp);
        }
      }
      break;
    case FEATDEF_PROT:
    case FEATDEF_mat_peptide_aa:
      if (choice_from != 0 && cdsp->cds_list != NULL)
      {
        found = FALSE;
        for (v = cdsp->prot_list; v != NULL && !found; v = v->next)
        {
          sfp = (SeqFeatPtr) v->data.ptrvalue;
          if (sfp != NULL && sfp->idx.subtype == choice_to)
          {
            found = FALSE;
          }
        }
        if (!found)
        {
          new_sfp = BuildOneNewCDSGeneProtFeature (cdsp, choice_to, choice_from);
          if (new_sfp != NULL)
          {
            /* set subtype so other functions will work */
            new_sfp->idx.subtype = choice_to;
            ValNodeAddPointer (&(cdsp->mrna_list), 0, new_sfp);
          }
        }
      }
      break;
  }
  return new_sfp;        
}

static Boolean CDSetOnBioseq (CDSetPtr cdsp, BioseqPtr bsp)
{
  SeqFeatPtr sfp;
  Boolean    is_on_bioseq = FALSE;
  ValNodePtr sfp_vnp;

  if (cdsp == NULL || bsp == NULL) return FALSE;

  for (sfp_vnp = cdsp->cds_list; sfp_vnp != NULL && !is_on_bioseq; sfp_vnp = sfp_vnp->next)
  {
    sfp = sfp_vnp->data.ptrvalue;
    if (sfp != NULL && BioseqFindFromSeqLoc (sfp->location) == bsp) {
      is_on_bioseq = TRUE;
    }
  }
  for (sfp_vnp = cdsp->gene_list; sfp_vnp != NULL && !is_on_bioseq; sfp_vnp = sfp_vnp->next)
  {
    sfp = sfp_vnp->data.ptrvalue;
    if (sfp != NULL && BioseqFindFromSeqLoc (sfp->location) == bsp) {
      is_on_bioseq = TRUE;
    }
  }
  for (sfp_vnp = cdsp->mrna_list; sfp_vnp != NULL && !is_on_bioseq; sfp_vnp = sfp_vnp->next)
  {
    sfp = sfp_vnp->data.ptrvalue;
    if (sfp != NULL && BioseqFindFromSeqLoc (sfp->location) == bsp) {
      is_on_bioseq = TRUE;
    }
  }
  return is_on_bioseq;
}

static Boolean ValNodeDataAlreadyInValNodeList (ValNodePtr list, ValNodePtr findme)
{
  if (findme == NULL) return FALSE;
  while (list != NULL) 
  {
    if (list->data.ptrvalue == findme->data.ptrvalue)
    {
      return TRUE;
    }
    list = list->next;
  }
  return FALSE;
}

static void BuildGeneForSegSet (BioseqSetPtr segset, ValNodePtr PNTR cdset_list, Boolean lonely_ok)
{
  SeqEntryPtr       this_sep, master_sep = NULL;
  BioseqSetPtr      part_set = NULL;
  SeqLocPtr         slp = NULL, slp_new, slp_last = NULL;
  SeqFeatPtr        sfp;
  SeqMgrFeatContext context;
  ValNodePtr        vnp, add_gene_to = NULL;
  Boolean           on_part;
  CDSetPtr          cdsp;
  
  if (segset == NULL || segset->seq_set == NULL)
  {
    return;
  }
  
  this_sep = segset->seq_set;
  while (this_sep != NULL && (part_set == NULL || master_sep == NULL))
  {
    if (IS_Bioseq_set (this_sep))
    {
      part_set = (BioseqSetPtr) this_sep->data.ptrvalue;
      if (part_set != NULL && part_set->_class != BioseqseqSet_class_parts)
      {
        part_set = NULL;
      }
    }
    else if (IS_Bioseq (this_sep))
    {
      master_sep = this_sep;
    }
    this_sep = this_sep->next;
  }
  if (part_set != NULL && master_sep != NULL)
  {
    this_sep = part_set->seq_set;
    sfp = SeqMgrGetNextFeature (master_sep->data.ptrvalue, NULL, 
                                SEQFEAT_GENE, FEATDEF_GENE, &context);
    
    while (this_sep != NULL && sfp == NULL)
    {
      if (IS_Bioseq (this_sep))
      {
        sfp = SeqMgrGetNextFeature (this_sep->data.ptrvalue, NULL, 
                                    SEQFEAT_GENE, FEATDEF_GENE, &context);
      }
      this_sep = this_sep->next;
    }
    if (sfp != NULL)
    {
      return;
    }

    /* no other genes anywhere in segmented set.  Any cdsets? */
    for (vnp = *cdset_list; vnp != NULL; vnp = vnp->next)
    {
      cdsp = (CDSetPtr) vnp->data.ptrvalue;
      if (CDSetOnBioseq(cdsp, master_sep->data.ptrvalue)
          && !ValNodeDataAlreadyInValNodeList(add_gene_to, vnp)) 
      {
        ValNodeAddPointer (&add_gene_to, 0, cdsp);
      }
      else
      {
        on_part = FALSE;
        for (this_sep = part_set->seq_set; this_sep != NULL && !on_part; this_sep = this_sep->next) 
        {
          if (CDSetOnBioseq(cdsp, this_sep->data.ptrvalue)
              && !ValNodeDataAlreadyInValNodeList(add_gene_to, vnp)) 
          {
            ValNodeAddPointer (&add_gene_to, 0, cdsp);
            on_part = TRUE;
          }
        }
      }
    }

    if (add_gene_to != NULL || lonely_ok) {    
      /* create gene */
      this_sep = part_set->seq_set;
      while (this_sep != NULL)
      {
        if (IS_Bioseq (this_sep))
        {
          slp_new = SeqLocWholeNew (this_sep->data.ptrvalue);
          if (slp_new != NULL)
          {
            if (slp_last == NULL)
            {
              slp = slp_new;
            }
            else
            {
              slp_last = ValNodeNew (slp);
              slp_last->choice = SEQLOC_NULL;
              slp_last->next = slp_new;
            }
            slp_last = slp_new;
          }
        }
        this_sep = this_sep->next;
      }
      slp_new = ValNodeNew (NULL);
      slp_new->choice = SEQLOC_MIX;
      slp_new->data.ptrvalue = slp;
      sfp = CreateNewFeature (master_sep, NULL, SEQFEAT_GENE, NULL);
      sfp->location = SeqLocFree (sfp->location);
      sfp->location = slp_new;
      sfp->data.value.ptrvalue = GeneRefNew ();

      /* add to sets */
      if (add_gene_to == NULL)
      {
        cdsp = (CDSetPtr) MemNew (sizeof (CDSetData));
        MemSet (cdsp, 0, sizeof (CDSetData));
        ValNodeAddPointer (&(cdsp->gene_list), 0, sfp);
        ValNodeAddPointer (cdset_list, 0, cdsp);
      }
      else
      {
        for (vnp = add_gene_to; vnp != NULL; vnp = vnp->next)
        {
          cdsp = (CDSetPtr) vnp->data.ptrvalue;
          ValNodeAddPointer (&(cdsp->gene_list), 0, sfp);
        }
      }
    }
    add_gene_to = ValNodeFree (add_gene_to);
  }
}

static Boolean LoneGeneOkWithFilter (FilterSetPtr fsp)
{
  if (fsp == NULL || fsp->cgp == NULL
      || fsp->cgp->constraint_type == CHOICE_CONSTRAINT_ANY
      || (fsp->cgp->constraint_type == CHOICE_CONSTRAINT_QUAL_PRESENT
          && fsp->cgp->qual_choice == NULL))
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

static void BuildNewGenes (SeqEntryPtr sep, ValNodePtr PNTR cdset_list, Boolean lonely_ok)
{
  BioseqSetPtr      bssp = NULL;
  BioseqPtr         bsp = NULL;
  SeqFeatPtr        sfp;
  SeqMgrFeatContext context;
  SeqLocPtr         slp = NULL;
  ValNodePtr        vnp;
  CDSetPtr          cdsp;
  ValNodePtr        add_gene_to;
  
  if (sep == NULL) 
  {
    return;
  }
  
  if (IS_Bioseq (sep))
  {
    bsp = (BioseqPtr) sep->data.ptrvalue;
    if (ISA_na (bsp->mol))
    {
      sfp = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_GENE, FEATDEF_GENE, &context);
      if (sfp == NULL)
      {
        /* are there any sets already on this Bioseq? */
        add_gene_to = NULL;
        for (vnp = *cdset_list; vnp != NULL; vnp = vnp->next) 
        {
          cdsp = (CDSetPtr) vnp->data.ptrvalue;
          if (CDSetOnBioseq(cdsp, bsp))
          {
            if (cdsp->gene_list == NULL)
            {
              ValNodeAddPointer (&add_gene_to, 0, cdsp);
            }
          }
        }
        if (add_gene_to != NULL || lonely_ok) 
        {
          slp = SeqLocWholeNew (bsp);
          sfp = CreateNewFeatureOnBioseq (bsp, SEQFEAT_GENE, slp);    
          sfp->data.value.ptrvalue = GeneRefNew();  
          if (add_gene_to == NULL)
          {
            cdsp = (CDSetPtr) MemNew (sizeof (CDSetData));
            MemSet (cdsp, 0, sizeof (CDSetData));
            ValNodeAddPointer (&(cdsp->gene_list), 0, sfp);
            ValNodeAddPointer (cdset_list, 0, cdsp);
          }
          else
          {
            for (vnp = add_gene_to; vnp != NULL; vnp = vnp->next)
            {
              cdsp = (CDSetPtr) vnp->data.ptrvalue;
              ValNodeAddPointer (&(cdsp->gene_list), 0, sfp);
            }
          }
        }
        add_gene_to = ValNodeFree (add_gene_to);
      }
    }
  }
  else if (IS_Bioseq_set (sep))
  {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp->_class == BioseqseqSet_class_segset)
    {
      BuildGeneForSegSet (bssp, cdset_list, lonely_ok);
    }
    else
    {
      BuildNewGenes (bssp->seq_set, cdset_list, lonely_ok);
    }
  }
  BuildNewGenes (sep->next, cdset_list, lonely_ok);
}

typedef Boolean (*Nlm_TestAECRChoiceProc) PROTO((Pointer));
typedef DialoG  (*Nlm_AECRMakeDlgProc) PROTO ((GrouP, Int4, Nlm_ChangeNotifyProc, Pointer, Uint2));
typedef DialoG  (*Nlm_SampleDialogProc) PROTO ((GrouP, Uint2));
typedef DialoG  (*Nlm_FieldListDlgProc) PROTO ((GrouP, Boolean, Nlm_ChangeNotifyProc, Pointer));
typedef DialoG  (*Nlm_SubtypeListDlgProc) PROTO ((GrouP, Boolean, SeqEntryPtr, Nlm_ChangeNotifyProc, Pointer));

#define AECR_VIB_MSG_SET_DEFAULT (NUM_VIB_MSG + 1)
#define AECR_VIB_MSG_CLEAR_TEXT  (NUM_VIB_MSG + 2)
#define AECR_VIB_MSG_AUTOPOPULATE (NUM_VIB_MSG + 3)

typedef void (*Nlm_ChangeAECRActionProc) PROTO ((DialoG, DialoG));

typedef struct applyeditconvertremove
{
  FORM_MESSAGE_BLOCK
  DialoG                   aecr_pages [NUM_AECR];
  DialoG                   accept_cancel;
  PopuP                    action_popup;
  DialoG                   constraints;
  ButtoN                   clear_constraints_on_action_change;
  Nlm_ChangeAECRActionProc change_action;
  Int4                     prev_page;
  Boolean                  crippled;
  Int4                     crippled_action;
} ApplyEditConvertRemoveData, PNTR ApplyEditConvertRemovePtr;

static void ApplyEditConvertRemoveChangeNotify (Pointer userdata)
{
  ApplyEditConvertRemovePtr mp;
  Int4                      action_choice;
  ValNodePtr                err_list;
  
  mp = (ApplyEditConvertRemovePtr) userdata;
  if (mp == NULL)
  {
    return;
  }
  
  if (mp->crippled)
  {
    action_choice = mp->crippled_action;
  }
  else
  {
    action_choice = GetValue (mp->action_popup);
  }
  
  if (action_choice > 0 && action_choice <= NUM_AECR)
  {
    err_list = TestDialog (mp->aecr_pages [action_choice - 1]);
    if (err_list != NULL)
    {
      err_list = ValNodeFree (err_list);
      DisableAcceptCancelDialogAccept (mp->accept_cancel);
    }
    else
    {
      EnableAcceptCancelDialogAccept (mp->accept_cancel);
    }
  }
  else
  {
    DisableAcceptCancelDialogAccept (mp->accept_cancel);
    return;
  }  
}


static void ApplyEditConvertRemoveClearText (Pointer userdata)
{
  ApplyEditConvertRemovePtr mp;
  FilterSetPtr              fsp;
  Int4                      i;

  mp = (ApplyEditConvertRemovePtr) userdata;
  if (mp != NULL)
  {
    for (i = 0; i < NUM_AECR; i++)
    {
      SendMessageToDialog (mp->aecr_pages[i], AECR_VIB_MSG_CLEAR_TEXT);
    }
    fsp = DialogToPointer (mp->constraints);
    FilterSetClearText (fsp);
    PointerToDialog (mp->constraints, fsp);
    FilterSetFree (fsp);
    ApplyEditConvertRemoveChangeNotify (mp);
  }
}

static void ApplyEditConvertRemoveClear (Pointer userdata)
{
  ApplyEditConvertRemovePtr mp;
  Int4                      i;

  mp = (ApplyEditConvertRemovePtr) userdata;
  if (mp != NULL)
  {  
    for (i = 0; i < NUM_AECR; i++)
    {
      PointerToDialog (mp->aecr_pages[i], NULL);
    }
    PointerToDialog (mp->constraints, NULL);    
    ApplyEditConvertRemoveChangeNotify (mp);
  }
}

static void 
ChangeApplyEditConvertRemoveAction 
(ApplyEditConvertRemovePtr mp,
 Int4                      action_choice)
{
  Int4 i;

  if (mp == NULL || action_choice < AECR_APPLY || action_choice > NUM_AECR)
  {
    return;
  }
    
  for (i = 0; i < NUM_AECR; i++)
  {
    if (i + 1 == action_choice)
    {
      Show (mp->aecr_pages[i]);
    }
    else
    {
      Hide (mp->aecr_pages[i]);
    }
  }
  
  if (GetStatus (mp->clear_constraints_on_action_change))
  {
    PointerToDialog (mp->constraints, NULL);
    PointerToDialog (mp->aecr_pages [action_choice - 1], NULL);
  }
  
  if (mp->change_action != NULL)
  {
    if (mp->prev_page < AECR_APPLY || mp->prev_page > NUM_AECR)
    {
      (mp->change_action) (NULL, mp->aecr_pages [action_choice - 1]);
    }
    else
    {
      (mp->change_action) (mp->aecr_pages [mp->prev_page - 1],
                           mp->aecr_pages [action_choice - 1]);
    }
  }
  
  mp->prev_page = action_choice;
  
  if (action_choice == AECR_APPLY)
  {
    SendMessageToDialog (mp->aecr_pages[AECR_APPLY - 1], AECR_VIB_MSG_AUTOPOPULATE);
  }
  else if (action_choice == AECR_EDIT)
  {
    SendMessageToDialog (mp->aecr_pages[AECR_EDIT - 1], AECR_VIB_MSG_AUTOPOPULATE);
  }

  ApplyEditConvertRemoveChangeNotify (mp);
}

static void ChangeApplyEditConvertRemoveActionPopup (PopuP p)
{
  ApplyEditConvertRemovePtr mp;
  Int4                      action_choice;

  mp = (ApplyEditConvertRemovePtr) GetObjectExtra (p);
  if (mp == NULL)
  {
    return;
  }
  
  action_choice = GetValue (mp->action_popup);
  ChangeApplyEditConvertRemoveAction (mp, action_choice);
}
typedef struct samplebtn
{
  Uint2                entityID;
  Nlm_SampleDialogProc make_sample_dlg;
  CharPtr              title;
} SampleBtnData, PNTR SampleBtnPtr;

static void AECRSampleButton (ButtoN b)
{
  SampleBtnPtr sbp;
  WindoW       w;
  
  sbp = GetObjectExtra (b);
  if (sbp != NULL && sbp->make_sample_dlg != NULL)
  {
    w = FixedWindow (-50, -33, -10, -10, sbp->title, StdCloseWindowProc); 
    (sbp->make_sample_dlg) (w, sbp->entityID);
    Show (w);
  }
}

static void 
ApplyEditConvertRemoveCombo 
(IteM                     i,
 Int4                     first_action_choice,
 Boolean                  crippled,
 CharPtr                  title,
 Nlm_AECRMakeDlgProc      make_dlg,
 Boolean                  show_sample,
 Nlm_SampleDialogProc     make_sample_dlg,
 Boolean                  string_constraint,
 Boolean                  source_constraint,
 Boolean                  location_constraint,
 Boolean                  cds_gene_prot_constraint,
 Boolean                  id_list_constraint,
 CharPtr                  string_constraint_label,
 Nlm_AcceptActnProc       accept_actn,
 Nlm_CancelActnProc       cancel_actn,
 Nlm_ChangeAECRActionProc change_action,
 OkToPreSample            presample_check_proc)
{
  BaseFormPtr               bfp;
  ApplyEditConvertRemovePtr mp;
  WindoW                    w;
  GrouP                     h, g1, k, n, m;
  Int4                      page_num;
  SampleBtnPtr              sbp;
  ButtoN                    b;
  DialoG                    sample_dlg = NULL;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  mp = (ApplyEditConvertRemovePtr) MemNew (sizeof (ApplyEditConvertRemoveData));
  if (mp == NULL) return;
  
  if (first_action_choice < AECR_APPLY || first_action_choice > AECR_REMOVE)
  {
    first_action_choice = AECR_APPLY;
  }
  mp->crippled = crippled;
  mp->crippled_action = first_action_choice;
  
  w = FixedWindow (-50, -33, -10, -10, title, StdCloseWindowProc); 
  SetObjectExtra (w, mp, StdCleanupExtraProc); 
  SetObjectExtra (w, mp, NULL);
  mp->form = (ForM) w;
  mp->input_entityID = bfp->input_entityID;

#ifndef WIN_MAC
  CreateStandardEditMenu (w);
#endif
  
  n = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (n, 10, 10);
  
  k = HiddenGroup (n, 2, 0, NULL);
  SetGroupSpacing (k, 10, 10);
  if (show_sample && make_sample_dlg != NULL && ! mp->crippled)
  {
    sample_dlg = make_sample_dlg (k, mp->input_entityID);
  }
  
  h = HiddenGroup (k, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);  
    
  if (!mp->crippled)
  {
    m = HiddenGroup (h, 2, 0, NULL);
    SetGroupSpacing (m, 10, 10);
  
    if (!show_sample && make_sample_dlg != NULL)
    {
      sbp = (SampleBtnPtr) MemNew (sizeof (SampleBtnData));
      sbp->entityID = mp->input_entityID;
      sbp->title = title;
      sbp->make_sample_dlg = make_sample_dlg;
      b = PushButton (m, "Show Qualifiers", AECRSampleButton);
      SetObjectExtra (b, sbp, StdCleanupExtraProc);
    }
 
    mp->action_popup = PopupList (m, TRUE, ChangeApplyEditConvertRemoveActionPopup);
    SetObjectExtra (mp->action_popup, mp, NULL);
    PopupItem (mp->action_popup, "Apply");
    PopupItem (mp->action_popup, "Edit");
    PopupItem (mp->action_popup, "Convert");
    PopupItem (mp->action_popup, "Swap");
    PopupItem (mp->action_popup, "Parse");
    PopupItem (mp->action_popup, "Remove");
    SetValue (mp->action_popup, first_action_choice);
  
    mp->change_action = change_action;
  
    mp->clear_constraints_on_action_change = CheckBox (h, "Clear when changing actions", NULL);
    SetStatus (mp->clear_constraints_on_action_change, TRUE);
  }
  
  g1 = HiddenGroup (h, 0, 0, NULL);
  for (page_num = 0; page_num < NUM_AECR; page_num++)
  {
    mp->aecr_pages [page_num] = make_dlg (g1, page_num + 1, 
                                          ApplyEditConvertRemoveChangeNotify,
                                          mp, mp->input_entityID);
  }
  AlignObjects (ALIGN_CENTER, (HANDLE) mp->aecr_pages[0],
                              (HANDLE) mp->aecr_pages[1],
                              (HANDLE) mp->aecr_pages[2],
                              (HANDLE) mp->aecr_pages[3],
                              (HANDLE) mp->aecr_pages[4],
                              (HANDLE) mp->aecr_pages[5],
                               NULL);

  if (! mp->crippled)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) m,
                                (HANDLE) mp->clear_constraints_on_action_change,
                                (HANDLE) g1,
                                (HANDLE) NULL);
    mp->constraints = FilterGroup (n, string_constraint, source_constraint, 
                                   location_constraint, cds_gene_prot_constraint,
                                   id_list_constraint,
                                   string_constraint_label);
  
  }
  
  mp->accept_cancel = AcceptCancelDialog (n, accept_actn, cancel_actn, 
                                          ApplyEditConvertRemoveClear, 
                                          ApplyEditConvertRemoveClearText, 
                                          (Pointer)mp, w);
                                          
  if (mp->crippled)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) k, (HANDLE) mp->accept_cancel, NULL);
  }
  else
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) k,
                                (HANDLE) mp->constraints,
                                (HANDLE) mp->accept_cancel,
                                NULL);
  }
                              
  mp->prev_page = -1;                              
  ChangeApplyEditConvertRemoveAction (mp, first_action_choice);
  /* initialize sample values if conditions are met */
  if (sample_dlg != NULL && (presample_check_proc == NULL || presample_check_proc(mp->input_entityID))) {
    SendMessageToDialog (sample_dlg, VIB_MSG_INIT);
  }
  Show (w);   
}


static void CountFeaturesCallback(SeqFeatPtr sfp, Pointer userdata)
{
  Int4Ptr pTotal;
  
  if (userdata == NULL || sfp == NULL) {
      return;
  }
  
  pTotal = (Int4Ptr) userdata;
  
  (*pTotal) ++;
}


static Boolean CheckFeaturesForPresample (Uint2 entityID)
{
  SeqEntryPtr sep;
  Int4        total = 0;
  
  sep = GetTopSeqEntryForEntityID(entityID);
  if (sep == NULL) {
    return FALSE;
  }
  
  VisitFeaturesInSep(sep, &total, CountFeaturesCallback);
  
  if (total > 1500) {
    return FALSE;
  } else {
    return TRUE;
  }
}


typedef struct conversionconflict 
{
  ConvertFieldPtr cfp;
  GetSamplePtr    gsp;
} ConversionConflictData, PNTR ConversionConflictPtr;

static void GetConversionConflictsFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ConversionConflictPtr ccp;
  CharPtr               src_str, dst_str;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  ccp = (ConversionConflictPtr) userdata;
  
  if (ccp->cfp == NULL
      || ccp->cfp->src_field_list == NULL 
      || ccp->cfp->dst_field_list == NULL
      || ccp->cfp->get_str_func == NULL
      || ccp->gsp == NULL)
  {
    return;
  }
  
  src_str = (ccp->cfp->get_str_func) (sfp, ccp->cfp->src_field_list, ccp->cfp->fsp);
  
  if (ccp->cfp->convert_type == CONVERT_TYPE_PARSE) 
  {
    dst_str = src_str;
    src_str = ReplaceStringForParse(src_str, ccp->cfp->text_portion);
    dst_str = MemFree (dst_str);
    if (src_str == NULL) 
    {
      return;
    }
  }
  
  if (!StringHasNoText (src_str))
  {
    dst_str = (ccp->cfp->get_str_func) (sfp, ccp->cfp->dst_field_list, ccp->cfp->fsp);
    if (!StringHasNoText (dst_str))
    {
      ccp->gsp->num_found ++;
      if (ccp->gsp->sample_text == NULL)
      {
        ccp->gsp->sample_text = dst_str;
      }
      else
      {
        if (StringCmp (dst_str, ccp->gsp->sample_text) != 0)
        {
          ccp->gsp->all_same = FALSE;
        }
        dst_str = MemFree (dst_str);
      }
    }
    src_str = MemFree (src_str);   
  }
}

static void GetConversionConflictsDescriptorCallback (SeqDescPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  ConversionConflictPtr ccp;
  CharPtr               src_str, dst_str;
  
  if (sdp == NULL || userdata == NULL)
  {
    return;
  }
  
  ccp = (ConversionConflictPtr) userdata;
  
  if (ccp->cfp == NULL
      || ccp->cfp->src_field_list == NULL 
      || ccp->cfp->dst_field_list == NULL
      || ccp->cfp->get_d_str_func == NULL
      || ccp->gsp == NULL)
  {
    return;
  }
  
  src_str = (ccp->cfp->get_d_str_func) (sdp, ccp->cfp->src_field_list, ccp->cfp->fsp);
  
  if (ccp->cfp->convert_type == CONVERT_TYPE_PARSE) 
  {
    dst_str = src_str;
    src_str = ReplaceStringForParse(src_str, ccp->cfp->text_portion);
    dst_str = MemFree (dst_str);
    if (src_str == NULL) 
    {
      return;
    }  
  }
  
  if (!StringHasNoText (src_str))
  {
    dst_str = (ccp->cfp->get_d_str_func) (sdp, ccp->cfp->dst_field_list, ccp->cfp->fsp);
    if (!StringHasNoText (dst_str))
    {
      ccp->gsp->num_found ++;
      if (ccp->gsp->sample_text == NULL)
      {
        ccp->gsp->sample_text = dst_str;
      }
      else
      {
        if (StringCmp (dst_str, ccp->gsp->sample_text) != 0)
        {
          ccp->gsp->all_same = FALSE;
        }
        dst_str = MemFree (dst_str);
      }
    }
    src_str = MemFree (src_str);   
  }
}

static GetSamplePtr CheckForConversionExistingTextInSeqEntry
(SeqEntryPtr              sep,
 ConvertFieldPtr          cfp,
 FilterSetPtr             fsp,
 Uint1                    seqfeat_choice,
 Uint1                    featdef_choice,
 Uint1                    descr_choice)
{
  ConversionConflictData ccd;
  
  ccd.cfp = cfp;
  ccd.gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
  if (ccd.gsp != NULL)
  {
    ccd.gsp->sample_text = NULL;
    ccd.gsp->fieldstring_func = NULL;
    ccd.gsp->descrstring_func = NULL;
    ccd.gsp->free_vn_proc = NULL;
    ccd.gsp->copy_vn_proc = NULL;
    ccd.gsp->requested_field = NULL;
    ccd.gsp->num_found = 0;
    ccd.gsp->all_same = TRUE;
    OperateOnSeqEntryConstrainedObjects (sep, fsp, GetConversionConflictsFeatureCallback, 
                                         GetConversionConflictsDescriptorCallback,
                                         seqfeat_choice, featdef_choice, descr_choice, &ccd);
  }
  return ccd.gsp;  
}

NLM_EXTERN void 
AddEditApplyDataToApplyValue 
(Int4 action_choice,
 EditApplyPtr edit_apply, 
 ApplyValuePtr avp)
{
  if (avp == NULL)
  {
    return;
  }
  
  if (edit_apply == NULL)
  {
    avp->text_to_replace = NULL;
    avp->new_text = NULL;
    avp->where_to_replace = EditApplyFindLocation_anywhere;

    return;
  }

  if (action_choice == AECR_EDIT)
  {
    avp->text_to_replace = StringSave (edit_apply->find_txt);
    avp->new_text = StringSave (edit_apply->repl_txt);
    avp->where_to_replace = edit_apply->find_location;
  }
  else if (action_choice == AECR_APPLY)
  {
    avp->new_text = StringSave (edit_apply->apply_txt);
    avp->text_to_replace = NULL; 
    avp->where_to_replace = EditApplyFindLocation_anywhere;
  }
  else
  {
    avp->text_to_replace = NULL;
    avp->new_text = NULL;
    avp->where_to_replace = EditApplyFindLocation_anywhere;
  } 
}

#define AECR_DATA_BLOCK \
  ValNodePtr      field_list;           \
  ValNodePtr      field_list_to;        \
  ValNodePtr      subtype_list;         \
  Boolean         leave_on_original;    \
  Boolean         strip_name_from_text; \
  EditApplyPtr    edit_apply;           \
  TextPortionXPtr  text_portion;         \
  Boolean         remove_parsed;        \
  FreeValNodeProc free_field_vn_proc;   \
  FreeValNodeProc free_subtype_vn_proc;

typedef struct simpleaecr
{
  AECR_DATA_BLOCK
} SimpleAECRData, PNTR SimpleAECRPtr;

static void AECRDataBlockFreeContents (SimpleAECRPtr sp)
{
  if (sp != NULL)
  {
    sp->field_list = ValNodeFuncFree (sp->field_list, sp->free_field_vn_proc);
    sp->field_list_to = ValNodeFuncFree (sp->field_list_to, sp->free_field_vn_proc);
    sp->subtype_list = ValNodeFuncFree (sp->subtype_list, sp->free_subtype_vn_proc);
    sp->edit_apply = EditApplyFree (sp->edit_apply);
    sp->text_portion = TextPortionXFree(sp->text_portion);
  }
}

static SimpleAECRPtr SimpleAECRFree (SimpleAECRPtr sp)
{
  if (sp != NULL)
  {
    AECRDataBlockFreeContents (sp);
    sp = MemFree (sp);
  }
  return sp;
}

/* This function is used to autopopulate the new_text and find fields */
typedef CharPtr (*GetAutoPopulateTextFunc) PROTO ((ValNodePtr, ValNodePtr, Uint2));

#define AECR_BLOCK \
  DIALOG_MESSAGE_BLOCK                                \
  DialoG          field_list;                         \
  DialoG          field_list_to;                      \
  DialoG          subtype_list;                       \
  ButtoN          leave_on_original;                  \
  ButtoN          strip_name_from_text;               \
  DialoG          edit_apply;                         \
  DialoG          text_portion;                       \
  ButtoN          remove_parsed;                      \
  Int4            action_choice;                      \
  /* These are necessary to produce the SimpleAECR structure for output */ \
  FreeValNodeProc free_field_vn_proc;                 \
  FreeValNodeProc free_subtype_vn_proc;               \
  /* These are used for autopopulating text fields */ \
  GetAutoPopulateTextFunc  get_autopopulate_text;     \
  Nlm_ChangeNotifyProc     change_notify;             \
  Pointer                  change_userdata;           \
  Uint2                    entityID;

typedef struct simpleaecrdlg
{
  AECR_BLOCK
} SimpleAECRDlgData, PNTR SimpleAECRDlgPtr;

static void ClearTextSimpleAECRDlg (SimpleAECRDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->edit_apply, NULL);
    PointerToDialog (dlg->text_portion, NULL);
  }
}

static void ResetSimpleAECRDlg (SimpleAECRDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->field_list, NULL);
    PointerToDialog (dlg->field_list_to, NULL);
    PointerToDialog (dlg->subtype_list, NULL);
    PointerToDialog (dlg->text_portion, NULL);
    SafeSetStatus (dlg->leave_on_original, FALSE);
    SafeSetStatus (dlg->strip_name_from_text, FALSE);
    SafeSetStatus (dlg->remove_parsed, FALSE);
    ClearTextSimpleAECRDlg (dlg);
  }
}

static void SimpleAECRToDialog (DialoG d, Pointer userdata)
{
  SimpleAECRDlgPtr dlg;
  SimpleAECRPtr    data;
  
  dlg = (SimpleAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetSimpleAECRDlg (dlg);
  data = (SimpleAECRPtr) userdata;
  if (data != NULL)
  {
    PointerToDialog (dlg->field_list, data->field_list);
    PointerToDialog (dlg->field_list_to, data->field_list_to);
    SendMessageToDialog (dlg->field_list_to, NUM_VIB_MSG + 1);
    PointerToDialog (dlg->subtype_list, data->subtype_list);
    PointerToDialog (dlg->edit_apply, data->edit_apply);
    PointerToDialog (dlg->text_portion, data->text_portion);
    SafeSetValue (dlg->leave_on_original, data->leave_on_original);
    SafeSetValue (dlg->strip_name_from_text, data->strip_name_from_text);
    SafeSetStatus (dlg->remove_parsed, data->remove_parsed);
    dlg->free_field_vn_proc = data->free_field_vn_proc;
    dlg->free_subtype_vn_proc = data->free_subtype_vn_proc;
  }
  else
  {
    SendMessageToDialog (dlg->field_list, NUM_VIB_MSG + 1);
    SendMessageToDialog (dlg->field_list_to, NUM_VIB_MSG + 1);
  }
}

static Pointer DialogToSimpleAECR (DialoG d)
{
  SimpleAECRDlgPtr dlg;
  SimpleAECRPtr    data;
  
  dlg = (SimpleAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  data = (SimpleAECRPtr) MemNew (sizeof (SimpleAECRData));
  if (data != NULL)
  {
    data->field_list = DialogToPointer (dlg->field_list);
    data->field_list_to = DialogToPointer (dlg->field_list_to);
    data->subtype_list = DialogToPointer (dlg->subtype_list);
    data->edit_apply = DialogToPointer (dlg->edit_apply);
    data->text_portion = DialogToPointer (dlg->text_portion);
    if (dlg->remove_parsed == NULL) {
      data->remove_parsed = FALSE;
    } 
    else
    {
      data->remove_parsed = GetStatus (dlg->remove_parsed);
    }
    if (dlg->leave_on_original != NULL)
    {
      data->leave_on_original = GetStatus (dlg->leave_on_original);
    }
    else
    {
      data->leave_on_original = FALSE;
    }
    if (dlg->strip_name_from_text != NULL)
    {
      data->strip_name_from_text = GetStatus (dlg->strip_name_from_text);
    }
    else
    {
      data->strip_name_from_text = FALSE;
    }
    data->free_field_vn_proc = dlg->free_field_vn_proc;
    data->free_subtype_vn_proc = dlg->free_subtype_vn_proc;
  }
  return data;
}

static void SimpleAECRDialogChangeNotify (Pointer userdata)
{
  SimpleAECRDlgPtr dlg;
  CharPtr          autopopulate_text;
  EditApplyPtr     eap;
  ValNodePtr       subtype_list, field_list;

  dlg = (SimpleAECRDlgPtr) userdata;
  if (dlg == NULL)
  { 
    return;
  }
  
  if (dlg->get_autopopulate_text != NULL
      && (dlg->action_choice == AECR_APPLY || dlg->action_choice == AECR_EDIT))
  {
    eap = DialogToPointer (dlg->edit_apply);
    subtype_list = DialogToPointer (dlg->subtype_list);
    field_list = DialogToPointer (dlg->field_list);
    autopopulate_text = (dlg->get_autopopulate_text) (subtype_list, field_list, dlg->entityID);
    subtype_list = ValNodeFuncFree (subtype_list, dlg->free_subtype_vn_proc);
    field_list = ValNodeFuncFree (field_list, dlg->free_field_vn_proc);
    if (dlg->action_choice == AECR_APPLY)
    {
      eap->apply_txt = MemFree (eap->find_txt);
      eap->apply_txt = autopopulate_text;
    }
    else
    {
      eap->find_txt = MemFree (eap->find_txt);
      eap->find_txt = autopopulate_text;
    }
    PointerToDialog (dlg->edit_apply, eap);
    EditApplyFree (eap);
  }
  
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static void SimpleAECRMessage (DialoG d, Int2 mssg)

{
  SimpleAECRDlgPtr  dlg;

  dlg = (SimpleAECRDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) 
    {
      case VIB_MSG_INIT :
        /* reset list */
        ResetSimpleAECRDlg (dlg);
        break;
      case VIB_MSG_ENTER :
        if (dlg->subtype_list != NULL)
        {
          Select (dlg->subtype_list);
        }
        else
        {
          Select (dlg->field_list);
        }
        break;
      case AECR_VIB_MSG_SET_DEFAULT :
        SendMessageToDialog (dlg->field_list, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->field_list_to, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->subtype_list, AECR_VIB_MSG_SET_DEFAULT);
        ClearTextSimpleAECRDlg (dlg);
        break;
      case AECR_VIB_MSG_AUTOPOPULATE:
        SimpleAECRDialogChangeNotify (dlg);
        break;
      case AECR_VIB_MSG_CLEAR_TEXT :
        ClearTextSimpleAECRDlg (dlg);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestSimpleAECR (DialoG d)
{
  SimpleAECRDlgPtr dlg;
  ValNodePtr       err_list = NULL;
  ValNodePtr       total_err_list = NULL;
  
  dlg = (SimpleAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return FALSE;
  }

  total_err_list = TestDialog (dlg->field_list);
  
  if (dlg->subtype_list != NULL)
  {
    err_list = TestDialog (dlg->subtype_list);
    total_err_list = ValNodeAppend (total_err_list, err_list);
  }
    
  err_list = NULL;  
  switch (dlg->action_choice)
  {
    case AECR_CONVERT:
    case AECR_SWAP:
      err_list = TestDialog (dlg->field_list_to);
      break;
    case AECR_APPLY:
    case AECR_EDIT:
      err_list = TestDialog (dlg->edit_apply);
      break;
  }

  total_err_list = ValNodeAppend (total_err_list, err_list);
    
  return total_err_list;
}

static void SimpleAECRButtonChange (ButtoN b)
{
  SimpleAECRDlgPtr dlg;

  dlg = (SimpleAECRDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

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

static DialoG SimpleAECRDialogEx
(GrouP                    h,
 Int4                     action_choice, 
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 FreeValNodeProc          free_field_vn_proc,
 FreeValNodeProc          free_subtype_vn_proc,
 Nlm_FieldListDlgProc     fieldlist_dlg_proc,
 Nlm_SubtypeListDlgProc   subtypelist_dlg_proc,
 GetAutoPopulateTextFunc  get_autopopulate_text,
 CharPtr                  field_label,
 CharPtr                  subtype_label,
 Boolean                  is_text,
 Boolean                  strip_name,
 Uint2                    entityID)
{
  SimpleAECRDlgPtr dlg;
  GrouP            p, g1;
  SeqEntryPtr      sep;
  
  dlg = (SimpleAECRDlgPtr) MemNew (sizeof (SimpleAECRDlgData));
  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 = SimpleAECRToDialog;
  dlg->fromdialog = DialogToSimpleAECR;
  dlg->dialogmessage = SimpleAECRMessage;
  dlg->testdialog = TestSimpleAECR;
  dlg->action_choice = action_choice;
  dlg->free_field_vn_proc = free_field_vn_proc;
  dlg->free_subtype_vn_proc = free_subtype_vn_proc;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->get_autopopulate_text = get_autopopulate_text;
  dlg->entityID = entityID;
  dlg->strip_name_from_text = NULL;
  
  dlg->subtype_list = NULL;
  
  sep = GetTopSeqEntryForEntityID(entityID);
  
  if (action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE)
  {
    if (subtypelist_dlg_proc == NULL)
    {
      g1 = HiddenGroup (p, 2, 0, NULL);
    }
    else
    {
      g1 = HiddenGroup (p, 3, 0, NULL);
      StaticPrompt (g1, subtype_label, 0, dialogTextHeight, systemFont, 'l');
    }
    StaticPrompt (g1, "From", 0, dialogTextHeight, systemFont, 'l');
    StaticPrompt (g1, "To", 0, dialogTextHeight, systemFont, 'l');
    
    if (subtypelist_dlg_proc != NULL)
    {
      dlg->subtype_list = (subtypelist_dlg_proc) (g1, TRUE, sep, change_notify, change_userdata);
    }
    dlg->field_list = fieldlist_dlg_proc (g1, FALSE, 
                                          change_notify, 
                                          change_userdata);
    dlg->field_list_to = fieldlist_dlg_proc (g1, FALSE, 
                                             change_notify, 
                                             change_userdata);
    if (action_choice == AECR_CONVERT && is_text)
    {
      dlg->leave_on_original = CheckBox (p, "Leave on original", SimpleAECRButtonChange);
      SetObjectExtra (dlg->leave_on_original, dlg, NULL);
    }
    if (strip_name)
    {
      dlg->strip_name_from_text = CheckBox (p, "Strip name from text", SimpleAECRButtonChange);
      SetObjectExtra (dlg->strip_name_from_text, dlg, NULL);
    }
  }
  else
  {
    if (subtypelist_dlg_proc == NULL)
    {
      g1 = HiddenGroup (p, 1, 0, NULL);
      
    }
    else
    {
      g1 = HiddenGroup (p, 2, 0, NULL);
      StaticPrompt (g1, subtype_label, 0, dialogTextHeight, systemFont, 'l');
    }
    StaticPrompt (g1, field_label, 0, dialogTextHeight, systemFont, 'l');
    if (action_choice == AECR_REMOVE)
    {
      if (subtypelist_dlg_proc != NULL)
      {
        dlg->subtype_list = (subtypelist_dlg_proc) (g1, TRUE, sep, change_notify, change_userdata);
      }
      dlg->field_list = fieldlist_dlg_proc (g1, TRUE, 
                                            change_notify, 
                                            change_userdata);
    }
    else
    {
      if (subtypelist_dlg_proc != NULL)
      {
        dlg->subtype_list = (subtypelist_dlg_proc) (g1, TRUE, sep,
                                                    SimpleAECRDialogChangeNotify,
                                                    dlg);
      }
      dlg->field_list = fieldlist_dlg_proc (g1, FALSE, 
                                            SimpleAECRDialogChangeNotify, 
                                            dlg);
    }
  }
  
  if (is_text && (action_choice == AECR_APPLY || action_choice == AECR_EDIT))
  {
    dlg->edit_apply = EditApplyDialog (p, 
                                       action_choice == AECR_APPLY ? eEditApplyChoice_Apply : eEditApplyChoice_Edit,
                                       "New Text", NULL,
                                       change_notify, change_userdata);
    dlg->text_portion = NULL;
    dlg->remove_parsed = NULL;
  }
  else if (action_choice == AECR_PARSE)
  {
    dlg->edit_apply = NULL;
    if (is_text) {
        dlg->text_portion = TextPortionXDialog(p);
    }
    dlg->remove_parsed = CheckBox (p, "Remove from parsed field", SimpleAECRButtonChange);
    SetObjectExtra (dlg->remove_parsed, dlg, NULL);
  }
  else
  {
    dlg->edit_apply = NULL;
    dlg->text_portion = NULL;
    dlg->remove_parsed = NULL;
  }

  if (action_choice == AECR_CONVERT)
  {
    if (is_text)
    {
      AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->leave_on_original, (HANDLE) dlg->strip_name_from_text, NULL);
    }
    else
    {
      AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->strip_name_from_text, NULL);
    }
  }
  else if (action_choice == AECR_SWAP)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->strip_name_from_text, NULL);
  }
  else if (action_choice == AECR_PARSE)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->text_portion, (HANDLE) dlg->remove_parsed, NULL);
  }
  else
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->edit_apply, NULL);
  }
  return (DialoG) p;
}

static DialoG SimpleAECRDialog
(GrouP                    h,
 Int4                     action_choice, 
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 FreeValNodeProc          free_field_vn_proc,
 FreeValNodeProc          free_subtype_vn_proc,
 Nlm_FieldListDlgProc     fieldlist_dlg_proc,
 Nlm_SubtypeListDlgProc   subtypelist_dlg_proc,
 GetAutoPopulateTextFunc  get_autopopulate_text,
 CharPtr                  field_label,
 CharPtr                  subtype_label,
 Boolean                  is_text,
 Uint2                    entityID)
{
  return SimpleAECRDialogEx(h, action_choice, change_notify, change_userdata,
                            free_field_vn_proc, free_subtype_vn_proc, 
                            fieldlist_dlg_proc, subtypelist_dlg_proc,
                            get_autopopulate_text,
                            field_label, subtype_label,
                            is_text, FALSE, entityID);
}

static CharPtr 
CDSGeneProtAutopopulate 
(ValNodePtr subtype_list,
 ValNodePtr field_list, 
 Uint2 entityID)
{
  GetSamplePtr gsp;
  CharPtr      rval;
  
  gsp = CheckForExistingText (entityID, field_list, 
                              GetCDSGeneProtField,
                              NULL,
                              NULL,
                              IntValNodeCopy,
                              NULL,
                              0, 0, 0);
  rval = gsp->sample_text;
  gsp->sample_text = NULL;
  GetSampleFree (gsp);
  return rval;
}

typedef struct cdsgeneprotaecrdlg
{
  AECR_BLOCK
  ButtoN also_mrna;
} CDSGeneProtAECRDlgData, PNTR CDSGeneProtAECRDlgPtr;

typedef struct cdsgeneprotaecr 
{
  AECR_DATA_BLOCK
  Boolean also_mrna;
} CDSGeneProtAECRData, PNTR CDSGeneProtAECRPtr;


static CDSGeneProtAECRPtr CDSGeneProtAECRFree (CDSGeneProtAECRPtr sp)
{
  if (sp != NULL)
  {
    AECRDataBlockFreeContents ((SimpleAECRPtr) sp);
    sp = MemFree (sp);
  }
  return sp;
}


static void PointerToCDSGeneProtAECRDialog (DialoG d, Pointer userdata)
{
  CDSGeneProtAECRDlgPtr dlg;
  CDSGeneProtAECRPtr    data;

  dlg = (CDSGeneProtAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  data = (CDSGeneProtAECRPtr) userdata;
  SimpleAECRToDialog (d, userdata);
  if (data != NULL)
  {
    SetStatus (dlg->also_mrna, data->also_mrna);
  }
  
}


static Boolean ShowingProteinName (CDSGeneProtAECRDlgPtr dlg)
{
  ValNodePtr vnp_from, vnp_to;
  Int4       protein_name_field_num = 2 + num_gene_fields + num_mrna_fields;
  Boolean    rval = FALSE;

  if (dlg == NULL) return FALSE;

  vnp_from = DialogToPointer (dlg->field_list);
  vnp_to = DialogToPointer (dlg->field_list_to);

  if ((vnp_from != NULL && vnp_from->data.intvalue == protein_name_field_num)
      || (vnp_to != NULL && vnp_to->data.intvalue == protein_name_field_num))
  {
    rval = TRUE;
  }
  vnp_from = ValNodeFree (vnp_from);
  vnp_to = ValNodeFree (vnp_to);

  return rval;
}


static Pointer CDSGeneProtAECRDialogToPointer (DialoG d)
{
  CDSGeneProtAECRDlgPtr dlg;
  SimpleAECRPtr         subset;
  CDSGeneProtAECRPtr    data = NULL;

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

  subset = DialogToSimpleAECR (d);
  if (subset != NULL)
  {
    data = (CDSGeneProtAECRPtr) MemNew (sizeof (CDSGeneProtAECRData));
    MemCpy (data, subset, sizeof (SimpleAECRData));
    MemSet (subset, 0, sizeof (SimpleAECRData));
    subset = MemFree (subset);
    if (ShowingProteinName (dlg))
    {
      data->also_mrna = GetStatus (dlg->also_mrna);
    }
    else
    {
      data->also_mrna = FALSE;
    }
  }
  return data;
}


static void CDSGeneProtAECRDialogChange (Pointer data)
{
  CDSGeneProtAECRDlgPtr dlg;

  dlg = (CDSGeneProtAECRDlgPtr) data;
  if (dlg == NULL) return;
  
  if (ShowingProteinName (dlg))
  {
    Show (dlg->also_mrna);
  }
  else
  {
    Hide (dlg->also_mrna);
  }

  SimpleAECRDialogChangeNotify (dlg);
}


static DialoG CDSGeneProtAECRDialog
(GrouP                    h,
 Int4                     action_choice, 
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  CDSGeneProtAECRDlgPtr dlg;
  GrouP                 p, g1;
  SeqEntryPtr           sep;
  
  dlg = (CDSGeneProtAECRDlgPtr) MemNew (sizeof (CDSGeneProtAECRDlgData));
  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 = PointerToCDSGeneProtAECRDialog;
  dlg->fromdialog = CDSGeneProtAECRDialogToPointer;
  dlg->dialogmessage = SimpleAECRMessage;
  dlg->testdialog = TestSimpleAECR;
  dlg->action_choice = action_choice;
  dlg->free_field_vn_proc = NULL;
  dlg->free_subtype_vn_proc = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->get_autopopulate_text = CDSGeneProtAutopopulate;
  dlg->entityID = entityID;
  dlg->strip_name_from_text = NULL;
  
  dlg->subtype_list = NULL;
  
  sep = GetTopSeqEntryForEntityID(entityID);
  
  if (action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE)
  {
    g1 = HiddenGroup (p, 2, 0, NULL);
    StaticPrompt (g1, "From", 0, dialogTextHeight, systemFont, 'l');
    StaticPrompt (g1, "To", 0, dialogTextHeight, systemFont, 'l');
    
    dlg->field_list = CDSGeneProtFieldSelectionDialog (g1, FALSE, 
                                          CDSGeneProtAECRDialogChange, 
                                          dlg);
    dlg->field_list_to = CDSGeneProtFieldSelectionDialog (g1, FALSE, 
                                             CDSGeneProtAECRDialogChange, 
                                             dlg);
    if (action_choice == AECR_CONVERT)
    {
      dlg->leave_on_original = CheckBox (p, "Leave on original", NULL);
    }
  }
  else
  {
    g1 = HiddenGroup (p, 1, 0, NULL);
    StaticPrompt (g1, "Field", 0, dialogTextHeight, systemFont, 'l');
    if (action_choice == AECR_REMOVE)
    {
      dlg->field_list = CDSGeneProtFieldSelectionDialog (g1, TRUE, 
                                            CDSGeneProtAECRDialogChange, 
                                            dlg);
    }
    else
    {
      dlg->field_list = CDSGeneProtFieldSelectionDialog (g1, FALSE, 
                                            CDSGeneProtAECRDialogChange, 
                                            dlg);
    }
  }
  
  if (action_choice == AECR_APPLY || action_choice == AECR_EDIT)
  {
    dlg->edit_apply = EditApplyDialog (p, 
                                       action_choice == AECR_APPLY ? eEditApplyChoice_Apply : eEditApplyChoice_Edit,
                                       "New Text", NULL,
                                       change_notify, change_userdata);
    dlg->text_portion = NULL;
    dlg->remove_parsed = NULL;
  }
  else if (action_choice == AECR_PARSE)
  {
    dlg->edit_apply = NULL;
    dlg->text_portion = TextPortionXDialog(p);
    dlg->remove_parsed = CheckBox (p, "Remove from parsed field", NULL);
  }
  else
  {
    dlg->edit_apply = NULL;
    dlg->text_portion = NULL;
    dlg->remove_parsed = NULL;
  }

  dlg->also_mrna = CheckBox (p, "Make mRNA product match CDS protein name", NULL);

  if (action_choice == AECR_CONVERT)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->leave_on_original, (HANDLE) dlg->also_mrna, NULL);
  }
  else if (action_choice == AECR_SWAP)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->strip_name_from_text, (HANDLE) dlg->also_mrna, NULL);
  }
  else if (action_choice == AECR_PARSE)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->text_portion, (HANDLE) dlg->remove_parsed, (HANDLE) dlg->also_mrna, NULL);
  }
  else
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->edit_apply, (HANDLE) dlg->also_mrna, NULL);
  }
  return (DialoG) p;
}


static void AddStringToSample (CharPtr str, GetSamplePtr gsp)
{
  if (gsp == NULL) return;

  if (str != NULL)
  {
    gsp->num_found ++;
  }
  if (gsp->sample_text == NULL)
  {
    gsp->sample_text = StringSave (str);
  }
  else
  {
    if (StringCmp (str, gsp->sample_text) != 0)
    {
      gsp->all_same = FALSE;
    }
  }
}

static GetSamplePtr 
GetCDSGeneProtAECRSample 
(ValNodePtr cdset_list,
 ValNodePtr field_list, 
 ValNodePtr field_list_to,
 FilterSetPtr fsp)
{
  GetSamplePtr gsp;
  ValNodePtr   cdset_vnp, sfp_vnp;
  CharPtr      str;
  CDSetPtr       cdsp;
 
  gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
  if (gsp == NULL)
  {
    return NULL;
  }
  gsp->free_vn_proc = NULL;
  gsp->copy_vn_proc = IntValNodeCopy;
  for (cdset_vnp = cdset_list; cdset_vnp != NULL; cdset_vnp = cdset_vnp->next)
  {
    cdsp = (CDSetPtr) cdset_vnp->data.ptrvalue;
    if (IsCDSetMatPeptideQualChoice(field_list) || IsCDSetMatPeptideQualChoice(field_list_to))
    {
      /* need to look at all mat_peptide features */
      for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
      {
        if (fsp != NULL && fsp->cgp != NULL && DoesConstraintDisqualifyFeature (sfp_vnp->data.ptrvalue, fsp->cgp))
        {
          /* this feature will be skipped in the action, so it should not be included in the sample */
        }
        else
        {
          str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, field_list, fsp);
          if (str != NULL && field_list_to != NULL)
          {
            str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, field_list_to, fsp);
          }
          AddStringToSample (str, gsp);     
          str = MemFree (str);
        }
      }
    } else {
      str = GetCDSetField (cdsp, field_list);
      if (str != NULL && field_list_to != NULL)
      {
        str = MemFree (str);
        str = GetCDSetField (cdset_vnp->data.ptrvalue, field_list_to);
      }
      AddStringToSample (str, gsp);     
      str = MemFree (str);
    }
  }
  return gsp;
}

static Boolean FeatureTypeMissingFromCDSet (CDSetPtr cdsp, Uint2 choice)
{
  ValNodePtr vnp;
  SeqFeatPtr sfp;
  Boolean    is_missing = TRUE;

  if (cdsp == NULL) return TRUE;
  switch (choice)
  {
    case FEATDEF_CDS:
      if (cdsp->cds_list != NULL)
      {
        is_missing = FALSE;
      }
      break;
    case FEATDEF_GENE:
      if (cdsp->gene_list != NULL)
      {
        is_missing = FALSE;
      }
      break;
    case FEATDEF_mRNA:
      if (cdsp->mrna_list != NULL)
      {
        is_missing = FALSE;
      }
      break;
    case FEATDEF_PROT:
    case FEATDEF_mat_peptide_aa:
      for (vnp = cdsp->prot_list; vnp != NULL; vnp = vnp->next)
      {
        sfp = (SeqFeatPtr) vnp->data.ptrvalue;
        if (sfp != NULL && sfp->idx.subtype == choice)
        {
          is_missing = FALSE;
        }
      }
      break;
  }
  return is_missing;
}

static Int4 
CountMissingFeatures 
(ValNodePtr cdset_list,
 ValNodePtr field_list, 
 ValNodePtr field_list_to,
 FilterSetPtr fsp)
{
  ValNodePtr   cdset_vnp, sfp_vnp;
  CharPtr      str;
  CDSetPtr     cdsp;
  Uint2        choice_from, choice_to;
  Int4         num_missing = 0;

  choice_from = FeatDefTypeFromFieldList (field_list);
  choice_to = FeatDefTypeFromFieldList (field_list_to);

  if (choice_from == 0 || choice_to == 0 || choice_from == choice_to)
  {
    return 0;
  }

  for (cdset_vnp = cdset_list; cdset_vnp != NULL; cdset_vnp = cdset_vnp->next)
  {
    cdsp = (CDSetPtr) cdset_vnp->data.ptrvalue;

    if (choice_from == FEATDEF_mat_peptide_aa)
    {
      /* need to look at all mat_peptide features */
      for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
      {
        if (fsp != NULL && fsp->cgp != NULL && DoesConstraintDisqualifyFeature (sfp_vnp->data.ptrvalue, fsp->cgp))
        {
          /* this feature will be skipped in the action, so it doesn't count towards missing sets */
        }
        else
        {
          str = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, field_list, fsp);
          if (str != NULL)
          {
            if (FeatureTypeMissingFromCDSet (cdsp, choice_to)) 
            {
              num_missing++;
            }
          }
          str = MemFree (str);
        }
      }
    } else {
      str = GetCDSetField (cdsp, field_list);
      if (str != NULL)
      {
        if (FeatureTypeMissingFromCDSet (cdsp, choice_to)) 
        {
          num_missing++;
        }
      }
      str = MemFree (str);
    }
  }
  return num_missing;
}


/* things get tricksy when we're dealing with mat_peptides, because there
 * can be more than one of them in the CDS-mRNA-Gene-Prot group.
 */

static Boolean CDSGeneProtApplyToAllThatMatchFilter 
(CharPtr         str_src,
 CDSetPtr        cdsp,
 ValNodePtr      field_list_to,
 FilterSetPtr    fsp,
 ExistingTextPtr etp)
{
  ApplyValueData avd;

  avd.where_to_replace = EditApplyFindLocation_anywhere;

  /* build gene if we're applying to gene but gene doesn't already exist */
  BuildNewCDSGeneProtFeature (cdsp, field_list_to, NULL);
  avd.field_list = NULL;
  avd.new_text = str_src;
  avd.text_to_replace = NULL;
  avd.etp = etp;
          
  return SetCDSetField (cdsp, field_list_to, &avd, fsp);
}

static Boolean CDSGeneProtApplyToOneFeature
(CharPtr         str_src,
 SeqFeatPtr      sfp,
 ValNodePtr      field_list_to,
 FilterSetPtr    fsp,
 ExistingTextPtr etp)
{
  ApplyValueData avd;

  avd.where_to_replace = EditApplyFindLocation_anywhere;

  avd.field_list = NULL;
  avd.new_text = str_src;
  avd.text_to_replace = NULL;
  avd.etp = etp;
          
  return SetCDSGeneProtField (sfp, field_list_to, &avd, fsp);
}


static void CopyProteinNameTomRNAName (CDSetPtr cdsp)
{
  Int4    protein_name_field_num = 2 + num_gene_fields + num_mrna_fields;
  Int4    mrna_product_field_num = 2 + num_gene_fields;
  ValNode vn_src, vn_dst;
  ApplyValueData avd;

  vn_src.data.intvalue = protein_name_field_num;
  vn_src.next = NULL;
  vn_src.choice = 0;

  vn_dst.data.intvalue = mrna_product_field_num;
  vn_dst.next = NULL;
  vn_dst.choice = 0;

  avd.new_text = GetCDSetField (cdsp, &vn_src);
  avd.etp = NULL;
  avd.field_list = NULL;
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;
  SetCDSetField (cdsp, &vn_dst, &avd, NULL);
}

static void CDSGeneProtConvert 
(CDSetPtr        cdsp,
 CDSGeneProtAECRPtr   sp,
 FilterSetPtr    fsp,
 ExistingTextPtr etp,
 Boolean         build_missing)
{
  CharPtr        str_src = NULL;
  CharPtr        str_dst = NULL;
  ValNodePtr     sfp_vnp;
  SeqFeatPtr     sfp;
  Boolean        able_to_apply = FALSE;
  Boolean        change_made = FALSE;

  if (IsCDSetMatPeptideQualChoice(sp->field_list)) 
  {
    for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
    {
      sfp = sfp_vnp->data.ptrvalue;
      able_to_apply = FALSE;
      if (fsp == NULL || !DoesConstraintDisqualifyFeature(sfp, fsp->cgp)) {
        str_src = GetCDSGeneProtField (sfp_vnp->data.ptrvalue, sp->field_list, NULL);
        if (str_src != NULL)
        {
          if (IsCDSetMatPeptideQualChoice (sp->field_list_to)) 
          {
            CDSGeneProtApplyToOneFeature (str_src, sfp, sp->field_list_to, fsp, etp);
            change_made = TRUE;
          } 
          else 
          {
            if (build_missing)
            {
              BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
            }
            able_to_apply = CDSGeneProtApplyToAllThatMatchFilter (str_src, cdsp, sp->field_list_to, fsp, etp);
            if (able_to_apply)
            {
              change_made = TRUE;
            }
          }
          if (!sp->leave_on_original && able_to_apply)
          {
            sp->field_list->choice = 1;
            RemoveCDSGeneProtField (sfp, sp->field_list, fsp);
            change_made = TRUE;
          }
        }
      }
    }    
  }
  else 
  {
    str_src = GetCDSetField (cdsp, sp->field_list);
    if (str_src != NULL)
    {
      if (build_missing)
      {
        BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
      }
      able_to_apply = CDSGeneProtApplyToAllThatMatchFilter (str_src, cdsp, sp->field_list_to, fsp, etp);
      if (!sp->leave_on_original && able_to_apply)
      {
        sp->field_list->choice = 1;
        RemoveCDSetField (cdsp, sp->field_list, fsp);
      }
      if (able_to_apply)
      {
        change_made = TRUE;
      }
    }   
  }
  if (sp->also_mrna && change_made)
  {
    CopyProteinNameTomRNAName (cdsp);
  }

}

static void CDSGeneProtSwap
(CDSetPtr        cdsp,
 CDSGeneProtAECRPtr   sp,
 FilterSetPtr    fsp,
 ExistingTextPtr etp,
 Boolean         build_missing)
{
  CharPtr        str_src = NULL;
  CharPtr        str_dst = NULL;
  ValNodePtr     sfp_vnp_src, sfp_vnp_dst, vnp_swap;
  SeqFeatPtr     sfp_src, sfp_dst;
  ApplyValueData avd;
  Uint2          choice_to, choice_from;
  Boolean        change_made = FALSE;

  avd.where_to_replace = EditApplyFindLocation_anywhere;

  choice_from = FeatDefTypeFromFieldList (sp->field_list);
  choice_to = FeatDefTypeFromFieldList (sp->field_list_to);

  if (choice_from == FEATDEF_mat_peptide_aa || choice_to == FEATDEF_mat_peptide_aa)
  {
    /* make sure mat_peptide qual if in field_list */
    if (!IsCDSetMatPeptideQualChoice(sp->field_list)) 
    {
      vnp_swap = sp->field_list;
      sp->field_list = sp->field_list_to;
      sp->field_list_to = vnp_swap;
    }
    for (sfp_vnp_src = cdsp->prot_list; sfp_vnp_src != NULL; sfp_vnp_src = sfp_vnp_src->next)
    {
      sfp_src = sfp_vnp_src->data.ptrvalue;
      if (fsp == NULL || !DoesConstraintDisqualifyFeature(sfp_src, fsp->cgp)) 
      {
        str_src = GetCDSGeneProtField (sfp_src, sp->field_list, NULL);
        if (IsCDSetMatPeptideQualChoice(sp->field_list_to)) 
        {
          for (sfp_vnp_dst = cdsp->prot_list; sfp_vnp_dst != NULL; sfp_vnp_dst = sfp_vnp_dst->next)
          {
            sfp_dst = sfp_vnp_dst->data.ptrvalue;
            if (fsp == NULL || !DoesConstraintDisqualifyFeature(sfp_dst, fsp->cgp))
            {
              str_dst = GetCDSGeneProtField (sfp_dst, sp->field_list_to, NULL);
              if (!StringHasNoText (str_src) || !StringHasNoText (str_dst))
              {
                if (build_missing)
                {
                  BuildNewCDSGeneProtFeature (cdsp, sp->field_list, sp->field_list_to);
                  BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
                }
                if (!FeatureTypeMissingFromCDSet(cdsp, choice_from) 
                    && !FeatureTypeMissingFromCDSet(cdsp, choice_to))
                {
                  avd.etp = NULL;
                  avd.text_to_replace = NULL;
                  avd.new_text = StringSave (str_src);
                  avd.field_list = NULL;
                  SetCDSGeneProtField (sfp_dst, sp->field_list_to, &avd, fsp);
                  avd.new_text = StringSave (str_dst);
                  SetCDSGeneProtField (sfp_src, sp->field_list, &avd, fsp);
                  change_made = TRUE;
                }
              }
              str_dst = MemFree (str_dst);
            }
          }
        } 
        else
        {
          str_dst = GetCDSetField (cdsp, sp->field_list_to);
          if (!StringHasNoText (str_src) || !StringHasNoText (str_dst))
          {
            if (build_missing) 
            {
              BuildNewCDSGeneProtFeature (cdsp, sp->field_list, sp->field_list_to);
              BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
            }
            if (!FeatureTypeMissingFromCDSet(cdsp, choice_from)
                && !FeatureTypeMissingFromCDSet(cdsp, choice_to))
            {
              avd.etp = NULL;
              avd.text_to_replace = NULL;
              avd.new_text = StringSave (str_src);
              avd.field_list = NULL;
              SetCDSetField (cdsp, sp->field_list_to, &avd, fsp);
              avd.new_text = StringSave (str_dst);
              SetCDSGeneProtField (sfp_src, sp->field_list, &avd, fsp);
              change_made = TRUE;
            }
          }
          str_dst = MemFree (str_dst);
        }
        str_src = MemFree (str_src);
      }
    } 
  }
  else
  {
    str_src = GetCDSetField (cdsp, sp->field_list);
    str_dst = GetCDSetField (cdsp, sp->field_list_to);
    if (!StringHasNoText (str_src) || !StringHasNoText (str_dst))
    {
      if (build_missing)
      {
        BuildNewCDSGeneProtFeature (cdsp, sp->field_list, sp->field_list_to);
        BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
      }
      if (!FeatureTypeMissingFromCDSet(cdsp, choice_from)
          && !FeatureTypeMissingFromCDSet(cdsp, choice_to))
      {
        avd.etp = NULL;
        avd.text_to_replace = NULL;
        avd.new_text = str_src;
        avd.field_list = NULL;
        SetCDSetField (cdsp, sp->field_list_to, &avd, fsp);
        avd.new_text = str_dst;
        SetCDSetField (cdsp, sp->field_list, &avd, fsp);
        change_made = TRUE;
      }
    }
  }
  if (change_made && sp->also_mrna)
  {
    CopyProteinNameTomRNAName (cdsp);
  }
}


static void CDSGeneProtParse
(CDSetPtr        cdsp,
 CDSGeneProtAECRPtr   sp,
 FilterSetPtr    fsp,
 ExistingTextPtr etp,
 Boolean         build_missing)
{
  CharPtr        str_src = NULL;
  CharPtr        str_dst = NULL;
  ValNodePtr     sfp_vnp;
  SeqFeatPtr     sfp;
  ApplyValueData avd;
  Boolean        able_to_apply;
  Boolean        change_made = FALSE;

  avd.where_to_replace = EditApplyFindLocation_anywhere;

  if (IsCDSetMatPeptideQualChoice(sp->field_list)) 
  {
    for (sfp_vnp = cdsp->prot_list; sfp_vnp != NULL; sfp_vnp = sfp_vnp->next)
    {
      sfp = sfp_vnp->data.ptrvalue;
      if (fsp == NULL || !DoesConstraintDisqualifyFeature(sfp, fsp->cgp)) 
      {
        str_src = GetCDSGeneProtField (sfp, sp->field_list, NULL);
        str_dst = str_src;
        str_src = ReplaceStringForParse(str_src, sp->text_portion);
        if (!sp->remove_parsed) {
          str_dst = MemFree (str_dst);
        }

        if (str_src != NULL) {
          if (build_missing) 
          {
            BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
          }
          able_to_apply = FALSE;
          if (IsCDSetMatPeptideQualChoice (sp->field_list_to)) 
          {
            able_to_apply = CDSGeneProtApplyToOneFeature (str_src, sfp, sp->field_list_to, fsp, etp);
          } 
          else 
          {
            able_to_apply = CDSGeneProtApplyToAllThatMatchFilter (str_src, cdsp, sp->field_list_to, fsp, etp);
          }
          if (sp->remove_parsed && able_to_apply) {
            CDSGeneProtApplyToOneFeature (str_dst, sfp, sp->field_list, fsp, NULL);
          }
          if (able_to_apply)
          {
            change_made = TRUE;
          }
          str_src = MemFree (str_src);
        }
        str_dst = MemFree (str_dst);
      }
    }
  }
  else
  {
    str_src = GetCDSetField (cdsp, sp->field_list);
    str_dst = str_src;
    str_src = ReplaceStringForParse(str_src, sp->text_portion);
    if (!sp->remove_parsed) {
      str_dst = MemFree (str_dst);
    }
    if (str_src != NULL) {
      if (build_missing) 
      {
        BuildNewCDSGeneProtFeature (cdsp, sp->field_list_to, sp->field_list);
      }
      able_to_apply = CDSGeneProtApplyToAllThatMatchFilter (str_src, cdsp, sp->field_list_to, fsp, etp);
      if (sp->remove_parsed && able_to_apply) {
        CDSGeneProtApplyToAllThatMatchFilter (str_dst, cdsp, sp->field_list, fsp, NULL);
      }
      if (able_to_apply)
      {
        change_made = TRUE;
      }
      str_src = MemFree (str_src);
    }   
    str_dst = MemFree (str_dst);
  }

  if (sp->also_mrna && change_made)
  {
    CopyProteinNameTomRNAName (cdsp);
  }
}     

static void CDSGeneProtEdit
(CDSetPtr        cdsp,
 CDSGeneProtAECRPtr   sp,
 FilterSetPtr    fsp,
 ExistingTextPtr etp)
{
  ApplyValueData avd;

  avd.where_to_replace = EditApplyFindLocation_anywhere;

  avd.field_list = sp->field_list;
  avd.etp = NULL;
  AddEditApplyDataToApplyValue (AECR_EDIT, sp->edit_apply, &avd);
  SetCDSetField (cdsp, sp->field_list, &avd, fsp);
  avd.text_to_replace = MemFree (avd.text_to_replace);
  avd.new_text = MemFree (avd.new_text);
  if (sp->also_mrna)
  {
    CopyProteinNameTomRNAName (cdsp);
  }
}

static ValNodePtr CollectCDSetList (Uint2 entityID, FilterSetPtr fsp)
{
  ValNodePtr cdset_list;

  /* need to create a list of sets */
  cdset_list = BuildCDSet2List (entityID, fsp == NULL || fsp->cgp == NULL ? NULL : fsp->cgp);

  return cdset_list;
}


static void MarkAlreadyEditedGenes (CDSetPtr cdsp, ValNodePtr cdset_list)
{
  ValNodePtr gene_vnp, list_vnp, genecheck_vnp;
  CDSetPtr   check_cdsp;

  if (cdsp == NULL || cdset_list == NULL || cdsp->gene_list == NULL) return;

  for (gene_vnp = cdsp->gene_list; gene_vnp != NULL; gene_vnp = gene_vnp->next)
  {
    if (gene_vnp->choice > 0) continue;
    for (list_vnp = cdset_list; list_vnp != NULL; list_vnp = list_vnp->next)
    {
      check_cdsp = (CDSetPtr) list_vnp->data.ptrvalue;
      if (check_cdsp != NULL) 
      {
        for (genecheck_vnp = check_cdsp->gene_list; genecheck_vnp != NULL; genecheck_vnp = genecheck_vnp->next)
        {
          if (genecheck_vnp->data.ptrvalue == gene_vnp->data.ptrvalue)
          {
            genecheck_vnp->choice = 1;
          }
        }
      }
    }
  }  
}


static Boolean CDSGeneProtAECRAction (Pointer userdata)
{
  ApplyEditConvertRemovePtr ap;
  CDSGeneProtAECRPtr        sp;
  Int4                      action_choice;
  SeqEntryPtr               sep;
  FilterSetPtr              fsp;
  GetSamplePtr              gsp = NULL;
  Boolean                   rval = TRUE;
  ValNodePtr                cdset_list, cdset_vnp;
  ExistingTextPtr           etp = NULL;
  Int4                      num_missing;
  Boolean                   build_missing = FALSE;
  MsgAnswer                 ans;

  ap = (ApplyEditConvertRemovePtr) userdata;
  if (ap == NULL)
  {
    return FALSE;
  }
  
  WatchCursor ();
  Update ();
  
  if (ap->crippled)
  {
    action_choice = ap->crippled_action;
  }
  else
  {
    action_choice = GetValue (ap->action_popup);
  }
  if (action_choice < 1 || action_choice > NUM_AECR)
  {
    return FALSE;
  }
  sp = (CDSGeneProtAECRPtr) DialogToPointer (ap->aecr_pages[action_choice - 1]);

  sep = GetTopSeqEntryForEntityID (ap->input_entityID);
  if (sp->field_list == NULL
      || ((action_choice == AECR_CONVERT || action_choice == AECR_SWAP) 
           && sp->field_list == NULL))
  {
    sp = CDSGeneProtAECRFree (sp);
    ArrowCursor ();
    Update ();
    return FALSE;
  }

  fsp = (FilterSetPtr) DialogToPointer (ap->constraints);
  
  /* need to create a list of sets */
  cdset_list = CollectCDSetList (ap->input_entityID, fsp);
  
  /* find existing text and how to handle it */
  if (action_choice == AECR_CONVERT || action_choice == AECR_APPLY || action_choice == AECR_PARSE)
  {
    gsp = GetCDSGeneProtAECRSample (cdset_list, sp->field_list, sp->field_list_to, fsp);
    etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, FALSE);
    gsp = GetSampleFree (gsp);
    if (etp != NULL && etp->existing_text_choice == eExistingTextChoiceCancel)
    {
      rval = FALSE;
    }
  }
  
  num_missing = CountMissingFeatures (cdset_list, sp->field_list, sp->field_list_to, fsp);
  if (action_choice == AECR_SWAP) 
  {
    num_missing += CountMissingFeatures (cdset_list, sp->field_list_to, sp->field_list, fsp);
  }
  if (num_missing > 0) 
  {
    ans = Message (MSG_YNC, "There are %d fields where the feature to which the field should be applied does not exist.  Do you want to create these features?", num_missing);
    if (ans == ANS_CANCEL)
    {
      rval = FALSE;
    } else if (ans == ANS_YES) {
      build_missing = TRUE;
    }
  }
  
  if (rval)
  {
    /* if we are applying gene locus, we need to make sure every sequence has a gene
     * and we need to recollect the cdset_list
     */
    if ((action_choice == AECR_APPLY || action_choice == AECR_PARSE)
        && sp->field_list != NULL
        && sp->field_list->data.intvalue == 1 + GENEFIELD_LOCUS)
    {
      BuildNewGenes (sep, &cdset_list, LoneGeneOkWithFilter(fsp));
    }
    
    for (cdset_vnp = cdset_list; cdset_vnp != NULL; cdset_vnp = cdset_vnp->next)
    {
      if (action_choice == AECR_CONVERT)
      {
        CDSGeneProtConvert (cdset_vnp->data.ptrvalue, sp, fsp, etp, build_missing);
      }
      else if (action_choice == AECR_SWAP)
      {
        CDSGeneProtSwap (cdset_vnp->data.ptrvalue, sp, fsp, etp, build_missing);
      }
      else if (action_choice == AECR_PARSE)
      {
        CDSGeneProtParse (cdset_vnp->data.ptrvalue, sp, fsp, etp, build_missing);
      }      
      else if (action_choice == AECR_APPLY)
      {
        CDSGeneProtApplyToAllThatMatchFilter (sp->edit_apply->apply_txt, cdset_vnp->data.ptrvalue, sp->field_list, fsp, etp);
      }
      else if (action_choice == AECR_REMOVE)
      {
        sp->field_list->choice = 1;
        RemoveCDSetField (cdset_vnp->data.ptrvalue, sp->field_list, fsp);
        if (sp->also_mrna)
        {
          CopyProteinNameTomRNAName (cdset_vnp->data.ptrvalue);
        }
      }
      else if (action_choice == AECR_EDIT)
      {
        CDSGeneProtEdit (cdset_vnp->data.ptrvalue, sp, fsp, etp);
      }
      if (((action_choice == AECR_APPLY || action_choice == AECR_EDIT) && IsCDSetGeneQualChoice (sp->field_list))
          || ((action_choice == AECR_PARSE || action_choice == AECR_CONVERT) && IsCDSetGeneQualChoice (sp->field_list_to))
          || (action_choice == AECR_SWAP && (IsCDSetGeneQualChoice (sp->field_list) || IsCDSetGeneQualChoice (sp->field_list_to))))
      {
        MarkAlreadyEditedGenes (cdset_vnp->data.ptrvalue, cdset_vnp->next);
      }
    }
  }
  FilterSetFree (fsp);
  sp = CDSGeneProtAECRFree (sp);
  FreeCDSetList (cdset_list);
  
  if (rval)
  {
    ObjMgrSetDirtyFlag (ap->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, ap->input_entityID, 0, 0);
  }
  ArrowCursor ();
  Update (); 
  
  return rval;

}

static void 
CDSGeneProtApplyEditConvertRemove 
(IteM    i, 
 Int4    first_action_choice, 
 Boolean crippled)
{
  ApplyEditConvertRemoveCombo (i, first_action_choice, crippled,
                               "CDS-Gene-Prot-mRNA",
                               CDSGeneProtAECRDialog, 
                               TRUE, GetCDSGeneProtSample,
                               FALSE, FALSE, FALSE, TRUE, FALSE, NULL,
                               CDSGeneProtAECRAction, NULL, NULL,
                               CheckFeaturesForPresample);
}

extern void ApplyCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_APPLY, FALSE);
}

extern void PublicApplyCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_APPLY, TRUE);
}

extern void EditCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_EDIT, FALSE);
}

extern void PublicEditCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_EDIT, TRUE);
}

extern void ConvertCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_CONVERT, FALSE);
}

extern void SwapCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_SWAP, FALSE);
}

extern void RemoveCDSGeneProt (IteM i)
{
  CDSGeneProtApplyEditConvertRemove (i, AECR_REMOVE, FALSE);
}

#define QUALKIND_SOURCE_QUAL 1
#define QUALKIND_STRINGS     2
#define QUALKIND_LOCATION    3
#define QUALKIND_ORIGIN      4

typedef struct sourcequalaecrdata
{
  Int4          qual_kind;
  SimpleAECRPtr source_qual;
  SimpleAECRPtr location;
  SimpleAECRPtr origin;
  SimpleAECRPtr strings;
  
  TaxnameOptionsPtr taxname_options;
} SourceQualAECRData, PNTR SourceQualAECRPtr;

static SourceQualAECRPtr SourceQualAECRFree (SourceQualAECRPtr sp)
{
  if (sp != NULL)
  {
    sp->source_qual = SimpleAECRFree (sp->source_qual);
    sp->taxname_options = MemFree (sp->taxname_options);
    sp = MemFree (sp);
  }
  return sp;
}

typedef struct sourcequalaecrdlg
{
  DIALOG_MESSAGE_BLOCK
  GrouP  qual_kind;
  
  DialoG source_qual;
  DialoG location;
  DialoG origin;
  DialoG strings;

  DialoG taxname_options;
  
  Int4   action_choice;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
  
} SourceQualAECRDlgData, PNTR SourceQualAECRDlgPtr;


static Boolean AreTaxNameOptionsRelevant (Int4 action_choice, SimpleAECRPtr sp)
{
  Boolean relevant = FALSE;
  if (sp == NULL) return FALSE;

  switch (action_choice)
  {
    case AECR_APPLY:
    case AECR_EDIT:
    case AECR_REMOVE:
      if (sp->field_list != NULL && sp->field_list->choice == 1)
      {
        relevant = TRUE;
      }
      break;
    case AECR_SWAP:
      if ((sp->field_list != NULL && sp->field_list->choice == 1) 
          || (sp->field_list_to != NULL && sp->field_list_to->choice == 1))
      {
        relevant = TRUE;
      }
      break;
    case AECR_CONVERT:
      if ((sp->field_list != NULL && sp->field_list->choice == 1 && !sp->leave_on_original) 
          || (sp->field_list_to != NULL && sp->field_list_to->choice == 1))
      {
        relevant = TRUE;
      }
      break;
    case AECR_PARSE:
      if ((sp->field_list != NULL && sp->field_list->choice == 1 && sp->remove_parsed)
          || (sp->field_list_to != NULL && sp->field_list_to->choice == 1))
      {
        relevant = TRUE;
      }
      break;
    default:
      relevant = FALSE;
      break;
  }
  return relevant;
}

static void ChangeQualKind (GrouP g)
{
  SourceQualAECRDlgPtr dlg;
  Int4                 qual_kind;
  SimpleAECRPtr        sp;

  dlg = (SourceQualAECRDlgPtr) GetObjectExtra (g);
  if (dlg == NULL)
  {
    return;
  }
  
  Hide (dlg->source_qual);
  Hide (dlg->location);
  Hide (dlg->origin);
  Hide (dlg->strings);
  Hide (dlg->taxname_options);
  qual_kind = GetValue (dlg->qual_kind);
  switch (qual_kind)
  {
    case QUALKIND_SOURCE_QUAL :
      Show (dlg->source_qual);
      break;
    case QUALKIND_LOCATION :
      Show (dlg->location);
      break;
    case QUALKIND_ORIGIN :
      Show (dlg->origin);
      break;
    case QUALKIND_STRINGS :
      Show (dlg->strings);
      Show (dlg->taxname_options);
      sp = DialogToPointer (dlg->strings);
      if (AreTaxNameOptionsRelevant (dlg->action_choice, sp))
      {
        Enable (dlg->taxname_options);
      }
      else
      {
        Disable (dlg->taxname_options);
      }
      sp = SimpleAECRFree (sp);
      break;
  }
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }
}

static void ResetSourceQualAECRDlg (SourceQualAECRDlgPtr dlg)
{
  if (dlg == NULL)
  {
    return;
  }
  
  PointerToDialog (dlg->source_qual, NULL);
  PointerToDialog (dlg->location, NULL);
  PointerToDialog (dlg->origin, NULL);
  PointerToDialog (dlg->strings, NULL);
  ChangeQualKind (dlg->qual_kind);
  PointerToDialog (dlg->taxname_options, NULL);
}

static void ClearTextSourceQualAECRDlg (SourceQualAECRDlgPtr dlg)
{
  if (dlg == NULL)
  {
    return;
  }
  SendMessageToDialog (dlg->source_qual, AECR_VIB_MSG_CLEAR_TEXT);
  SendMessageToDialog (dlg->location, AECR_VIB_MSG_CLEAR_TEXT);
  SendMessageToDialog (dlg->origin, AECR_VIB_MSG_CLEAR_TEXT);
  SendMessageToDialog (dlg->strings, AECR_VIB_MSG_CLEAR_TEXT);
}

static void SourceQualAECRToDialog (DialoG d, Pointer userdata)
{
  SourceQualAECRDlgPtr dlg;
  SourceQualAECRPtr    data;
  
  dlg = (SourceQualAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetSourceQualAECRDlg (dlg);
  
  data = (SourceQualAECRPtr) userdata;
  if (data == NULL)
  {
    return;
  }
  PointerToDialog (dlg->source_qual, data->source_qual);
  PointerToDialog (dlg->location, data->location);
  PointerToDialog (dlg->origin, data->origin);
  PointerToDialog (dlg->strings, data->strings);
  
  if (data->qual_kind < QUALKIND_SOURCE_QUAL)
  {
    SetValue (dlg->qual_kind, QUALKIND_SOURCE_QUAL);
  }
  else if (data->qual_kind > QUALKIND_STRINGS 
           && (dlg->action_choice == AECR_EDIT || dlg->action_choice == AECR_SWAP))
  {
    SetValue (dlg->qual_kind, QUALKIND_STRINGS);
    PointerToDialog (dlg->taxname_options, data->taxname_options);
  }
  else if (data->qual_kind > QUALKIND_ORIGIN)
  {
    SetValue (dlg->qual_kind, QUALKIND_ORIGIN);
  }
  else
  {
    SetValue (dlg->qual_kind, data->qual_kind);
  }
  ChangeQualKind (dlg->qual_kind);  
}

static Pointer DialogToSourceQualAECR (DialoG d)
{
  SourceQualAECRDlgPtr dlg;
  SourceQualAECRPtr    data;
  
  dlg = (SourceQualAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  data = (SourceQualAECRPtr) MemNew (sizeof (SourceQualAECRData));
  if (data != NULL)
  {
    data->taxname_options = NULL;
    data->qual_kind = GetValue (dlg->qual_kind);
    switch (data->qual_kind)
    {
      case QUALKIND_SOURCE_QUAL :
        data->source_qual = DialogToPointer (dlg->source_qual);
        break;
      case QUALKIND_LOCATION :
        data->location = DialogToPointer (dlg->location);
        break;
      case QUALKIND_ORIGIN :
        data->origin = DialogToPointer (dlg->origin);
        break;
      case QUALKIND_STRINGS :
        data->strings = DialogToPointer (dlg->strings);
        if (AreTaxNameOptionsRelevant (dlg->action_choice, data->strings))
        {
          data->taxname_options = DialogToPointer (dlg->taxname_options);
        }
        break;
    }
    
  }
  return data;
}

static void SourceQualAECRMessage (DialoG d, Int2 mssg)

{
  SourceQualAECRDlgPtr  dlg;

  dlg = (SourceQualAECRDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) 
    {
      case VIB_MSG_INIT :
        /* reset list */
        ResetSourceQualAECRDlg (dlg);
        break;
      case VIB_MSG_ENTER :
        Select (dlg->qual_kind);
        break;
      case AECR_VIB_MSG_SET_DEFAULT :
        SetValue (dlg->qual_kind, QUALKIND_SOURCE_QUAL);
        SendMessageToDialog (dlg->source_qual, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->location, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->origin, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->strings, AECR_VIB_MSG_SET_DEFAULT);
        PointerToDialog (dlg->taxname_options, NULL);
        ChangeQualKind (dlg->qual_kind);
        break;
      case AECR_VIB_MSG_CLEAR_TEXT :
        ClearTextSourceQualAECRDlg (dlg);
        break;
      default :
        break;
    }
  }
}

static ValNodePtr TestSourceQualAECR (DialoG d)
{
  SourceQualAECRDlgPtr dlg;
  ValNodePtr           total_err_list = NULL;
  Int4                 qual_kind;
  
  dlg = (SourceQualAECRDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return FALSE;
  }

  qual_kind = GetValue (dlg->qual_kind);
  switch (qual_kind)
  {
    case QUALKIND_SOURCE_QUAL :
      total_err_list = TestDialog (dlg->source_qual);
      break;
    case QUALKIND_LOCATION :
      total_err_list = TestDialog (dlg->location);
      break;
    case QUALKIND_ORIGIN :
      total_err_list = TestDialog (dlg->origin);
      break;
    case QUALKIND_STRINGS :
      total_err_list = TestDialog (dlg->strings);
      break;
    default:
      ValNodeAddPointer (&total_err_list, 0, "qual kind");
      break;
  }
  return total_err_list;
}

typedef struct applysourcequaldlg
{
  DIALOG_MESSAGE_BLOCK
  DialoG               field_list;
  DialoG               edit_apply;
  PopuP                true_false;
  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_userdata;
  Uint2                entityID;
  
} ApplySourceQualDlgData, PNTR ApplySourceQualDlgPtr;

static void SourceQualChangeNotify (Pointer userdata)
{
  ApplySourceQualDlgPtr dlg;
  ValNodePtr            vnp;
  SourceQualDescPtr     sqdp;
  
  dlg = (ApplySourceQualDlgPtr) userdata;
  if (dlg == NULL)
  {
    return;
  }
  
  vnp = (ValNodePtr) DialogToPointer (dlg->field_list);
  if (vnp == NULL || vnp->data.ptrvalue == NULL)
  {
    Hide (dlg->edit_apply);
    Hide (dlg->true_false);
  }
  else 
  {
    sqdp = (SourceQualDescPtr) vnp->data.ptrvalue;
    if (IsNonTextModifier (sqdp->name))
    {
      Hide (dlg->true_false);
      Hide (dlg->edit_apply);
    }
    else
    {
      Show (dlg->edit_apply);
      Hide (dlg->true_false);
    }
  }
  vnp = ValNodeFreeData (vnp);
  
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}

static void ClearTextApplySourceQualDialog (ApplySourceQualDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->edit_apply, NULL);
  }
}

static void ResetApplySourceQualDialog (ApplySourceQualDlgPtr dlg)
{
  if (dlg == NULL)
  {
    return;
  }
  PointerToDialog (dlg->field_list, NULL);
  PointerToDialog (dlg->edit_apply, NULL);
  SetValue (dlg->true_false, 1);
  SourceQualChangeNotify (dlg);
}

static void ApplySourceQualToDialog (DialoG d, Pointer data)
{
  ApplySourceQualDlgPtr dlg;
  SimpleAECRPtr         sap;
  SourceQualDescPtr     sqdp;
  
  dlg = (ApplySourceQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  ResetApplySourceQualDialog (dlg);
  
  sap = (SimpleAECRPtr) data;
  if (sap != NULL)
  {
    PointerToDialog (dlg->field_list, sap->field_list);

    if (sap->field_list != NULL && sap->field_list->data.ptrvalue != NULL)
    {
      sqdp = (SourceQualDescPtr) sap->field_list->data.ptrvalue;
      if (IsNonTextModifier (sqdp->name))
      {
        if (sap->edit_apply == NULL 
            || StringHasNoText (sap->edit_apply->apply_txt))
        {
          SetValue (dlg->true_false, 2);
        }
        else
        {
          SetValue (dlg->true_false, 1);
        }
      }
      else
      {
        PointerToDialog (dlg->edit_apply, sap->edit_apply);
      }
    }
  }
  SourceQualChangeNotify (dlg);
}

static Pointer DialogToApplySourceQual (DialoG d)
{
  ApplySourceQualDlgPtr dlg;
  SimpleAECRPtr         sap;
  SourceQualDescPtr     sqdp;
  
  dlg = (ApplySourceQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  sap = (SimpleAECRPtr) MemNew (sizeof (SimpleAECRData));
  if (sap == NULL)
  {
    return NULL;
  }
  sap->field_list = DialogToPointer (dlg->field_list);
  sap->edit_apply = DialogToPointer (dlg->edit_apply);
  if (sap->field_list != NULL && sap->field_list->data.ptrvalue != NULL)
  {
    sqdp = (SourceQualDescPtr) sap->field_list->data.ptrvalue;
    if (IsNonTextModifier (sqdp->name))
    {
      sap->edit_apply->apply_txt = MemFree (sap->edit_apply->apply_txt);
      if (GetValue (dlg->true_false) == 1)
      {
        sap->edit_apply->apply_txt = StringSave ("TRUE");
      }
    }
  }
  
  sap->free_field_vn_proc = ValNodeSimpleDataFree;
  sap->free_subtype_vn_proc = NULL;
  return sap;  
}

static void ApplySourceQualMessage (DialoG d, Int2 mssg)

{
  ApplySourceQualDlgPtr  dlg;

  dlg = (ApplySourceQualDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) 
    {
      case VIB_MSG_INIT :
        /* reset list */
        ResetApplySourceQualDialog (dlg);
        break;
      case VIB_MSG_ENTER :
        Select (dlg->field_list);
        break;
      case AECR_VIB_MSG_SET_DEFAULT :
        SendMessageToDialog (dlg->field_list, AECR_VIB_MSG_SET_DEFAULT);
        ClearTextApplySourceQualDialog (dlg);
        break;
      case AECR_VIB_MSG_CLEAR_TEXT :
        ClearTextApplySourceQualDialog (dlg);
        break;
      case AECR_VIB_MSG_AUTOPOPULATE:
        SourceQualChangeNotify (dlg);
        break; 
      default :
        break;
    }
  }
}

static ValNodePtr TestApplySourceQual (DialoG d)
{
  ApplySourceQualDlgPtr dlg;
  ValNodePtr            err_list = NULL;
  ValNodePtr            total_err_list = NULL;
  ValNodePtr            field_list;
  SourceQualDescPtr     sqdp;
   
  dlg = (ApplySourceQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }

  err_list = TestDialog (dlg->field_list);
  total_err_list = ValNodeAppend (total_err_list, err_list);
      
  field_list = DialogToPointer (dlg->field_list);
  if (field_list != NULL && field_list->data.ptrvalue != NULL)
  {
    sqdp = (SourceQualDescPtr) field_list->data.ptrvalue;
    if (IsNonTextModifier (sqdp->name))
    {
      /* any true or false value is valid */
    }
    else
    {
      err_list = TestDialog (dlg->edit_apply);
      total_err_list = ValNodeAppend (total_err_list, err_list);
    }
  }
  field_list = ValNodeFreeData (field_list);
      
  return total_err_list;
}

static DialoG ApplySourceQualDialog
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  ApplySourceQualDlgPtr dlg;  
  GrouP                 p, g1, g2;
  
  dlg = (ApplySourceQualDlgPtr) MemNew (sizeof (ApplySourceQualDlgData));
  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 = ApplySourceQualToDialog;
  dlg->fromdialog = DialogToApplySourceQual;
  dlg->dialogmessage = ApplySourceQualMessage;
  dlg->testdialog = TestApplySourceQual;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->entityID = entityID;
  
  g1 = HiddenGroup (p, 1, 0, NULL);
  StaticPrompt (g1, "Qualifier", 0, dialogTextHeight, systemFont, 'l');

  dlg->field_list = SourceQualTypeSelectionDialog (g1, FALSE, 
                                                SourceQualChangeNotify,
                                                dlg);
  
  g2 = HiddenGroup (p, 0, 0, NULL);
  dlg->edit_apply = EditApplyDialog (g2, eEditApplyChoice_Apply, "New Text", NULL,
                                     change_notify, change_userdata);
  
  dlg->true_false = PopupList (g2, TRUE, NULL);
  PopupItem (dlg->true_false, "TRUE");
  PopupItem (dlg->true_false, "FALSE");
  SetValue (dlg->true_false, 1);
  
  Hide (dlg->edit_apply);
  Hide (dlg->true_false);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->edit_apply, (HANDLE) dlg->true_false, NULL);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) g2, NULL);
  
  SourceQualChangeNotify (dlg);
  
  return (DialoG) p;
}

static DialoG ConvertSourceQualDialog
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  SimpleAECRDlgPtr dlg;
  GrouP            p, g1;
  
  dlg = (SimpleAECRDlgPtr) MemNew (sizeof (SimpleAECRDlgData));
  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 = SimpleAECRToDialog;
  dlg->fromdialog = DialogToSimpleAECR;
  dlg->dialogmessage = SimpleAECRMessage;
  dlg->testdialog = TestSimpleAECR;
  dlg->action_choice = AECR_CONVERT;
  dlg->free_field_vn_proc = ValNodeSimpleDataFree;
  dlg->free_subtype_vn_proc = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->get_autopopulate_text = NULL;
  dlg->entityID = entityID;
  
  dlg->subtype_list = NULL;
  
  g1 = HiddenGroup (p, 2, 0, NULL);
  StaticPrompt (g1, "From", 0, dialogTextHeight, systemFont, 'l');
  StaticPrompt (g1, "To", 0, dialogTextHeight, systemFont, 'l');
    
  dlg->field_list = SourceQualTypeDiscSelectionDialog (g1, FALSE, 
                                          change_notify, 
                                          change_userdata);
  dlg->field_list_to = SourceQualTypeSelectionDialog (g1, FALSE, 
                                             change_notify, 
                                             change_userdata);
  dlg->leave_on_original = CheckBox (p, "Leave on original", NULL);
  dlg->strip_name_from_text = CheckBox (p, "Strip name from text", NULL);  
  
  dlg->edit_apply = NULL;

  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->leave_on_original, (HANDLE) dlg->strip_name_from_text, NULL);
  return (DialoG) p;
}


static DialoG ConvertSourceLocationDialog
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  SimpleAECRDlgPtr dlg;
  GrouP            p, g1;
  
  dlg = (SimpleAECRDlgPtr) MemNew (sizeof (SimpleAECRDlgData));
  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 = SimpleAECRToDialog;
  dlg->fromdialog = DialogToSimpleAECR;
  dlg->dialogmessage = SimpleAECRMessage;
  dlg->testdialog = TestSimpleAECR;
  dlg->action_choice = AECR_CONVERT;
  dlg->free_field_vn_proc = ValNodeSimpleDataFree;
  dlg->free_subtype_vn_proc = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->get_autopopulate_text = NULL;
  dlg->entityID = entityID;
  
  dlg->subtype_list = NULL;
  
  g1 = HiddenGroup (p, 2, 0, NULL);
  StaticPrompt (g1, "From", 0, dialogTextHeight, systemFont, 'l');
  StaticPrompt (g1, "To", 0, dialogTextHeight, systemFont, 'l');
    
  dlg->field_list = SourceLocationDiscSelectionDialog (g1, FALSE, 
                                          change_notify, 
                                          change_userdata);
  dlg->field_list_to = SourceLocationSelectionDialog (g1, FALSE, 
                                             change_notify, 
                                             change_userdata);
  dlg->leave_on_original = CheckBox (p, "Leave on original", NULL);
  
  dlg->edit_apply = NULL;

  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) dlg->leave_on_original, NULL);
  return (DialoG) p;
}


static void ChangeTaxonomyStrings (Pointer data)
{
  SourceQualAECRDlgPtr dlg;
  SimpleAECRPtr        sp;

  dlg = (SourceQualAECRDlgPtr) data;
  if (dlg == NULL) return;

  if (GetValue (dlg->qual_kind) == QUALKIND_STRINGS)
  {
    sp = DialogToPointer (dlg->strings);
    if (AreTaxNameOptionsRelevant (dlg->action_choice, sp))
    {
      Enable (dlg->taxname_options);
    }
    else
    {
      Disable (dlg->taxname_options);
    }
    sp = SimpleAECRFree (sp);
  }
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }

}

static DialoG SourceQualAECRDialog 
(GrouP                    h,
 Int4                     action_choice, 
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{

  SourceQualAECRDlgPtr dlg;
  GrouP                p, g1;
  
  dlg = (SourceQualAECRDlgPtr) MemNew (sizeof (SourceQualAECRDlgData));
  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 = SourceQualAECRToDialog;
  dlg->fromdialog = DialogToSourceQualAECR;
  dlg->dialogmessage = SourceQualAECRMessage;
  dlg->testdialog = TestSourceQualAECR;
  dlg->action_choice = action_choice;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->qual_kind = HiddenGroup (p, 4, 0, ChangeQualKind);
  SetObjectExtra (dlg->qual_kind, dlg, NULL);
  RadioButton (dlg->qual_kind, "Qualifiers");
  RadioButton (dlg->qual_kind, "Taxonomy");
  if (action_choice != AECR_EDIT && action_choice != AECR_SWAP)
  {
    RadioButton (dlg->qual_kind, "Location");
    RadioButton (dlg->qual_kind, "Origin");
  }
  SetValue (dlg->qual_kind, QUALKIND_SOURCE_QUAL);
    
  g1 = HiddenGroup (p, 0, 0, NULL);
  /* Source Qualifiers dialog */
  if (action_choice == AECR_APPLY)
  {
    dlg->source_qual = ApplySourceQualDialog (g1, 
                                              change_notify, 
                                              change_userdata,
                                              entityID);
  }
  else if (action_choice == AECR_REMOVE)
  {
    dlg->source_qual = SimpleAECRDialog (g1, action_choice, 
                           change_notify, change_userdata,
                           ValNodeSimpleDataFree, NULL,
                           SourceQualTypeDiscSelectionDialog, NULL, NULL,
                           "Qualifier", NULL, TRUE, entityID);    
  }
  else if (action_choice == AECR_CONVERT)
  {
    dlg->source_qual = ConvertSourceQualDialog (g1,
                                                change_notify, 
                                                change_userdata,
                                                entityID);
  }
  else if (action_choice == AECR_SWAP)
  {
    dlg->source_qual = SimpleAECRDialogEx (g1, action_choice, 
                           change_notify, change_userdata,
                           ValNodeSimpleDataFree, NULL,
                           SourceQualTypeSelectionDialog, NULL, NULL,
                           "Qualifier", NULL, TRUE, TRUE, entityID);    
  }
  else
  {
    dlg->source_qual = SimpleAECRDialog (g1, action_choice, 
                           change_notify, change_userdata,
                           ValNodeSimpleDataFree, NULL,
                           SourceQualTypeSelectionDialog, NULL, NULL,
                           "Qualifier", NULL, TRUE, entityID);    
  }
  
  /* Taxonomy Strings dialog */
  dlg->strings = SimpleAECRDialog (g1, action_choice, ChangeTaxonomyStrings, dlg,
                           ValNodeSimpleDataFree, NULL,
                           SourceStringSelectionDialog, NULL, NULL,
                           "Taxonomy", NULL, TRUE, entityID);
                          
  if (action_choice != AECR_EDIT && action_choice != AECR_SWAP)
  {
    /* Location Dialog */
    if (action_choice == AECR_APPLY)
    {
      dlg->location = SimpleAECRDialog (g1, action_choice,
                                        change_notify, change_userdata,
                                        ValNodeSimpleDataFree, NULL,
                                        SourceLocationSelectionDialog, NULL,
                                        NULL, "Location", NULL, FALSE, entityID);
    }
    else if (action_choice == AECR_CONVERT)
    {
      dlg->location = ConvertSourceLocationDialog (g1,
                                                    change_notify,
                                                    change_userdata, 
                                                    entityID);
    }
    else
    {
      dlg->location = SimpleAECRDialog (g1, action_choice,
                                        change_notify, change_userdata,
                                        ValNodeSimpleDataFree, NULL,
                                        SourceLocationDiscSelectionDialog, 
                                        NULL, NULL, "Location", NULL, FALSE,
                                        entityID);
    }

    /* Origin Dialog */
    dlg->origin = SimpleAECRDialog (g1, action_choice, change_notify, change_userdata,
                           ValNodeSimpleDataFree, NULL,
                           SourceOriginSelectionDialog, NULL, NULL,
                           "Origin", NULL, FALSE, entityID);
  }

  dlg->taxname_options = TaxnameOptionsDialog (p, change_notify, change_userdata);

  Disable (dlg->taxname_options);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->source_qual,
                              (HANDLE) dlg->location,
                              (HANDLE) dlg->origin,
                              (HANDLE) dlg->strings,
                              NULL);
                              
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->qual_kind,
                              (HANDLE) g1,
                              (HANDLE) dlg->taxname_options,
                              NULL);
                           
  ChangeQualKind (dlg->qual_kind);                         
  return (DialoG) p;
}


static Boolean BioSourceHasMultipleQuals (BioSourcePtr biop, SourceQualDescPtr sqdp, FilterSetPtr fsp)
{
  OrgModPtr    mod;
  SubSourcePtr ssp;
  Boolean      found_one = FALSE;

  if (biop == NULL || sqdp == NULL) return FALSE;

  if (sqdp->isOrgMod)
  {
    if (biop->org != NULL && biop->org->orgname != NULL)
    {
      mod = biop->org->orgname->mod;
      while (mod != NULL)
      {
        if (mod->subtype == sqdp->subtype
            && !StringHasNoText (mod->subname)
            && OrgModSpecialMatch (mod, fsp))
        {
          if (found_one) {
            return TRUE;
          } else {
            found_one = TRUE;
          }
        }
        mod = mod->next;
      }
    }
  }
  else
  {
    ssp = biop->subtype;
    while (ssp != NULL)
    {
      if (ssp->subtype == sqdp->subtype
          && !StringHasNoText (ssp->name)
          && SubSrcSpecialMatch (ssp, fsp))
      {
        if (found_one) {
          return TRUE;
        } else {
          found_one = TRUE;
        }
      }
      ssp = ssp->next;
    }
  }
  return FALSE;
}

static Boolean CheckBioSrcListForMultipleQuals (ValNodePtr biosrc_list, SourceQualDescPtr sqdp, FilterSetPtr fsp)
{
  ValNodePtr   vnp;
  SeqFeatPtr   sfp;
  SeqDescrPtr  sdp;
  Boolean      has_multiple = FALSE;

  if (sqdp == NULL) return FALSE;

  for (vnp = biosrc_list; vnp != NULL && !has_multiple; vnp = vnp->next)
  {
    if (vnp->data.ptrvalue == NULL) continue;
    if (vnp->choice == OBJ_SEQFEAT)
    {
      sfp = (SeqFeatPtr) vnp->data.ptrvalue;
      if (sfp->data.choice == SEQFEAT_BIOSRC)
      {
        has_multiple = BioSourceHasMultipleQuals ((BioSourcePtr)sfp->data.value.ptrvalue, sqdp, fsp);
      }
    }
    else if (vnp->choice == OBJ_SEQDESC)
    {
      sdp = (SeqDescrPtr) vnp->data.ptrvalue;
      if (sdp->choice == Seq_descr_source)
      {
        has_multiple = BioSourceHasMultipleQuals ((BioSourcePtr)sdp->data.ptrvalue, sqdp, fsp);
      }
    }
  }
  return has_multiple;
}


typedef struct taxnameoptionsdlg
{
  DIALOG_MESSAGE_BLOCK
  ButtoN               remove_taxref;
  ButtoN               remove_common;
  ButtoN               remove_old_name;
  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_userdata;
} TaxnameOptionsDlgData, PNTR TaxnameOptionsDlgPtr;

static void TaxnameOptionsToDialog (DialoG d, Pointer data)
{
  TaxnameOptionsDlgPtr dlg;
  TaxnameOptionsPtr    top;

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

  top = (TaxnameOptionsPtr) data;
  if (top == NULL)
  {
    SetStatus (dlg->remove_taxref, TRUE);
    SetStatus (dlg->remove_common, TRUE);
    SetStatus (dlg->remove_old_name, TRUE);
  }
  else
  {
    SetStatus (dlg->remove_taxref, top->remove_taxref);
    SetStatus (dlg->remove_common, top->remove_common);
    SetStatus (dlg->remove_old_name, top->remove_old_name);
  }
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}

static Pointer TaxnameOptionsFromDialog (DialoG d)
{
  TaxnameOptionsDlgPtr dlg;
  TaxnameOptionsPtr    top;

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

  top = (TaxnameOptionsPtr) MemNew (sizeof (TaxnameOptionsData));
  top->remove_taxref = GetStatus (dlg->remove_taxref);
  top->remove_common = GetStatus (dlg->remove_common);
  top->remove_old_name = GetStatus (dlg->remove_old_name);
  return top;
}


static void ChangeTaxnameOptions (ButtoN b)
{
  TaxnameOptionsDlgPtr dlg;

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


extern DialoG TaxnameOptionsDialog (GrouP h, Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  TaxnameOptionsDlgPtr dlg;
  GrouP                p;
  
  dlg = (TaxnameOptionsDlgPtr) MemNew (sizeof (TaxnameOptionsDlgData));
  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 = TaxnameOptionsToDialog;
  dlg->fromdialog = TaxnameOptionsFromDialog;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->remove_taxref = CheckBox (p, "Remove taxonomy xref", ChangeTaxnameOptions);
  SetObjectExtra (dlg->remove_taxref, dlg, NULL);
  SetStatus (dlg->remove_taxref, TRUE);
  dlg->remove_common = CheckBox (p, "Remove GenBank common name", ChangeTaxnameOptions);
  SetObjectExtra (dlg->remove_common, dlg, NULL);
  SetStatus (dlg->remove_common, TRUE);
  dlg->remove_old_name = CheckBox (p, "Remove old_name qualifier", ChangeTaxnameOptions);
  SetObjectExtra (dlg->remove_old_name, dlg, NULL);
  SetStatus (dlg->remove_old_name, TRUE);
  return (DialoG) p;
}

extern void ApplyTaxnameOptionsToBioSource (BioSourcePtr biop, TaxnameOptionsPtr top)
{
  if (biop == NULL || top == NULL) return;

  if (top->remove_taxref)
  {
    RemoveTaxRef (biop->org);
  }
  if (top->remove_old_name)
  {
    RemoveOldName (biop->org);
  }
  if (top->remove_common) 
  {
    if (biop->org != NULL)
    {
      biop->org->common = MemFree (biop->org->common);
    }
  }
}

static void HandleTaxnameOptionsFeature (SeqFeatPtr sfp, TaxnameOptionsPtr top)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || top == NULL)
  {
    return;
  }
  ApplyTaxnameOptionsToBioSource (sfp->data.value.ptrvalue, top);
}

static void HandleTaxnameOptionsDescriptorCallback (SeqDescrPtr sdp, TaxnameOptionsPtr top)
{
  if (sdp == NULL || sdp->choice != Seq_descr_source || top == NULL)
  {
    return;
  }
  ApplyTaxnameOptionsToBioSource (sdp->data.ptrvalue, top);
}


static void HandleTaxnameOptionsForBioSourceList (ValNodePtr biosrc_list, TaxnameOptionsPtr top)
{
  ValNodePtr vnp;

  if (top == NULL) return;
  for (vnp = biosrc_list; vnp != NULL; vnp = vnp->next)
  {
    if (vnp->choice == OBJ_SEQFEAT) 
    {
      HandleTaxnameOptionsFeature (vnp->data.ptrvalue, top);
    }
    else if (vnp->choice == OBJ_SEQDESC)
    {
      HandleTaxnameOptionsDescriptorCallback (vnp->data.ptrvalue, top);
    }
  }  
}

static void CollectBioSourceFeatureFieldCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  ValNodeAddPointer ((ValNodePtr PNTR) userdata, OBJ_SEQFEAT, sfp);  
}

static void CollectBioSourceDescriptorFieldCallback (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{  
  if (sdp == NULL || userdata == NULL)
  {
    return;
  }
  
  ValNodeAddPointer ((ValNodePtr PNTR) userdata, OBJ_SEQDESC, sdp);  
}


static void RemoveSourceQualEditNonMatches (ValNodePtr PNTR biosrc_list, ApplyValuePtr avp)
{
  ValNodePtr vnp_prev = NULL, vnp_next, vnp_this;
  CharPtr    val1, val2;
  Boolean    do_remove;

  if (biosrc_list == NULL || avp == NULL || StringHasNoText (avp->text_to_replace))
  {
    return;
  }

  vnp_this = *biosrc_list;
  while (vnp_this != NULL)
  {
    vnp_next = vnp_this->next;
    do_remove = FALSE;
    if (vnp_this->choice == OBJ_SEQFEAT)
    {
      val1 = GetSourceQualFeatureString (vnp_this->data.ptrvalue, avp->field_list, NULL);
    }
    else if (vnp_this->choice == OBJ_SEQDESC)
    {
      val1 = GetSourceQualDescrString (vnp_this->data.ptrvalue, avp->field_list, NULL);
    } 
    else
    {
      val1 = NULL;
    }
    if (val1 == NULL)
    {
      do_remove = TRUE;
    }
    else
    {
      val2 = HandleApplyValue (StringSave (val1), avp);
      if (StringCmp (val1, val2) == 0)
      {
        do_remove = TRUE;
      }
      val1 = MemFree (val1);
      val2 = MemFree (val2);
    }
    if (do_remove)
    {
      if (vnp_prev == NULL)
      {
        *biosrc_list = vnp_this->next;
      }
      else
      {
        vnp_prev->next = vnp_this->next;
      }
      vnp_this->next = NULL;
      vnp_this = ValNodeFree (vnp_this);
    }
    else
    {
      vnp_prev = vnp_this;
    }
    vnp_this = vnp_next;
  }
}


static Boolean 
SourceQualQualApplyEditConvertRemoveAction
(ApplyEditConvertRemovePtr   ap,
 SimpleAECRPtr               sp,
 Int4                        action_choice,
 SetFeatureFieldString       feature_apply_action,
 SetDescriptorFieldString    descriptor_apply_action,
 RemoveFeatureFieldString    feature_remove_action,
 RemoveDescriptorFieldString descriptor_remove_action,
 CopyValNodeDataProc         copy_vn_proc,
 GetFeatureFieldString       fieldstring_func,
 GetDescriptorFieldString    descrstring_func,
 Boolean                     non_text,
 NameFromValNodeProc         name_field_func,
 TaxnameOptionsPtr           top)

{
  SeqEntryPtr               sep;
  ApplyValueData            avd;
  ConvertFieldData          cfd;
  FilterSetPtr              fsp;
  GetSamplePtr              gsp;
  Boolean                   rval = TRUE;
  ValNodePtr                vnp;
  CharPtr                   field_name = NULL;
  MsgAnswer                 ans;
  ValNodePtr                biosrc_list = NULL;
  SourceQualDescPtr         multi_sqdp = NULL;
  
  sep = GetTopSeqEntryForEntityID (ap->input_entityID);
  if (sp == NULL
      || sp->field_list == NULL
      || feature_apply_action == NULL
      || descriptor_apply_action == NULL
      || feature_remove_action == NULL
      || descriptor_remove_action == NULL
      || ((action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE) 
           && sp->field_list == NULL))
  {
    return FALSE;
  }
  
  WatchCursor ();
  Update ();
  
  avd.where_to_replace = EditApplyFindLocation_anywhere;

  fsp = (FilterSetPtr) DialogToPointer (ap->constraints);

  /* collect list of BioSources that will be acted upon */
  OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                       CollectBioSourceFeatureFieldCallback,
                                       CollectBioSourceDescriptorFieldCallback,
                                       SEQFEAT_BIOSRC, 0, Seq_descr_source,
                                       &biosrc_list);    
  
  if (action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE)
  {
    /* check for multiple sources */
    if ((sp->field_list->choice == 0
         && (multi_sqdp = sp->field_list->data.ptrvalue) != NULL
         && CheckBioSrcListForMultipleQuals(biosrc_list, multi_sqdp, fsp))
        || (action_choice == AECR_SWAP && sp->field_list_to->choice == 0
            && (multi_sqdp = sp->field_list_to->data.ptrvalue) != NULL
            && CheckBioSrcListForMultipleQuals(biosrc_list, multi_sqdp, fsp))) {
      if (name_field_func != NULL) {  
        if (action_choice == AECR_SWAP && multi_sqdp == sp->field_list_to->data.ptrvalue) {
          field_name = name_field_func(sp->field_list_to);
        } else {
          field_name = name_field_func(sp->field_list);
        }
      }
      if (field_name != NULL) {
        ans = Message (MSG_OKC, "One or more BioSources has multiple source quals.  These will be combined.  Do you want to continue?");
      } else {
        ans = Message (MSG_OKC, "One or more BioSources has multiple %s source quals.  These will be combined.  Do you want to continue?", field_name);
      }
      field_name = MemFree (field_name);
      if (ans == ANS_CANCEL) {
        FilterSetFree (fsp);
        ArrowCursor();
        Update();
        return FALSE;
      }
    }
    cfd.src_field_list = sp->field_list;
    cfd.dst_field_list = sp->field_list_to;
    cfd.etp = NULL;
    cfd.get_str_func = fieldstring_func;
    cfd.set_str_func = feature_apply_action;
    cfd.remove_str_func = feature_remove_action;
    cfd.get_d_str_func = descrstring_func;
    cfd.set_d_str_func = descriptor_apply_action;
    cfd.remove_d_str_func = descriptor_remove_action;
    cfd.fsp = fsp;
    cfd.strip_name_from_text = sp->strip_name_from_text;
    cfd.remove_parsed = sp->remove_parsed;
    cfd.name_field_func = name_field_func;
    cfd.text_portion = sp->text_portion;
    if (action_choice == AECR_CONVERT)
    {
      if (sp->leave_on_original)
      {
        cfd.convert_type = CONVERT_TYPE_COPY;
      }
      else
      {
        cfd.convert_type = CONVERT_TYPE_MOVE;
      }
    }
    else if (action_choice == AECR_SWAP)
    {
      cfd.convert_type = CONVERT_TYPE_SWAP;
    }
    else if (action_choice == AECR_PARSE)
    {
      cfd.convert_type = CONVERT_TYPE_PARSE;
    }
    if ((cfd.convert_type == CONVERT_TYPE_MOVE || cfd.convert_type == CONVERT_TYPE_COPY || cfd.convert_type == CONVERT_TYPE_PARSE)
        && ! non_text)
    {
      gsp = CheckForConversionExistingTextInSeqEntry (sep, &cfd, fsp, 
                                                      SEQFEAT_BIOSRC, 0,
                                                      Seq_descr_source);
      cfd.etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, non_text);
      gsp = GetSampleFree (gsp);
      if (cfd.etp != NULL && cfd.etp->existing_text_choice == eExistingTextChoiceCancel)
      {
        rval = FALSE;
      }
    }
    else
    {
      cfd.etp = NULL;
    }  

    if (rval)
    {
      if (non_text)
      {
        OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                             ConvertNonTextFeatureFieldCallback,
                                             ConvertNonTextDescriptorFieldCallback,
                                             SEQFEAT_BIOSRC, 0, Seq_descr_source,
                                             &cfd);    
      }
      else
      {
        OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                             ConvertFeatureFieldCallback,
                                             ConvertDescriptorFieldCallback,
                                             SEQFEAT_BIOSRC, 0, Seq_descr_source,
                                             &cfd);    
      }
      
    }
    cfd.etp = MemFree (cfd.etp);
  }
  else
  {
    avd.field_list = sp->field_list;
  
    /* get handling for existing text */
    if (action_choice == AECR_APPLY)
    {
      gsp = CheckForExistingText (ap->input_entityID,
                                  avd.field_list,
                                  fieldstring_func,
                                  descrstring_func,
                                  ValNodeSimpleDataFree,
                                  copy_vn_proc,
                                  fsp, SEQFEAT_BIOSRC, 0, Seq_descr_source);
      avd.etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, non_text);
      gsp = GetSampleFree (gsp);
    
      if (avd.etp != NULL 
          && avd.etp->existing_text_choice == eExistingTextChoiceCancel)
      {
        rval = FALSE;
      }
    }
    else
    {
      avd.etp = NULL;
    }
  
    if (rval)
    {
      AddEditApplyDataToApplyValue (action_choice, sp->edit_apply, &avd);

      if (action_choice == AECR_EDIT)
      {
        /* remove items that don't contain text that will be replaced */
        RemoveSourceQualEditNonMatches (&biosrc_list, &avd);
      }

   
      if (action_choice == AECR_EDIT || action_choice == AECR_APPLY)
      {
        OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                             feature_apply_action,
                                             descriptor_apply_action,
                                             SEQFEAT_BIOSRC, 0, Seq_descr_source, &avd);  
      }
      else if (action_choice == AECR_REMOVE)
      {
        for (vnp = sp->field_list; vnp != NULL; vnp = vnp->next)
        {
          avd.field_list = vnp;
          OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                               feature_remove_action,
                                               descriptor_remove_action,
                                               SEQFEAT_BIOSRC, 0, Seq_descr_source, &avd);
        }
      }

      avd.text_to_replace = MemFree (avd.text_to_replace);
      avd.new_text = MemFree (avd.new_text);
    }
    avd.etp = MemFree (avd.etp);
  }

  FilterSetFree (fsp);
  
  if (rval)
  {
    /* Handle taxname options */
    HandleTaxnameOptionsForBioSourceList (biosrc_list, top);

    ObjMgrSetDirtyFlag (ap->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, ap->input_entityID, 0, 0);
  }
  biosrc_list = ValNodeFree (biosrc_list);
  ArrowCursor ();
  Update (); 
  
  return rval;
}

static Boolean SourceQualApplyEditConvertRemoveAction (Pointer userdata)
{
  ApplyEditConvertRemovePtr ap;  
  SourceQualAECRPtr         sp;
  Int4                      action_choice;
  Boolean                   rval = FALSE;
  
  ap = (ApplyEditConvertRemovePtr) userdata;
  if (ap == NULL)
  {
    return FALSE;
  }
  
  if (ap->crippled)
  {
    action_choice = ap->crippled_action;
  }
  else
  {
    action_choice = GetValue (ap->action_popup);
  }
  if (action_choice < 1 || action_choice > NUM_AECR)
  {
    return FALSE;
  }
  sp = (SourceQualAECRPtr) DialogToPointer (ap->aecr_pages[action_choice - 1]);
  
  switch (sp->qual_kind)
  {
    case QUALKIND_SOURCE_QUAL :
      rval = SourceQualQualApplyEditConvertRemoveAction (ap, sp->source_qual, 
                                                         action_choice,
                                       ApplySourceQualFeatureCallback,
                                       ApplySourceQualDescriptorCallback,
                                       RemoveSourceQualFromSourceFeature,
                                       RemoveSourceQualFromSourceDescriptor,
                                       SourceQualValNodeDataCopy,
                                       GetSourceQualFeatureString,
                                       GetSourceQualDescrString,
                                       FALSE,
                                       SourceQualValNodeName,
                                       NULL);
      break;
    case QUALKIND_STRINGS :
      rval = SourceQualQualApplyEditConvertRemoveAction (ap, sp->strings, 
                                                         action_choice,
                                       ApplyStringToSourceFeature,
                                       ApplyStringToSourceDescriptor,
                                       RemoveStringFromSourceFeature,
                                       RemoveStringFromSourceDescriptor,
                                       ValNodeStringCopy,
                                       GetStringFromSourceFeature,
                                       GetStringFromSourceDescriptor,
                                       FALSE,
                                       NULL,
                                       sp->taxname_options);
      break;
    case QUALKIND_LOCATION :
      rval = SourceQualQualApplyEditConvertRemoveAction (ap, sp->location, 
                                                         action_choice,
                                       ApplyLocationToSourceFeature,
                                       ApplyLocationToSourceDescriptor,
                                       RemoveLocationFromSourceFeature,
                                       RemoveLocationFromSourceDescriptor,
                                       ValNodeStringCopy,
                                       GetLocationFromSourceFeature,
                                       GetLocationFromSourceDescriptor,
                                       TRUE,
                                       ValNodeStringName,
                                       NULL);
      break;      
    case QUALKIND_ORIGIN :
      rval = SourceQualQualApplyEditConvertRemoveAction (ap, sp->origin, 
                                                         action_choice,
                                       ApplyOriginToSourceFeature,
                                       ApplyOriginToSourceDescriptor,
                                       RemoveOriginFromSourceFeature,
                                       RemoveOriginFromSourceDescriptor,
                                       ValNodeStringCopy,
                                       GetOriginFromSourceFeature,
                                       GetOriginFromSourceDescriptor,
                                       TRUE,
                                       ValNodeStringName,
                                       NULL);
      break;      
  }
  sp = SourceQualAECRFree (sp);
  return rval;
}

static void ChangeSourceQualAction (DialoG prev_page, DialoG new_page)
{
  SourceQualAECRPtr         prev_sp, new_sp;
  
  if (prev_page == NULL || new_page == NULL)
  {
    return;
  }
  
  prev_sp = (SourceQualAECRPtr) DialogToPointer (prev_page);
  new_sp = (SourceQualAECRPtr) DialogToPointer (new_page);
  if (prev_sp != NULL && new_sp != NULL)
  {
    new_sp->qual_kind = prev_sp->qual_kind;
    PointerToDialog (new_page, new_sp);
  }
  new_sp = SourceQualAECRFree (new_sp);
  prev_sp = SourceQualAECRFree (prev_sp);
}

static DialoG GetSourceQualSample (GrouP h, Uint2 entityID)
{
  DialoG                d;
  SetSampleData         ssd;

  d = SampleDialog (h);
  ssd.fieldstring_func = GetSourceQualFeatureString;
  ssd.descrstring_func = GetSourceQualDescrString;
  ssd.entityID = entityID;
  ssd.field_list = GetSourceQualDescList (TRUE, TRUE, TRUE, FALSE);
  ValNodeAddPointer (&(ssd.field_list), 1, StringSave ("Scientific Name"));
  ValNodeAddPointer (&(ssd.field_list), 1, StringSave ("Location"));
  
  ssd.fsp = NULL;
  ssd.free_vn_proc = ValNodeSimpleDataFree;
  ssd.copy_vn_proc = SourceQualValNodeDataCopy;
  ssd.match_vn_proc = SourceQualValNodeMatch;
  ssd.label_vn_proc = SourceQualValNodeName;  
  PointerToDialog (d, &ssd);
  return d;
}

static void 
SourceQualApplyEditConvertRemove 
(IteM    i,
 Int4    first_action_choice,
 Boolean crippled)
{
  ApplyEditConvertRemoveCombo (i, first_action_choice, crippled,
                               "Source Qual",
                               SourceQualAECRDialog, 
                               FALSE, GetSourceQualSample,
                               FALSE, TRUE, FALSE, FALSE, FALSE, NULL,
                               SourceQualApplyEditConvertRemoveAction, NULL,
                               ChangeSourceQualAction, NULL);
}

extern void ApplySourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_APPLY, FALSE);
}

extern void PublicApplySourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_APPLY, TRUE);
}

extern void EditSourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_EDIT, FALSE);
}

extern void PublicEditSourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_EDIT, TRUE);
}

extern void ConvertSourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_CONVERT, FALSE);
}

extern void SwapSourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_SWAP, FALSE);
}

extern void RemoveSourceQual (IteM i)
{
  SourceQualApplyEditConvertRemove (i, AECR_REMOVE, FALSE);
}

static CharPtr rna_sample_field_list [] = { "Product", 
                                            "Comment" };
static Int2    num_rna_sample_fields = sizeof (rna_sample_field_list) / sizeof (CharPtr);


static CharPtr GetRNASampleFieldString (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  RnaRefPtr  rrp;
  CharPtr    str = NULL;
  Int2       rna_subtype, rna_field_type;
  GeneRefPtr grp;
  SeqFeatPtr gene;
  ValNode    vn;
  SeqMgrFeatContext    fcontext;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || vnp == NULL)
  {
    return NULL;
  }
  
  rrp = sfp->data.value.ptrvalue;
  
  while (vnp != NULL && str == NULL)
  {
    rna_subtype = (vnp->data.intvalue - 1) / (num_rna_sample_fields + num_gene_fields);
    rna_field_type = (vnp->data.intvalue - 1) % (num_rna_sample_fields + num_gene_fields);
    switch (rna_subtype)
    {
      case 0:
        if (rrp->type != 255 
            || (rrp->ext.choice == 1
                && (StringCmp (rrp->ext.value.ptrvalue, "ncRNA") == 0
                    || StringCmp (rrp->ext.value.ptrvalue, "tmRNA") == 0)))
        {
          vnp = vnp->next;
          continue;
        }
        break;
      case 1:
      case 2:
      case 3:
      case 4:
        if (rna_subtype != rrp->type)
        {
          vnp = vnp->next;
          continue;
        }
        break;
      case 5:
        if (rrp->type != 255 
            || rrp->ext.choice != 1
            || StringCmp (rrp->ext.value.ptrvalue, "ncRNA") != 0)
        {
          vnp = vnp->next;
          continue;
        }
        break;
      case 6:
        if (rrp->type != 255 
            || rrp->ext.choice != 1
            || StringCmp (rrp->ext.value.ptrvalue, "tmRNA") != 0)
        {
          vnp = vnp->next;
          continue;
        }
        break;
    }

    switch (rna_field_type)
    {
      case 0 :
        str = GetRNAProductString (sfp, NULL);
        break;
      case 1 :
        if (!StringHasNoText (sfp->comment))
        {
          str = StringSave (sfp->comment);
        }
        break;
      default :
        vn.next = NULL;
        vn.choice = 0;
        vn.data.intvalue = rna_field_type - num_rna_sample_fields + 1;
        grp = SeqMgrGetGeneXref (sfp);
        if (grp == NULL)
        {
          gene = SeqMgrGetOverlappingGene (sfp->location, &fcontext);
          str = GetGeneFieldString (gene, &vn, NULL);
        }
        else
        {
          str = GetGeneFieldString (sfp, &vn, NULL);     
        }
        break;
    }
    vnp = vnp->next;
  }
  return str;
}

static CharPtr GetRNAQualSampleName (ValNodePtr vnp)
{
  CharPtr label = NULL;
  Int2    rna_subtype, rna_field_type;
  
  if (vnp == NULL)
  {
    return NULL;
  }
  rna_subtype = (vnp->data.intvalue - 1) / (num_rna_sample_fields + num_gene_fields);
  rna_field_type = (vnp->data.intvalue - 1) % (num_rna_sample_fields + num_gene_fields);
  
  if (rna_field_type < num_rna_sample_fields)
  {
    label = (CharPtr) MemNew ((StringLen (rna_subtype_list [rna_subtype])
                              + StringLen (rna_sample_field_list[rna_field_type])
                              + 2) * sizeof (Char));
    if (label != NULL)
    {
      sprintf (label, "%s %s", rna_subtype_list [rna_subtype],
               rna_sample_field_list[rna_field_type]);
    }
  }
  else if (rna_field_type < num_rna_sample_fields + num_gene_fields)
  {
    label = (CharPtr) MemNew ((StringLen (rna_subtype_list [rna_subtype])
                              + StringLen (gene_field_list[rna_field_type - num_rna_sample_fields])
                              + 7) * sizeof (Char));
    if (label != NULL)
    {
      sprintf (label, "%s Gene %s", rna_subtype_list [rna_subtype],
               gene_field_list[rna_field_type - num_rna_sample_fields]);
    }
  }
  return label;
}

static DialoG GetRNAQualSample (GrouP h, Uint2 entityID)
{
  DialoG        d;
  SetSampleData ssd;
  Int2          rna_subtype, field_type;
  Int4          list_index;      

  d = SampleDialog (h);
  ssd.fieldstring_func = GetRNASampleFieldString;
  ssd.descrstring_func = NULL;
  ssd.entityID = entityID;
  
  /* construct RNA field list for separate types of RNAs */
  ssd.free_vn_proc = NULL;
  ssd.copy_vn_proc = IntValNodeCopy;
  ssd.match_vn_proc = IntValNodeMatch;
  ssd.label_vn_proc = GetRNAQualSampleName;
  
  ssd.field_list = NULL;
  for (rna_subtype = 0; rna_subtype < num_rna_subtypes; rna_subtype++)
  {
    for (field_type = 0; field_type < num_rna_sample_fields; field_type++)
    {
      list_index = (rna_subtype * (num_rna_sample_fields + num_gene_fields)) + field_type;
      ValNodeAddInt (&ssd.field_list, 0, list_index + 1);
    }
    for (field_type = 0; field_type < num_gene_fields; field_type ++)
    {
      list_index = (rna_subtype * (num_rna_sample_fields + num_gene_fields))
                    + field_type + num_rna_sample_fields;
      ValNodeAddInt (&ssd.field_list, 0, list_index + 1);
    }
  }
  ssd.fsp = NULL;
  PointerToDialog (d, &ssd);
  
  /* now free up field list (SampleDialog maintains its own copy */
  ssd.field_list = ValNodeFree (ssd.field_list);
  
  return d;
}

typedef struct aecrrnaqual
{
  ValNodePtr   field_list;
  ValNodePtr   field_list_to;
  RnaTypePtr   rna_type;
  EditApplyPtr edit_apply;
  ValNodePtr   codons;
  SeqLocPtr    anticodon;
  TextPortionXPtr text_portion;
  Boolean        leave_on_original;
  Boolean        remove_parsed;
} AECRRNAQualData, PNTR AECRRNAQualPtr;

static AECRRNAQualPtr AECRRNAQualFree (AECRRNAQualPtr ap)
{
  if (ap != NULL)
  {
    ap->field_list = ValNodeFree (ap->field_list);
    ap->field_list_to = ValNodeFree (ap->field_list_to);
    ap->rna_type = RnaTypeFree (ap->rna_type);
    ap->edit_apply = EditApplyFree (ap->edit_apply);
    ap->codons = ValNodeFreeData (ap->codons);
    ap = MemFree (ap);
  }
  return ap;
}

typedef struct aecrrnaqualdlg
{
  DIALOG_MESSAGE_BLOCK
  DialoG               field_list;
  DialoG               field_list_to;
  DialoG               rna_type;
  DialoG               edit_apply;
  DialoG               codons;
  DialoG               ncrna_class;
  DialoG               text_portion;
  ButtoN               leave_on_original;
  ButtoN               remove_parsed;

  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_userdata;
  Uint2                entityID;
  
  /* The following is used to create the anticodon location dialog */
  FeatureForm          ff;
  DialoG               anticodon;
} AECRRNAQualDlgData, PNTR AECRRNAQualDlgPtr;

static void ClearTextAECRRNAQualDlg (AECRRNAQualDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->edit_apply, NULL);
    PointerToDialog (dlg->codons, NULL);
    PointerToDialog (dlg->ncrna_class, NULL);
  }
}

static void ResetAECRRNAQualDlg (AECRRNAQualDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->field_list, NULL);
    PointerToDialog (dlg->field_list_to, NULL);
    SendMessageToDialog (dlg->field_list, NUM_VIB_MSG + 1);
    PointerToDialog (dlg->rna_type, NULL);
    PointerToDialog (dlg->text_portion, NULL);
    SafeSetStatus (dlg->leave_on_original, FALSE);
    SafeSetStatus (dlg->remove_parsed, FALSE);
    ClearTextAECRRNAQualDlg (dlg);
  }
}

static void AECRRNAQualToDialog (DialoG d, Pointer userdata)
{
  AECRRNAQualDlgPtr dlg;
  AECRRNAQualPtr    data;
  
  dlg = (AECRRNAQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  
  ResetAECRRNAQualDlg (dlg);
  data = (AECRRNAQualPtr) userdata;
  if (data != NULL)
  {
    PointerToDialog (dlg->field_list, data->field_list);
    PointerToDialog (dlg->field_list_to, data->field_list_to);
    PointerToDialog (dlg->rna_type, data->rna_type);
    PointerToDialog (dlg->edit_apply, data->edit_apply);
    PointerToDialog (dlg->codons, data->codons);
    if (data->field_list != NULL && data->field_list->data.intvalue == RNAFIELD_NCRNA_CLASS) {
      if (data->edit_apply == NULL) {
        PointerToDialog (dlg->ncrna_class, NULL);
      } else {
        PointerToDialog (dlg->ncrna_class, data->edit_apply->apply_txt);
      }
    }
    PointerToDialog (dlg->anticodon, data->anticodon);
    PointerToDialog (dlg->text_portion, data->text_portion);
    SafeSetStatus (dlg->leave_on_original, data->leave_on_original);
    SafeSetStatus (dlg->remove_parsed, data->remove_parsed);
  }
}

static Pointer DialogToAECRRNAQual (DialoG d)
{
  AECRRNAQualDlgPtr dlg;
  AECRRNAQualPtr    data;
  
  dlg = (AECRRNAQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  data = (AECRRNAQualPtr) MemNew (sizeof (AECRRNAQualData));
  if (data != NULL)
  {
    data->field_list = DialogToPointer (dlg->field_list);
    data->field_list_to = DialogToPointer (dlg->field_list_to);
    data->rna_type = DialogToPointer (dlg->rna_type);
    if (data->field_list != NULL && data->field_list->data.intvalue == RNAFIELD_NCRNA_CLASS
        && dlg->ncrna_class != NULL) {
      data->edit_apply = EditApplyNew ();
      data->edit_apply->apply_txt = DialogToPointer (dlg->ncrna_class);
    } else {
      data->edit_apply = DialogToPointer (dlg->edit_apply);
    }
    data->codons = DialogToPointer (dlg->codons);
    data->anticodon = DialogToPointer (dlg->anticodon);
    data->text_portion = DialogToPointer (dlg->text_portion);
    data->leave_on_original = (dlg->leave_on_original == NULL) ? FALSE : GetStatus (dlg->leave_on_original);
    data->remove_parsed = (dlg->remove_parsed == NULL) ? FALSE : GetStatus (dlg->remove_parsed);
  }
  return data;
}

static ValNodePtr GetRNAFeatureList (SeqEntryPtr sep, FilterSetPtr fsp, RnaTypePtr rtp);
static GetSamplePtr GetRnaExistingTextForList (ValNodePtr rna_list, ValNodePtr requested_field);

static CharPtr 
RNAQualAutopopulate 
(RnaTypePtr rna_type,
 ValNodePtr field_list,
 Uint2      entityID)
{
  SeqEntryPtr  sep;
  GetSamplePtr gsp;
  ValNodePtr   rna_list;
  CharPtr      rval = NULL;

  sep = GetTopSeqEntryForEntityID (entityID);
  rna_list = GetRNAFeatureList (sep, NULL, rna_type);
  gsp = GetRnaExistingTextForList (rna_list, field_list);
  if (gsp != NULL && !StringHasNoText (gsp->sample_text))
  {
    rval = gsp->sample_text;
    gsp->sample_text = NULL;
  }
  GetSampleFree (gsp);
  rna_list = ValNodeFree (rna_list);
  return rval;
}

static void RNAQualChangeNotify (Pointer userdata)
{
  AECRRNAQualDlgPtr  dlg;
  ValNodePtr         field_list = NULL;
  RnaTypePtr         rna_type;
  EditApplyPtr       eap;
  CharPtr            ncrna_class;

  dlg = (AECRRNAQualDlgPtr) userdata;
  if (dlg == NULL)
  {
    return;
  }
  
  field_list = DialogToPointer (dlg->field_list);
  if (field_list == NULL)
  {
    Hide (dlg->edit_apply);
    Hide (dlg->anticodon);
    Hide (dlg->codons);
    Hide (dlg->ncrna_class);
  }  
  else if (field_list->data.intvalue == RNAFIELD_CODONS_RECOGNIZED)
  {
    Show (dlg->codons);
    Hide (dlg->edit_apply);
    Hide (dlg->anticodon);
    Hide (dlg->ncrna_class);
  }
  else if (field_list->data.intvalue == RNAFIELD_ANTICODON)
  {
    Show (dlg->anticodon);
    Hide (dlg->edit_apply);
    Hide (dlg->codons);
    Hide (dlg->ncrna_class);
  }
  else if (field_list->data.intvalue == RNAFIELD_NCRNA_CLASS && dlg->ncrna_class != NULL)
  {
    Show (dlg->ncrna_class);
    Hide (dlg->codons);
    Hide (dlg->edit_apply);
    Hide (dlg->anticodon);
    /* autopopulate */
    rna_type = DialogToPointer (dlg->rna_type);
    ncrna_class = RNAQualAutopopulate (rna_type, field_list, dlg->entityID);
    PointerToDialog (dlg->ncrna_class, ncrna_class);
    ncrna_class = MemFree (ncrna_class);
  }
  else
  {
    Show (dlg->edit_apply);
    Hide (dlg->codons);
    Hide (dlg->anticodon);
    Hide (dlg->ncrna_class);

    /* autopopulate text fields */
    if (dlg->edit_apply != NULL)
    {    
      rna_type = DialogToPointer (dlg->rna_type);
      eap = DialogToPointer (dlg->edit_apply);
      eap->apply_txt = MemFree (eap->apply_txt);
      eap->apply_txt = RNAQualAutopopulate (rna_type, field_list, dlg->entityID);
      PointerToDialog (dlg->edit_apply, eap);
      EditApplyFree (eap);    
      rna_type = RnaTypeFree (rna_type);
    }
  }
  
  field_list = ValNodeFree (field_list);
  
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }  
}

static void AECRRNAQualMessage (DialoG d, Int2 mssg)

{
  AECRRNAQualDlgPtr  dlg;

  dlg = (AECRRNAQualDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) 
    {
      case VIB_MSG_INIT :
        /* reset list */
        ResetAECRRNAQualDlg (dlg);
        break;
      case VIB_MSG_ENTER :
        Select (dlg->rna_type);
        break;
      case AECR_VIB_MSG_SET_DEFAULT :
        SendMessageToDialog (dlg->field_list, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->rna_type, AECR_VIB_MSG_SET_DEFAULT);
        ClearTextAECRRNAQualDlg (dlg);
        break;
      case AECR_VIB_MSG_CLEAR_TEXT :
        ClearTextAECRRNAQualDlg (dlg);
        break;
      case AECR_VIB_MSG_AUTOPOPULATE:
        RNAQualChangeNotify (dlg);
        break; 
      default :
        break;
    }
  }
}

static ValNodePtr TestAECRRNAQual (DialoG d)
{
  AECRRNAQualDlgPtr dlg;
  ValNodePtr         total_err_list = NULL;
  ValNodePtr         field_list;
  
  dlg = (AECRRNAQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }

  total_err_list = TestDialog (dlg->rna_type);
  
  ValNodeLink (&total_err_list, TestDialog (dlg->field_list));
      
  field_list = DialogToPointer (dlg->field_list);
  if (field_list != NULL) {
    if (field_list->data.intvalue == RNAFIELD_NCRNA_CLASS && dlg->ncrna_class != NULL) {
      ValNodeLink (&total_err_list, TestDialog (dlg->ncrna_class));
    } else if (field_list->data.intvalue != RNAFIELD_CODONS_RECOGNIZED) {
      ValNodeLink (&total_err_list, TestDialog (dlg->edit_apply));
    }
  }
      
  return total_err_list;
}


static void AECRRNAQualButtonChange (ButtoN b)
{
  AECRRNAQualDlgPtr dlg;

  dlg = (AECRRNAQualDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

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


static DialoG RNAQualListDialog
(GrouP                    h,
 Int4                     action_choice, 
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  AECRRNAQualDlgPtr dlg;
  GrouP              p, g1, g2 = NULL;
  SeqEntryPtr        sep;
  
  dlg = (AECRRNAQualDlgPtr) MemNew (sizeof (AECRRNAQualDlgData));
  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 = AECRRNAQualToDialog;
  dlg->fromdialog = DialogToAECRRNAQual;
  dlg->dialogmessage = AECRRNAQualMessage;
  dlg->testdialog = TestAECRRNAQual;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->entityID = entityID;


  if (action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE)
  {
    g1 = HiddenGroup (p, 3, 0, NULL);
    StaticPrompt (g1, "", 0, dialogTextHeight, systemFont, 'l');
    StaticPrompt (g1, "From", 0, dialogTextHeight, systemFont, 'l');
    StaticPrompt (g1, "To", 0, dialogTextHeight, systemFont, 'l');
    dlg->rna_type = RnaTypeDialog (g1, TRUE, RNAQualChangeNotify, dlg);

    dlg->field_list = RNAFieldSelectionDialog (g1, FALSE, 
                                          change_notify, 
                                          change_userdata);
    dlg->field_list_to = RNAFieldSelectionDialog (g1, FALSE, 
                                             change_notify, 
                                             change_userdata);
    if (action_choice == AECR_CONVERT)
    {
      dlg->leave_on_original = CheckBox (p, "Leave on original", AECRRNAQualButtonChange);
      SetObjectExtra (dlg->leave_on_original, dlg, NULL);
    }
  }
  else
  {
    g1 = HiddenGroup (p, 2, 0, NULL);
    StaticPrompt (g1, "", 0, dialogTextHeight, systemFont, 'l');
    StaticPrompt (g1, "RNA Field", 0, dialogTextHeight, systemFont, 'l');
    dlg->rna_type = RnaTypeDialog (g1, TRUE, RNAQualChangeNotify, dlg);
    if (action_choice == AECR_REMOVE)
    {
      dlg->field_list = RNARemoveFieldSelectionDialog (g1, TRUE, 
                                            RNAQualChangeNotify, dlg);
    }
    else
    {
      dlg->field_list = RNAFieldSelectionDialog (g1, FALSE, 
                                            RNAQualChangeNotify, 
                                            dlg);
    }
  }

  if (action_choice == AECR_APPLY)
  {
    g2 = HiddenGroup (p, 0, 0, NULL);
    dlg->edit_apply = EditApplyDialog (g2, eEditApplyChoice_Apply, "New Text", NULL,
                                       change_notify, change_userdata);
  
    dlg->codons = CreateVisibleStringDialog (g2, 3, -1, 4);

    dlg->ncrna_class = CreatencRNAClassDialog (g2, FALSE, change_notify, change_userdata);
  
    sep = GetTopSeqEntryForEntityID (entityID);
  
    dlg->anticodon = NULL;
  /*
    dlg->anticodon = CreateIntervalEditorDialogEx (g2, NULL, 2, 2, sep, TRUE, FALSE,
                                                    TRUE, TRUE, FALSE,
                                                    &(dlg->ff),
                                                    NULL); */

    AlignObjects (ALIGN_CENTER, (HANDLE) dlg->edit_apply,
                                (HANDLE) dlg->codons,
                                (HANDLE) dlg->ncrna_class,
                                (HANDLE) dlg->anticodon,
                                NULL);
  }
  else if (action_choice == AECR_EDIT)
  {
    g2 = HiddenGroup (p, 1, 0, NULL);
    dlg->edit_apply = EditApplyDialog (g2, eEditApplyChoice_Edit,
                                       "New Text", NULL,
                                       change_notify, change_userdata);
    dlg->text_portion = NULL;
    dlg->remove_parsed = NULL;
  }
  else if (action_choice == AECR_PARSE)
  {
    g2 = HiddenGroup (p, -1, 0, NULL);
    dlg->edit_apply = NULL;
    dlg->text_portion = TextPortionXDialog(p);
    dlg->remove_parsed = CheckBox (p, "Remove from parsed field", SimpleAECRButtonChange);
    SetObjectExtra (dlg->remove_parsed, dlg, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) dlg->edit_apply, (HANDLE) dlg->remove_parsed, NULL);
  }
  else
  {
    dlg->edit_apply = NULL;
    dlg->text_portion = NULL;
    dlg->remove_parsed = NULL;
  }
  

  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) g2, NULL);
  
  RNAQualChangeNotify (dlg);
  
  return (DialoG) p;
}


static void ApplyRNACodonsCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  ValNodePtr codons;
  RnaRefPtr  rrp;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA)
  {
    return;
  }
  
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  codons = (ValNodePtr) userdata;
  if (rrp != NULL && rrp->type == 3 && rrp->ext.choice == 2) 
  {
    AddCodonListTotRNA (rrp->ext.value.ptrvalue, codons);
  }
}

static void ApplyRNAAnticodonCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  RnaRefPtr  rrp;
  tRNAPtr    trp;
  SeqLocPtr  anticodon_loc, slp;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA 
      || sfp->data.value.ptrvalue == NULL)
  {
    return;
  }
  
  anticodon_loc = (SeqLocPtr) userdata;

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;

  if (rrp->ext.choice == 2) 
  {
    trp = (tRNAPtr) rrp->ext.value.ptrvalue;
    if (trp != NULL) 
    {
      trp->anticodon = SeqLocFree (trp->anticodon);
      if (anticodon_loc != NULL)
      {
        slp = (SeqLocPtr) AsnIoMemCopy (anticodon_loc,
                                        (AsnReadFunc) SeqLocAsnRead,
                                        (AsnWriteFunc) SeqLocAsnWrite);

        slp = SeqLocReplaceID (slp, SeqLocId (sfp->location));
        trp->anticodon = slp;
      }
    }
  }
}


static void CollectRNAFeaturesCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{  
  if (sfp != NULL && sfp->data.choice == SEQFEAT_RNA && userdata != NULL)
  {
    ValNodeAddPointer ((ValNodePtr PNTR) userdata, OBJ_SEQFEAT, sfp);
  }
  
}


static ValNodePtr GetRNAFeatureList (SeqEntryPtr sep, FilterSetPtr fsp, RnaTypePtr rtp)
{
  ValNodePtr rna_list = NULL, vnp_rna;
  ValNodePtr not_match;
  SeqFeatPtr sfp;
  Boolean    matches;

  if (sep == NULL) return NULL;

  OperateOnSeqEntryConstrainedObjects (sep, fsp, CollectRNAFeaturesCallback,
                                       NULL, SEQFEAT_RNA, 0, 0, &rna_list);  

  if (rtp != NULL)
  {
    for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
    {  
      matches = FALSE;
      sfp = vnp_rna->data.ptrvalue;
      if (!MatchesRnaType (sfp, rtp))
      {
        vnp_rna->choice = 0;
      }
    }

    not_match = ValNodeExtractList (&rna_list, 0);
    not_match = ValNodeFree (not_match);
  }
  return rna_list;
}


static GetSamplePtr GetRnaExistingTextForList (ValNodePtr rna_list, ValNodePtr requested_field)
{
  GetSamplePtr gsp;
  ValNodePtr   vnp;
  SeqFeatPtr   sfp;

  if (rna_list == NULL || requested_field == NULL) return NULL;

  gsp = GetSampleNew ();
  gsp->sample_text = NULL;
  gsp->fieldstring_func = GetRNAFieldString;
  gsp->descrstring_func = NULL;
  gsp->free_vn_proc = NULL;
  gsp->copy_vn_proc = IntValNodeCopy;
  gsp->requested_field = (gsp->copy_vn_proc) (requested_field);
  gsp->num_found = 0;
  gsp->all_same = TRUE;

  for (vnp = rna_list; vnp != NULL; vnp = vnp->next)
  {  
    sfp = vnp->data.ptrvalue;
    GetSampleFeatureCallback (sfp, gsp, NULL);
  }
  return gsp;
}


static Boolean ApplyToRNAList (ValNodePtr rna_list, AECRRNAQualPtr rp, FilterSetPtr fsp)
{
  ValNodePtr      vnp_rna;
  SeqFeatPtr      sfp;
  ApplyValueData  avd;
  Boolean         rval = TRUE;
  GetSamplePtr    gsp;

  if (rna_list == NULL || rp == NULL || rp->field_list == NULL)
  {
    return FALSE;
  }

  if (rp->field_list->data.intvalue == RNAFIELD_CODONS_RECOGNIZED)
  {
    for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
    {
      sfp = vnp_rna->data.ptrvalue;
      if (sfp->idx.subtype == FEATDEF_tRNA)
      {
        ApplyRNACodonsCallback (sfp, rp->codons, fsp);
      }
    }
  }
  else if (rp->field_list->data.intvalue == RNAFIELD_ANTICODON)
  {
    for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
    {
      sfp = vnp_rna->data.ptrvalue;
      if (sfp->idx.subtype == FEATDEF_tRNA)
      {
        ApplyRNAAnticodonCallback (sfp, rp->anticodon, fsp);
      }
    }
  }
  else
  {
    avd.field_list = rp->field_list;
    avd.where_to_replace = EditApplyFindLocation_anywhere;
    
    /* get handling for existing text */
    gsp = GetRnaExistingTextForList (rna_list, rp->field_list);
    avd.etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, FALSE);
    gsp = GetSampleFree (gsp);
  
    if (avd.etp != NULL 
        && avd.etp->existing_text_choice == eExistingTextChoiceCancel)
    {
      rval = FALSE;
    }

    if (rval)
    {
      AddEditApplyDataToApplyValue (AECR_APPLY, rp->edit_apply, &avd);
     
      for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
      {
        sfp = vnp_rna->data.ptrvalue;
        SetRNAFieldString (sfp, &avd, fsp);
      }
      avd.text_to_replace = MemFree (avd.text_to_replace);
      avd.new_text = MemFree (avd.new_text);
    }
    avd.etp = MemFree (avd.etp);
  }
  return rval;
}


static Boolean 
RNAQualConvertSwapParseList
(ValNodePtr      rna_list,
 AECRRNAQualPtr  rp,
 Int4            action_choice,
 FilterSetPtr    fsp)
{
  ConvertFieldData       cfd;
  ConversionConflictData ccd;
  ValNodePtr             vnp_rna;
  ApplyValueData         avd;
  Boolean                rval = TRUE;
  SeqFeatPtr             sfp;
  
  if (rna_list == NULL
      || rp == NULL
      || action_choice < AECR_EDIT 
      || action_choice > NUM_AECR
      || rp->field_list == NULL
      || rp->field_list_to == NULL)
  {
    return FALSE;
  }

  avd.where_to_replace = EditApplyFindLocation_anywhere;
  
  cfd.src_field_list = rp->field_list;
  cfd.dst_field_list = rp->field_list_to;
  cfd.etp = NULL;
  cfd.convert_type = CONVERT_TYPE_MOVE;
  cfd.get_str_func = GetRNAFieldString;
  cfd.set_str_func = SetRNAFieldString;
  cfd.remove_str_func = RemoveRNAField;
  cfd.get_d_str_func = NULL;
  cfd.set_d_str_func = NULL;
  cfd.remove_d_str_func = NULL;
  cfd.fsp = fsp;
  cfd.strip_name_from_text = FALSE;
  cfd.name_field_func = NULL;
  cfd.text_portion = rp->text_portion;
  cfd.remove_parsed = rp->remove_parsed;

  if (action_choice == AECR_CONVERT)
  {
    if (rp->leave_on_original)
    {
      cfd.convert_type = CONVERT_TYPE_COPY;
    }
    else
    {
      cfd.convert_type = CONVERT_TYPE_MOVE;
    }
  }
  else if (action_choice == AECR_SWAP)
  {
    cfd.convert_type = CONVERT_TYPE_SWAP;
  }
  else if (action_choice == AECR_PARSE)
  {
    cfd.convert_type = CONVERT_TYPE_PARSE;
  }
  
  if (cfd.convert_type == CONVERT_TYPE_MOVE || cfd.convert_type == CONVERT_TYPE_COPY || cfd.convert_type == CONVERT_TYPE_PARSE)
  {
    /* get existing text sample */
    ccd.cfp = &cfd;
    ccd.gsp = GetSampleNew();

    for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
    {
      sfp = vnp_rna->data.ptrvalue;
      GetConversionConflictsFeatureCallback (sfp, &ccd, fsp);
    }
    cfd.etp = GetExistingTextHandlerInfo (ccd.gsp == NULL ? 0 : ccd.gsp->num_found, FALSE);
    ccd.gsp = GetSampleFree (ccd.gsp);
    if (cfd.etp != NULL && cfd.etp->existing_text_choice == eExistingTextChoiceCancel)
    {
      rval = FALSE;
    }
  }
  else
  {
    cfd.etp = NULL;
  }  

  if (rval)
  {
    for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
    {
      sfp = vnp_rna->data.ptrvalue;
      ConvertFeatureFieldCallback (sfp, &cfd, fsp);
    }
  }

  return rval;
}


static Boolean RNAQualEditRemoveList 
(ValNodePtr    rna_list,
 AECRRNAQualPtr  rp,
 Int4          action_choice,
 FilterSetPtr  fsp)
{
  SeqFeatPtr       sfp;
  ValNodePtr       vnp_rna;
  ApplyValueData   avd;
  Boolean          rval = TRUE;
  
  if (rna_list == NULL
      || rp == NULL
      || (action_choice != AECR_EDIT && action_choice != AECR_REMOVE)
      || rp->field_list == NULL)
  {
    return FALSE;
  }

  avd.where_to_replace = EditApplyFindLocation_anywhere;
  avd.field_list = rp->field_list;
  avd.etp = NULL;
  AddEditApplyDataToApplyValue (action_choice, rp->edit_apply, &avd);

  for (vnp_rna = rna_list; vnp_rna != NULL; vnp_rna = vnp_rna->next)
  {
    sfp = vnp_rna->data.ptrvalue;
    if (action_choice == AECR_EDIT)
    {
      SetRNAFieldString (sfp, &avd, fsp);
    }
    else if (action_choice == AECR_REMOVE)
    {
      RemoveRNAField (sfp, &avd, fsp);
    }
  }
    
  avd.text_to_replace = MemFree (avd.text_to_replace);
  avd.new_text = MemFree (avd.new_text);
  return TRUE;
}




static Boolean AcceptRNAQualApplyEditRemoveConvert (Pointer userdata)
{
  ApplyEditConvertRemovePtr ap;
  AECRRNAQualPtr            rp;
  Int4                      action_choice;
  Boolean                   rval = TRUE;
  FilterSetPtr              fsp;
  ValNodePtr                rna_list;
  SeqEntryPtr               sep;

  ap = (ApplyEditConvertRemovePtr) userdata;
  if (ap == NULL)
  {
    return FALSE;
  }
  
  WatchCursor ();
  Update ();
  
  if (ap->crippled)
  {
    action_choice = ap->crippled_action;
  }
  else
  {
    action_choice = GetValue (ap->action_popup);
  }

  rp = (AECRRNAQualPtr) DialogToPointer (ap->aecr_pages[action_choice - 1]);
  if (rp == NULL) return FALSE;

  fsp = (FilterSetPtr) DialogToPointer (ap->constraints);
  sep = GetTopSeqEntryForEntityID (ap->input_entityID);
  rna_list = GetRNAFeatureList (sep, fsp, rp->rna_type);
  
  if (action_choice == AECR_APPLY)
  {
    rval = ApplyToRNAList (rna_list, rp, fsp);
  }
  else if (action_choice == AECR_CONVERT || action_choice == AECR_SWAP
           || action_choice == AECR_PARSE)
  {
    rval = RNAQualConvertSwapParseList (rna_list, rp, action_choice, fsp);
  }
  else if (action_choice > AECR_APPLY && action_choice <= NUM_AECR)
  {
    rval = RNAQualEditRemoveList (rna_list, rp, action_choice, fsp);
  }
    
  if (rval)
  {
    ObjMgrSetDirtyFlag (ap->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, ap->input_entityID, 0, 0);
  }
  ArrowCursor ();
  Update (); 
  
  return rval;
}

static void 
RNAQualApplyEditRemoveConvert 
(IteM    i, 
 Int4    first_action_choice,
 Boolean crippled)
{
  ApplyEditConvertRemoveCombo (i, first_action_choice, crippled,
                               "RNA Qualifiers",
                               RNAQualListDialog, TRUE, GetRNAQualSample,
                               TRUE, FALSE, TRUE, FALSE, FALSE, "Where feature text",
                               AcceptRNAQualApplyEditRemoveConvert, NULL, NULL,
                               CheckFeaturesForPresample);
}

extern void PublicApplyRNAQual (IteM i)
{
  RNAQualApplyEditRemoveConvert (i, AECR_APPLY, TRUE);
}

extern void EditRNAQual (IteM i)
{
  RNAQualApplyEditRemoveConvert (i, AECR_EDIT, FALSE);
}

extern void PublicEditRNAQual (IteM i)
{
  RNAQualApplyEditRemoveConvert (i, AECR_EDIT, TRUE);
}

extern void ConvertRNAQual (IteM i)
{
  RNAQualApplyEditRemoveConvert (i, AECR_CONVERT, FALSE);
}

extern void SwapRNAQual (IteM i)
{
  RNAQualApplyEditRemoveConvert (i, AECR_SWAP, FALSE);
}

extern void RemoveRNAQual (IteM i)
{
  RNAQualApplyEditRemoveConvert (i, AECR_REMOVE, FALSE);
}


typedef struct removegbqualdlg 
{
  DIALOG_MESSAGE_BLOCK
  DialoG          field_list;
  DialoG          subtype_list;
  GrouP           list_or_txt_grp;
  DialoG          qual_name_constraint;
  
  /* These are necessary to produce the SimpleAECR structure for output */
  FreeValNodeProc free_field_vn_proc;
  FreeValNodeProc free_subtype_vn_proc;
  
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
  Uint2                    entityID;
  
} RemoveGBQualDlgData, PNTR RemoveGBQualDlgPtr;


typedef struct removegbqual
{
  ValNodePtr          field_list;
  ValNodePtr          subtype_list;
  StringConstraintXPtr scp;
  Boolean             use_field_list;
} RemoveGBQualData, PNTR RemoveGBQualPtr;

static RemoveGBQualPtr RemoveGBQualFree (RemoveGBQualPtr ap)
{
  if (ap != NULL)
  {
    ap->field_list = ValNodeFree (ap->field_list);
    ap->subtype_list = ValNodeFree (ap->subtype_list);
    ap->scp = StringConstraintXFree (ap->scp);
    ap = MemFree (ap);
  }
  return ap;
}


static void ClearTextRemoveGBQualDlg (RemoveGBQualDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->qual_name_constraint, NULL);    
  }
}

static void ResetRemoveGBQualDlg (RemoveGBQualDlgPtr dlg)
{
  if (dlg != NULL)
  {
    PointerToDialog (dlg->field_list, NULL);
    SendMessageToDialog (dlg->field_list, NUM_VIB_MSG + 1);
    PointerToDialog (dlg->subtype_list, NULL);
    ClearTextRemoveGBQualDlg (dlg);
    SetValue (dlg->list_or_txt_grp, 1);
    Enable (dlg->subtype_list);
    Disable (dlg->qual_name_constraint);
  }
}


static void RemoveGBQualToDialog (DialoG d, Pointer userdata)
{
  RemoveGBQualDlgPtr dlg;
  RemoveGBQualPtr    data;
  
  dlg = (RemoveGBQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
    
  data = (RemoveGBQualPtr) userdata;
  if (data == NULL)
  {
    PointerToDialog (dlg->field_list, NULL);
    PointerToDialog (dlg->subtype_list, NULL);
    ClearTextRemoveGBQualDlg (dlg);
    SetValue (dlg->list_or_txt_grp, 1);
    Enable (dlg->subtype_list);
    Disable (dlg->qual_name_constraint);
  }
  else
  {
    PointerToDialog (dlg->field_list, data->field_list);
    PointerToDialog (dlg->subtype_list, data->subtype_list);
    PointerToDialog (dlg->qual_name_constraint, data->scp);
    if (data->use_field_list)
    {
      SetValue (dlg->list_or_txt_grp, 1);
      Enable (dlg->subtype_list);
      Disable (dlg->qual_name_constraint);
    }
    else
    {
      SetValue (dlg->list_or_txt_grp, 2);
      Disable (dlg->subtype_list);
      Enable (dlg->qual_name_constraint);
    }
  }
}


static Pointer DialogToRemoveGBQual (DialoG d)
{
  RemoveGBQualDlgPtr dlg;
  RemoveGBQualPtr    data;
  
  dlg = (RemoveGBQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  data = (RemoveGBQualPtr) MemNew (sizeof (RemoveGBQualData));
  if (data != NULL)
  {
    data->field_list = DialogToPointer (dlg->field_list);
    data->subtype_list = DialogToPointer (dlg->subtype_list);
    data->scp = DialogToPointer (dlg->qual_name_constraint);
    if (GetValue (dlg->list_or_txt_grp) == 1)
    {
      data->use_field_list = TRUE;
    }
    else
    {
      data->use_field_list = FALSE;
    }
  }
  return data;
}


static void RemoveGBQualMessage (DialoG d, Int2 mssg)

{
  RemoveGBQualDlgPtr  dlg;

  dlg = (RemoveGBQualDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    switch (mssg) 
    {
      case VIB_MSG_INIT :
        /* reset list */
        ResetRemoveGBQualDlg (dlg);
        break;
      case VIB_MSG_ENTER :
        if (dlg->subtype_list != NULL)
        {
          Select (dlg->subtype_list);
        }
        else
        {
          Select (dlg->field_list);
        }
        break;
      case AECR_VIB_MSG_SET_DEFAULT :
        SendMessageToDialog (dlg->field_list, AECR_VIB_MSG_SET_DEFAULT);
        SendMessageToDialog (dlg->subtype_list, AECR_VIB_MSG_SET_DEFAULT);
        ClearTextRemoveGBQualDlg (dlg);
        break;
      case AECR_VIB_MSG_CLEAR_TEXT :
        ClearTextRemoveGBQualDlg (dlg);
        break;
      case AECR_VIB_MSG_AUTOPOPULATE:
        break; 
      default :
        break;
    }
  }
}

static ValNodePtr TestRemoveGBQual (DialoG d)
{
  RemoveGBQualDlgPtr dlg;
  ValNodePtr         err_list = NULL;
  ValNodePtr         total_err_list = NULL;
  
  dlg = (RemoveGBQualDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }

  total_err_list = TestDialog (dlg->subtype_list);

  if (GetValue (dlg->list_or_txt_grp) == 1)
  {
    err_list = TestDialog (dlg->field_list);
    total_err_list = ValNodeAppend (total_err_list, err_list);
  }      
      
  return total_err_list;
}


static void GBQualChangeNotify (GrouP b)
{
  RemoveGBQualDlgPtr dlg;

  dlg = (RemoveGBQualDlgPtr) GetObjectExtra (b);
  if (dlg == NULL)
  {
    return;
  }
  
  if (GetValue (dlg->list_or_txt_grp) == 1)
  {
    Enable (dlg->field_list);
    Disable (dlg->qual_name_constraint);
  }
  else
  {
    Disable (dlg->field_list);
    Enable (dlg->qual_name_constraint);
  }
    
  if (dlg->change_notify != NULL)
  {
    (dlg->change_notify)(dlg->change_userdata);
  }  
}


static DialoG GBQualRemoveDialog
(GrouP                    h,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  RemoveGBQualDlgPtr dlg;
  GrouP              p, g1;
  SeqEntryPtr        sep;
  
  dlg = (RemoveGBQualDlgPtr) MemNew (sizeof (RemoveGBQualDlgData));
  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 = RemoveGBQualToDialog;
  dlg->fromdialog = DialogToRemoveGBQual;
  dlg->dialogmessage = RemoveGBQualMessage;
  dlg->testdialog = TestRemoveGBQual;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->entityID = entityID;
  sep = GetTopSeqEntryForEntityID(entityID);
  
  g1 = HiddenGroup (p, 2, 0, NULL);
  StaticPrompt (g1, "Feature", 0, dialogTextHeight, systemFont, 'l');
  StaticPrompt (g1, "Qualifier", 0, dialogTextHeight, systemFont, 'l');
  dlg->subtype_list = FeatureSelectionDialogEx (g1, TRUE, sep,
                                                 change_notify,
                                                 change_userdata);
  dlg->list_or_txt_grp = HiddenGroup (g1, 2, 0, GBQualChangeNotify);
  SetObjectExtra (dlg->list_or_txt_grp, dlg, NULL);
  RadioButton (dlg->list_or_txt_grp, "Choose from Standard List");
  RadioButton (dlg->list_or_txt_grp, "Select Qual Name Constraint");
  dlg->field_list = GBQualSelectionDialog (dlg->list_or_txt_grp, FALSE, 
                                                change_notify,
                                                change_userdata);
  dlg->qual_name_constraint = StringConstraintDialogX (dlg->list_or_txt_grp, "Where qualifier name contains", FALSE);
  SetValue (dlg->list_or_txt_grp, 1);
  Disable (dlg->qual_name_constraint);
    
  return (DialoG) p;
}


static DialoG 
GBQualListDialog
(GrouP                    h,
 Int4                     action_choice,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 Uint2                    entityID)
{
  if (action_choice == AECR_REMOVE)
  {
    return GBQualRemoveDialog (h, change_notify, change_userdata, entityID);
  }
  else
  {
    return SimpleAECRDialog (h, action_choice, change_notify, change_userdata,
                             NULL, ValNodeSimpleDataFree,
                             GBQualSelectionDialog, FeatureSelectionDialogEx, NULL,
                             "Qualifier", "Feature", TRUE, entityID);
  }
}

static GetSamplePtr 
GetGBQualExistingText 
(SeqEntryPtr  sep,
 ValNodePtr   feature_type_list,
 ValNodePtr   requested_field,
 FilterSetPtr fsp)
{
  ValNodePtr   vnp;
  GetSamplePtr gsp;
  
  if (sep == NULL || feature_type_list == NULL)
  {
    return NULL;
  }
  
  gsp = (GetSamplePtr) MemNew (sizeof (GetSampleData));
  if (gsp == NULL)
  {
    return NULL;
  }
  gsp->sample_text = NULL;
  gsp->fieldstring_func = GetGBQualString;
  gsp->descrstring_func = NULL;
  gsp->free_vn_proc = NULL;
  gsp->copy_vn_proc = IntValNodeCopy;
    
  gsp->requested_field = (gsp->copy_vn_proc) (requested_field);
  gsp->num_found = 0;
  gsp->all_same = TRUE;
  for (vnp = feature_type_list; vnp != NULL; vnp = vnp->next)
  {
    OperateOnSeqEntryConstrainedObjects (sep, fsp, GetSampleFeatureCallback, 
                                         NULL,
                                         0, vnp->choice, 0, gsp);
  }
  return gsp;  
}


static Boolean AcceptGBQualApplyEditRemoveConvert (Pointer userdata)
{
  ApplyEditConvertRemovePtr ap;
  SimpleAECRPtr             gp = NULL;
  RemoveGBQualPtr           rgp = NULL;
  Int4                      action_choice;
  SeqEntryPtr               sep;
  ApplyValueData            avd;
  ConvertFieldData          cfd;
  FilterSetPtr              fsp;
  GetSamplePtr              gsp = NULL, gsp_new, gsp_sum;
  ValNodePtr                vnp, qual_vnp;
  Boolean                   rval = TRUE;

  ValNodePtr                field_list = NULL;
  ValNodePtr                subtype_list = NULL;
  EditApplyPtr              eap = NULL;


  ap = (ApplyEditConvertRemovePtr) userdata;
  if (ap == NULL)
  {
    return FALSE;
  }
  
  if (ap->crippled)
  {
    action_choice = ap->crippled_action;
  }
  else
  {
    action_choice = GetValue (ap->action_popup);
  }
  if (action_choice < 1 || action_choice > NUM_AECR)
  {
    return FALSE;
  }

  avd.where_to_replace = EditApplyFindLocation_anywhere;
  
  if (action_choice == AECR_REMOVE)
  {
    rgp = (RemoveGBQualPtr) DialogToPointer (ap->aecr_pages[action_choice - 1]);
    if (rgp == NULL)
    {
      return FALSE;
    }
    field_list = rgp->field_list;
    subtype_list = rgp->subtype_list;
  }
  else
  {
    gp = (SimpleAECRPtr) DialogToPointer (ap->aecr_pages[action_choice - 1]);
    if (gp == NULL)
    {
      return FALSE;
    }
    field_list = gp->field_list;
    subtype_list = gp->subtype_list;
    eap = gp->edit_apply;
  }

  WatchCursor ();
  Update ();
  
  sep = GetTopSeqEntryForEntityID (ap->input_entityID);
  if (subtype_list == NULL || (action_choice != AECR_REMOVE && field_list == NULL)
      || (action_choice == AECR_REMOVE && rgp->use_field_list && field_list == NULL)
      || ((action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE) 
           && gp->field_list_to == NULL))
  {
    gp = SimpleAECRFree (gp);
    rgp = RemoveGBQualFree (rgp);
    ArrowCursor ();
    Update ();
    return FALSE;
  }

  fsp = (FilterSetPtr) DialogToPointer (ap->constraints);
  
  if (action_choice == AECR_CONVERT || action_choice == AECR_SWAP || action_choice == AECR_PARSE)
  {
    cfd.src_field_list = gp->field_list;
    cfd.dst_field_list = gp->field_list_to;
    cfd.etp = NULL;
    cfd.get_str_func = GetGBQualString;
    cfd.set_str_func = SetGBQualString;
    cfd.remove_str_func = RemoveGBQualField;
    cfd.get_d_str_func = NULL;
    cfd.set_d_str_func = NULL;
    cfd.remove_d_str_func = NULL;
    cfd.fsp = fsp;
    cfd.strip_name_from_text = gp->strip_name_from_text;
    cfd.remove_parsed = gp->remove_parsed;
    cfd.name_field_func = NULL;
    cfd.text_portion = gp->text_portion;
    
    if (action_choice == AECR_CONVERT)
    {
      if (gp->leave_on_original)
      {
        cfd.convert_type = CONVERT_TYPE_COPY;
      }
      else
      {
        cfd.convert_type = CONVERT_TYPE_MOVE;
      }
    }
    else if (action_choice == AECR_SWAP)
    {
      cfd.convert_type = CONVERT_TYPE_SWAP;
    }
    else if (action_choice == AECR_PARSE)
    {
      cfd.convert_type = CONVERT_TYPE_PARSE;
    }
    
    if (cfd.convert_type == CONVERT_TYPE_MOVE || cfd.convert_type == CONVERT_TYPE_COPY || cfd.convert_type == CONVERT_TYPE_PARSE)
    {
      /* get existing text data */
      gsp_sum = NULL;
      for (vnp = gp->subtype_list; vnp != NULL; vnp = vnp->next)
      {
        gsp = CheckForConversionExistingTextInSeqEntry (sep, &cfd, fsp,
                                                        0,
                                                        vnp->choice,
                                                        0);
        gsp_new = GetSampleAdd (gsp_sum, gsp);
        gsp = GetSampleFree (gsp);
        gsp_sum = GetSampleFree (gsp_sum);
        gsp_sum = gsp_new;
      }
      cfd.etp = GetExistingTextHandlerInfo (gsp_sum == NULL ? 0 : gsp_sum->num_found, FALSE);
      gsp_sum = GetSampleFree (gsp_sum);
      if (cfd.etp != NULL && cfd.etp->existing_text_choice == eExistingTextChoiceCancel)
      {
        rval = FALSE;
      }
    }
    else
    {
      cfd.etp = NULL;
    }        
    
    if (rval)
    {
      for (vnp = gp->subtype_list; vnp != NULL; vnp = vnp->next)
      {
        OperateOnSeqEntryConstrainedObjects (sep, fsp, ConvertFeatureFieldCallback,
                                             NULL, 0, vnp->choice, 0, &cfd);  
      }
    }
    cfd.etp = MemFree (cfd.etp);
  }
  else if (action_choice == AECR_REMOVE && rgp != NULL && ! rgp->use_field_list)
  {
    for (vnp = subtype_list; vnp != NULL; vnp = vnp->next)
    {
      OperateOnSeqEntryConstrainedObjects (sep, fsp, RemoveGBQualByNameConstraint,
                                           NULL, 0, vnp->choice, 0, rgp->scp);  
    }
  }
  else
  {
    avd.field_list = field_list;
  
    /* get handling for existing text */
    if (action_choice == AECR_APPLY)
    {
      gsp = GetGBQualExistingText (sep, subtype_list,
                                   avd.field_list,
                                   fsp);
      avd.etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, FALSE);
      gsp = GetSampleFree (gsp);
    
      if (avd.etp != NULL 
        && avd.etp->existing_text_choice == eExistingTextChoiceCancel)
      {
        rval = FALSE;
      }
    }
    else
    {
      avd.etp = NULL;
    }
  
    AddEditApplyDataToApplyValue (action_choice, eap, &avd);
        
    for (vnp = subtype_list; vnp != NULL; vnp = vnp->next)
    {
      if (action_choice == AECR_EDIT || action_choice == AECR_APPLY)
      {
        OperateOnSeqEntryConstrainedObjects (sep, fsp, SetGBQualString,
                                             NULL, 0, vnp->choice, 0, &avd);  
      }
      else if (action_choice == AECR_REMOVE)
      {
        for (qual_vnp = field_list; qual_vnp != NULL; qual_vnp = qual_vnp->next)
        {
          avd.field_list = qual_vnp;
          OperateOnSeqEntryConstrainedObjects (sep, fsp, RemoveGBQualField,
                                               NULL, 0, vnp->choice, 0, &avd);  
        }
      }
    }

    avd.text_to_replace = MemFree (avd.text_to_replace);
    avd.new_text = MemFree (avd.new_text);
  }
  FilterSetFree (fsp);
  gp = SimpleAECRFree (gp);
  rgp = RemoveGBQualFree (rgp);
  
  if (rval)
  {
    ObjMgrSetDirtyFlag (ap->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, ap->input_entityID, 0, 0);
  }
  ArrowCursor ();
  Update (); 
  
  return rval;
}


static CharPtr GetGBQualSampleName (ValNodePtr vnp)
{
  CharPtr    label = NULL;
  Int2       feature_subtype, gb_field_choice;
  ValNodePtr feature_list, f_vnp;
  Int4       num_feature_choices;
  CharPtr    feature_label = "";
  
  if (vnp == NULL)
  {
    return NULL;
  }
  
  feature_list = BuildFeatureDialogList (FALSE, NULL);
  num_feature_choices = ValNodeLen (feature_list);
  
  feature_subtype = (vnp->data.intvalue - 1) / NumEditQualifiers;
  gb_field_choice = (vnp->data.intvalue - 1) % NumEditQualifiers;
  
  for (f_vnp = feature_list; f_vnp != NULL; f_vnp = f_vnp->next)
  {
    if (f_vnp->choice == feature_subtype)
    {
      feature_label = f_vnp->data.ptrvalue;
    }
  }
  
  
  label = (CharPtr) MemNew ((StringLen (feature_label)
                            + StringLen (EditQualifierList[gb_field_choice].name)
                            + 2) * sizeof (Char));
  if (label != NULL)
  {
    sprintf (label, "%s %s", feature_label,
             EditQualifierList[gb_field_choice].name);
  }
  ValNodeFreeData (feature_list);
  return label;
}

static CharPtr GetGBQualSampleFieldString (SeqFeatPtr sfp, ValNodePtr vnp, FilterSetPtr fsp)
{
  CharPtr   str = NULL;
  Int2      feature_subtype, gb_field_choice;
  ValNode   vn;
  
  if (sfp == NULL || vnp == NULL)
  {
    return NULL;
  }

  feature_subtype = (vnp->data.intvalue - 1) / NumEditQualifiers;
  gb_field_choice = (vnp->data.intvalue - 1) % NumEditQualifiers;
    
  if (feature_subtype != sfp->idx.subtype)
  {
    return NULL;
  }

  vn.next = NULL;
  vn.choice = 0;
  vn.data.intvalue = gb_field_choice + 1;
  str = GetGBQualString (sfp, &vn, NULL);
  
  return str;
}

static DialoG GetGBQualSample (GrouP h, Uint2 entityID)
{
  DialoG        d;
  SetSampleData ssd;
  Int4          qual_choice, list_index;  
  ValNodePtr    feature_list, f_vnp;    

  d = SampleDialog (h);
  ssd.fieldstring_func = GetGBQualSampleFieldString;
  ssd.descrstring_func = NULL;
  ssd.entityID = entityID;
  ssd.free_vn_proc = NULL;
  ssd.copy_vn_proc = IntValNodeCopy;
  ssd.match_vn_proc = IntValNodeMatch;
  ssd.label_vn_proc = GetGBQualSampleName;
  ssd.fsp = NULL;
 
  /* construct list of Features and GBQualifiers */
  ssd.field_list = NULL;

  feature_list = BuildFeatureDialogList (FALSE, NULL);
  
  for (f_vnp = feature_list; f_vnp != NULL; f_vnp = f_vnp->next)
  {
    for (qual_choice = 0; qual_choice < NumEditQualifiers; qual_choice++)
    {
      list_index = f_vnp->choice * NumEditQualifiers
                   + qual_choice;
      ValNodeAddInt (&ssd.field_list, 0, list_index + 1);    
    }
  }

  PointerToDialog (d, &ssd);
  
  /* now free up field list (SampleDialog maintains its own copy */
  ssd.field_list = ValNodeFree (ssd.field_list);
  feature_list = ValNodeFreeData (feature_list);
  return d;
}

static void 
GBQualApplyEditRemoveConvert 
(IteM    i,
 Int4    first_action_choice,
 Boolean crippled)
{
  ApplyEditConvertRemoveCombo (i, first_action_choice, crippled,
                               "Import Qualifiers",
                               GBQualListDialog, FALSE, GetGBQualSample,
                               TRUE, FALSE, TRUE, FALSE, FALSE, "Where feature text",
                               AcceptGBQualApplyEditRemoveConvert, NULL, NULL,
                               CheckFeaturesForPresample);
}

extern void ApplyGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_APPLY, FALSE);
}

extern void PublicApplyGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_APPLY, TRUE);
}

extern void EditGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_EDIT, FALSE);
}

extern void PublicEditGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_EDIT, TRUE);
}

extern void ConvertGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_CONVERT, FALSE);
}

extern void SwapGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_SWAP, FALSE);
}

extern void RemoveGBQual (IteM i)
{
  GBQualApplyEditRemoveConvert (i, AECR_REMOVE, FALSE);
}


typedef enum 
{
  eAllTargetsAllActions_BioSource = 0,
  eAllTargetsAllActions_GBQual,
  eAllTargetsAllActions_RNAQual,
  eAllTargetsAllActions_CDSGeneProtmRNA,
  eNumAllTargetsAllActions
} EAllTargetsAllActions;

typedef struct allactionsalltargetsdlg
{
  DIALOG_MESSAGE_BLOCK
  DialoG                   tbs; /* folder tab for switching between target types */
  Int2                     currentPage;
  GrouP                    target_pages [eNumAllTargetsAllActions];
  DialoG                   aecr_pages [NUM_AECR * eNumAllTargetsAllActions];
  PopuP                    action_popup;

  DialoG                   accept_cancel;
  DialoG                   constraints;
  ButtoN                   clear_constraints_on_action_change;
  Nlm_ChangeAECRActionProc change_action;
  Int4                     prev_page;
  Boolean                  crippled;
  Int4                     crippled_action;
  Uint2                    entityID;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} AllActionsAllTargetsDlgData, PNTR AllActionsAllTargetsDlgPtr;


extern void AllActionsAllTargets (IteM i)
{

}

typedef struct locustagtooldlocustag 
{
  FORM_MESSAGE_BLOCK
  DialoG         constraints;
  DialoG         accept_cancel;
  
  SeqEntryPtr    sep;
} LocusTagToOldLocusTagData, PNTR LocusTagToOldLocusTagPtr;

static void LocusTagToOldLocusTagActionClear (Pointer userdata)
{
  LocusTagToOldLocusTagPtr mp;
  
  mp = (LocusTagToOldLocusTagPtr) userdata;
  if (mp == NULL)
  {
    return;
  }
  PointerToDialog (mp->constraints, NULL);
}

static void LocusTagToOldLocusTagActionClearText (Pointer userdata)
{
  LocusTagToOldLocusTagPtr mp;
  FilterSetPtr             fsp;
  
  mp = (LocusTagToOldLocusTagPtr) userdata;
  if (mp == NULL)
  {
    return;
  }
  fsp = (FilterSetPtr) DialogToPointer (mp->constraints);
  FilterSetClearText (fsp);
  PointerToDialog (mp->constraints, fsp);
  FilterSetFree (fsp);
}


static void 
ConvertGeneLocusTagToOldLocusTagCallback 
(SeqFeatPtr   gene,
 Pointer      userdata, 
 FilterSetPtr fsp)
{
  GeneRefPtr               grp;
  CharPtr                  locus_tag;
  GBQualPtr                new_qual, prev_qual;
  LocusTagToOldLocusTagPtr mp;
  
  if (gene == NULL || gene->data.choice != SEQFEAT_GENE || userdata == NULL) return;
  
  mp = (LocusTagToOldLocusTagPtr)userdata;
  
  grp = (GeneRefPtr) gene->data.value.ptrvalue;
  if (grp == NULL || StringHasNoText (grp->locus_tag))
  {
      return;
  }
  locus_tag = grp->locus_tag;
  grp->locus_tag = NULL;
  
  new_qual = GBQualNew ();
  if (new_qual == NULL) return;
  new_qual->qual = StringSave ("old_locus_tag");
  new_qual->val = locus_tag;
  new_qual->next = NULL;
  
  if (gene->qual == NULL)
  {
      gene->qual = new_qual;
  }
  else
  {
      for (prev_qual = gene->qual; prev_qual->next != NULL; prev_qual = prev_qual->next)
      {
      }
      prev_qual->next = new_qual;
  }
}

static Boolean LocusTagToOldLocusTagAction (Pointer userdata)
{
  LocusTagToOldLocusTagPtr mp;
  FilterSetPtr             fsp;
  
  mp = (LocusTagToOldLocusTagPtr) userdata;
  if (mp == NULL)
  {
    return FALSE;
  }
  
  WatchCursor ();
  Update ();
  
  mp->sep = GetTopSeqEntryForEntityID (mp->input_entityID);
  fsp = (FilterSetPtr) DialogToPointer (mp->constraints);
  OperateOnSeqEntryConstrainedObjects (mp->sep, fsp, ConvertGeneLocusTagToOldLocusTagCallback, 
                                       NULL, SEQFEAT_GENE, 0, 0, mp);
  ObjMgrSetDirtyFlag (mp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, mp->input_entityID, 0, 0);
  ArrowCursor ();
  Update ();  
  return TRUE;
}

extern void ConvertLocusTagToOldLocusTag (IteM i)
{
  BaseFormPtr         bfp;
  LocusTagToOldLocusTagPtr     mp;
  WindoW              w;
  GrouP               h;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  mp = (LocusTagToOldLocusTagPtr) MemNew (sizeof (LocusTagToOldLocusTagData));
  if (mp == NULL) return;
  
  w = FixedWindow (-50, -33, -10, -10, "Convert Locus Tag to Old Locus Tag", StdCloseWindowProc); 
  SetObjectExtra (w, mp, StdCleanupExtraProc); 
  SetObjectExtra (w, mp, NULL);
  mp->form = (ForM) w;
  mp->input_entityID = bfp->input_entityID;
  
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  
  mp->constraints = FilterGroup (h, TRUE, FALSE, TRUE, FALSE, FALSE, "Where feature text"); 
  mp->accept_cancel = AcceptCancelDialog (h, LocusTagToOldLocusTagAction, NULL, 
                                          LocusTagToOldLocusTagActionClear,
                                          LocusTagToOldLocusTagActionClearText,
                                          (Pointer)mp, w);  
  AlignObjects (ALIGN_CENTER, (HANDLE) mp->constraints,
                              (HANDLE) mp->accept_cancel,
                              NULL);
  Show (w);   
}

static CharPtr GetLastLineageFromString (CharPtr lineage)
{
  CharPtr last_semicolon, lineage_start, penultimate_semicolon;
  
  
  if (StringHasNoText (lineage))
  {
    return lineage;
  }
  
  last_semicolon = StringRChr (lineage, ';');
  if (last_semicolon == NULL)
  {
    return lineage;
  }
  
  /* skip past semicolon */
  lineage_start = last_semicolon + 1;

  /* skip over whitespace */
  lineage_start += StringSpn (lineage_start, " \t");
  
  if (StringICmp (lineage_start, "environmental samples") == 0 
      && last_semicolon != lineage)
  {
    *last_semicolon = 0;
    penultimate_semicolon = StringRChr (lineage, ';');
    if (penultimate_semicolon == NULL)
    {
      lineage_start = lineage;
    }
    else
    {
      lineage_start = penultimate_semicolon + 1;
    }
    lineage_start += StringSpn (lineage_start, " \t");
  }
  return lineage_start;
}

static void ExportLastLineageCallback (BioseqPtr bsp, Pointer userdata)
{
  LogInfoPtr        lip;
  SeqFeatPtr        sfp;
  SeqMgrFeatContext fcontext;
  SeqDescrPtr       sdp;
  SeqMgrDescContext dcontext;
  SeqIdPtr          sip;
  Char              tmp[128];
  CharPtr           val_str = NULL;
  ValNode           lineage_field;
  CharPtr           last_lineage;
  Boolean           found_text = FALSE;
  
  if (bsp == NULL || userdata == NULL || ISA_aa (bsp->mol))
  {
    return;
  }
  
  lip = (LogInfoPtr) userdata;
  
  sip = SeqIdFindBest (bsp->id, 0);
  SeqIdWrite (sip, tmp, PRINTID_REPORT, sizeof (tmp));
  
  fprintf (lip->fp, "%s", tmp);
  
  lineage_field.next = NULL;
  lineage_field.choice = 1;
  lineage_field.data.ptrvalue = "Lineage";

  sdp = SeqMgrGetNextDescriptor (bsp, NULL, Seq_descr_source, &dcontext);
  while (sdp != NULL)
  {
    val_str = GetSourceQualDescrString (sdp, &lineage_field, NULL);
    last_lineage = GetLastLineageFromString (val_str);
    if (last_lineage != NULL)
    {
      if (found_text)
      {
        fprintf (lip->fp, ";%s", last_lineage);
      }
      else
      {
        fprintf (lip->fp, "\t%s", last_lineage);
      }
      val_str = MemFree (val_str);
      found_text = TRUE;
    }
    sdp = SeqMgrGetNextDescriptor (bsp, sdp, 0, &dcontext);
  }

  sfp = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_BIOSRC, 0, &fcontext);
  while (sfp != NULL)
  {
    val_str = GetSourceQualFeatureString (sfp, &lineage_field, NULL);
    last_lineage = GetLastLineageFromString (val_str);
    if (last_lineage != NULL)
    {
      if (found_text)
      {
        fprintf (lip->fp, ";%s", last_lineage);
      }
      else
      {
        fprintf (lip->fp, "\t%s", last_lineage);
      }
      val_str = MemFree (val_str);
      found_text = TRUE;
    }
    sfp = SeqMgrGetNextFeature (bsp, sfp, SEQFEAT_BIOSRC, 0, &fcontext);
  }
  fprintf (lip->fp, "\n");
  lip->data_in_log = TRUE;
}

extern void ExportLastLineage (IteM i)
{
  BaseFormPtr         bfp;
  SeqEntryPtr         sep;
  LogInfoPtr          lip;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;
  
  lip = OpenLog ("Lineage");
  WatchCursor ();
  Update ();
  VisitBioseqsInSep (sep, lip, ExportLastLineageCallback);
  CloseLog (lip);
  lip = FreeLog (lip);    
  ArrowCursor ();
  Update ();
}

typedef struct combinecds
{
  FORM_MESSAGE_BLOCK
  GrouP               action_choice_grp;
  GrouP               new_cds_grp;
  ButtoN              cover_sequence_btn;
  DialoG              string_constraint_dlg;
  
  Int2                action_choice;
  Boolean             cover_sequence;
  StringConstraintXPtr string_constraint;
  LogInfoPtr          lip;
  
  /* for string constraint */
  ObjectHasStringData ohsd;
  ObjMgrTypePtr       omtp;
  AsnIoPtr            aip;
  
  /* for finding location for master CDS */
  Int4                left_end;
  Int4                right_end;
  Boolean             partial5;
  Boolean             partial3;  
  Uint1               strand;
  SeqFeatPtr          first_cds;
  
  /* for product name for master CDS */
  GrouP               prod_name_choice_grp;
  TexT                prod_name_txt;
} CombineCDSData, PNTR CombineCDSPtr;


static SeqFeatPtr GetProteinForCDS (SeqFeatPtr sfp)
{
  BioseqPtr protbsp;
  SeqMgrFeatContext context;

  if (sfp == NULL 
      || sfp->data.choice != SEQFEAT_CDREGION
      || sfp->product == NULL
      || (protbsp = BioseqFind(SeqLocId (sfp->product))) == NULL) {
    return NULL;
  }
  return SeqMgrGetNextFeature (protbsp, NULL, SEQFEAT_PROT, FEATDEF_PROT, &context);
}


static Boolean DoesCDSFieldMatchStringConstraint (SeqFeatPtr sfp, CombineCDSPtr ccp)
{
  SeqFeatPtr prot;
  SeqMgrFeatContext fcontext;

  if (sfp == NULL || ccp == NULL) {
    return FALSE;
  } else if (ccp->ohsd.scp == NULL) {
    return TRUE;
  } else if (DoesObjectMatchStringConstraint (ccp->omtp, ccp->aip, sfp, &(ccp->ohsd))) {
    return TRUE;
  } else if ((prot = GetProteinForCDS(sfp)) != NULL && DoesObjectMatchStringConstraint (ccp->omtp, ccp->aip, prot, &(ccp->ohsd))) {
    return TRUE;
  }
  sfp = SeqMgrGetDesiredFeature (ccp->input_entityID, NULL, 0, 0, sfp, &fcontext);
  if (sfp != NULL && DoesStringMatchConstraintX (fcontext.label, ccp->ohsd.scp))
  {
    return TRUE;
  }
  prot = SeqMgrGetDesiredFeature (ccp->input_entityID, NULL, 0, 0, prot, &fcontext);
  if (prot != NULL && DoesStringMatchConstraintX (fcontext.label, ccp->ohsd.scp))
  {
    return TRUE;
  }
  return FALSE;
}


static void GetCombinedCDSLocationCallback (SeqFeatPtr sfp, Pointer userdata)
{
  CombineCDSPtr     ccp;
  Int4              start, stop;
  Boolean           partial5, partial3;
  Uint1             strand;

  if (sfp == NULL 
      || sfp->data.choice != SEQFEAT_CDREGION 
      || sfp->location == NULL
      || userdata == NULL)
  {
    return;
  }
  
  ccp = (CombineCDSPtr) userdata;
  
  if (!DoesCDSFieldMatchStringConstraint(sfp, ccp)) {
    return;
  }
  
  start = SeqLocStart (sfp->location);
  stop = SeqLocStop (sfp->location);
  strand = SeqLocStrand (sfp->location);
  CheckSeqLocForPartial (sfp->location, &partial5, &partial3);
  if (start < ccp->left_end)
  {
    ccp->first_cds = sfp;
    ccp->left_end = start;
    if (strand == Seq_strand_minus)
    {
      ccp->partial3 = partial3;
    }
    else
    {
      ccp->partial5 = partial5;
    }
    ccp->strand = strand;
  }
  
  if (stop > ccp->right_end)
  {
    ccp->right_end = stop;
    if (strand == Seq_strand_minus)
    {
      ccp->partial5 = partial5;
    }
    else
    {
      ccp->partial3 = partial3;
    }
    ccp->strand = strand;
  }
}


static void ApplyProductName (CombineCDSPtr ccp, SeqFeatPtr new_cds)
{
  BioseqPtr         first_prot_bsp, new_prot_bsp;
  SeqFeatPtr        first_prot = NULL;
  ProtRefPtr        prp;
  CharPtr           product_name = NULL;
  
  if (ccp == NULL || new_cds == NULL)
  {
    return;
  }

  if (GetValue (ccp->prod_name_choice_grp) == 1)
  {
    first_prot_bsp = BioseqFindFromSeqLoc (ccp->first_cds->product);
    if (first_prot_bsp != NULL)
    {
      first_prot = GetProtFeature (first_prot_bsp);
      if (first_prot != NULL)
      {
        prp = (ProtRefPtr) first_prot->data.value.ptrvalue;
        if (prp != NULL && prp->name != NULL 
            && ! StringHasNoText (prp->name->data.ptrvalue))
        {
          product_name = StringSave (prp->name->data.ptrvalue);
        }
      }
    }
  }
  else
  {
    product_name = SaveStringFromText (ccp->prod_name_txt);
    if (StringHasNoText (product_name))
    {
      product_name = MemFree (product_name);
    }
  }
  
  if (product_name != NULL)
  {
    new_prot_bsp = BioseqFindFromSeqLoc (new_cds->product);
    if (new_prot_bsp != NULL)
    {
      first_prot = GetProtFeature (new_prot_bsp);
      if (first_prot != NULL)
      {
        prp = (ProtRefPtr) first_prot->data.value.ptrvalue;
        if (prp != NULL)
        {
          ValNodeAddPointer (&(prp->name), 0, product_name);
          product_name = NULL;
        }
      }
    }
  }
  product_name = MemFree (product_name);
}

static SeqLocPtr GetMasterCDSLocation (BioseqPtr bsp, CombineCDSPtr ccp)
{
  SeqFeatPtr        sfp;
  SeqMgrFeatContext fcontext;
  SeqLocPtr         cover_loc;
  
  if (bsp == NULL || ! ISA_na (bsp->mol) || ccp == NULL)
  {
    return NULL;
  }
  
  /* create location for master CDS */
  ccp->left_end = bsp->length - 1;
  ccp->right_end = 0;
  ccp->partial5 = FALSE;
  ccp->partial3 = FALSE;
  
  sfp = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_CDREGION, 0, &fcontext);
  if (sfp == NULL)
  {
    return NULL;
  }
  if (ccp->cover_sequence)
  {
    ccp->left_end = 0;
    ccp->right_end = bsp->length - 1;
    ccp->partial5 = FALSE;
    ccp->partial3 = FALSE;
    ccp->first_cds = sfp;
  }
  else
  {
    while (sfp != NULL)
    {
      GetCombinedCDSLocationCallback (sfp, ccp);
      sfp = SeqMgrGetNextFeature (bsp, sfp, SEQFEAT_CDREGION, 0, &fcontext);
    }
    if (ccp->left_end > ccp->right_end || ccp->first_cds == NULL)
    {
      return NULL;
    }
  }
   
  cover_loc = SeqLocIntNew (ccp->left_end, ccp->right_end, ccp->strand, bsp->id);
  SetSeqLocPartial (cover_loc, ccp->partial5, ccp->partial3);

  return cover_loc;  
}


static void CreateMasterCodingRegions (BioseqPtr bsp, Pointer userdata)
{
  CombineCDSPtr ccp;
  SeqLocPtr     cover_loc;
  SeqFeatPtr    cds;
  
  
  if (bsp == NULL || userdata == NULL)
  {
    return;
  }
  
  ccp = (CombineCDSPtr) userdata;
  
  cover_loc = GetMasterCDSLocation (bsp,  ccp);
  if (cover_loc == NULL)
  {
    return;
  }
  
  cds = CreateNewFeatureOnBioseq (bsp, SEQFEAT_CDREGION, cover_loc);
  cds->partial = (ccp->partial5 | ccp->partial3);
  
  cds->data.value.ptrvalue = AsnIoMemCopy (ccp->first_cds->data.value.ptrvalue,
                                            (AsnReadFunc) CdRegionAsnRead,
                                            (AsnWriteFunc) CdRegionAsnWrite);
  RetranslateOneCDS (cds, ccp->first_cds->idx.entityID, TRUE, FALSE);

  ApplyProductName (ccp, cds);    
}


static Boolean ConvertCDSToMatPeptideNoOverlap (SeqFeatPtr sfp, LogInfoPtr lip)
{
  SeqMgrFeatContext fcontext;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION) {
    return FALSE;
  }
  if (!CreateMatPeptideFromCDS (sfp)) {
    SeqMgrGetDesiredFeature (sfp->idx.entityID, NULL, sfp->idx.itemID, 0, sfp, &fcontext);
    if (lip != NULL && lip->fp != NULL) {
      fprintf (lip->fp, "Coding region %s has no product sequence\n", fcontext.label);
      lip->data_in_log = TRUE;
    } else {
      Message (MSG_ERROR, "Coding region %s has no product sequence\n", fcontext.label);
    }
    return FALSE;
  } else {
    return TRUE;
  }
}


static void ConvertCDSToMatPeptideNoOverlapCallback (BioseqPtr bsp, Pointer userdata)
{
  LogInfoPtr lip;
  SeqMgrFeatContext fcontext;
  SeqFeatPtr sfp;

  if (bsp == NULL || ISA_aa (bsp->mol)) {
    return;
  }

  lip = (LogInfoPtr) userdata;
  for (sfp = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_CDREGION, 0, &fcontext);
       sfp != NULL;
       sfp = SeqMgrGetNextFeature (bsp, sfp, SEQFEAT_CDREGION, 0, &fcontext)) {
    ConvertCDSToMatPeptideNoOverlap (sfp, lip);
  }
}


static void ConvertCDSToMatPeptideOverlap (SeqFeatPtr sfp, SeqFeatPtr top_cds, LogInfoPtr lip)
{
  SeqMgrFeatContext gene_context;
  SeqFeatPtr gene;

  if (sfp == NULL || top_cds == NULL || sfp->data.choice != SEQFEAT_CDREGION || top_cds->data.choice != SEQFEAT_CDREGION) {
    return;
  }

  if (!ConvertCDSToMatPeptideForOverlappingCDS (sfp, top_cds, TRUE)) {
    if (lip != NULL && lip->fp != NULL)
    {
      fprintf (lip->fp, "Invalid coordinates for mat_peptide conversion\n");
      lip->data_in_log = TRUE;
    }
    else
    {
      Message (MSG_ERROR, "Invalid coordinates for mat_peptide conversion");
    }
  } else {
    /* Mark gene with same location for deletion */
    gene = SeqMgrGetOverlappingGene (sfp->location, &gene_context);
    if (gene != NULL && SeqLocCompare (gene->location, sfp->location) == SLC_A_EQ_B)
    {
      gene->idx.deleteme = 1;
    }
  }
}


static void ConvertInnerCDSsToMatPeptidesCallback (BioseqPtr bsp, Pointer userdata)
{
  LogInfoPtr        lip;
  SeqFeatPtr        sfp = NULL;
  ValNodePtr        top_level_cds_list = NULL;
  SeqMgrFeatContext context;
  ValNodePtr        vnp;
  SeqFeatPtr        top_cds;
  
  if (bsp == NULL || ! ISA_na (bsp->mol)) return;
  lip = (LogInfoPtr) userdata;
  
  sfp = SeqMgrGetNextFeature (bsp, sfp, SEQFEAT_CDREGION, 0, &context);
  while (sfp != NULL)
  {
    top_cds = NULL;
    for (vnp = top_level_cds_list;
         vnp != NULL && top_cds == NULL; 
         vnp = vnp->next)
    {
      top_cds = (SeqFeatPtr) vnp->data.ptrvalue;
      if (top_cds != NULL)
      {
        if (SeqLocCompare (top_cds->location, sfp->location) != SLC_B_IN_A)
        {
          top_cds = NULL;
        }
      }
    }

    if (top_cds == NULL)
    {
      /* add to list of top level CDSs */
      ValNodeAddPointer (&top_level_cds_list, 0, sfp);
    }
    else
    {
      ConvertCDSToMatPeptideOverlap (sfp, top_cds, lip);
    }
    sfp = SeqMgrGetNextFeature (bsp, sfp, SEQFEAT_CDREGION, 0, &context);
  }
  ValNodeFree (top_level_cds_list);
}


static void DoCombineCDS (ButtoN b)
{
  CombineCDSPtr      ccp;
  SeqEntryPtr        sep, old_scope;
  ObjMgrPtr          omp;
  AsnExpOptPtr       aeop;

  ccp = (CombineCDSPtr) GetObjectExtra (b);
  if (ccp == NULL)
  {
    return;
  }
  
  sep = GetTopSeqEntryForEntityID (ccp->input_entityID);
  if (NULL == sep)
    return;
  
  old_scope = SeqEntrySetScope (sep);
  
  omp = ObjMgrGet ();
  if (omp == NULL) return;

  ccp->action_choice = GetValue (ccp->action_choice_grp);
  ccp->cover_sequence = GetStatus (ccp->cover_sequence_btn);
  ccp->string_constraint = DialogToPointer (ccp->string_constraint_dlg);

  WatchCursor ();
  Update ();
  
  ccp->lip = OpenLog ("Combine CDS Features");
  
  if (ccp->string_constraint != NULL)
  {
    ccp->omtp = ObjMgrTypeFind (omp, OBJ_SEQFEAT, NULL, NULL);
    ccp->aip = AsnIoNullOpen ();
    ccp->ohsd.scp = ccp->string_constraint;
    aeop = AsnExpOptNew (ccp->aip, NULL, NULL, AsnWriteConstraintCallBack);
    if (aeop != NULL) {
      aeop->user_data = (Pointer) &(ccp->ohsd);
    }
  }
  else
  {
    ccp->omtp = NULL;
    ccp->aip = NULL;
    ccp->ohsd.scp = NULL;
    aeop = NULL;
  }

  if (ccp->action_choice == 2) {
    VisitBioseqsInSep (sep, ccp, CreateMasterCodingRegions);
    SeqMgrIndexFeatures (ccp->input_entityID, NULL);
  }
  
  if (ccp->action_choice == 1 || ccp->action_choice == 2) {
    VisitBioseqsInSep (sep, ccp->lip, ConvertInnerCDSsToMatPeptidesCallback);
  } else {
    VisitBioseqsInSep (sep, ccp->lip, ConvertCDSToMatPeptideNoOverlapCallback);
  }

  AsnIoClose (ccp->aip);
  SeqEntrySetScope (old_scope);
  
  CloseLog (ccp->lip);
  FreeLog (ccp->lip);  
  ccp->string_constraint = StringConstraintXFree (ccp->string_constraint);
  Remove (ccp->form);
  DeleteMarkedObjects (ccp->input_entityID, 0, NULL);
  ObjMgrSetDirtyFlag (ccp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, ccp->input_entityID, 0, 0);
  ArrowCursor ();
  Update ();
}

static void EnableNewCDSControls (GrouP g)
{
  CombineCDSPtr ccp;

  ccp = (CombineCDSPtr) GetObjectExtra (g);
  if (ccp == NULL) return;
  
  if (GetValue (ccp->action_choice_grp) == 2)
  {
    Enable (ccp->new_cds_grp);
  }
  else
  {
    Disable (ccp->new_cds_grp);
  }
}

static void EnableProdName (GrouP g)
{
  CombineCDSPtr ccp;

  ccp = (CombineCDSPtr) GetObjectExtra (g);
  if (ccp == NULL) return;
  
  if (GetValue (ccp->prod_name_choice_grp) == 1)
  {
    Disable (ccp->prod_name_txt);
  }
  else
  {
    Enable (ccp->prod_name_txt);
  }
}

extern void CombineMultipleCDS (IteM i)
{
  BaseFormPtr   bfp;
  CombineCDSPtr ccp;
  WindoW        w;
  GrouP         h, c;
  ButtoN        b;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  ccp = (CombineCDSPtr) MemNew (sizeof (CombineCDSData));
  if (ccp == NULL) return;
  
  w = FixedWindow (-50, -33, -10, -10, "Convert CDS to Mat-peptide", StdCloseWindowProc);
  SetObjectExtra (w, ccp, StdCleanupExtraProc);
  ccp->form = (ForM) w;
  ccp->input_entityID = bfp->input_entityID;
  
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  ccp->action_choice_grp = HiddenGroup (h, 0, 3, EnableNewCDSControls);
  RadioButton (ccp->action_choice_grp, "Convert inner CDSs to mat_peptides");
  RadioButton (ccp->action_choice_grp, "Merge multiple CDSs into one CDS and convert inner CDSs to mat_peptides");
  RadioButton (ccp->action_choice_grp, "Create mat-peptide from protein on each CDS");
  SetValue (ccp->action_choice_grp, 1);
  SetObjectExtra (ccp->action_choice_grp, ccp, NULL);
  
  ccp->new_cds_grp = HiddenGroup (h, -1, 0, NULL);
  ccp->cover_sequence_btn = CheckBox (ccp->new_cds_grp, "New CDS should cover entire sequence", NULL);
  SetStatus (ccp->cover_sequence_btn, FALSE);
  
  ccp->prod_name_choice_grp = HiddenGroup (ccp->new_cds_grp, 0, 2, EnableProdName);
  SetObjectExtra (ccp->prod_name_choice_grp, ccp, NULL);
  RadioButton (ccp->prod_name_choice_grp, "Use product name from first CDS");
  RadioButton (ccp->prod_name_choice_grp, "Use this product name:");
  StaticPrompt (ccp->prod_name_choice_grp, "", 0, 0, programFont, 'l');
  ccp->prod_name_txt = DialogText (ccp->prod_name_choice_grp, " ", 10, NULL);
  SetValue (ccp->prod_name_choice_grp, 1);
  Disable (ccp->prod_name_txt);
  
  ccp->string_constraint_dlg = StringConstraintDialogX (ccp->new_cds_grp, "Where CDS field", TRUE);
  Disable (ccp->new_cds_grp);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) ccp->cover_sequence_btn,
                              (HANDLE) ccp->prod_name_choice_grp,
                              (HANDLE) ccp->string_constraint_dlg,
                              NULL);
  
  c = HiddenGroup (h, 2, 0, NULL);
  b = PushButton (c, "Accept", DoCombineCDS);
  SetObjectExtra (b, ccp, NULL);
  b = PushButton (c, "Cancel", StdCancelButtonProc);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) ccp->action_choice_grp,
                              (HANDLE) ccp->new_cds_grp,
                              (HANDLE) c,
                              NULL);
  RealizeWindow (w);
  Show (w);
  Select (w);
  Update ();
}


static void MoveToProteinSequenceCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  SeqLocPtr         prot_loc;
  SeqFeatPtr        overlapping_cds, new_sfp;
  SeqMgrFeatContext cds_context, fcontext;
  Int4              offset;
  CdRegionPtr       crp;
  BioseqPtr         prot_bsp;
  LogInfoPtr        lip;
  
  if (sfp == NULL) {
    return;
  }
  
  lip = (LogInfoPtr) userdata;
  
  overlapping_cds = SeqMgrGetOverlappingCDS(sfp->location, &cds_context);
  if (overlapping_cds == NULL) {
    SeqMgrGetDesiredFeature (sfp->idx.entityID, NULL, sfp->idx.itemID, 0, sfp, &fcontext);
    if (lip != NULL && lip->fp != NULL)
    {
      fprintf (lip->fp, "No overlapping coding region for %s\n", fcontext.label);
      lip->data_in_log = TRUE;
    }
    else
    {
      Message (MSG_ERROR, "No overlapping coding region for %s\n", fcontext.label);
    }
    return;
  }
  
  prot_bsp = BioseqFindFromSeqLoc(overlapping_cds->product);
  if (prot_bsp == NULL) {
    if (lip != NULL && lip->fp != NULL)
    {
      fprintf (lip->fp, "No protein product for %s\n", cds_context.label);
      lip->data_in_log = TRUE;
    }
    else
    {
      Message (MSG_ERROR, "No protein product for %s\n", cds_context.label);
    }
    return;
  }
  
  offset = cds_context.left;
  crp = (CdRegionPtr) overlapping_cds->data.value.ptrvalue;
  if (crp != NULL) {
    if (crp->frame == 2)
    {
      offset += 1;
    }
    else if (crp->frame == 3)
    {
      offset += 2;
    }
  }
  
  prot_loc = BuildProtLoc (overlapping_cds, sfp->location, NULL);
  if (prot_loc == NULL)
  {
    SeqMgrGetDesiredFeature (sfp->idx.entityID, NULL, sfp->idx.itemID, 0, sfp, &fcontext);
    if (lip != NULL && lip->fp != NULL)
    {
      fprintf (lip->fp, "Unable to translate coordinates for %s\n", fcontext.label);
      lip->data_in_log = TRUE;
    }
    else
    {
      Message (MSG_ERROR, "Unable to translate coordinates for %s\n", fcontext.label);
    }
  } 
  else 
  {
    new_sfp = CreateNewFeatureOnBioseq (prot_bsp, sfp->data.choice, prot_loc);
    if (new_sfp != NULL)
    {
      new_sfp->data.value.ptrvalue = sfp->data.value.ptrvalue;
      sfp->data.value.ptrvalue = NULL;
      sfp->idx.deleteme = TRUE;
    }
  }
  
}


typedef struct maptoprotein {
  FORM_MESSAGE_BLOCK
  DialoG feature_selection;
  DialoG constraints;
} MapToProteinData, PNTR MapToProteinPtr;


static void MapToProteinSequence(ButtoN b)
{
  SeqEntryPtr     sep;
  MapToProteinPtr mtpp;
  FilterSetPtr    fsp;
  LogInfoPtr      lip;
  ValNodePtr      feature_type_list, vnp;
  Uint1           feat_def_choice;
  
  mtpp = (MapToProteinPtr) GetObjectExtra (b);
  if (mtpp == NULL) {
    return;
  }
  
  feature_type_list = (ValNodePtr) DialogToPointer (mtpp->feature_selection);
  if (feature_type_list != NULL)
  {
    sep = GetTopSeqEntryForEntityID (mtpp->input_entityID); 
    fsp = DialogToPointer (mtpp->constraints);
    lip = OpenLog ("Map to Protein Sequence");
    
    for (vnp = feature_type_list; vnp != NULL; vnp = vnp->next)
    {
      feat_def_choice = vnp->choice;
      if (feat_def_choice == 255)
      {
        feat_def_choice = 0;
      }
      OperateOnSeqEntryConstrainedObjects (sep, fsp, MoveToProteinSequenceCallback, 
                                           NULL, 0, feat_def_choice, 0, lip);
    }
    fsp = FilterSetFree (fsp);
    CloseLog (lip);
    lip = FreeLog (lip);
    DeleteMarkedObjects (mtpp->input_entityID, 0, NULL);
    ObjMgrSetDirtyFlag (mtpp->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, mtpp->input_entityID, 0, 0);
    Update ();
    Remove (mtpp->form);
    ValNodeFree (feature_type_list);
  }
}


extern void MapFeaturesToProteinSequence(IteM i)
{
  ButtoN             b;
  BaseFormPtr        bfp;
  GrouP              c;
  GrouP              g;
  WindoW             w;
  MapToProteinPtr    mtpp;
  PrompT             p;
  SeqEntryPtr        sep;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  
  mtpp = (MapToProteinPtr) MemNew (sizeof (MapToProteinData));
  if (mtpp == NULL) return;
  mtpp->input_entityID = bfp->input_entityID;
  sep = GetTopSeqEntryForEntityID(bfp->input_entityID);
  w = MovableModalWindow (-50, -33, -10, -10,
                          "Map to Protein Sequence",
                          StdCloseWindowProc);
  SetObjectExtra (w, mtpp, StdCleanupFormProc);
  mtpp->form = (ForM) w;

  g = HiddenGroup (w, -1, 0, NULL);
  p = StaticPrompt (g, "Move features to protein of type", 0, dialogTextHeight, systemFont, 'l');

  mtpp->feature_selection = FeatureSelectionDialogEx (g, TRUE, sep, NULL, NULL);
  mtpp->constraints = FilterGroup (g, TRUE, FALSE, FALSE, FALSE, FALSE, "Where feature text");
  AlignObjects (ALIGN_CENTER, (HANDLE) p, (HANDLE) mtpp->feature_selection, (HANDLE) mtpp->constraints, NULL);

  c = HiddenGroup (w, 2, 0, NULL);
  b = DefaultButton (c, "Accept", MapToProteinSequence);
  SetObjectExtra (b, mtpp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) c, NULL);
  RealizeWindow (w);
  Show (w);
  Select (w);
  Update ();
}


typedef struct uniquevaluecollection {
  ValNodePtr               requested_field;
  GetFeatureFieldString    fieldstring_func;
  GetDescriptorFieldString descrstring_func;
  FreeValNodeProc          free_vn_proc;
  CopyValNodeDataProc      copy_vn_proc;
  ValNodePtr               value_lists;
} UniqueValueCollectionData, PNTR UniqueValueCollectionPtr;


static ClickableItemPtr FindExistingCategory (CharPtr description, ValNodePtr category_list)
{
  ClickableItemPtr cip;

  while (category_list != NULL) {
    cip = (ClickableItemPtr) category_list->data.ptrvalue;
    if (cip != NULL && 
        (StringCmp (description, cip->description) == 0
         || (StringHasNoText (description) && StringHasNoText (cip->description)))) {
      return cip;
    }
    category_list = category_list->next;
  }
  return NULL;
}


static ValNodePtr DataInValNodeList (ValNodePtr list, Uint1 choice, Pointer ptrvalue)
{
  while (list != NULL) {
    if (choice == list->choice && ptrvalue == list->data.ptrvalue) {
      return list;
    }
    list = list->next;
  }
  return NULL;
}


static void CombineClickableItemLists (ValNodePtr PNTR list1, ValNodePtr list2)
{
  ValNodePtr add_vnp, prev_vnp = NULL, next_vnp;

  if (*list1 == NULL) {
    *list1 = list2;
    return;
  } else if (list2 == NULL) {
    return;
  }

  add_vnp = list2;
  while (add_vnp != NULL) {
    next_vnp = add_vnp->next;
    if (DataInValNodeList (*list1, add_vnp->choice, add_vnp->data.ptrvalue)) {
      if (prev_vnp == NULL) {
        list2 = next_vnp;
      } else {
        prev_vnp->next = next_vnp;
      }
      add_vnp->next = NULL;
      ValNodeFree (add_vnp);
    } else {
      prev_vnp = add_vnp;
    }
    add_vnp = next_vnp;
  }
  ValNodeLink (list1, list2); 
}


static void CombineClickableLists (ValNodePtr PNTR list1, ValNodePtr list2)
{
  ClickableItemPtr cip1, cip2;
  ValNodePtr vnp;
  for (vnp = list2; vnp != NULL; vnp = vnp->next) {
    cip2 = vnp->data.ptrvalue;
    if (cip2 != NULL) {
      cip1 = FindExistingCategory (cip2->description, *list1);
      if (cip1 == NULL) {
        ValNodeAddPointer (list1, 0, cip2);
        vnp->data.ptrvalue = NULL;
      } else {
        CombineClickableItemLists (&(cip1->item_list), cip2->item_list);
        cip2->item_list = NULL;
      }
    }
  }
  list2 = FreeClickableList (list2);
}


static void GetUniqueValuesFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  UniqueValueCollectionPtr uvcp;
  CharPtr      str;
  ValNodePtr   check_vnp;
  ClickableItemPtr cip;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  uvcp = (UniqueValueCollectionPtr) userdata;
  if (uvcp->fieldstring_func == NULL)
  {
    return;
  }
  
  str = uvcp->fieldstring_func (sfp, uvcp->requested_field, NULL);
  if (str == NULL) {
    return;
  }
  cip = FindExistingCategory (str, uvcp->value_lists);
  if (cip == NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = str;
    ValNodeAddPointer (&cip->item_list, OBJ_SEQFEAT, sfp);
    ValNodeAddPointer (&(uvcp->value_lists), 0, cip);
  } else {
    str = MemFree (str);
    /* make sure feature isn't already in the list */
    for (check_vnp = cip->item_list; check_vnp != NULL; check_vnp = check_vnp->next) {
      if (check_vnp->choice == OBJ_SEQFEAT && sfp == check_vnp->data.ptrvalue) break;
    }
    if (check_vnp == NULL) {
      ValNodeAddPointer (&cip->item_list, OBJ_SEQFEAT, sfp);
    }
  }
}

static void GetUniqueValuesDescriptorCallback (SeqDescrPtr sdp, Pointer userdata, FilterSetPtr fsp)
{
  UniqueValueCollectionPtr uvcp;
  ClickableItemPtr         cip;
  CharPtr      str;
  ValNodePtr   check_vnp;
  
  if (sdp == NULL || userdata == NULL)
  {
    return;
  }
  
  uvcp = (UniqueValueCollectionPtr) userdata;
  if (uvcp->descrstring_func == NULL)
  {
    return;
  }

  str = uvcp->descrstring_func (sdp, uvcp->requested_field, NULL);

  cip = FindExistingCategory (str, uvcp->value_lists);

  if (cip == NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = str;
    ValNodeAddPointer (&cip->item_list, OBJ_SEQDESC, sdp);
    ValNodeAddPointer (&(uvcp->value_lists), 0, cip);
  } else {
    str = MemFree (str);
    /* make sure feature isn't already in the list */
    for (check_vnp = cip->item_list; check_vnp != NULL; check_vnp = check_vnp->next) {
      if (check_vnp->choice == OBJ_SEQDESC && sdp == check_vnp->data.ptrvalue) break;
    }
    if (check_vnp == NULL) {
      ValNodeAddPointer (&cip->item_list, OBJ_SEQDESC, sdp);
    }
  }
}


static ValNodePtr CheckForUniqueValuesInSeqEntry
(SeqEntryPtr              sep,
 ValNodePtr               requested_field,
 GetFeatureFieldString    fieldstring_func,
 GetDescriptorFieldString descrstring_func,
 FreeValNodeProc          free_vn_proc,
 CopyValNodeDataProc      copy_vn_proc,
 FilterSetPtr             fsp,
 Uint1                    seqfeat_choice,
 Uint1                    featdef_choice,
 Uint1                    descr_choice)
{
  UniqueValueCollectionData uvcd;
  
  uvcd.fieldstring_func = fieldstring_func;
  uvcd.descrstring_func = descrstring_func;
  uvcd.free_vn_proc = free_vn_proc;
  uvcd.copy_vn_proc = copy_vn_proc;
  uvcd.requested_field = (copy_vn_proc) (requested_field);
  uvcd.value_lists = NULL;
  OperateOnSeqEntryConstrainedObjects (sep, fsp, GetUniqueValuesFeatureCallback, 
                                         GetUniqueValuesDescriptorCallback,
                                         seqfeat_choice, featdef_choice,
                                         descr_choice, &uvcd);
  return uvcd.value_lists;  
}


static ValNodePtr
GetUniqueValueListForSeqEntry
(SeqEntryPtr   sep,
 Uint2         entityID, 
 ParseFieldPtr dst_field_data,
 FilterSetPtr  fsp)
{
  ValNodePtr   unique_values = NULL;
  ValNodePtr   requested_field = NULL, vnp;
  SeqEntryPtr  orig_sep;

  if (sep == NULL || dst_field_data == NULL)
  {
    return NULL;
  }
  
  switch (dst_field_data->parse_field_type)
  {
    case PARSE_FIELD_SOURCE_QUAL :
      unique_values = CheckForUniqueValuesInSeqEntry (sep, 
                                            dst_field_data->feature_field, 
                                            GetSourceQualFeatureString,
                                            GetSourceQualDescrString,
                                            ValNodeSimpleDataFree,
                                            SourceQualValNodeDataCopy,
                                            fsp,
                                            SEQFEAT_BIOSRC, 0,
                                            Seq_descr_source);
      break;
    case PARSE_FIELD_DEFLINE:
      requested_field = ValNodeNew (NULL);
      requested_field->data.intvalue = Seq_descr_title;
      unique_values = CheckForUniqueValuesInSeqEntry (sep, requested_field, 
                                            NULL,
                                            GetStringFromStringDescriptor,
                                            NULL, IntValNodeCopy,
                                            fsp, 0, 0, Seq_descr_title);
      requested_field = ValNodeFree (requested_field);
      break;
    case PARSE_FIELD_BIOSRC_STRING:
      unique_values = CheckForUniqueValuesInSeqEntry (sep, 
                                            dst_field_data->feature_field, 
                                            GetSourceFeatureString,
                                            GetSourceDescriptorString,
                                            NULL, IntValNodeCopy,
                                            fsp,
                                            SEQFEAT_BIOSRC, 0,
                                            Seq_descr_source);
      break;
    case PARSE_FIELD_DBXREF:
      unique_values = CheckForUniqueValuesInSeqEntry (sep, dst_field_data->feature_field,
                                            GetBioSourceFeatureDbxrefString,
                                            GetBioSourceDescriptorDbxrefString,
                                            ValNodeSimpleDataFree,
                                            ValNodeStringCopy,
                                            fsp,
                                            SEQFEAT_BIOSRC, 0, Seq_descr_source);
      break;
    case PARSE_FIELD_GENE_FIELD:
      unique_values = CheckForUniqueValuesInSeqEntry (sep, 
                                            dst_field_data->feature_field, 
                                            GetGeneFieldString,
                                            NULL,
                                            NULL, IntValNodeCopy,
                                            fsp,
                                            SEQFEAT_GENE, 0, 0);
      break;
    case PARSE_FIELD_RNA_FIELD:
      unique_values = CheckForUniqueValuesInSeqEntry (sep, 
                                            dst_field_data->feature_field, 
                                            GetRNAFieldString,
                                            NULL,
                                            NULL, IntValNodeCopy,
                                            fsp,
                                            SEQFEAT_RNA, 
                                            dst_field_data->feature_subtype == NULL ? 0 : dst_field_data->feature_subtype->data.intvalue,
                                            0);
      break;
    case PARSE_FIELD_CDS_COMMENT:
      unique_values = CheckForUniqueValuesInSeqEntry (sep, 
                                            NULL, 
                                            GetCDSComment,
                                            NULL,
                                            NULL, IntValNodeCopy,
                                            fsp,
                                            SEQFEAT_CDREGION, FEATDEF_CDS, 0);
      break;
    case PARSE_FIELD_COMMENT_DESC:
      requested_field = ValNodeNew (NULL);
      requested_field->data.intvalue = Seq_descr_comment;

      unique_values = CheckForUniqueValuesInSeqEntry (sep, requested_field, 
                                            NULL,
                                            GetStringFromStringDescriptor,
                                            NULL, IntValNodeCopy,
                                            fsp, 0, 0, Seq_descr_comment);
      requested_field = ValNodeFree (requested_field);
      break;
    case PARSE_FIELD_PROTEIN_FIELD:
      unique_values = CheckForUniqueValuesInSeqEntry (sep, 
                                            dst_field_data->feature_field, 
                                            GetProteinFieldString,
                                            NULL,
                                            NULL, IntValNodeCopy,
                                            fsp,
                                            SEQFEAT_PROT, 0, 0);
      break;
    case PARSE_FIELD_IMPORT_QUAL:
      orig_sep = sep;
      for (vnp = dst_field_data->feature_subtype; vnp != NULL; vnp = vnp->next)
      {
        CombineClickableLists (&unique_values, 
                               CheckForUniqueValuesInSeqEntry (sep, 
                                                               dst_field_data->feature_field, 
                                                               GetGBQualString,
                                                               NULL,
                                                               NULL, IntValNodeCopy,
                                                               fsp, 0, vnp->choice, 0));
        /* if we are also looking at features other than mat_peptides, use
         * original SeqEntry */
        sep = orig_sep;
      }
      break;
    case PARSE_FIELD_FEATURE_NOTE:
      orig_sep = sep;      
      for (vnp = dst_field_data->feature_field; vnp != NULL; vnp = vnp->next)
      {
        CombineClickableLists (&unique_values, 
                               CheckForUniqueValuesInSeqEntry (sep, vnp, 
                                                               GetFeatureNote,
                                                               NULL,
                                                               ValNodeSimpleDataFree,
                                                               ValNodeStringCopy,
                                                               fsp, 0, vnp->choice, 0));
        /* if we are also looking at features other than mat_peptides, use
         * original SeqEntry */
        sep = orig_sep;
      }
      break;
  }
  return unique_values;  
}


static ValNodePtr GetSeqEntryListForItem (ValNodePtr vnp)
{
  SeqFeatPtr       sfp;
  SeqDescrPtr      sdp;
  ObjValNodePtr    ovp;
  SeqEntryPtr      sep = NULL;
  BioseqPtr        bsp;
  BioseqSetPtr     bssp;
  ValNodePtr       sep_list = NULL;

  if (vnp == NULL || vnp->data.ptrvalue == NULL) return NULL;

  if (vnp->choice == OBJ_SEQFEAT) {
    sfp = vnp->data.ptrvalue;
    sep = GetBestTopParentForData (sfp->idx.entityID, BioseqFindFromSeqLoc (sfp->location));
    ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
  } else if (vnp->choice == OBJ_SEQDESC) {
    sdp = vnp->data.ptrvalue;
    if (sdp->extended != 0) {
      ovp = (ObjValNodePtr) sdp;
      if (ovp->idx.parenttype == OBJ_BIOSEQSET) {
        bssp = (BioseqSetPtr) ovp->idx.parentptr;
        if (bssp != NULL) {
          /* nuc_prot sets, segsets, and members of parts should travel together */
          if (bssp->_class == BioseqseqSet_class_nuc_prot) {
            sep = SeqMgrGetSeqEntryForData (bssp);
            if (sep != NULL) {
              ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
            }
          } else if (bssp->_class == BioseqseqSet_class_segset
                     || bssp->_class == BioseqseqSet_class_parts) {
            sep = bssp->seq_set;
            if (sep != NULL && IS_Bioseq (sep)) {
              bsp = sep->data.ptrvalue;
              sep = GetBestTopParentForData (bsp->idx.entityID, bsp);
              if (sep != NULL) {
                ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
              }
            }
          } else {            
            sep = bssp->seq_set;
            while (sep != NULL) {
              ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
              sep = sep->next;
            }
          }
        }
      } else if (ovp->idx.parenttype == OBJ_BIOSEQ) {
        sep = GetBestTopParentForData (ovp->idx.entityID, ovp->idx.parentptr);
        if (sep != NULL) {
          ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
        }
      }
    }
  } else if (vnp->choice == OBJ_BIOSEQ) {
    bsp = (BioseqPtr) vnp->data.ptrvalue;
    sep = GetBestTopParentForData (bsp->idx.entityID, bsp);
    ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
  } else if (vnp->choice == OBJ_SEQENTRY) {
    sep = vnp->data.ptrvalue;
    ValNodeAddPointer (&sep_list, OBJ_SEQENTRY, sep);
  }
  return sep_list;  
}


static Boolean RemoveItemAndCategory (ClickableItemPtr cip, Uint1 choice, Pointer ptrvalue)
{
  ValNodePtr vnp, prev = NULL;
  Boolean    found = FALSE;
  CharPtr    description = NULL;
  ClickableItemPtr subcat;

  vnp = cip->item_list;
  while (vnp != NULL) {
    if (vnp->choice == choice && vnp->data.ptrvalue == ptrvalue) {
      found = TRUE;
      if (prev == NULL) {
        cip->item_list = vnp->next;
      } else {
        prev->next = vnp->next;
      }
      vnp->next = NULL;
      description = GetDiscrepancyItemText (vnp);
      vnp = ValNodeFree (vnp);
    } else {
      prev = vnp;
      vnp = vnp->next;
    }
  }
  if (found) {
    /* also remove subcategory */
    prev = NULL;
    vnp = cip->subcategories;
    while (vnp != NULL) {
      subcat = (ClickableItemPtr) vnp->data.ptrvalue;
      if (subcat != NULL && StringCmp (subcat->description, description) == 0) {
        if (prev == NULL) {
          cip->subcategories = vnp->next;
        } else {
          prev->next = vnp->next;
        }
        vnp->next = NULL;
        vnp = FreeClickableList (vnp);
      } else {
        prev = vnp;
        vnp = vnp->next;
      }
    }
  }
  return found;
}

static ValNodePtr RearrangeForSeqEntriesInMultipleCategories (ValNodePtr value_lists)
{
  ClickableItemPtr cip1, cip2, existing_cat;
  ValNodePtr       vnp, check_vnp, last_vnp, vnp_item1, prev, vnp_next;
  Boolean          found;
  CharPtr          new_description;

  /* now check for SeqEntries that appear in multiple lists */
  for (vnp = value_lists; vnp != NULL && vnp->next != NULL; vnp = vnp->next) {
    if (vnp->data.ptrvalue == NULL) continue;
    last_vnp = vnp->next;
    while (last_vnp->next != NULL) {
      last_vnp = last_vnp->next;
    }
    cip1 = (ClickableItemPtr) vnp->data.ptrvalue;
    for (vnp_item1 = cip1->item_list; 
         vnp_item1 != NULL;
         vnp_item1 = vnp_next) {
      vnp_next = vnp_item1->next;
      found = FALSE;
      if (vnp_item1->data.ptrvalue != NULL && vnp_item1->choice == OBJ_SEQENTRY) {
        check_vnp = vnp->next;
        while (check_vnp != NULL && !found) {
          if (check_vnp->data.ptrvalue != NULL) {
            cip2 = check_vnp->data.ptrvalue;
            found = RemoveItemAndCategory (cip2, vnp_item1->choice, vnp_item1->data.ptrvalue);
          } 
          if (!found) {
            if (check_vnp == last_vnp) {
              check_vnp = NULL;
            } else {
              check_vnp = check_vnp->next;
            }
          }
        }
      }
      if (found) {
        /* need to take this SeqEntry out of both lists, put in new category */
        new_description = (CharPtr) MemNew (sizeof (Char) * (StringLen (cip1->description)
                                                             + StringLen (cip2->description) + 6));
        if (StringCmp (cip1->description, cip2->description) < 0) {
          sprintf (new_description, "%s AND %s", cip1->description, cip2->description);
        } else {
          sprintf (new_description, "%s AND %s", cip2->description, cip1->description);
        }
        existing_cat = FindExistingCategory (new_description, check_vnp->next);
        if (existing_cat == NULL) {
          /* make new category, add to end of list */
          existing_cat = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
          MemSet (existing_cat, 0, sizeof (ClickableItemData));
          existing_cat->description = new_description;
          ValNodeAddPointer (&value_lists, 0, existing_cat);
        } else {
          new_description = MemFree (new_description);
        }
        /* don't add item twice */
        if (!DataInValNodeList (existing_cat->item_list, vnp_item1->choice, vnp_item1->data.ptrvalue)) {
          ValNodeAddPointer (&(existing_cat->item_list), vnp_item1->choice, vnp_item1->data.ptrvalue);
        }
        /* remove from cip1 */
        RemoveItemAndCategory (cip1, vnp_item1->choice, vnp_item1->data.ptrvalue);
      }  
    }   
  }

  /* remove empty categories */
  for (vnp = value_lists, prev = NULL; vnp != NULL; vnp = vnp_next) {
    vnp_next = vnp->next;
    cip1 = (ClickableItemPtr) vnp->data.ptrvalue;
    if (cip1 == NULL || cip1->item_list == NULL) {
      if (prev == NULL) {
        value_lists = vnp->next;
      } else {
        prev->next = vnp->next;
      }
      vnp->next = NULL;
      vnp = FreeClickableList (vnp);
    } else {
      prev = vnp;
    }
  }
  return value_lists;
}


static ValNodePtr ChangeUniqueValueListsToSeqEntryLists (ValNodePtr value_lists)
{
  ValNodePtr vnp, vnp_item, new_item_list, new_vnp, sep_list, sep_vnp;
  ClickableItemPtr cip, subcat;
  SeqEntryPtr      sep;
  CharPtr          description;

  /* First, add SeqEntry for each feature or descriptor to the item list */
  for (vnp = value_lists; vnp != NULL; vnp = vnp->next) {
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    new_item_list = NULL;
    if (cip != NULL) {
      for (vnp_item = cip->item_list; vnp_item != NULL; vnp_item = vnp_item->next) {
        if (vnp_item->data.ptrvalue == NULL) continue;
        sep_list = GetSeqEntryListForItem (vnp_item);
        for (sep_vnp = sep_list; sep_vnp != NULL; sep_vnp = sep_vnp->next) {
          sep = sep_vnp->data.ptrvalue;
          new_vnp = DataInValNodeList (new_item_list, OBJ_SEQENTRY, sep);
          /* add to list for this category */
          if (new_vnp == NULL) {
            new_vnp = ValNodeAddPointer (&new_item_list, OBJ_SEQENTRY, sep);
          }
          description = GetDiscrepancyItemText(new_vnp);
          subcat = FindExistingCategory (description, cip->subcategories);
          if (subcat == NULL) {
            subcat = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
            MemSet (subcat, 0, sizeof (ClickableItemData));
            subcat->description = description;
            ValNodeAddPointer (&(cip->subcategories), 0, subcat);
          } else {
            description = MemFree (description);
          }
          /* don't add item twice */
          if (!DataInValNodeList (subcat->item_list, vnp_item->choice, vnp_item->data.ptrvalue)) {
            ValNodeAddPointer (&(subcat->item_list), vnp_item->choice, vnp_item->data.ptrvalue);
          }
        }
        sep_list = ValNodeFree (sep_list);
      }
      
      cip->item_list = ValNodeFree (cip->item_list);
      cip->item_list = new_item_list;
    }
  }
  value_lists = RearrangeForSeqEntriesInMultipleCategories (value_lists);
  return value_lists;
}


static void GetMolTypes (SeqDescrPtr sdp, Pointer userdata)
{
  ValNodePtr PNTR   type_list;
  ClickableItemPtr  cip;
  CharPtr           str;
  MolInfoPtr        mip;
  
  if (sdp == NULL || sdp->choice != Seq_descr_molinfo || sdp->data.ptrvalue == NULL || userdata == NULL)
  {
    return;
  }
  
  type_list = (ValNodePtr PNTR) userdata;

  mip = (MolInfoPtr) sdp->data.ptrvalue;
  /* don't look for peptides */
  if (mip->biomol == 8) return;

  str = GetMoleculeTypeName (mip->biomol);

  cip = FindExistingCategory (str, *type_list);

  if (cip == NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = StringSave (str);
    ValNodeAddPointer (&cip->item_list, OBJ_SEQDESC, sdp);
    ValNodeAddPointer (type_list, 0, cip);
  } else {
    /* make sure feature isn't already in the list */
    if (DataInValNodeList (cip->item_list, OBJ_SEQDESC, sdp) == NULL) {
      ValNodeAddPointer (&cip->item_list, OBJ_SEQDESC, sdp);
    }
  }
}

static void GetMolClasses (BioseqPtr bsp, Pointer userdata)
{
  ValNodePtr PNTR   type_list;
  ClickableItemPtr  cip;
  CharPtr           str;
  
  if (bsp == NULL || ISA_aa(bsp->mol) || userdata == NULL)
  {
    return;
  }
  
  type_list = (ValNodePtr PNTR) userdata;

  str = GetMoleculeClassName (bsp->mol);

  cip = FindExistingCategory (str, *type_list);

  if (cip == NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = StringSave (str);
    ValNodeAddPointer (&cip->item_list, OBJ_BIOSEQ, bsp);
    ValNodeAddPointer (type_list, 0, cip);
  } else {
    /* make sure feature isn't already in the list */
    if (DataInValNodeList (cip->item_list, OBJ_BIOSEQ, bsp) == NULL) {
      ValNodeAddPointer (&cip->item_list, OBJ_BIOSEQ, bsp);
    }
  }
}

static void GetMolTopologies (BioseqPtr bsp, Pointer userdata)
{
  ValNodePtr PNTR   type_list;
  ClickableItemPtr  cip;
  CharPtr           str;
  
  if (bsp == NULL || ISA_aa(bsp->mol) || userdata == NULL)
  {
    return;
  }
  
  type_list = (ValNodePtr PNTR) userdata;

  str = GetMoleculeTopologyName (bsp->topology);

  cip = FindExistingCategory (str, *type_list);

  if (cip == NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = StringSave (str);
    ValNodeAddPointer (&cip->item_list, OBJ_BIOSEQ, bsp);
    ValNodeAddPointer (type_list, 0, cip);
  } else {
    /* make sure feature isn't already in the list */
    if (DataInValNodeList (cip->item_list, OBJ_BIOSEQ, bsp) == NULL) {
      ValNodeAddPointer (&cip->item_list, OBJ_BIOSEQ, bsp);
    }
  }
}


typedef struct segfeatures {
  Uint2      featdef;
  ValNodePtr feature_list;
} SegFeaturesData, PNTR SegFeaturesPtr;

static void FindFeaturesForSeg (SeqFeatPtr sfp, Pointer userdata)
{
  SegFeaturesPtr fp;

  if (sfp == NULL || userdata == NULL) return;

  fp = (SegFeaturesPtr) userdata;

  if (sfp->idx.subtype == fp->featdef) {
    ValNodeAddPointer (&(fp->feature_list), OBJ_SEQFEAT, sfp);
  }
}

typedef struct segdescriptors {
  Uint2      desc_type;
  ValNodePtr descriptor_list;
} SegDescriptorsData, PNTR SegDescriptorsPtr;

static void FindDescriptorsForSeg (SeqDescrPtr sdp, Pointer userdata)
{
  SegDescriptorsPtr fp;

  if (sdp == NULL || userdata == NULL) return;

  fp = (SegDescriptorsPtr) userdata;

  if (sdp->choice == fp->desc_type) {
    ValNodeAddPointer (&(fp->descriptor_list), OBJ_SEQDESC, sdp);
  }
}


extern ValNodePtr 
CreateSeqEntryListsForUniqueValues
(SeqEntryPtr   sep,
 Uint2         entityID, 
 ParseFieldPtr dst_field_data,
 FilterSetPtr  fsp)
{
  ValNodePtr value_lists;

  value_lists = GetUniqueValueListForSeqEntry (sep, entityID, dst_field_data, fsp);

  value_lists = ChangeUniqueValueListsToSeqEntryLists (value_lists);
  return value_lists;
}

static void AddPubFeatureWithText (SeqFeatPtr sfp, Pointer userdata)
{
  ValNodePtr PNTR pub_list;

  if (sfp == NULL || userdata == NULL) return;
  pub_list = (ValNodePtr PNTR) userdata;

  ValNodeAddPointer (pub_list, OBJ_SEQFEAT, sfp);
}

static void AddPubDescriptorWithText (SeqDescrPtr sdp, Pointer userdata)
{
  ValNodePtr PNTR pub_list;

  if (sdp == NULL || userdata == NULL || sdp->choice != Seq_descr_pub) return;
  pub_list = (ValNodePtr PNTR) userdata;

  ValNodeAddPointer (pub_list, OBJ_SEQDESC, sdp);
}

static ValNodePtr GetTextPubListsForSeqEntry (SeqEntryPtr sep, CharPtr search_text)
{
  FeaturesWithTextData    ftd;
  DescriptorsWithTextData dtd;
  ValNodePtr              obj_list = NULL;

  ftd.seqFeatChoice = SEQFEAT_PUB;
  ftd.featDefChoice = FEATDEF_PUB;
  ftd.search_text = search_text;
  ftd.act_when_string_not_present = FALSE;
  ftd.case_insensitive = TRUE;
  ftd.whole_word = FALSE;
  ftd.no_text = FALSE;
  ftd.callback = AddPubFeatureWithText;
  ftd.userdata = &obj_list;

  dtd.search_text = search_text;
  dtd.act_when_string_not_present = FALSE;
  dtd.case_insensitive = TRUE;
  dtd.whole_word = FALSE;
  dtd.no_text = FALSE;
  dtd.callback = AddPubDescriptorWithText;
  dtd.userdata = &obj_list;

  OperateOnSeqEntryFeaturesWithText (sep, &ftd);
  OperateOnSeqEntryDescriptorsWithText (sep, &dtd);

  return obj_list;
}

static ValNodePtr GetTextSeqEntryListForSeqEntry (SeqEntryPtr sep, Uint2 entityID, CharPtr search_text, ParseFieldPtr dst_field_data)
{
  ValNodePtr value_lists = NULL;
  ValNodePtr obj_list = NULL, tmp_list, vnp;
  ClickableItemPtr cip;
  CharPtr          desc_fmt = "%d sequences contain %s";
  StringConstraintData scd;
  ChoiceConstraintData ccd;
  FilterSetData        fsd;

  if (dst_field_data == NULL) {
    /* do nothing */
  } else if (dst_field_data->parse_field_type == SEARCH_FIELD_PUBLICATION) {
    obj_list = GetTextPubListsForSeqEntry(sep, search_text);
  } else {
    scd.match_text = search_text;
    scd.match_location = 1;
    scd.insensitive = TRUE;
    scd.whole_word = FALSE;
    scd.not_present = FALSE;

    ccd.constraint_type = CHOICE_CONSTRAINT_STRING;
    ccd.qual_choice = 
    ccd.qual_choice_match = NULL;
    ccd.string_constraint = &scd;
    ccd.pseudo_constraint = NULL;
    ccd.free_vn_proc = NULL;
    ccd.copy_vn_proc = NULL;

    MemSet (&fsd, 0, sizeof (FilterSetData));
    fsd.ccp = &ccd;

    tmp_list = GetUniqueValueListForSeqEntry (sep, entityID, dst_field_data, NULL);

    for (vnp = tmp_list; vnp != NULL; vnp = vnp->next) {
      cip = (ClickableItemPtr) vnp->data.ptrvalue;
      if (cip != NULL && StringISearch (cip->description, search_text) != NULL) {
        ValNodeLink (&obj_list, cip->item_list);
        cip->item_list = NULL;
      }
    }
    tmp_list = FreeClickableList (tmp_list);
  }
  if (obj_list != NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = StringSave (search_text);
    cip->item_list = obj_list;
    cip->chosen = TRUE;
    ValNodeAddPointer (&value_lists, 0, cip);
    value_lists = ChangeUniqueValueListsToSeqEntryLists (value_lists);
    /* rename description */
    if (value_lists != NULL) {
      cip = value_lists->data.ptrvalue;
      if (cip != NULL) {
        cip->description = MemFree (cip->description);
        cip->description = (CharPtr) MemNew (sizeof(Char) * (StringLen (desc_fmt) + StringLen (search_text) + 15));
        sprintf (cip->description, desc_fmt, ValNodeLen (cip->item_list), search_text);
      }
    }
  }

  return value_lists;
}

typedef enum vectorhitlevel {
  eVectorHitLevelSuspect = 1,
  eVectorHitLevelWeak,
  eVectorHitLevelModerate,
  eVectorHitLevelStrong } EVectorHitLevel;

typedef struct vectorcontaminationoptions {
  Int4    internal_level;
  Int4    end5_level;
  Int4    end3_level;
  ValNodePtr bioseq_list;
} VectorContaminationOptionsData, PNTR VectorContaminationOptionsPtr;

typedef struct vectorcontaminationoptionsdlg {
  DIALOG_MESSAGE_BLOCK

  ButtoN internal;
  ButtoN end5;
  ButtoN end3;
  GrouP  internal_level;
  GrouP  end5_level;
  GrouP  end3_level;

  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} VectorContaminationOptionsDlgData, PNTR VectorContaminationOptionsDlgPtr;

static void DataToVectorContaminationOptionsDlg (DialoG d, Pointer data)
{
  VectorContaminationOptionsDlgPtr dlg;
  VectorContaminationOptionsPtr    options;

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

  options = (VectorContaminationOptionsPtr) data;
  if (options == NULL) {
    SetStatus (dlg->internal, FALSE);
    SetStatus (dlg->end5, FALSE);
    SetStatus (dlg->end3, FALSE);
    SetValue (dlg->internal_level, eVectorHitLevelStrong);
    SetValue (dlg->end5_level, eVectorHitLevelStrong);
    SetValue (dlg->end3_level, eVectorHitLevelStrong);
  } else {
    if (options->internal_level == 0) {
      SetStatus (dlg->internal, FALSE);
    } else {
      SetStatus (dlg->internal, TRUE);
      SetValue (dlg->internal_level, options->internal_level);
    }
      
    if (options->end5_level == 0) {
      SetStatus (dlg->end5, FALSE);
    } else {
      SetStatus (dlg->end5, TRUE);
      SetValue (dlg->end5_level, options->end5_level);
    }
    if (options->end3_level == 0) {
      SetStatus (dlg->end3, FALSE);
    } else {
      SetStatus (dlg->end3, TRUE);
      SetValue (dlg->end3_level, options->end3_level);
    }
  }
}


static Pointer VectorContaminationOptionsDlgToData (DialoG d)
{
  VectorContaminationOptionsDlgPtr dlg;
  VectorContaminationOptionsPtr    options = NULL;

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

  options = (VectorContaminationOptionsPtr) MemNew (sizeof (VectorContaminationOptionsData));
  if (GetStatus (dlg->internal)) {
    options->internal_level = GetValue (dlg->internal_level);
  } else {
    options->internal_level = 0;
  }
  if (GetStatus (dlg->end5)) {
    options->end5_level = GetValue (dlg->end5_level);
  } else {
    options->end5_level = 0;
  }
  if (GetStatus (dlg->end3)) {
    options->end3_level = GetValue (dlg->end3_level);
  } else {
    options->end3_level = 0;
  }

  options->bioseq_list = NULL;
  return (Pointer) options;
}


static void ChangeVectorContaminationButton (ButtoN b)
{
  VectorContaminationOptionsDlgPtr dlg;

  dlg = (VectorContaminationOptionsDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) {
    return;
  }

  if (GetStatus (dlg->internal)) {
    Enable (dlg->internal_level);
  } else {
    Disable (dlg->internal_level);
  }
  if (GetStatus (dlg->end5)) {
    Enable (dlg->end5_level);
  } else {
    Disable (dlg->end5_level);
  }
  if (GetStatus (dlg->end3)) {
    Enable (dlg->end3_level);
  } else {
    Disable (dlg->end3_level);
  }

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


static void ChangeVectorContaminationGroup (GrouP g)
{
  VectorContaminationOptionsDlgPtr dlg;

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


static DialoG VectorContaminationOptionsDialog (GrouP h, Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  VectorContaminationOptionsDlgPtr dlg;
  GrouP                     p;
  
  dlg = (VectorContaminationOptionsDlgPtr) MemNew (sizeof (VectorContaminationOptionsDlgData));
  if (dlg == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, 2, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  SetGroupSpacing (p, 10, 10);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = DataToVectorContaminationOptionsDlg;
  dlg->fromdialog = VectorContaminationOptionsDlgToData;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->internal = CheckBox (p, "Internal", ChangeVectorContaminationButton);
  SetObjectExtra (dlg->internal, dlg, NULL);
  dlg->internal_level = HiddenGroup (p, 4, 0, ChangeVectorContaminationGroup);
  SetObjectExtra (dlg->internal_level, dlg, NULL);
  RadioButton (dlg->internal_level, "Suspect");
  RadioButton (dlg->internal_level, "Weak");
  RadioButton (dlg->internal_level, "Moderate");
  RadioButton (dlg->internal_level, "Strong");
  SetValue (dlg->internal_level, 1);
  Disable (dlg->internal_level);

  dlg->end5 = CheckBox (p, "5' End", ChangeVectorContaminationButton);
  SetObjectExtra (dlg->end5, dlg, NULL);
  dlg->end5_level = HiddenGroup (p, 4, 0, ChangeVectorContaminationGroup);
  SetObjectExtra (dlg->end5_level, dlg, NULL);
  RadioButton (dlg->end5_level, "Suspect");
  RadioButton (dlg->end5_level, "Weak");
  RadioButton (dlg->end5_level, "Moderate");
  RadioButton (dlg->end5_level, "Strong");
  SetValue (dlg->end5_level, 1);
  Disable (dlg->end5_level);

  dlg->end3 = CheckBox (p, "3' End", ChangeVectorContaminationButton);
  SetObjectExtra (dlg->end3, dlg, NULL);
  dlg->end3_level = HiddenGroup (p, 4, 0, ChangeVectorContaminationGroup);
  SetObjectExtra (dlg->end3_level, dlg, NULL);
  RadioButton (dlg->end3_level, "Suspect");
  RadioButton (dlg->end3_level, "Weak");
  RadioButton (dlg->end3_level, "Moderate");
  RadioButton (dlg->end3_level, "Strong");
  SetValue (dlg->end3_level, 1);
  Disable (dlg->end3_level);

  return (DialoG) p;
}


typedef struct chooseseqsbylen {
  ValNodePtr bsp_list;
  Int4 min;
  Int4 max;
} ChooseSeqsByLenData, PNTR ChooseSeqsByLenPtr;


typedef struct chooseseqsbylendlg {
  DIALOG_MESSAGE_BLOCK
  TexT min;
  TexT max;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} ChooseSeqsByLenDlgData, PNTR ChooseSeqsByLenDlgPtr;


static void DataToChooseSeqsByLenDlg (DialoG d, Pointer data)
{
  ChooseSeqsByLenDlgPtr dlg;
  ChooseSeqsByLenPtr    options;
  Char                  buf[255];

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

  options = (ChooseSeqsByLenPtr) data;
  if (options == NULL) {
    SetTitle (dlg->min, "0");
    SetTitle (dlg->max, "0");
  } else {
    sprintf (buf, "%d", options->min);
    SetTitle (dlg->min, buf);
    sprintf (buf, "%d", options->max);
    SetTitle (dlg->max, buf);
  }
}


static Pointer ChooseSeqsByLenDlgToData (DialoG d)
{
  ChooseSeqsByLenDlgPtr dlg;
  ChooseSeqsByLenPtr    options = NULL;
  CharPtr               buf;

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

  options = (ChooseSeqsByLenPtr) MemNew (sizeof (ChooseSeqsByLenData));
  if (TextHasNoText (dlg->min)) {
    options->min = 0;
  } else {
    buf = SaveStringFromText (dlg->min);
    options->min = atoi (buf);
    buf = MemFree (buf);
  }
  if (TextHasNoText (dlg->max)) {
    options->max = 0;
  } else {
    buf = SaveStringFromText (dlg->max);
    options->max = atoi (buf);
    buf = MemFree (buf);
  }
  return (Pointer) options;
}


static void ChangeChooseSeqsByLenText (TexT t)
{
  ChooseSeqsByLenDlgPtr dlg;

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

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


static DialoG ChooseSeqsByLenDialog (GrouP h, Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  ChooseSeqsByLenDlgPtr dlg;
  GrouP                     p;
  
  dlg = (ChooseSeqsByLenDlgPtr) MemNew (sizeof (ChooseSeqsByLenDlgData));
  if (dlg == NULL)
  {
    return NULL;
  }

  p = HiddenGroup (h, 2, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  SetGroupSpacing (p, 10, 10);
  
  dlg->dialog = (DialoG) p;
  dlg->todialog = DataToChooseSeqsByLenDlg;
  dlg->fromdialog = ChooseSeqsByLenDlgToData;
  dlg->dialogmessage = NULL;
  dlg->testdialog = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  StaticPrompt (p, "Minimum length", 0, dialogTextHeight, programFont, 'c');
  dlg->min = DialogText (p, "0", 10, ChangeChooseSeqsByLenText);
  SetObjectExtra (dlg->min, dlg, NULL);
  StaticPrompt (p, "Maximum length", 0, dialogTextHeight, programFont, 'c');
  dlg->max = DialogText (p, "50", 10, ChangeChooseSeqsByLenText);
  SetObjectExtra (dlg->max, dlg, NULL);

  return (DialoG) p;
}


typedef enum {
  eSegPageId = 1,
  eSegPageText,
  eSegPageField,
  eSegPageMolInfo,
  eSegPageFeatureType,
  eSegPageDescriptorType,
  eSegPageLength,
  eSegPageVectorContamination,
  eSegPageNumFinalSets,
  eSegPageFile,
  eSegPageConstraint,
  eSegPageStructuredCommentField,
  eNumSegPages
} ESegPage;

typedef struct segbyfield {
  FORM_MESSAGE_BLOCK
  GrouP        seg_choice_grp;

  GrouP        pages[eNumSegPages];
  /* for segregating by field */
  DialoG       field_dlg;
  DialoG       group_list_dlg;

  /* for segregating by molinfo */
  ButtoN       mol_type_btn;
  ButtoN       mol_class_btn;
  ButtoN       mol_topology_btn;

  /* for segregating by feature */
  DialoG       feature_select;

  /* for segregating by descriptor */
  DialoG       descriptor_select;

  /* for segregating by text */
  DialoG       search_field;
  TexT         search_text;

  /* for segregating by ID */
  DialoG       id_constraint_dlg;

  /* for segregating by length */
  DialoG       len_options;

  /* for segregating by vector contamination */
  DialoG       vector_options;

  /* for segregating into a specific number of sets */
  TexT         num_sets;

  /* for segregating by any constraint */
  DialoG       seq_constraint_dlg;

  /* for segregating by structured comment field */
  DialoG       struc_comm_field;

  Nlm_BtnActnProc actn;
  ButtoN       accept;
  ButtoN       leave_dlg_up;
  BioseqSetPtr target_set;
  ValNodePtr   value_lists;
} SegByFieldData, PNTR SegByFieldPtr;

static void ChangeSegChoice (GrouP g);


static void PullChosenIntoOneGroup (ValNodePtr PNTR value_list, ClickableItemPtr chosen)
{
  ValNodePtr vnp_prev = NULL, vnp_next, vnp_this;
  ClickableItemPtr cip;

  if (value_list == NULL || chosen == NULL) return;

  vnp_this = *value_list;
  while (vnp_this != NULL)
  {
    vnp_next = vnp_this->next;
    cip = (ClickableItemPtr) vnp_this->data.ptrvalue;
    if (cip == NULL)
    {
      vnp_this->next = NULL;
      if (vnp_prev == NULL) 
      {
        *value_list = vnp_next;
      }
      else
      {
        vnp_prev->next = vnp_next;
      }
      vnp_this = ValNodeFree (vnp_this);
    }
    else if (cip->chosen)
    {
      vnp_this->next = NULL;
      if (vnp_prev == NULL) 
      {
        *value_list = vnp_next;
      }
      else
      {
        vnp_prev->next = vnp_next;
      }
      ValNodeLink (&(chosen->item_list), cip->item_list);
      cip->item_list = NULL;
      vnp_this = FreeClickableList (vnp_this);
    }
    else
    {
      PullChosenIntoOneGroup (&(cip->subcategories), chosen);
      vnp_prev = vnp_this;
    }
    vnp_this = vnp_next;
  }
}


static Int4 NumChosenThatContainSequencesInAlignments (ValNodePtr value_lists, Boolean count_all)
{
  Int4 rval = 0;
  ClickableItemPtr cip;
  ValNodePtr vnp, vnp_item;
  BioseqPtr  bsp;
  BioseqSetPtr bssp;
  SeqEntryPtr sep;

  if (value_lists == NULL) 
  {
    return 0;
  }

  for (vnp = value_lists; vnp != NULL; vnp = vnp->next) 
  {
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    if (cip == NULL)
    {
      /* ignore */
    } 
    else if (cip->chosen || count_all) 
    {
      if (cip->item_list == NULL) 
      {
        rval += NumChosenThatContainSequencesInAlignments (cip->subcategories, TRUE);
      } 
      else 
      {
        for (vnp_item = cip->item_list; vnp_item != NULL; vnp_item = vnp_item->next) 
        {
          if (vnp_item->choice == OBJ_BIOSEQ) 
          {
            bsp = (BioseqPtr) vnp_item->data.ptrvalue;
            if (bsp != NULL && IsBioseqInAnyAlignment (bsp, bsp->idx.entityID))
            {
              rval++;
            }
          }
          else if (vnp_item->choice == OBJ_BIOSEQSET)
          {
            bssp = (BioseqSetPtr) vnp_item->data.ptrvalue;
            if (bssp != NULL && AreAnyElementsOfSetInAnyAlignment (bssp, bssp->idx.entityID))
            {
              rval++;
            }
          }
          else if (vnp_item->choice == OBJ_SEQENTRY)
          {
            sep = vnp_item->data.ptrvalue;
            if (IS_Bioseq (sep)) 
            {
              bsp = (BioseqPtr) sep->data.ptrvalue;
              if (bsp != NULL && IsBioseqInAnyAlignment (bsp, bsp->idx.entityID))
              {
                rval++;
              }
            }
            else if (IS_Bioseq_set(sep))
            {
              bssp = (BioseqSetPtr) sep->data.ptrvalue;
              if (bssp != NULL && AreAnyElementsOfSetInAnyAlignment (bssp, bssp->idx.entityID))
              {
                rval++;
              }
            }
          }
        }
      }
    }
    else 
    {
      rval += NumChosenThatContainSequencesInAlignments (cip->subcategories, FALSE);
    }
  }

  return rval;
}


static void RemoveAlignmentsWithChosenSegregateSequences (ValNodePtr value_lists, Uint2 entityID, Boolean all_chosen)
{
  ClickableItemPtr cip;
  ValNodePtr vnp, vnp_item;
  BioseqPtr  bsp;
  SeqEntryPtr sep;

  if (value_lists == NULL) 
  {
    return;
  }

  for (vnp = value_lists; vnp != NULL; vnp = vnp->next) 
  {
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    if (cip == NULL)
    {
      /* ignore */
    } 
    else if (cip->chosen || all_chosen) 
    {
      if (cip->item_list == NULL) 
      {
        RemoveAlignmentsWithChosenSegregateSequences (cip->subcategories, entityID, TRUE);
      } 
      else 
      {
        for (vnp_item = cip->item_list; vnp_item != NULL; vnp_item = vnp_item->next) 
        {
          if (vnp_item->choice == OBJ_BIOSEQ) 
          {
            bsp = (BioseqPtr) vnp_item->data.ptrvalue;
            RemoveAlignmentsWithSequence (bsp, entityID);
          }
          else if (vnp_item->choice == OBJ_BIOSEQSET)
          {
            RemoveAlignmentsWithElementsOfSet (vnp_item->data.ptrvalue, entityID);
          }
          else if (vnp_item->choice == OBJ_SEQENTRY)
          {
            sep = vnp_item->data.ptrvalue;
            if (IS_Bioseq (sep)) 
            {
              bsp = (BioseqPtr) sep->data.ptrvalue;
              RemoveAlignmentsWithSequence (bsp, entityID);
            }
            else if (IS_Bioseq_set(sep))
            {
              RemoveAlignmentsWithElementsOfSet (sep->data.ptrvalue, entityID);
            }
          }

        }
      }
    }
    else 
    {
      RemoveAlignmentsWithChosenSegregateSequences (cip->subcategories, entityID, FALSE);
    }
  }
}

static void PropagateDescriptorBeforeSegregate(SeqEntryPtr sep)
{
    BioseqSetPtr bssp;

    if (sep == NULL || !IS_Bioseq_set(sep)
        || (bssp = (BioseqSetPtr) sep->data.ptrvalue) == NULL
        || bssp->_class == BioseqseqSet_class_nuc_prot) {
        return;
    }
    SetDescriptorPropagate(bssp);
    for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
        PropagateDescriptorBeforeSegregate(sep);
    }
}


static void SegregateByField_Callback (ButtoN b)
{
  SegByFieldPtr sfp;
  Int4          num_chosen = 0, num_in_align;
  ClickableItemPtr cip;
  Int2             val;
  MsgAnswer        ans;
  SeqEntryPtr      sep, orig_sep;
  
  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp == NULL) return;

  num_chosen = CountChosenDiscrepancies (sfp->value_lists, FALSE);
  if (num_chosen < 1) {
    if (ANS_OK == Message (MSG_OKC, "You have not chosen any groups.  Create all?")) {
      ChooseAllDiscrepancies (sfp->value_lists);
    } else {
      return;
    }
  }

  sep = GetTopSeqEntryForEntityID (sfp->input_entityID);
  if (sep == NULL) {
    Message (MSG_ERROR, "No Seq-entry to segregate!");
  } else if (!IS_Bioseq_set(sep)) {
    Message (MSG_ERROR, "Cannot segregate a single sequence!");
  }

  orig_sep = SeqEntrySetScope (sep);
  num_in_align = NumChosenThatContainSequencesInAlignments(sfp->value_lists, FALSE);
  SeqEntrySetScope (orig_sep);
  if (num_in_align > 0) {
    ans = Message (MSG_YNC, "%d sequences are in alignments.  Do you want to remove the alignments?", num_in_align);
    if (ans == ANS_CANCEL) {
      ChangeSegChoice (sfp->seg_choice_grp);
      return;
    } else if (ans == ANS_YES) {
      RemoveAlignmentsWithChosenSegregateSequences (sfp->value_lists, sfp->input_entityID, FALSE);
    }
  }

  RemovePopsetTitles (sep);
  PropagateDescriptorBeforeSegregate (sep);

  val = GetValue (sfp->seg_choice_grp);
  if (val == eSegPageId || val == eSegPageVectorContamination || val == eSegPageLength || val == eSegPageConstraint) {
    /* put all selected sequences into one group */
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    cip->chosen = TRUE;
    PullChosenIntoOneGroup (&(sfp->value_lists), cip);
    ValNodeAddPointer (&(sfp->value_lists), 0, cip);
  }

  sfp->target_set = MakeGroupsForUniqueValues (sfp->target_set, sfp->value_lists);

  if (GetStatus (sfp->leave_dlg_up)) {
    ChangeSegChoice (sfp->seg_choice_grp);
  } else {
    Remove (sfp->form);
  }
}

static void ChangeSegField (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;
  ParseFieldPtr dst_field_data;

  if (sfp == NULL || sfp->target_set == NULL) return;

  WatchCursor();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  dst_field_data = DialogToPointer (sfp->field_dlg);
  if (dst_field_data == NULL) {
    Disable (sfp->accept);
    return;
  }

  sfp->value_lists = CreateSeqEntryListsForUniqueValues (sep, sfp->target_set->idx.entityID, dst_field_data, NULL);
  dst_field_data = ParseFieldFree (dst_field_data);
  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  Enable (sfp->accept);
  ArrowCursor();
  Update();
}


static void ChangeSegStructuredCommentField (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;
  ValNodePtr    vnp;

  if (sfp == NULL || sfp->target_set == NULL) return;

  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  vnp = DialogToPointer (sfp->struc_comm_field);
  if (vnp == NULL) {
    Disable (sfp->accept);
    return;
  }

  WatchCursor();
  Update();

  sfp->value_lists = GetUniqueValuesForStructuredCommentField (sep, vnp->data.ptrvalue);
  sfp->value_lists = ChangeUniqueValueListsToSeqEntryLists (sfp->value_lists);
  vnp = ValNodeFreeData (vnp);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  Enable (sfp->accept);
  ArrowCursor();
  Update();
}


static void SegTextChangeNotify (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;
  ParseFieldPtr dst_field_data;
  CharPtr       search_text;

  if (sfp == NULL || sfp->target_set == NULL) return;

  WatchCursor();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  if (TextHasNoText (sfp->search_text)) {
    Disable (sfp->accept);
    ArrowCursor();
    Update();
    return;
  }
  dst_field_data = DialogToPointer (sfp->search_field);
  if (dst_field_data == NULL) {
    Disable (sfp->accept);
    ArrowCursor();
    Update();
    return;
  }
  search_text = SaveStringFromText (sfp->search_text);

  sfp->value_lists = GetTextSeqEntryListForSeqEntry (sep, sfp->target_set->idx.entityID, search_text, dst_field_data);

  search_text = MemFree (search_text);

  dst_field_data = ParseFieldFree (dst_field_data);
  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  if (sfp->value_lists == NULL) {
    Disable(sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}

static void SegTextChange (TexT t)
{
  SegByFieldPtr sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (t);

  SegTextChangeNotify (sfp);

}


static Boolean IsBankitId (SeqIdPtr sip)
{
  DbtagPtr dbtag;

  if (sip == NULL || sip->choice != SEQID_GENERAL) {
    return FALSE;
  }
  if ((dbtag = (DbtagPtr) sip->data.ptrvalue) == NULL) {
    return FALSE;
  }
  if (StringCmp (dbtag->db, "BankIt") == 0) {
    return TRUE;
  } else {
    return FALSE;
  }
}


NLM_EXTERN ClickableItemPtr ClickableItemForBioseq (BioseqPtr bsp)
{
  ClickableItemPtr cip = NULL;
  SeqIdPtr         sip, sip2;
  Char             id_str[3][100];
  Int4             num_id = 1, len = 0;
  Int4             i;

  if (bsp != NULL && ! ISA_aa (bsp->mol))
  {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    sip = SeqIdFindBest (bsp->id, SEQID_GENBANK);
    SeqIdWrite (sip, id_str[0], PRINTID_REPORT, sizeof (id_str[0]) - 1);
    len = StringLen (id_str[0]) + 1;
    if (!IsBankitId(sip)) {
      sip2 = bsp->id;
      while (sip2 != NULL && !IsBankitId(sip2)) {
        sip2 = sip2->next;
      }
      if (sip2 != NULL) {
        SeqIdWrite (sip2, id_str[1], PRINTID_REPORT, sizeof (id_str[1]) - 1);
        len += StringLen (id_str[1]) + 1;
        num_id++;
      }
    }
    if (!IsNCBIFileID(sip)) {
      sip2 = bsp->id;
      while (sip2 != NULL && !IsNCBIFileID(sip2)) {
        sip2 = sip2->next;
      }
      if (sip2 != NULL) {
        SeqIdWrite (sip2, id_str[1], PRINTID_REPORT, sizeof (id_str[1]) - 1);
        len += StringLen (id_str[1]) + 1;
        num_id++;
      }
    }

    /* space for length */
    len += 20;

    cip->description = (CharPtr) MemNew (sizeof (Char) * len);      
    sprintf (cip->description, "%s", id_str[0]);
    for (i = 1; i < num_id; i++) {
      StringCat (cip->description, "|");
      StringCat (cip->description, id_str[i]);
    }
    sprintf (cip->description + StringLen (cip->description), ":%d", bsp->length);

    ValNodeAddPointer (&(cip->item_list), OBJ_BIOSEQ, bsp);
  }
  return cip;
}


extern void ListAllSequences (BioseqPtr bsp, Pointer userdata)
{
  ClickableItemPtr cip;

  cip = ClickableItemForBioseq (bsp);
  if (cip != NULL) 
  {
    ValNodeAddPointer ((ValNodePtr PNTR) userdata, 0, cip);
  }
}


static void ChooseSegId (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;

  if (sfp == NULL || sfp->target_set == NULL) return;

  WatchCursor();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  VisitBioseqsInSep (sep, &sfp->value_lists, ListAllSequences);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  if (sfp->value_lists == NULL) {
    Disable(sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}


static Boolean DoesLevelStringMatchLevel (CharPtr str, Int4 level)
{
  Boolean rval = FALSE;

  switch (level) {
    case eVectorHitLevelSuspect:
      if (StringISearch (str, "suspect") != NULL) {
        rval = TRUE;
      }
      /* note - absence of break is deliberate */
    case eVectorHitLevelWeak:
      if (StringISearch (str, "weak") != NULL) {
        rval = TRUE;
      }
      /* note - absence of break is deliberate */
    case eVectorHitLevelModerate:
      if (StringISearch (str, "moderate") != NULL) {
        rval = TRUE;
      }
      /* note - absence of break is deliberate */
    case eVectorHitLevelStrong:
      if (StringISearch (str, "strong") != NULL) {
        rval = TRUE;
      }
      break;
  }
  return rval;
}


static Boolean DoesVectorMatchOptions (ClickableItemPtr cip, VectorContaminationOptionsPtr options)
{
  Boolean rval = FALSE;

  if (cip == NULL || options == NULL || StringHasNoText (cip->description)) {
    return FALSE;
  }

  if (options->internal_level > 0 
      && StringNICmp (cip->description, "Internal", 8) == 0
      && DoesLevelStringMatchLevel (cip->description, options->internal_level)) {
    rval = TRUE;
  } else if (options->end5_level > 0
      && StringNICmp (cip->description, "5'", 2) == 0
      && DoesLevelStringMatchLevel (cip->description, options->end5_level)) {
    rval = TRUE;
  } else if (options->end3_level > 0
      && StringNICmp (cip->description, "3'", 2) == 0
      && DoesLevelStringMatchLevel (cip->description, options->end3_level)) {
    rval = TRUE;
  }
  return rval;
}


static void GetVectorBioseqList (BioseqPtr bsp, Pointer data)
{
  VectorContaminationOptionsPtr options;
  SeqFeatPtr                    sfp;
  SeqMgrFeatContext             fcontext;
  Boolean                       isvector;
  GBQualPtr                     gbq;
  Int4                          last_right;
  ClickableItemPtr              cip = NULL, cip_match;
  ValNodePtr                    vector_list = NULL, vnp;
  Int4                          desc_len = 0;
  Boolean                       is_match = FALSE;

  if (bsp == NULL || data == NULL) {
    return;
  }

  options = (VectorContaminationOptionsPtr) data;
  
  for (sfp = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_IMP, FEATDEF_misc_feature, &fcontext);
       sfp != NULL;
       sfp = SeqMgrGetNextFeature (bsp, sfp, SEQFEAT_IMP, FEATDEF_misc_feature, &fcontext)) {         
    for (isvector = FALSE, gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
      if (StringCmp (gbq->qual, "standard_name") == 0) {
        if (StringCmp (gbq->val, "Vector Contamination") == 0) {
          isvector = TRUE;
        }
      }
    }
    if (isvector) {
      if (cip != NULL && last_right >= fcontext.left - 1) {
        ValNodeAddPointer (&(cip->item_list), OBJ_SEQFEAT, sfp);
        last_right = fcontext.right;
      } else {        
        /* calculate description for previous list */
        CalculateVectorDescription (cip);
        cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
        if (cip != NULL) {
          cip->clickable_item_type = 0;
          cip->callback_func = NULL;
          cip->datafree_func = NULL;
          cip->callback_data = NULL;
          cip->item_list = NULL;
          cip->subcategories = NULL;
          cip->expanded = FALSE;
          cip->level = 0;
          cip->description = NULL;
  
          ValNodeAddPointer (&(cip->item_list), OBJ_SEQFEAT, sfp);

          ValNodeAddPointer (&vector_list, 0, cip);
          last_right = fcontext.right;
        }
      }
    }
  }
  /* calculate description for last item */
  CalculateVectorDescription (cip);

  /* now determine whether we have a hit that matches our requirements */
  for (vnp = vector_list; vnp != NULL; vnp = vnp->next) {
    cip = vnp->data.ptrvalue;
    if (DoesVectorMatchOptions (cip, options)) {
      is_match = TRUE;
    }
    desc_len += StringLen (cip->description) + 2;
  }

  if (is_match) {
    cip_match = MemNew (sizeof (ClickableItemData));
    cip_match->description = (CharPtr) MemNew (sizeof (Char) * desc_len);
    cip_match->description[0] = 0;
    for (vnp = vector_list; vnp != NULL; vnp = vnp->next) {
      cip = vnp->data.ptrvalue;
      StringCat (cip_match->description, cip->description);
      if (vnp->next != NULL) {
        StringCat (cip_match->description, ";");
      }
    }
    ValNodeAddPointer (&(cip_match->item_list), OBJ_BIOSEQ, bsp);
    cip_match->chosen = TRUE;
    ValNodeAddPointer (&(options->bioseq_list), 0, cip_match);
  }
  vector_list = FreeClickableList (vector_list);
}


static void ChooseVectorSeqs (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;
  VectorContaminationOptionsPtr options;

  if (sfp == NULL || sfp->target_set == NULL) return;

  WatchCursor();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  options = DialogToPointer (sfp->vector_options);

  VisitBioseqsInSep (sep, options, GetVectorBioseqList);

  sfp->value_lists = options->bioseq_list;
  options->bioseq_list = NULL;

  options = MemFree (options);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  if (sfp->value_lists == NULL) {
    Disable(sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}


static void SeqsByLenCallback (BioseqPtr bsp, Pointer userdata)
{
  ChooseSeqsByLenPtr p = (ChooseSeqsByLenPtr) userdata;
  ClickableItemPtr   cip_match;
  CharPtr            fmt = "%s %d";
  Char               id_str[45];

  if (bsp == NULL || ISA_aa (bsp->mol) || p == NULL) {
    return;
  }

  if (bsp->length >= p->min && bsp->length <= p->max) {
    cip_match = MemNew (sizeof (ClickableItemData));

    /* create description (use ID and length ) */
    SeqIdWrite (SeqIdFindBest (bsp->id, SEQID_GENBANK), id_str, PRINTID_REPORT, sizeof (id_str) - 1);
    cip_match->description = (CharPtr) MemNew (sizeof (Char) * (StringLen (fmt) + StringLen (id_str) + 15));
    sprintf (cip_match->description, fmt, id_str, bsp->length);

    ValNodeAddPointer (&(cip_match->item_list), OBJ_BIOSEQ, bsp);
    cip_match->chosen = TRUE;
    ValNodeAddPointer (&(p->bsp_list), 0, cip_match);
  }
}


static void ChooseSeqsByLen (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;
  ChooseSeqsByLenPtr options;

  if (sfp == NULL || sfp->target_set == NULL) return;

  WatchCursor();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  options = DialogToPointer (sfp->len_options);

  VisitBioseqsInSep (sep, options, SeqsByLenCallback);

  sfp->value_lists = options->bsp_list;
  options->bsp_list = NULL;

  options = MemFree (options);
  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  if (sfp->value_lists == NULL) {
    Disable(sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}


static void ChangeMol (ButtoN b)
{
  SegByFieldPtr sfp;
  SeqEntryPtr   sep;

  sfp = (SegByFieldPtr) GetObjectExtra (b);

  if (sfp == NULL) return;

  WatchCursor ();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  if (GetStatus (sfp->mol_class_btn)) {
    VisitBioseqsInSep (sep, &(sfp->value_lists), GetMolClasses);
  }
  if (GetStatus (sfp->mol_type_btn)) {
    VisitDescriptorsInSep (sep, &(sfp->value_lists), GetMolTypes);
  }
  if (GetStatus (sfp->mol_topology_btn)) {
   VisitBioseqsInSep (sep, &(sfp->value_lists), GetMolTopologies);
  }

  sfp->value_lists = ChangeUniqueValueListsToSeqEntryLists (sfp->value_lists);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  if (ValNodeLen (sfp->value_lists) < 2) {
    Disable(sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}


static void SegFeatureChangeNotify (Pointer userdata)
{
  SegByFieldPtr sfp = (SegByFieldPtr) userdata;
  SeqEntryPtr   sep;
  SegFeaturesData sfd;
  ClickableItemPtr cip;
  ValNodePtr       vnp;
  CharPtr          desc_fmt = "%d %ss";

  if (sfp == NULL) return;

  vnp = DialogToPointer (sfp->feature_select);
  if (vnp == NULL) {
    PointerToDialog (sfp->group_list_dlg, NULL);
    sfp->value_lists = FreeClickableList (sfp->value_lists);
    Disable (sfp->accept);
    return;
  }

  WatchCursor ();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  sfd.featdef = vnp->choice;
  sfd.feature_list = NULL;
  VisitFeaturesInSep (sep, &sfd, FindFeaturesForSeg);

  if (sfd.feature_list != NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = (CharPtr) MemNew (sizeof(Char) * (StringLen (desc_fmt) + StringLen (vnp->data.ptrvalue) + 15));
    sprintf (cip->description, desc_fmt, ValNodeLen (sfd.feature_list), vnp->data.ptrvalue);
    cip->item_list = sfd.feature_list;
    cip->chosen = TRUE;
    ValNodeAddPointer (&(sfp->value_lists), 0, cip);
    sfp->value_lists = ChangeUniqueValueListsToSeqEntryLists (sfp->value_lists);
  }
  vnp = ValNodeFree (vnp);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);

  if (sfp->value_lists == NULL) {
    Disable (sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}

static void SegDescriptorChangeNotify (Pointer data)
{
  SegByFieldPtr      sfp;
  ValNodePtr         vnp;
  SegDescriptorsData sdd;
  ClickableItemPtr   cip;
  CharPtr            desc_fmt = "%d %ss";
  SeqEntryPtr        sep;

  sfp = (SegByFieldPtr) data;
  if (sfp == NULL) return;

  vnp = DialogToPointer (sfp->descriptor_select);
  if (vnp == NULL) {
    PointerToDialog (sfp->group_list_dlg, NULL);
    sfp->value_lists = FreeClickableList (sfp->value_lists);
    Disable (sfp->accept);
    return;
  }

  WatchCursor ();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  sdd.desc_type = vnp->choice;
  sdd.descriptor_list = NULL;
  VisitDescriptorsInSep (sep, &sdd, FindDescriptorsForSeg);

  if (sdd.descriptor_list != NULL) {
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    MemSet (cip, 0, sizeof (ClickableItemData));
    cip->description = (CharPtr) MemNew (sizeof(Char) * (StringLen (desc_fmt) + StringLen (vnp->data.ptrvalue) + 15));
    sprintf (cip->description, desc_fmt, ValNodeLen (sdd.descriptor_list), vnp->data.ptrvalue);
    cip->item_list = sdd.descriptor_list;
    cip->chosen = TRUE;
    ValNodeAddPointer (&(sfp->value_lists), 0, cip);
    sfp->value_lists = ChangeUniqueValueListsToSeqEntryLists (sfp->value_lists);
  }
  vnp = ValNodeFree (vnp);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);

  if (sfp->value_lists == NULL) {
    Disable (sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();

}

static void ChooseSeqsForNumSets (Pointer data)
{
  SegByFieldPtr      sfp;
  SeqEntryPtr        sep;
  Int4               num_sets;
  CharPtr            str;

  sfp = (SegByFieldPtr) data;
  if (sfp == NULL) return;

  str = SaveStringFromText (sfp->num_sets);
  if (str == NULL) {
    Disable (sfp->accept);
    return;
  }

  num_sets = atoi (str);
  str = MemFree (str);
  if (num_sets < 1) {
    Disable (sfp->accept);
    return;
  }

  WatchCursor ();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  sfp->value_lists = PrepareSequenceListForSegregateByNumberOfSets (num_sets, sep);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);

  if (sfp->value_lists == NULL) {
    Disable (sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}

static void SegNumSetsChange (TexT t)
{
  SegByFieldPtr sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (t);

  ChooseSeqsForNumSets (sfp);
}


static void GroupSeqByFileCallback (BioseqPtr bsp, Pointer data)
{
  ValNodePtr PNTR list;
  ValNodePtr vnp;
  SeqIdPtr   sip;
  DbtagPtr   dbtag;
  Char       id_buf[15];
  CharPtr    id = NULL, last_slash;
  ClickableItemPtr cip;
  Boolean          found = FALSE;

  if (bsp == NULL || ISA_aa (bsp->mol) || (list = (ValNodePtr PNTR) data) == NULL) {
    return;
  }

  sip = bsp->id; 
  while (sip != NULL && !IsNCBIFileID(sip)) {
    sip = sip->next;
  }

  if (sip != NULL && (dbtag = (DbtagPtr) sip->data.ptrvalue) != NULL) {
    if (dbtag->tag->id > 0) {
      sprintf (id_buf, "%d", dbtag->tag->id);
      id = id_buf;
    } else {
      id = dbtag->tag->str;
    }
    id = StringSave (id);
    if ((last_slash = StringRChr (id, '/')) != NULL) {
      *last_slash = 0;
    }
    for (vnp = *list; vnp != NULL && !found; vnp = vnp->next) {
      cip = (ClickableItemPtr) vnp->data.ptrvalue;
      if (cip != NULL && StringCmp (id, cip->description) == 0) {
        ValNodeAddPointer (&cip->item_list, OBJ_BIOSEQ, bsp);
        found = TRUE;
        id = MemFree (id);
      }
    }
    if (!found) {
      cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
      cip->description = id;
      ValNodeAddPointer (&cip->item_list, OBJ_BIOSEQ, bsp);
      ValNodeAddPointer (list, 0, cip);
    }
  }
}


static void ChooseFile (SegByFieldPtr sfp)
{
  SeqEntryPtr        sep;

  if (sfp == NULL) return;

  WatchCursor ();
  Update();
  sep = SeqMgrGetSeqEntryForData (sfp->target_set);

  PointerToDialog (sfp->group_list_dlg, NULL);
  sfp->value_lists = FreeClickableList (sfp->value_lists);

  VisitBioseqsInSep (sep, &(sfp->value_lists), GroupSeqByFileCallback);

  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);

  if (sfp->value_lists == NULL) {
    Disable (sfp->accept);
  } else {
    Enable (sfp->accept);
  }
  ArrowCursor();
  Update();
}


static void ChangeSegChoice (GrouP g)
{
  SegByFieldPtr sfp;
  Int4          seg_choice, i;

  sfp = (SegByFieldPtr) GetObjectExtra (g);
  if (sfp == NULL) return;

  seg_choice = GetValue (sfp->seg_choice_grp);

    for (i = 0; i < eNumSegPages; i++) {
    if (i != seg_choice - 1) {
        SafeHide (sfp->pages[i]);
      }
    }
    Update ();

    for (i = 0; i < eNumSegPages; i++) {
    if (i == seg_choice - 1) {
        SafeShow (sfp->pages[i]);
      }
    }
    Update ();

    switch (seg_choice) {
      case eSegPageField:
        ChangeSegField (sfp);
          break;
      case eSegPageMolInfo:
        ChangeMol(sfp->mol_class_btn);
        break;
      case eSegPageFeatureType:
        SegFeatureChangeNotify (sfp);
        break;
    case eSegPageDescriptorType:
        SegDescriptorChangeNotify (sfp);
        break;
    case eSegPageText:
        SegTextChangeNotify (sfp);
        break;
    case eSegPageId:
    case eSegPageConstraint:
        ChooseSegId (sfp);
        break;
    case eSegPageVectorContamination:
        ChooseVectorSeqs (sfp);
        break;
    case eSegPageLength:
        ChooseSeqsByLen (sfp);
        break;
    case eSegPageNumFinalSets:
        ChooseSeqsForNumSets (sfp);
        break;
    case eSegPageFile:
        ChooseFile(sfp);
        break;
    case eSegPageStructuredCommentField:
        ChangeSegStructuredCommentField (sfp);
        break;
  }
  if (seg_choice == eSegPageId || seg_choice == eSegPageConstraint 
      || seg_choice == eSegPageVectorContamination || seg_choice == eSegPageLength) {
    SetClickableListDialogTitles (sfp->group_list_dlg, "Sequences for New Set", "",
                                                       "Use checkbox to mark sequences for new set",
                                                       "Single click to navigate to sequence in record");
    SetClickableListDisplayChosen (sfp->group_list_dlg, TRUE);
  } else {
    SetClickableListDialogTitles (sfp->group_list_dlg, "New Sets", "Sequences in Highlighted Set",
                                                       "Use checkbox to mark sets to create",
                                                       "");
    SetClickableListDisplayChosen (sfp->group_list_dlg, FALSE);
  }
}


extern void ChooseCategories (ValNodePtr value_list, Boolean do_choose)
{
  ClickableItemPtr cip;

  while (value_list != NULL) {
    cip = (ClickableItemPtr) value_list->data.ptrvalue;
      if (cip != NULL) {
        cip->chosen = do_choose;
        ChooseCategories (cip->subcategories, FALSE);
      }
      value_list = value_list->next;
  }
}


static void SelectSegCategories (ButtoN b)
{
  SegByFieldPtr sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp != NULL) {
    ChooseCategories (sfp->value_lists, TRUE);
    PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  }
}


static void UnselectSegCategories (ButtoN b)
{
  SegByFieldPtr sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp != NULL) {
    ChooseCategories (sfp->value_lists, FALSE);
    PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  }
}


static void ResortMarkedSegCategories (ButtoN b)
{
  SegByFieldPtr sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp != NULL) {
    sfp->value_lists = ValNodeSort (sfp->value_lists, SortVnpByClickableItemChosen);
    PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
  }
}


static void SelectSequenceIDsForSegregate (ButtoN b)
{
  SegByFieldPtr       sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp == NULL) return;

  ChooseCategoriesForSegregateIdDialog(sfp->id_constraint_dlg, sfp->value_lists);
  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
}


static void SelectSequenceIDsByConstraintForSegregate (ButtoN b)
{
  SegByFieldPtr       sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp == NULL) return;

  ChooseCategoriesForSegregateConstraintDialog(sfp->seq_constraint_dlg, sfp->value_lists);
  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
}


static void UnselectSequenceIdsByConstraintForSegregate (ButtoN b)
{
  SegByFieldPtr       sfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp == NULL) return;

  UnchooseCategoriesForSegregateConstraintDialog(sfp->seq_constraint_dlg, sfp->value_lists);
  PointerToDialog (sfp->group_list_dlg, sfp->value_lists);
}


static void CleanupSegregateByFieldForm (GraphiC g, VoidPtr data)

{
  SegByFieldPtr      sfp;

  sfp = (SegByFieldPtr) data;
  if (sfp != NULL) {
    sfp->value_lists = FreeClickableList (sfp->value_lists);
  }
  StdCleanupFormProc (g, data);
}


static void CreateSegregateFormMenus (WindoW w)

{
  BaseFormPtr   bfp;
  MenU          m;

  bfp = (BaseFormPtr) GetObjectExtra (w);
  if (bfp != NULL) {
    m = PulldownMenu (w, "File");
    FormCommandItem (m, "Accept", bfp, VIB_MSG_ACCEPT);
    FormCommandItem (m, "Cancel", bfp, VIB_MSG_CLOSE);
  }
}

static void SegregateFormMessage (ForM f, Int2 mssg)
{
  SegByFieldPtr frm;

  frm = (SegByFieldPtr) GetObjectExtra (f);
  if (frm != NULL) {
    switch (mssg) {
      case VIB_MSG_ACCEPT :
        frm->actn(frm->accept);
        break;
      case VIB_MSG_CLOSE:
        Remove ((WindoW)(frm->form));
        break;
      default :
        break;
    }
  }
}


static Int2 NewSegregateBioseqSet (BioseqSetPtr bssp, Nlm_BtnActnProc actn, ESegPage default_type, CharPtr title)
{
  GrouP              c;
  SegByFieldPtr      cfp;
  GrouP              h, k, page_grp, g1, g2, g3;
  PrompT             p, p2;
  StdEditorProcsPtr  sepp;
  WindoW             w;
  ButtoN             b;


  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  cfp = (SegByFieldPtr) MemNew (sizeof (SegByFieldData));
  if (cfp == NULL)
    return OM_MSG_RET_ERROR;
  cfp->target_set = bssp;
  cfp->actn = actn;

  w = FixedWindow (-50, -33, -10, -10, title,
           StdCloseWindowProc);
  SetObjectExtra (w, cfp, CleanupSegregateByFieldForm);
  cfp->form = (ForM) w;

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

  cfp->input_entityID = bssp->idx.entityID;
  cfp->input_itemID = bssp->idx.itemID;
  cfp->input_itemtype = OBJ_BIOSEQSET;
  cfp->formmessage = SegregateFormMessage;

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

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

  cfp->group_list_dlg = CreateClickableListDialogEx (h, "New Sets", "Sequences for New Set",
                                                      "Use checkbox to mark sets to create",
                                                      "",
                                                      ScrollToDiscrepancyItem, EditDiscrepancyItem, NULL,
                                                      GetDiscrepancyItemText,
                                                      stdCharWidth * 30,
                                                      stdCharWidth * 30 + 5,
                                                      TRUE, FALSE);

  g3 = NormalGroup (h, 2, 0, "", programFont, NULL);
  SetGroupSpacing (g3, 10, 10);
  StaticPrompt (g3, "Segregate Based on", 0, dialogTextHeight, programFont, 'c');
  cfp->seg_choice_grp = HiddenGroup (g3, eNumSegPages, 0, ChangeSegChoice);
  SetObjectExtra (cfp->seg_choice_grp, cfp, NULL);
  SetGroupSpacing (cfp->seg_choice_grp, 10, 10);

  RadioButton (cfp->seg_choice_grp, "ID");
  RadioButton (cfp->seg_choice_grp, "Text");
  RadioButton (cfp->seg_choice_grp, "Field");
  RadioButton (cfp->seg_choice_grp, "MolInfo");
  RadioButton (cfp->seg_choice_grp, "Feature Type");
  RadioButton (cfp->seg_choice_grp, "Descriptor Type");
  RadioButton (cfp->seg_choice_grp, "Nucleotide Sequence Length");
  RadioButton (cfp->seg_choice_grp, "Vector Contamination");
  RadioButton (cfp->seg_choice_grp, "Number of Sets");
  RadioButton (cfp->seg_choice_grp, "File Name");
  RadioButton (cfp->seg_choice_grp, "Constraint");
  RadioButton (cfp->seg_choice_grp, "Structured Comment Field");

  page_grp = HiddenGroup (h, 0, 0, NULL);
  /* identical fields */
  cfp->pages[eSegPageField - 1] = HiddenGroup (page_grp, 2, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageField - 1], 10, 10);
  p = StaticPrompt (cfp->pages[eSegPageField - 1], "Segregate sequences with identical", 0, dialogTextHeight,
                    programFont, 'c');  
  cfp->field_dlg = ParseFieldDestDialog (cfp->pages[eSegPageField - 1], ChangeSegField, cfp);

  /* molinfo */
  cfp->pages[eSegPageMolInfo - 1] = HiddenGroup (page_grp, 0, 4, NULL);
  SetGroupSpacing (cfp->pages[eSegPageMolInfo - 1], 10, 10);
  cfp->mol_class_btn = CheckBox (cfp->pages[eSegPageMolInfo - 1], "Molecule Class", ChangeMol);
  SetObjectExtra (cfp->mol_class_btn, cfp, NULL);
  SetStatus (cfp->mol_class_btn, TRUE);
  cfp->mol_type_btn = CheckBox (cfp->pages[eSegPageMolInfo - 1], "Molecule Type", ChangeMol);
  SetObjectExtra (cfp->mol_type_btn, cfp, NULL);
  SetStatus (cfp->mol_type_btn, TRUE);
  cfp->mol_topology_btn = CheckBox (cfp->pages[eSegPageMolInfo - 1], "Molecule Topology", ChangeMol);
  SetObjectExtra (cfp->mol_topology_btn, cfp, NULL);
  SetStatus (cfp->mol_topology_btn, TRUE);

  /* features */
  cfp->pages[eSegPageFeatureType - 1] = HiddenGroup (page_grp, 2, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageFeatureType - 1], 10, 10);
  StaticPrompt (cfp->pages[eSegPageFeatureType - 1], "Segregate by Feature", 0, dialogTextHeight,
                programFont, 'c');
  cfp->feature_select =  FeatureSelectionDialogEx (cfp->pages[eSegPageFeatureType - 1], FALSE, NULL,
                                                   SegFeatureChangeNotify, 
                                                   cfp);

  /* descriptors */
  cfp->pages[eSegPageDescriptorType - 1] = HiddenGroup (page_grp, 2, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageDescriptorType - 1], 10, 10);
  StaticPrompt (cfp->pages[eSegPageDescriptorType - 1], "Segregate by Descriptor", 0, dialogTextHeight,
                programFont, 'c');
  cfp->descriptor_select = DescriptorSelectionDialog (cfp->pages[eSegPageDescriptorType - 1], FALSE, 
                                                      SegDescriptorChangeNotify, cfp);

  /* text */
  cfp->pages[eSegPageText - 1] = HiddenGroup (page_grp, 4, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageText - 1], 10, 10);
  StaticPrompt (cfp->pages[eSegPageText - 1], "Create set with sequences where", 0, dialogTextHeight, programFont, 'l');
  cfp->search_text = DialogText (cfp->pages[eSegPageText - 1], "", 10, SegTextChange);
  SetObjectExtra (cfp->search_text, cfp, NULL);
  StaticPrompt (cfp->pages[eSegPageText - 1], "appears in", 0, dialogTextHeight, programFont, 'l');
  cfp->search_field = SearchFieldDialog (cfp->pages[eSegPageText - 1], SegTextChangeNotify, cfp);

  /* id */
  cfp->pages[eSegPageId - 1] = HiddenGroup (page_grp, -1, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageId - 1], 10, 10);
  p2 = StaticPrompt (cfp->pages[eSegPageId - 1], "Create set with marked sequences", 0, dialogTextHeight, programFont, 'l');
  g1 = HiddenGroup (cfp->pages[eSegPageId - 1], 3, 0, NULL);
  g2 = HiddenGroup (cfp->pages[eSegPageId - 1], 2, 0, NULL);
  cfp->id_constraint_dlg = SegregateIdDialog (g2);
  b = PushButton (g2, "Mark", SelectSequenceIDsForSegregate);
  SetObjectExtra (b, cfp, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) p2, (HANDLE) g1, (HANDLE) g2, NULL);

  /* length */
  cfp->pages[eSegPageLength - 1] = HiddenGroup (page_grp, -1, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageLength - 1], 10, 10);
  cfp->len_options = ChooseSeqsByLenDialog (cfp->pages[eSegPageLength - 1], ChooseSeqsByLen, cfp);

  /* vector contamination */
  cfp->pages[eSegPageVectorContamination - 1] = HiddenGroup (page_grp, -1, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageVectorContamination - 1], 10, 10);
  cfp->vector_options = VectorContaminationOptionsDialog (cfp->pages[eSegPageVectorContamination - 1], ChooseVectorSeqs, cfp);

  /* number of sets */
  cfp->pages[eSegPageNumFinalSets - 1] = HiddenGroup (page_grp, 2, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageNumFinalSets - 1], 10, 10);
  StaticPrompt (cfp->pages[eSegPageNumFinalSets - 1], "Number of sets to create", 0, dialogTextHeight, programFont, 'l');
  cfp->num_sets = DialogText (cfp->pages[eSegPageNumFinalSets - 1], "", 10, SegNumSetsChange);
  SetObjectExtra (cfp->num_sets, cfp, NULL);

  /* don't need controls for File */
  cfp->pages[eSegPageFile - 1] = NULL;

  /* constraint */
  cfp->pages[eSegPageConstraint- 1] = HiddenGroup (page_grp, -1, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageConstraint - 1], 10, 10);
  p2 = StaticPrompt (cfp->pages[eSegPageConstraint - 1], "Create set with marked sequences", 0, dialogTextHeight, programFont, 'l');
  g1 = HiddenGroup (cfp->pages[eSegPageConstraint - 1], 2, 0, NULL);
  cfp->seq_constraint_dlg = SegregateConstraintDialog (g1);
  g2 = HiddenGroup (g1, 0, 2, NULL);
  SetGroupSpacing (g2, 10, 10);
  b = PushButton (g2, "Mark", SelectSequenceIDsByConstraintForSegregate);
  SetObjectExtra (b, cfp, NULL);
  b = PushButton (g2, "Unmark", UnselectSequenceIdsByConstraintForSegregate);
  SetObjectExtra (b, cfp, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) p2, (HANDLE) g1, NULL);

  cfp->pages[eSegPageStructuredCommentField - 1] = HiddenGroup (page_grp, 2, 0, NULL);
  SetGroupSpacing (cfp->pages[eSegPageStructuredCommentField - 1], 10, 10);
  StaticPrompt (cfp->pages[eSegPageStructuredCommentField - 1], "Choose Structured Comment Field Name", 0, dialogTextHeight, programFont, 'l');
  cfp->struc_comm_field = ValNodeSelectionDialog (cfp->pages[eSegPageStructuredCommentField - 1],
                                                  GetStructuredCommentFieldNames(SeqMgrGetSeqEntryForData(bssp)),
                                                  8, ValNodeStringName, ValNodeSimpleDataFree,
                                                  ValNodeStringCopy, ValNodeStringMatch, "fieldname", 
                                                  ChangeSegStructuredCommentField, cfp, FALSE);

  AlignObjects (ALIGN_CENTER, (HANDLE) cfp->pages[0],
                                (HANDLE) cfp->pages[1], 
                                (HANDLE) cfp->pages[2], 
                              (HANDLE) cfp->pages[3],
                              (HANDLE) cfp->pages[4],
                              (HANDLE) cfp->pages[5],
                              (HANDLE) cfp->pages[6],
                              (HANDLE) cfp->pages[7],
                              (HANDLE) cfp->pages[8],
                              (HANDLE) cfp->pages[9],
                              (HANDLE) cfp->pages[10],
                              (HANDLE) cfp->pages[11],
                              (HANDLE) cfp->pages[12],
                              NULL);

  SetValue (cfp->seg_choice_grp, default_type);

  /* add select all/unselect all buttons */
  k = HiddenGroup (h, 3, 0, NULL);
  b = PushButton (k, "Mark All", SelectSegCategories);
  SetObjectExtra (b, cfp, NULL);
  b = PushButton (k, "Unmark All", UnselectSegCategories);
  SetObjectExtra (b, cfp, NULL);  
  b = PushButton (k, "Resort Marked", ResortMarkedSegCategories);
  SetObjectExtra (b, cfp, NULL);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 3, 0, NULL);
  cfp->accept = DefaultButton (c, "Accept", actn);
  SetObjectExtra (cfp->accept, cfp, NULL);
  Disable (cfp->accept);
  PushButton (c, "Cancel", StdCancelButtonProc);
  cfp->leave_dlg_up = CheckBox (c, "Leave Dialog Up", NULL);

  /* Line things up nicely */

  AlignObjects (ALIGN_CENTER, (HANDLE) g3,
                              (HANDLE) cfp->group_list_dlg,
                              (HANDLE) page_grp,
                              (HANDLE) k,
                              (HANDLE) c, NULL);

  /* initialize the display */
  ChangeSegChoice (cfp->seg_choice_grp);

  /* Display the window now */
  CreateSegregateFormMenus(w);
  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (cfp->accept);
  Update ();
  return OM_MSG_RET_OK;
}

static Int2 LIBCALLBACK SegregateSetsByFieldEx (Pointer data, Nlm_BtnActnProc actn, ESegPage default_type, CharPtr title)
{
  OMProcControlPtr   ompcp;

  /* Check parameters and get a pointer to the current data */

  ompcp = (OMProcControlPtr) data;
  if (ompcp == NULL
      || ompcp->input_itemtype != OBJ_BIOSEQSET 
      || ompcp->input_data == NULL) {
    Message (MSG_ERROR, "You must select a set to segregate!");
    return OM_MSG_RET_ERROR;
  } 
  return NewSegregateBioseqSet (ompcp->input_data, actn, default_type, title);
}


extern Int2 LIBCALLBACK SegregateSetsByField (Pointer data)
{
  return SegregateSetsByFieldEx (data, SegregateByField_Callback, eSegPageText, "Segregate Sets");
}


NLM_EXTERN void SegregateBioseqSetEntityID (Uint2 entityID)
{
  SeqEntryPtr sep;

  sep = GetTopSeqEntryForEntityID (entityID);
  if (sep == NULL || !IS_Bioseq_set (sep)) {
    Message (MSG_ERROR, "This record does not have a top-levelset!");
  } else {
    NewSegregateBioseqSet (FindTopLevelSetForDesktopFunction((BioseqSetPtr) sep->data.ptrvalue), SegregateByField_Callback, eSegPageText, "Segregate Sets");
  }
}


extern void NewSegregateBioseqSetMenuItem (IteM i)
{
  BaseFormPtr bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  SegregateBioseqSetEntityID (bfp->input_entityID);
}


typedef struct unsequester{
  SeqEntryPtr  sep;
  BioseqSetPtr orig_parent;
  Int4         set_pos;
} UnsequesterData, PNTR UnsequesterPtr;


static UnsequesterPtr UnsequesterNew (SeqEntryPtr sep, BioseqSetPtr parent, Int4 set_pos)
{
  UnsequesterPtr u;

  u = (UnsequesterPtr) MemNew (sizeof (UnsequesterData));
  u->sep = sep; 
  u->orig_parent = parent;
  u->set_pos = set_pos;
  return u;
}


static UnsequesterPtr GetUnsequesterForSep (SeqEntryPtr sep, ValNodePtr list)
{
  UnsequesterPtr u;

  while (list != NULL) {
    if ((u = list->data.ptrvalue) != NULL && u->sep == sep) {
      return u;
    } else {
      list = list->next;
    }
  }
  return NULL;
}


static ValNodePtr unsequester_list = NULL;

static BioseqSetPtr sequester_set = NULL;
static const CharPtr SEQUESTER_BACKUP_FILE = "sequestr.bkp";
static Boolean doReportAfterUnsequester = 0;

NLM_EXTERN void SetDoReportAfterUnsequester (Int4 report_type)
{
  doReportAfterUnsequester = report_type;
}


static const CharPtr placeholder_id_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZMagicCookiePlaceHolderABCDEFGHIJKLMNOPQRSTUVWXYZ";

static BioseqPtr PlaceholderBioseq (void)
{
  BioseqPtr bsp;
  ObjectIdPtr oip;

  oip = ObjectIdNew ();
  oip->str = StringSave (placeholder_id_string);
  bsp = BioseqNew ();
  bsp->id = ValNodeNew (NULL);
  bsp->id->choice = SEQID_LOCAL;
  bsp->id->data.ptrvalue = oip;
  return bsp;
}


static Boolean IsPlaceholderBioseq (BioseqPtr bsp)
{
  ObjectIdPtr oip;

  if (bsp == NULL || bsp->id == NULL 
      || bsp->id->choice != SEQID_LOCAL 
      || (oip = (ObjectIdPtr) bsp->id->data.ptrvalue) == NULL
      || StringCmp (oip->str, placeholder_id_string) != 0) {
    return FALSE;
  } else {
    return TRUE;
  }
}


/* sequesters is different from segregate - we aren't creating a permanent new set with
 * the combined descriptors of the previous set; we're creating a temporary holding set.
 */
static void SequesterCategorySeqEntries (BioseqSetPtr newset, ClickableItemPtr category)
{
  ValNodePtr vnp_item;
  SeqEntryPtr sep, last_sep, prev_sep, remove_sep, set_sep, tmp_sep;
  BioseqSetPtr bssp, orig_parent, prev_new_parent = NULL, prev_orig_parent = NULL;
  BioseqPtr bsp;
  Int4      set_pos;

  if (newset == NULL || category == NULL || (category->item_list == NULL && category->subcategories == NULL)) return;

  if (category->chosen) {
    last_sep = newset->seq_set;
    while (last_sep != NULL && last_sep->next != NULL) {
      last_sep = last_sep->next;
    }

    for (vnp_item = category->item_list; vnp_item != NULL; vnp_item = vnp_item->next) {
      sep = GetBestSeqEntryForItem (vnp_item);
      if (sep == NULL || sep->data.ptrvalue == NULL) continue;
      orig_parent = NULL;
      if (IS_Bioseq (sep)) {
        bsp = sep->data.ptrvalue;
        if (bsp->idx.parenttype == OBJ_BIOSEQSET) {
          orig_parent = bsp->idx.parentptr;
          bsp->idx.parentptr = NULL;
        }
      } else if (IS_Bioseq_set (sep)) {
        bssp = sep->data.ptrvalue;
        if (bssp->idx.parenttype == OBJ_BIOSEQSET) {
          orig_parent = bssp->idx.parentptr;
          bssp->idx.parentptr = NULL;
        }
      } else {
        continue;
      }

      if (orig_parent == NULL) {
        continue;
      }
         
      /* remove this seq-entry from the original parent */
      prev_sep = NULL;
      set_pos = 0;
      for (remove_sep = orig_parent->seq_set;
            remove_sep != NULL && remove_sep != sep;
            remove_sep = remove_sep->next) {
        prev_sep = remove_sep;
        set_pos++;
      }
      if (remove_sep == sep) {
        if (prev_sep == NULL) {
          orig_parent->seq_set = orig_parent->seq_set->next;
          if (orig_parent->seq_set == NULL) {
            orig_parent->seq_set = SeqEntryNew ();
            orig_parent->seq_set->choice = 1;
            orig_parent->seq_set->data.ptrvalue = PlaceholderBioseq();     
            SeqMgrLinkSeqEntry (orig_parent->seq_set, OBJ_BIOSEQSET, orig_parent);
          }
        } else {
          prev_sep->next = sep->next;
        }
        sep->next = NULL;
      }

      /* if this seq-entry is from the same parent as the previous one, just add this to
       * our current placeholder.
       */
      if (prev_orig_parent != orig_parent) {
        prev_new_parent = BioseqSetNew ();
        prev_new_parent->_class = orig_parent->_class;
        /* add descriptors from the orig_parent to the new parent */
        AddNewUniqueDescriptors (&(prev_new_parent->descr), orig_parent->descr);

        /* add annotations from the orig_parent to the new parent */
        AddNewUniqueAnnotations (&(prev_new_parent->annot), orig_parent->annot);

        /* add new parent to set */
        set_sep = SeqEntryNew ();
        set_sep->choice = 2;
        set_sep->data.ptrvalue = prev_new_parent;

        if (newset->seq_set == NULL) {
          newset->seq_set = set_sep;
        } else {
          for (tmp_sep = newset->seq_set; tmp_sep->next != NULL; tmp_sep = tmp_sep->next) {
          }
          tmp_sep->next = set_sep;
        }
        SeqMgrLinkSeqEntry (set_sep, OBJ_BIOSEQSET, newset);
        last_sep = NULL;
      }
      /* add seqentry to new parent */
      if (last_sep == NULL) {
        prev_new_parent->seq_set = sep;
      } else {
        last_sep->next = sep;
      }
      last_sep = sep;

      SeqMgrLinkSeqEntry (sep, OBJ_BIOSEQSET, prev_new_parent);

      /* create unsequester entry */
      ValNodeAddPointer (&unsequester_list, 0, UnsequesterNew (sep, orig_parent, set_pos));

      prev_orig_parent = orig_parent;
    }
  } else {
    for (vnp_item = category->subcategories; vnp_item != NULL; vnp_item = vnp_item->next) {
      SequesterCategorySeqEntries (newset, vnp_item->data.ptrvalue);
    }
  }
}


static void RestoreSequesteredSets (ForM f)
{
  Uint2 new_entityID;
  BaseFormPtr bfp;
  BioseqViewFormPtr vfp;

  bfp = GetBaseFormForEntityID (sequester_set->idx.entityID);
  if (bfp == NULL) {
    return;
  }
  SequinSeqViewFormMessage (f, VIB_MSG_CLOSE);

  new_entityID = RestoreEntityIDFromFile (SEQUESTER_BACKUP_FILE, bfp->input_entityID);
  if (new_entityID != 0) {
    bfp->input_entityID = new_entityID;
    ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
    ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
  }
  Show (bfp->form);
  vfp = (BioseqViewFormPtr) bfp;
  Show (vfp->toolForm);

  sequester_set = NULL;
}


static Boolean IsSeqDescInSet (SeqDescrPtr sdp, SeqDescrPtr set)
{
  while (set != NULL) {
    if (AsnIoMemComp (sdp, set, (AsnWriteFunc) SeqDescAsnWrite)) {
      return TRUE;
    } else {
      set = set->next;
    }
  }
  return FALSE;
}

static SeqDescrPtr PropagateToSeqEntryIfNotDup (SeqEntryPtr sep, SeqDescrPtr sdp)
{
  BioseqPtr    bsp;
  BioseqSetPtr bssp;
  SeqDescrPtr  new_sdp = NULL;
  
  if (sep != NULL && sep->data.ptrvalue != NULL) {
    if (IS_Bioseq (sep)) {
      bsp = (BioseqPtr) sep->data.ptrvalue;
      if (!IsSeqDescInSet (sdp, bsp->descr)) {
        new_sdp = AsnIoMemCopy ((Pointer) sdp,
                                (AsnReadFunc) SeqDescrAsnRead,
                                (AsnWriteFunc) SeqDescrAsnWrite);
        ValNodeLink (&(bsp->descr), new_sdp);
      }
    } else if (IS_Bioseq_set (sep)) {
      bssp = (BioseqSetPtr) sep->data.ptrvalue;
      if (!IsSeqDescInSet (sdp, bssp->descr)) {
        new_sdp = AsnIoMemCopy ((Pointer) sdp,
                                (AsnReadFunc) SeqDescrAsnRead,
                                (AsnWriteFunc) SeqDescrAsnWrite);
        ValNodeLink (&(bssp->descr), new_sdp);
      }
    }
  }
  return new_sdp;
}


static void AddSepBackToSet (SeqEntryPtr add_back_sep, BioseqSetPtr bssp, SeqDescrPtr parent_descriptors, Int4 set_pos)
{
  ObjMgrDataPtr  omdptop;
  ObjMgrData     omdata;
  Uint2          parenttype;
  Pointer        parentptr;
  SeqEntryPtr    sep, prev, tmp;
  SeqDescrPtr    sdp, new_sdp, sdp_tmp;
  ObjValNodePtr  ovp, new_ovp;
  Boolean        found;
  Int4           pos;
  BioseqPtr      bsp;

  /* any descriptors currently on bssp that are not in parent_descriptors must be propagated */
  /* mark parent descriptor as found, so we don't add it again */
  for (sdp = bssp->descr; sdp != NULL; sdp = sdp->next) {
    if (sdp->extended == 0) {
      continue;
    }
    /* don't copy set titles */
    if (sdp->choice == Seq_descr_title) {
      continue;
    }
    ovp = (ObjValNodePtr) sdp;
    found = FALSE;
    for (new_sdp = parent_descriptors; new_sdp != NULL && !found; new_sdp = new_sdp->next) {
      if (new_sdp->extended == 0) {
        continue;
      }
      new_ovp = (ObjValNodePtr) new_sdp;
      if (new_ovp->idx.deleteme) {
        continue;
      }
      if (AsnIoMemComp (sdp, new_sdp, (AsnWriteFunc) SeqDescAsnWrite)) {
        found = TRUE;
        new_ovp->idx.deleteme = TRUE;
      }
    }
    if (!found) {
      /* propagate descriptor to members of bssp */
      sdp_tmp = sdp->next;
      sdp->next = NULL;
      for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
        PropagateToSeqEntryIfNotDup (sep, sdp);
      }
      sdp->next = sdp_tmp;
      ovp->idx.deleteme = TRUE;
    }
  }
  /* now add any parent descriptors that weren't marked to the contents of add_back_sep
   * (and unmark those that were marked) */
  for (new_sdp = parent_descriptors; new_sdp != NULL; new_sdp = new_sdp->next) {
    if (new_sdp->extended == 0) {
      continue;
    }
    new_ovp = (ObjValNodePtr) new_sdp;
    if (new_ovp->idx.deleteme) {
      new_ovp->idx.deleteme = FALSE;
    } else {
      /* add to added_back_sep */
      PropagateToSeqEntryIfNotDup (add_back_sep, new_sdp);
    }
  }

  /* mark placeholders in destination set for removal */
  for (tmp = bssp->seq_set; tmp != NULL; tmp = tmp->next) {
    if (IS_Bioseq (tmp) && (bsp = tmp->data.ptrvalue) != NULL && IsPlaceholderBioseq (bsp)) {
      bsp->idx.deleteme = TRUE;
    }
  }

  sep = SeqMgrGetSeqEntryForData (bssp);
  SaveSeqEntryObjMgrData (sep, &omdptop, &omdata);
  GetSeqEntryParent (sep, &parentptr, &parenttype);
  prev = NULL;
  pos = 0;
  for (tmp = bssp->seq_set; tmp != NULL && pos < set_pos; tmp = tmp->next) {
    prev = tmp;
    pos++;
  }
  if (prev == NULL) {
    add_back_sep->next = bssp->seq_set;
    bssp->seq_set = add_back_sep;
  } else {
    add_back_sep->next = tmp;
    prev->next = add_back_sep;
  }
  SeqMgrLinkSeqEntry (add_back_sep, OBJ_BIOSEQSET, bssp);

  SeqMgrLinkSeqEntry (sep, parenttype, parentptr);
  RestoreSeqEntryObjMgrData (sep, omdptop, &omdata);
}


static void ReintegrateSequesteredSets (Uint2 entityID)
{
  BaseFormPtr bfp;
  BioseqViewFormPtr vfp;
  SeqEntryPtr new_parent_sep, sep, add_back_sep, next_sep;
  BioseqSetPtr   top_bssp, bssp;
  UnsequesterPtr u;

  add_back_sep = GetTopSeqEntryForEntityID (entityID);
  if (!IS_Bioseq_set (add_back_sep)) {
    return;
  }
  bfp = GetBaseFormForEntityID (entityID);
  RemoveSeqEntryViewer (bfp->form);

  /* remove any popset titles created */
  RemovePopsetTitles (add_back_sep);

  /* we created a genbank set to hold the actual parent sets */
  top_bssp = (BioseqSetPtr) add_back_sep->data.ptrvalue;

 /* propagate any other descriptors that ended up on the top-level set
   * (like cit-sub-updates)
   */
  SetDescriptorPropagate(top_bssp);

  /* need to process entries in seq_set in reverse, so that the set_pos will be 
   * correct relative to when the seq-entry was removed */
  ValNodeReverse (&(top_bssp->seq_set));
  new_parent_sep = top_bssp->seq_set;

  while (new_parent_sep != NULL) {
    bssp = new_parent_sep->data.ptrvalue;
    ValNodeReverse (&(bssp->seq_set));
    sep = bssp->seq_set;

    while (sep != NULL) {
      next_sep = sep->next;
      sep->next = NULL;
      u = GetUnsequesterForSep (sep, unsequester_list);
      if (u == NULL || u->orig_parent == NULL) {
        AddSepBackToSet (sep, sequester_set, bssp->descr, 0);
      } else {
        AddSepBackToSet (sep, u->orig_parent, bssp->descr, u->set_pos);
      }
      sep = next_sep;
    }
    bssp->seq_set = NULL;
    new_parent_sep = new_parent_sep->next;
  }

  ObjMgrDelete (OBJ_SEQENTRY, add_back_sep);
  top_bssp->seq_set = NULL;
  top_bssp->idx.deleteme = TRUE;
  DeleteMarkedObjects (entityID, 0, NULL);

  sep = GetTopSeqEntryForEntityID (sequester_set->idx.entityID);
  DeleteMarkedObjects (sequester_set->idx.entityID, 0, NULL);
  ObjMgrSetDirtyFlag (sequester_set->idx.entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, sequester_set->idx.entityID, 0, 0);

  bfp = GetBaseFormForEntityID (sequester_set->idx.entityID);
  if (bfp != NULL) {
    Show (bfp->form);
    vfp = (BioseqViewFormPtr) bfp;
    Show (vfp->toolForm);
  }

}


static void FinishedSequester (void)
{
  sequester_set = NULL;
  unsequester_list = ValNodeFreeData (unsequester_list);
  FileRemove(SEQUESTER_BACKUP_FILE);
  if (doReportAfterUnsequester > 0) {
    CreateReportWindow (doReportAfterUnsequester);
    doReportAfterUnsequester = 0;
  }
}


static void SequesterShutdown (ForM f) 
{
  MsgAnswer     ans;
  BaseFormPtr   bfp;

  bfp = (BaseFormPtr) GetObjectExtra (f);
  if (bfp != NULL) {
    ans = Message (MSG_YNC, "Accept changes and add sequences back to original set?  Hit No to continue working, Cancel to restore original set");
    if (ans == ANS_YES) {
      ReintegrateSequesteredSets (bfp->input_entityID);
      FinishedSequester();
    } else if (ans == ANS_CANCEL) {
      RestoreSequesteredSets (f);
      FinishedSequester();
    }
  }
}


static void SequesterFormMessage (ForM f, Int2 mssg)

{
  BaseFormPtr   bfp;

  bfp = (BaseFormPtr) GetObjectExtra (f);
  if (bfp != NULL) {
    switch (mssg) {
      case VIB_MSG_CLOSE :
      case VIB_MSG_QUIT :
      case VIB_MSG_ACCEPT :
        SequesterShutdown (f);
        break;
      default:
        SequinSeqViewFormMessage (f, mssg);
        break;
    }
  }
}


static void SequesterSets (ButtoN b)
{
  SegByFieldPtr sfp;
  Int4          num_chosen = 0;
  ValNodePtr       vnp;
  ClickableItemPtr cip;
  Int2             val;
  SeqEntryPtr      top_sep, pulled_sep;
  BioseqSetPtr     new_set;
  ObjMgrDataPtr  omdptop;
  ObjMgrData     omdata;
  Uint2          parenttype;
  Pointer        parentptr;
  Uint2          entityID;
  FormMessageFunc old_message_handler;
  BaseFormPtr     bfp;
  BioseqViewFormPtr vfp;

  sfp = (SegByFieldPtr) GetObjectExtra (b);
  if (sfp == NULL) return;

  if (unsequester_list != NULL || sequester_set != NULL) {
    Message (MSG_ERROR, "You are already sequestering a set.  You cannot sequester until you have restored these sequences to the original set.");
    return;
  }

  WriteTheEntityID (sfp->input_entityID, SEQUESTER_BACKUP_FILE, FALSE);

  num_chosen = CountChosenDiscrepancies (sfp->value_lists, FALSE);
  if (num_chosen < 1) {
    if (ANS_OK == Message (MSG_OKC, "You have not chosen any groups.  Create all?")) {
      ChooseAllDiscrepancies (sfp->value_lists);
    } else {
      return;
    }
  }

  val = GetValue (sfp->seg_choice_grp);
  if (val == eSegPageId || val == eSegPageVectorContamination || val == eSegPageLength) {
    /* put all selected sequences into one group */
    cip = (ClickableItemPtr) MemNew (sizeof (ClickableItemData));
    cip->chosen = TRUE;
    PullChosenIntoOneGroup (&(sfp->value_lists), cip);
    ValNodeAddPointer (&(sfp->value_lists), 0, cip);
  }

  sequester_set = sfp->target_set;

  /* create a new set with just the chosen sequences */
  top_sep = SeqMgrGetSeqEntryForData (sfp->target_set);
  SaveSeqEntryObjMgrData (top_sep, &omdptop, &omdata);
  GetSeqEntryParent (top_sep, &parentptr, &parenttype);

  new_set = BioseqSetNew ();
  new_set->_class = BioseqseqSet_class_genbank;

  for (vnp = sfp->value_lists; vnp != NULL; vnp = vnp->next) {      
    cip = (ClickableItemPtr) vnp->data.ptrvalue;
    if (cip == NULL || (!cip->chosen && ! AnyDiscrepanciesChosen (cip->subcategories))) {
      continue;
    }

    /* add SeqEntries for this category here */
    SequesterCategorySeqEntries (new_set, cip);

  }

  RestoreSeqEntryObjMgrData (top_sep, omdptop, &omdata); 
  DeleteMarkedObjects (sfp->input_entityID, 0, NULL);
  ObjMgrSetDirtyFlag (sfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, sfp->input_entityID, 0, 0);

  pulled_sep = SeqEntryNew ();
  pulled_sep->choice = 2;
  pulled_sep->data.ptrvalue = new_set;
  SeqMgrLinkSeqEntry (pulled_sep, 0, NULL);
  
  entityID = ObjMgrRegister (OBJ_SEQENTRY, pulled_sep);
  seqviewprocs.filepath = NULL;
  seqviewprocs.forceSeparateViewer = TRUE;
  old_message_handler = seqviewprocs.handleMessages;
  seqviewprocs.handleMessages = SequesterFormMessage;


  SeqEntrySetScope (NULL);
  GatherProcLaunch (OMPROC_VIEW, FALSE, entityID, 1,
                              OBJ_BIOSEQ, 0, 0, OBJ_BIOSEQ, 0);

  seqviewprocs.handleMessages = old_message_handler;

  /* hide viewer with the remaining sequences */
  bfp = GetBaseFormForEntityID (sfp->input_entityID);
  if (bfp != NULL) {
    Hide (bfp->form);
    vfp = (BioseqViewFormPtr) bfp;
    Hide (vfp->toolForm);
  }

  if (GetStatus (sfp->leave_dlg_up)) {
    ChangeSegChoice (sfp->seg_choice_grp);
  } else {
    Remove (sfp->form);
  }
}

NLM_EXTERN Boolean OkToSequester (void)
{
  if (unsequester_list != NULL || sequester_set != NULL) {
    return FALSE;
  } else {
    return TRUE;
  }
}


NLM_EXTERN Uint2 SequesterClickableItem (Uint2 entityID, ClickableItemPtr cip)
{
  SeqEntryPtr sep;
  BioseqSetPtr bssp, new_set;
  ObjMgrDataPtr  omdptop;
  ObjMgrData     omdata;
  Uint2          parenttype;
  Pointer        parentptr;
  Uint2          new_entityID;
  FormMessageFunc old_message_handler;
  BaseFormPtr     bfp;
  BioseqViewFormPtr vfp;
  SeqEntryPtr       pulled_sep;

  if (unsequester_list != NULL || sequester_set != NULL) {
    Message (MSG_ERROR, "You are already sequestering a set.  You cannot sequester until you have restored these sequences to the original set.");
    return 0;
  }

  sep = GetTopSeqEntryForEntityID (entityID);
  if (sep == NULL || !IS_Bioseq_set (sep)) {
    Message (MSG_ERROR, "This record does not have a top-levelset!");
    return 0;
  }

  bssp = FindTopLevelSetForDesktopFunction((BioseqSetPtr) sep->data.ptrvalue);
  if (bssp == NULL) {
    Message (MSG_ERROR, "Unable to find top level set");
    return 0;
  }

  WriteTheEntityID (entityID, SEQUESTER_BACKUP_FILE, FALSE);
  sequester_set = bssp;

  /* create a new set with just the chosen sequences */
  SaveSeqEntryObjMgrData (sep, &omdptop, &omdata);
  GetSeqEntryParent (sep, &parentptr, &parenttype);

  new_set = BioseqSetNew ();
  new_set->_class = BioseqseqSet_class_genbank;

  SequesterCategorySeqEntries (new_set, cip);

  RestoreSeqEntryObjMgrData (sep, omdptop, &omdata); 
  DeleteMarkedObjects (entityID, 0, NULL);
  ObjMgrSetDirtyFlag (entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, entityID, 0, 0);

  pulled_sep = SeqEntryNew ();
  pulled_sep->choice = 2;
  pulled_sep->data.ptrvalue = new_set;
  SeqMgrLinkSeqEntry (pulled_sep, 0, NULL);
  
  new_entityID = ObjMgrRegister (OBJ_SEQENTRY, pulled_sep);
  seqviewprocs.filepath = NULL;
  seqviewprocs.forceSeparateViewer = TRUE;
  old_message_handler = seqviewprocs.handleMessages;
  seqviewprocs.handleMessages = SequesterFormMessage;


  SeqEntrySetScope (NULL);
  GatherProcLaunch (OMPROC_VIEW, FALSE, new_entityID, 1,
                              OBJ_BIOSEQ, 0, 0, OBJ_BIOSEQ, 0);

  seqviewprocs.handleMessages = old_message_handler;

  /* hide viewer with the remaining sequences */
  bfp = GetBaseFormForEntityID (entityID);
  if (bfp != NULL) {
    Hide (bfp->form);
    vfp = (BioseqViewFormPtr) bfp;
    Hide (vfp->toolForm);
  }  
  return new_entityID;
}
  

NLM_EXTERN void LIBCALLBACK SequesterSequenceList (Uint2 entityID, ValNodePtr bsp_list)
{
  ClickableItemData cid;
  Uint2          new_entityID;
  BaseFormPtr     bfp;

  if (bsp_list == NULL) {
    return;
  }

  MemSet (&cid, 0, sizeof (ClickableItemData));
  cid.chosen = TRUE;
  cid.item_list = bsp_list;
  if ((new_entityID = SequesterClickableItem (entityID, &cid)) > 0) {
    /* get rid of existing validator window, replace with validator for new sequences */
    FreeValidateWindow ();
    bfp = GetBaseFormForEntityID (new_entityID);
    ValSeqEntryForm (bfp->form);
  }
}


extern Int2 LIBCALLBACK SequesterSequences (Pointer data)
{
  /* note - in future, will need to make sequester dialog modal. */
  return SegregateSetsByFieldEx (data, SequesterSets, eSegPageId, "Sequester Sets");
}


extern void SequesterSequencesEntityID (Uint2 entityID)
{
  SeqEntryPtr sep;

  sep = GetTopSeqEntryForEntityID (entityID);
  if (sep == NULL || !IS_Bioseq_set (sep)) {
    Message (MSG_ERROR, "This record does not have a top-level set!");
  } else {
    NewSegregateBioseqSet (FindTopLevelSetForDesktopFunction((BioseqSetPtr) sep->data.ptrvalue), SequesterSets, eSegPageId, "Sequester Sets");
  }
}


extern void SequesterSequencesMenuItem (IteM i)
{
  BaseFormPtr bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;

  SequesterSequencesEntityID (bfp->input_entityID);
}


NLM_EXTERN void LIBCALLBACK SegregateSequenceList (Uint2 entityID, ValNodePtr bsp_list)
{
  SeqEntryPtr sep;
  BioseqSetPtr bssp;
  ClickableItemData cid;
  ValNodePtr list = NULL;

  if (bsp_list == NULL) {
    return;
  }

  sep = GetTopSeqEntryForEntityID (entityID);
  if (sep == NULL || !IS_Bioseq_set (sep)) {
    Message (MSG_ERROR, "This record does not have a top-level set!");
    return;
  }

  bssp = FindTopLevelSetForDesktopFunction((BioseqSetPtr) sep->data.ptrvalue);
  if (bssp == NULL) {
    Message (MSG_ERROR, "Unable to find top level set");
    return;
  }

  MemSet (&cid, 0, sizeof (ClickableItemData));
  cid.item_list = bsp_list;
  cid.chosen = TRUE;
  ValNodeAddPointer (&list, 0, &cid);

  MakeGroupsForUniqueValues (bssp, list);
  list = ValNodeFree (list);
}


static void FormatBoxColumn (ColPtr col)
{
  if (col == NULL) return;
  col->pixWidth = stdCharWidth;
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  col->last = FALSE;
}

static ColPtr AllocateDocColumnsForFieldList (BulkEdFieldPtr field_list, Int4Ptr p_len, Int4 last_sorted_col, Int4 display_offset, Int4 columns_shown)
{
  Int4   i, num_columns = 4, len = 0, col;
  ColPtr col_list;

  if (field_list == NULL) {
    return NULL;
  }

  /* set the font so that the pixwidth will be correct */
  SelectFont (programFont);

  /* start with 3 columns, one for expand/collapse and one for checkbox for row selection, one for right scroll, and one for final blank */
  for (i = 0; field_list[i].name != NULL; i++) {
    num_columns ++;
  }
  col_list = (ColPtr) MemNew (num_columns * sizeof (ColData));
  col = 0;
  FormatBoxColumn (col_list + col);
  len += col_list[col].pixWidth;
  col++;

  for (i = 0; field_list[i].name != NULL; i++) {
    if (i < display_offset) {
      continue;
    } else if (i >= display_offset + columns_shown) {
      break;
    }
    if (i == last_sorted_col) {
      FormatBoxColumn (col_list + col);
      len += col_list[col].pixWidth;
      col++;
    }
    len += (field_list[i].format_col_func) (col_list + col, field_list[i].name);
    col_list[col].last = FALSE;
    col++;
  }
  
  /* right scroll */
  FormatBoxColumn (col_list + col);
  len += col_list[col].pixWidth;
  col++;

  /* final blank column */
  col_list[col].pixWidth = 0;
  col_list[col].pixInset = 0;
  col_list[col].charWidth = 0;
  col_list[col].charInset = 0;
  col_list[col].font = NULL;
  col_list[col].just = 'l';
  col_list[col].wrap = 1;
  col_list[col].bar = 0;
  col_list[col].underline = 0;
  col_list[col].left = 0;
  col_list[col].last = TRUE;
  if (p_len != NULL) {
    *p_len = len;
  }
  return col_list;
}

typedef struct bulkeditorrow {
  ValNodePtr values_list;
  ValNodePtr object_list;
  struct bulkeditorrow PNTR subrows;
  Int4       copied_to_meta;
  Int4       copied_to_sub;
  Boolean    expanded;
  Boolean    selected;
} BulkEditorRowData, PNTR BulkEditorRowPtr;

static ValNodePtr FreeBulkEditorValues (ValNodePtr values, BulkEdFieldPtr field_list)
{
  ValNodePtr       vnp;
  Int4             col;

  for (col = 0, vnp = values;
       vnp != NULL;
       col++, vnp = vnp->next) {
    if (field_list[col].free_func == NULL) {
      vnp->data.ptrvalue = MemFree (vnp->data.ptrvalue);
    } else {
      (field_list[col].free_func) (vnp->data.ptrvalue);
      vnp->data.ptrvalue = NULL;
    }
  }
  values = ValNodeFree (values);
  return values;
}

static void FreeBulkEditorValuesList (ValNodePtr PNTR values_list, Int4 num_rows, BulkEdFieldPtr field_list)
{
  Int4             i;

  if (values_list == NULL || field_list == NULL) return;
  for (i = 0; i < num_rows; i++) {
    values_list[i] = FreeBulkEditorValues (values_list[i], field_list);
  }
  values_list = MemFree (values_list);
}

static BulkEditorRowPtr FreeBulkEditorRows (BulkEditorRowPtr rows, BulkEdFieldPtr field_list)
{
  Int4 row_num;

  if (rows == NULL) return NULL;
  for (row_num = 0; rows[row_num].object_list != NULL; row_num++) {
    rows[row_num].values_list = FreeBulkEditorValues (rows[row_num].values_list, field_list);
    rows[row_num].object_list = ValNodeFree (rows[row_num].object_list);
    FreeBulkEditorRows (rows[row_num].subrows, field_list);
    rows[row_num].subrows = NULL;
  }
  rows = MemFree (rows);
  return rows;
}


static void CopyToSingleBulkEditRow (BulkEditorRowPtr dst, BulkEditorRowPtr src, BulkEdFieldPtr field_list)
{
  ValNodePtr vnp;
  Int4       col;

  if (dst == NULL || src == NULL) return;
  
  /* copy values */
  dst->values_list = NULL;
  for (vnp = src->values_list, col = 0; vnp != NULL; vnp = vnp->next, col++) {
    ValNodeAddPointer (&(dst->values_list), vnp->choice, field_list[col].copy_func(vnp->data.ptrvalue));
  }

  /* copy object */
  dst->object_list = NULL;
  ValNodeAddPointer (&(dst->object_list), src->object_list->choice, src->object_list->data.ptrvalue);

  /* copy selection */
  dst->selected = src->selected;
}


static void ExpandAllBulkEditRows (BulkEditorRowPtr row_list) 
{
  Int4 row_num;

  if (row_list == NULL) return;
  for (row_num = 0; row_list[row_num].object_list != NULL; row_num++) {
    if (row_list[row_num].subrows != NULL) {
      row_list[row_num].expanded = TRUE;
    }
  }
}

static void CollapseAllBulkEditRows (BulkEditorRowPtr row_list) 
{
  Int4 row_num;

  if (row_list == NULL) return;
  for (row_num = 0; row_list[row_num].object_list != NULL; row_num++) {
    if (row_list[row_num].subrows != NULL) {
      row_list[row_num].expanded = FALSE;
    }
  }
}


/* sort_col is zero-based data column, already corrected for selection and expansion columns */
static CharPtr GetTextForBulkEditorRow (BulkEditorRowPtr berp, BulkEdFieldPtr field_list, Int4 sort_col, Int4 display_offset, Int4 columns_shown)
{
  ValNodePtr column_values = NULL, vnp;
  Int4 text_len = 6, col;
  CharPtr str, line_text = NULL, tmp_str;
 
  if (berp == NULL) return NULL;
  for (vnp = berp->values_list, col = 0; vnp != NULL; vnp = vnp->next, col++) {
    if (col < display_offset) {
      continue;
    } else if (col >= display_offset + columns_shown) {
      break;
    }
    if (field_list[col].display_func == NULL) {
      str = NULL;
    } else {
      str = (field_list[col].display_func)(vnp->data.ptrvalue);
    }
    if (str == NULL) {
      str = StringSave ("");
    }
    if (berp->subrows != NULL && col == sort_col) {
      tmp_str = (CharPtr) MemNew (sizeof (Char) * (18 + StringLen (str)));
      sprintf (tmp_str, "%s%s(%d)", field_list[col].draw_col_func == NULL ? "" : " ",               
                                    str, ValNodeLen (berp->object_list));
      str = MemFree (str);
      str = tmp_str;
    }
    ValNodeAddPointer (&column_values, 0, str);
    text_len += StringLen (str) + 1;
  }
  line_text = (CharPtr) MemNew (text_len * sizeof (Char));
  line_text[0] = 0;
  /* add blank column for row selector */
  StringCat (line_text, "\t");
  for (vnp = column_values, col = display_offset; vnp != NULL; vnp = vnp->next, col++) {
    if (col == sort_col) {
      /* add blank column for expand/collapse */
      StringCat (line_text, "\t");
    }
    StringCat (line_text, vnp->data.ptrvalue);
    StringCat (line_text, "\t");
  }
  column_values = ValNodeFreeData (column_values);
  /* add blank column at end, to allow last real column to be highlighted when blank */
  StringCat (line_text, "\t\n");
  return line_text;
}


static Boolean DataPosFromBulkEdDlgRow (Int2 row, BulkEditorRowPtr row_list, Int4Ptr pRowNum, Int4Ptr pSubNum)
{
  Int4 row_num = 0, sub_num;
  Int2 display_row = 1;

  if (row < 1 || row_list == NULL || pRowNum == NULL || pSubNum == NULL) return FALSE;

  while (row_list[row_num].object_list != NULL) {
    if (display_row == row) {
      *pRowNum = row_num;
      *pSubNum = -1;
      return TRUE;
    }
    display_row++;
    if (row_list[row_num].subrows != NULL && row_list[row_num].expanded) {
      sub_num = 0;
      while (display_row != row && row_list[row_num].subrows[sub_num].object_list != NULL) {
        display_row++;
        sub_num++;
      }
      if (row_list[row_num].subrows[sub_num].object_list == NULL) {
        sub_num = -1;
      } else {
        if (display_row == row) {
          *pRowNum = row_num;
          *pSubNum = sub_num;
          return TRUE;
        }
      }
    }
    row_num++;
  }
  *pRowNum = -1;
  *pSubNum = -1;
  return FALSE;
}


static Int4 BulkEdDlgRowFromDataPos (Int4 row_num, Int4 sub_num, BulkEditorRowPtr row_list)
{
  Int4 i, j, display_row = 1;
  
  if (row_list == NULL) return 0;
  /* count subrows also passed */
  for (i = 0; row_list[i].object_list != NULL && i < row_num; i++) {
    display_row++;
    if (row_list[i].subrows != NULL && row_list[i].expanded) {
      for (j = 0; row_list[i].subrows[j].object_list != NULL; j++) {
        display_row++;
      }
    }
  }
  if (sub_num > -1) {
    display_row += sub_num + 1;
  }
  return display_row; 
}


static Int4 BulkEdDataColumnFromDocColumn (Int4 doc_col, Int4 sort_col, Int4 display_offset)
{
  /* subtract offset (document maps first column to 1) */
  doc_col--;
  /* subtract selection column */
  doc_col--;

  /* skip over columns not shown */
  doc_col += display_offset;

  /* subtract expand/collapse column */
  if (doc_col > sort_col && sort_col >= display_offset) {
    doc_col--;
  }
  return doc_col;
}


static ValNodePtr GetBulkEditorRowValueByColumn (BulkEditorRowPtr berp, Int4 col)
{
  Int4       i;
  ValNodePtr vnp;

  if (col < 0) return NULL;

  for (i = 0, vnp = berp->values_list;
       i < col && vnp != NULL;
       i++, vnp = vnp->next) {}
  return vnp;
}


static void SelectBulkEditorRows (BulkEditorRowPtr berp, Boolean val)
{
  if (berp == NULL) return;

  while (berp->object_list != NULL) {
    berp->selected = val;
    SelectBulkEditorRows (berp->subrows, val);
    berp++;
  }
}


static Int4 CountBulkEditorRowsSelected (BulkEditorRowPtr berp)
{
  Int4 num = 0;
  if (berp == NULL) return 0;

  while (berp->object_list != NULL) {
    if (berp->subrows == NULL) {
      if (berp->selected) {
        num++;
      }
    } else {
      num+= CountBulkEditorRowsSelected (berp->subrows);
    }
    berp++;
  }
  return num;
}


static void GetBulkEditorSelectedObjects (BulkEditorRowPtr berp, ValNodePtr PNTR object_list)
{
  ValNodePtr vnp;
  if (berp == NULL || object_list == NULL) return;

  while (berp->object_list != NULL) {
    if (berp->subrows == NULL) {
      if (berp->selected) {
        for (vnp = berp->object_list; vnp != NULL; vnp = vnp->next) {
          ValNodeAddPointer (object_list, vnp->choice, vnp->data.ptrvalue);
        }
      }
    } else {
      GetBulkEditorSelectedObjects (berp->subrows, object_list);
    }
    berp++;
  }
}


static Boolean AllBulkEditorRowsSelected (BulkEditorRowPtr berp)
{
  if (berp == NULL) return FALSE;

  while (berp->object_list != NULL) {
    /* note - don't need to check subrows, because metarow would
     * have been unselected if any subrows were unselected */
    if (!berp->selected) {
      return FALSE;
    }
    berp++;
  }
  return TRUE;
}


static void SelectBulkEditorRowsByStringConstraint (BulkEditorRowPtr berp, BulkEdFieldPtr field_list, Int4 col, StringConstraintXPtr scp, Boolean val)
{
  ValNodePtr vnp;
  CharPtr    str = NULL;
  Boolean    match;

  if (berp == NULL || field_list == NULL || col < 0 || field_list[col].display_func == NULL) return;

  while (berp->object_list != NULL) {
    if (berp->subrows == NULL) {
      vnp = GetBulkEditorRowValueByColumn (berp, col);
      if (vnp != NULL) {
        str = field_list[col].display_func(vnp->data.ptrvalue);
      }
      match = DoesStringMatchConstraintX (str, scp);
      str = MemFree (str);
      if (scp != NULL && scp->not_present) {
        match = !match;
      }
      if (match) {
        berp->selected = val;
      }
    } else {
      SelectBulkEditorRowsByStringConstraint (berp->subrows, field_list, col, scp, val);
      berp->selected = AllBulkEditorRowsSelected (berp->subrows);
    }
    berp++;
  }
}


/* call nulls and null values bigger */
/* return 0 if a and b are equal, -1 if a is smaller, and 1 if b is smaller */
static Int4 CompareBulkRowValues (Pointer val_a, Pointer val_b, BulkDisplayFieldFunc display_func)
{
  CharPtr str_a, str_b;
  Int4    rval = 0;

  if (val_a == NULL && val_b == NULL) {
    rval = 0;
  } else if (val_a == NULL) {
    rval = 1;
  } else if (val_b == NULL) {
    rval = -1;
  } else if (display_func == NULL) {
    rval = 0;
  } else {
    str_a = display_func(val_a);
    str_b = display_func(val_b);
    rval = StringCmp (str_a, str_b);
  }
  return rval;
}

/* call nulls and null values bigger */
/* return 0 if a and b are equal, -1 if a is smaller, and 1 if b is smaller */
static Int4 CompareBulkRows (BulkEditorRowPtr row_a, BulkEditorRowPtr row_b, Int4 sort_column, BulkEdFieldPtr field_list)
{
  Int4 col;
  ValNodePtr vnp_a, vnp_b;
  Int4 rval = 0;

  if (row_a == NULL && row_b == NULL) {
    rval = 0;
  } else if (row_a == NULL) {
    rval = 1;
  } else if (row_b == NULL) {
    rval = -1;
  } else if (row_a->values_list == NULL && row_b->values_list == NULL) {
    rval = 0;
  } else if (row_a->values_list == NULL) {
    rval = 1;
  } else if (row_b->values_list == NULL) {
    rval = -1;
  } else {
    col = 0;
    vnp_a = GetBulkEditorRowValueByColumn (row_a, sort_column);
    vnp_b = GetBulkEditorRowValueByColumn (row_b, sort_column);

    if (vnp_a == NULL && vnp_b == NULL) {
      rval = 0;
    } else if (vnp_a == NULL) {
      rval = 1;
    } else if (vnp_b == NULL) {
      rval = -1;
    } else {
      rval = CompareBulkRowValues (vnp_a->data.ptrvalue, vnp_b->data.ptrvalue, field_list[sort_column].display_func);
    }
  }
  return rval;
}

static Int4 CountIndividualRows (BulkEditorRowPtr row_list)
{
  Int4 num_rows = 0, row_num;

  if (row_list == NULL) return 0;

  for (row_num = 0; row_list[row_num].object_list != NULL; row_num++) {
    if (row_list[row_num].subrows == NULL) {
      num_rows++;
    } else {
      num_rows += CountIndividualRows(row_list[row_num].subrows);
    }
  }
  return num_rows;
}


static void SetDefaultCopiedToValues (BulkEditorRowPtr row_list)
{
  Int4 row_num;

  if (row_list == NULL) return;

  for (row_num = 0; row_list[row_num].object_list != NULL; row_num++) {
    row_list[row_num].copied_to_meta = -1;
    row_list[row_num].copied_to_sub = -1;
    if (row_list[row_num].subrows != NULL) {
      SetDefaultCopiedToValues(row_list[row_num].subrows);
    }
  }
}


static void SummarizeOneSubrowSet (BulkEditorRowPtr row, Int4 last_sorted_col)
{
  Int4       i, num_columns, row_num;
  ValNodePtr vnp;
  BoolPtr    all_present, any_present, all_same;
  CharPtr PNTR values;

  if (row == NULL || row->subrows == NULL) 
  {
    return;
  }
  /* count columns */
  for (num_columns = 0, vnp = row->values_list; vnp != NULL; num_columns++, vnp = vnp->next)
  {}

  all_present = (BoolPtr) MemNew (sizeof (Boolean) * num_columns);
  any_present = (BoolPtr) MemNew (sizeof (Boolean) * num_columns);
  all_same = (BoolPtr) MemNew (sizeof (Boolean) * num_columns);
  values = (CharPtr PNTR) MemNew (sizeof (CharPtr) * num_columns);

  for (i = 0; i < num_columns; i++) 
  {
    all_present[i] = TRUE;
    any_present[i] = FALSE;
    all_same[i] = TRUE;
    values[i] = NULL;
  }

  for (row_num = 0; row->subrows[row_num].object_list != NULL; row_num++) 
  {
    for (i = 0, vnp = row->subrows[row_num].values_list; vnp != NULL && i < num_columns; i++, vnp = vnp->next)
    {
      if (StringHasNoText (vnp->data.ptrvalue)) 
      {
        all_present[i] = FALSE;
      } 
      else 
      {
        any_present[i] = TRUE;
        if (values[i] == NULL) 
        {
          values[i] = vnp->data.ptrvalue;
        } 
        else if (StringCmp (values[i], vnp->data.ptrvalue) != 0) 
        {
          all_same[i] = FALSE;
        }
      }
    }
    while (i < num_columns) 
    {
      all_present[i] = FALSE;
      i++;
    }
  }

  for (i = 0, vnp = row->values_list; vnp != NULL; i++, vnp = vnp->next) 
  {
    if (i == last_sorted_col) {
      continue;
    }
    vnp->data.ptrvalue = MemFree (vnp->data.ptrvalue);
    if (any_present[i]) {
      if (all_present[i]) {
        if (all_same[i]) {
          vnp->data.ptrvalue = StringSave (values[i]);
        } else {
          vnp->data.ptrvalue = StringSave ("All present, mixed");
        }
      } else if (all_same[i]) {
        vnp->data.ptrvalue = StringSave ("Some missing, one unique");
      } else {
        vnp->data.ptrvalue = StringSave ("Some missing, mixed");
      }
    }
  }
  all_present = MemFree (all_present);
  any_present = MemFree (any_present);
  all_same = MemFree (all_same);
  values = MemFree (values);
}


static void AddSummaryInfoToMetaRows (BulkEditorRowPtr row_list, Int4 last_sorted_col)
{
  Int4 row_num;

  if (row_list == NULL) return;

  for (row_num = 0; row_list[row_num].object_list != NULL; row_num++) {
    SummarizeOneSubrowSet(row_list + row_num, last_sorted_col);
    AddSummaryInfoToMetaRows(row_list[row_num].subrows, last_sorted_col);
  }
}


static BulkEdFieldPtr s_CurrentSortingFieldList;
static Int4 s_CurrentSortColumn;


NLM_EXTERN int LIBCALLBACK SortVnpByBulkEditorRow (VoidPtr ptr1, VoidPtr ptr2)

{
  BulkEditorRowPtr row1;
  BulkEditorRowPtr row2;
  ValNodePtr  vnp1;
  ValNodePtr  vnp2;

  if (ptr1 != NULL && ptr2 != NULL) {
    vnp1 = *((ValNodePtr PNTR) ptr1);
    vnp2 = *((ValNodePtr PNTR) ptr2);
    if (vnp1 != NULL && vnp2 != NULL) {

      row1 = (BulkEditorRowPtr) vnp1->data.ptrvalue;
      row2 = (BulkEditorRowPtr) vnp2->data.ptrvalue;
      if (row1 != NULL && row2 != NULL) {
        return CompareBulkRows (row1, row2, s_CurrentSortColumn, s_CurrentSortingFieldList);
      }
    }
  }
  return 0;
}


static Int4 FlatCopyRows (BulkEditorRowPtr new_rows, BulkEditorRowPtr orig_rows, BulkEdFieldPtr field_list)
{
  Int4 num_n = 0, num_o = 0;

  while (orig_rows[num_o].object_list != NULL) {
    if (orig_rows[num_o].subrows != NULL) {
      num_n += FlatCopyRows (new_rows + num_n, orig_rows[num_o].subrows, field_list);
    } else {
      CopyToSingleBulkEditRow (new_rows + num_n, orig_rows + num_o, field_list);
      num_n++;
    }
    num_o++;
  }
  return num_n;
}


static void AddBulkEditorRowsToBlock (ValNodeBlockPtr vb, BulkEditorRowPtr rows)
{
  Int4 i = 0;

  if (rows == NULL) {
    return;
  }
  while (rows[i].object_list != NULL) {
    if (rows[i].subrows == NULL) {
      ValNodeAddPointerToEnd (vb, 0, rows + i);
    } else {
      AddBulkEditorRowsToBlock (vb, rows[i].subrows);
    }
    i++;
  }
}


static BulkEditorRowPtr SortBulkEditorRowsByValue (BulkEditorRowPtr orig_rows, BulkEdFieldPtr field_list, Int4 new_sort_column)
{
  Int4         num_individual_rows = 0;
  ValNodeBlock vb;
  Int4         i;
  ValNodePtr   vnp;
  BulkEditorRowPtr new_rows;

  /* count individual rows (will need this many or fewer) */
  num_individual_rows = CountIndividualRows(orig_rows);
  new_rows = (BulkEditorRowPtr) MemNew (sizeof (BulkEditorRowData) * (num_individual_rows + 1));
  MemSet (new_rows, 0, sizeof (BulkEditorRowData) * (num_individual_rows + 1));

  InitValNodeBlock (&vb, NULL);
  AddBulkEditorRowsToBlock (&vb, orig_rows);
  s_CurrentSortingFieldList = field_list;
  s_CurrentSortColumn = new_sort_column;

  vb.head = ValNodeSort (vb.head, SortVnpByBulkEditorRow);
  for (vnp = vb.head, i = 0; vnp != NULL; vnp = vnp->next, i++) {
    CopyToSingleBulkEditRow (new_rows + i, vnp->data.ptrvalue, field_list);    
  }
  return new_rows;
}


static void ZeroOutBulkEditorRow (BulkEditorRowPtr row, BulkEdFieldPtr field_list)
{
  row->values_list = FreeBulkEditorValues (row->values_list, field_list);
  row->object_list = ValNodeFree (row->object_list);
  FreeBulkEditorRows (row->subrows, field_list);
  row->subrows = NULL;
}


static BulkEditorRowPtr ResortBulkEditorRows (BulkEditorRowPtr orig_rows, BulkEdFieldPtr field_list, Int4 new_sort_column)
{
  Int4 num_individual_rows = 0;
  Int4 row_num, sub_num, last_inserted = 0, comp, col;
  Int4 smallest_pos = 0, insert_pos = 0;
  BulkEditorRowPtr new_rows = NULL, smallest = NULL;
  ValNodePtr       matches = NULL, vnp;
  Boolean          all_selected;
  BulkEditorRowData swap;

  new_rows = SortBulkEditorRowsByValue (orig_rows, field_list, new_sort_column);
  /* count individual rows (will need this many or fewer) */
  num_individual_rows = CountIndividualRows(orig_rows);

  for (row_num = 0; new_rows[row_num].object_list != NULL; row_num++) {
    if (smallest == NULL) {
      smallest = new_rows + row_num;
      smallest_pos = row_num;
    } else {
      comp = CompareBulkRows (new_rows + row_num, smallest, new_sort_column, field_list);
      if (comp == 0) {
        /* equal - will be combined into meta row after first non-match is found */
      } else {
        if (smallest_pos < row_num - 1) {
          /* move first match to temporary location */
          CopyToSingleBulkEditRow (&swap, smallest, field_list);
          /* create meta row */
          ZeroOutBulkEditorRow (new_rows + insert_pos, field_list);
          /* add value for only sort column */
          for (col = 0, vnp = swap.values_list; vnp != NULL; col++, vnp = vnp->next) {
            if (col == new_sort_column) {
              ValNodeAddPointer (&(new_rows[insert_pos].values_list), vnp->choice, field_list[col].copy_func(vnp->data.ptrvalue));
            } else {
              ValNodeAddPointer (&(new_rows[insert_pos].values_list), 0, NULL);
            }
          }
          /* add features and subrows */
          new_rows[insert_pos].subrows = (BulkEditorRowPtr) MemNew (sizeof(BulkEditorRowData) * (row_num - smallest_pos + 1));
          MemSet (new_rows[insert_pos].subrows, 0, sizeof(BulkEditorRowData) * (row_num - smallest_pos + 1));
          /* copy in swap row */
          sub_num = 0;
          ValNodeAddPointer (&(new_rows[insert_pos].object_list), swap.object_list->choice, swap.object_list->data.ptrvalue);
          CopyToSingleBulkEditRow (new_rows[insert_pos].subrows + sub_num, &swap, field_list);
          all_selected = swap.selected;
          sub_num++;
          while (sub_num + smallest_pos < row_num) {
            ValNodeAddPointer (&(new_rows[insert_pos].object_list), new_rows[smallest_pos + sub_num].object_list->choice, new_rows[smallest_pos + sub_num].object_list->data.ptrvalue);
            CopyToSingleBulkEditRow (new_rows[insert_pos].subrows + sub_num, new_rows + smallest_pos + sub_num, field_list);
            all_selected &= new_rows[smallest_pos + sub_num].selected;
            sub_num++;
          }
          new_rows[insert_pos].selected = all_selected;
        } else {
          if (insert_pos != row_num - 1) {
            ZeroOutBulkEditorRow (new_rows + insert_pos, field_list);
            CopyToSingleBulkEditRow (new_rows + insert_pos, new_rows + row_num - 1, field_list);
          }
        }
        insert_pos++;
        smallest_pos = row_num;
        smallest = new_rows + smallest_pos;
      }            
    }
  }
  
  /* final row */
  if (smallest_pos < row_num - 1) {
    /* move first match to temporary location */
    CopyToSingleBulkEditRow (&swap, smallest, field_list);
    /* create meta row */
    ZeroOutBulkEditorRow (new_rows + insert_pos, field_list);
    /* add value for only sort column */
    for (col = 0, vnp = swap.values_list; vnp != NULL; col++, vnp = vnp->next) {
      if (col == new_sort_column) {
        ValNodeAddPointer (&(new_rows[insert_pos].values_list), vnp->choice, field_list[col].copy_func(vnp->data.ptrvalue));
      } else {
        ValNodeAddPointer (&(new_rows[insert_pos].values_list), 0, NULL);
      }
    }
    /* add features and subrows */
    new_rows[insert_pos].subrows = (BulkEditorRowPtr) MemNew (sizeof(BulkEditorRowData) * (row_num - smallest_pos + 1));
    MemSet (new_rows[insert_pos].subrows, 0, sizeof(BulkEditorRowData) * (row_num - smallest_pos + 1));
    /* copy in swap row */
    sub_num = 0;
    ValNodeAddPointer (&(new_rows[insert_pos].object_list), swap.object_list->choice, swap.object_list->data.ptrvalue);
    CopyToSingleBulkEditRow (new_rows[insert_pos].subrows + sub_num, &swap, field_list);
    all_selected = swap.selected;
    sub_num++;
    while (sub_num + smallest_pos < row_num) {
      ValNodeAddPointer (&(new_rows[insert_pos].object_list), new_rows[smallest_pos + sub_num].object_list->choice, new_rows[smallest_pos + sub_num].object_list->data.ptrvalue);
      CopyToSingleBulkEditRow (new_rows[insert_pos].subrows + sub_num, new_rows + smallest_pos + sub_num, field_list);
      all_selected &= new_rows[smallest_pos + sub_num].selected;
      sub_num++;
    }
    new_rows[insert_pos].selected = all_selected;
  } else {
    if (insert_pos != row_num - 1) {
      ZeroOutBulkEditorRow (new_rows + insert_pos, field_list);
      CopyToSingleBulkEditRow (new_rows + insert_pos, new_rows + row_num - 1, field_list);
    }
  }
  insert_pos++;

  while (insert_pos < num_individual_rows) {
    ZeroOutBulkEditorRow (new_rows + insert_pos, field_list);
    insert_pos++;
  }

  AddSummaryInfoToMetaRows (new_rows, new_sort_column);
  return new_rows;
}


/* sort_column is zero-based data column, already corrected for selection and expansion columns */
static Boolean AllSubrowsSameValue (BulkEditorRowPtr berp, BulkEdFieldPtr field_list, Int4 sort_column)
{
  Int4 i;
  Boolean all_same = TRUE;

  if (berp == NULL || berp->subrows == NULL || berp->subrows[0].object_list == NULL) {
    return TRUE;
  }
  
  for (i = 1; berp->subrows[i].object_list != NULL && all_same; i++) {
    if (CompareBulkRows (berp->subrows + i - 1, berp->subrows + i, sort_column, field_list) != 0) {
      all_same = FALSE;
    }
  }

  return all_same;
}


/* sort_column is zero-based data column, already corrected for selection and expansion columns */
/* look to see if data in columns is different from data in editor */
static Boolean AnyChangeInBulkFields (BulkEditorRowPtr berp, BulkEdFieldPtr field_list, DialoG editor, Int4 sort_column)
{
  Pointer    m_data;
  ValNodePtr vnp;
  Int4       comp;

  if (berp == NULL || berp->subrows == NULL || berp->subrows[0].object_list == NULL) return FALSE;
  if (field_list == NULL || editor == NULL) return FALSE;

  if (!AllSubrowsSameValue (berp, field_list, sort_column)) {
    return TRUE;
  }

  /* find value for sort_column from first subrow */
  vnp = GetBulkEditorRowValueByColumn (berp, sort_column);
  
  if (vnp == NULL) return TRUE;
  
  /* compare with data from editor */
  m_data = DialogToPointer (editor);

  comp = CompareBulkRowValues (m_data, vnp->data.ptrvalue, field_list[sort_column].display_func);
  
  /* free data from editor */
  if (field_list[sort_column].free_func) {
    (field_list[sort_column].free_func)(m_data);
  }
  
  if (comp == 0) {
    return FALSE;
  } else {
    return TRUE;
  }
}


/* col is zero-based data column, already corrected for selection and expansion columns */
static void ApplyValueToOneBulkEditorRow (BulkEditorRowPtr berp, Int2 col, BulkEdFieldPtr field_list, DialoG editor)
{
  ValNodePtr vnp;

  if (berp == NULL || field_list == NULL ||  editor == NULL) return;

  vnp = GetBulkEditorRowValueByColumn (berp, col);

  if (vnp != NULL) {
    if (field_list[col].free_func == NULL) {
      vnp->data.ptrvalue = MemFree (vnp->data.ptrvalue);
    } else {
      (field_list[col].free_func) (vnp->data.ptrvalue);
      vnp->data.ptrvalue = NULL;
    }
    vnp->data.ptrvalue = DialogToPointer (editor);
  }
}


static void PopulateSingleBulkEditRowFromData (BulkEditorRowPtr berp, Uint1 data_choice, Pointer data, BulkEdFieldPtr field_list, Pointer metadata)
{
  Int4 col;

  /* add values for all columns */
  for (col = 0; field_list[col].name != NULL; col++) {
    ValNodeAddPointer (&(berp->values_list), 0, (field_list[col].get_func (data_choice, data, metadata)));
  }
  /* add feature */
  ValNodeAddPointer (&(berp->object_list), data_choice, data);
}

static BulkEditorRowPtr MakeBulkEditorRowsFromObjectList (ValNodePtr object_list, BulkEdFieldPtr field_list, Int4 sort_column, Pointer metadata)
{
  ValNodePtr       vnp;
  BulkEditorRowPtr bulk_ed_rows = NULL, sorted_rows = NULL;
  Int4             num_rows, row_num;

  if (object_list == NULL || field_list == NULL) return NULL;

  /* first, create list with one row per object */
  num_rows = ValNodeLen (object_list);
  
  bulk_ed_rows = (BulkEditorRowPtr) MemNew (sizeof (BulkEditorRowData) * (num_rows + 1));
  MemSet (bulk_ed_rows, 0, sizeof (BulkEditorRowData) * (num_rows + 1));

  for (vnp = object_list, row_num = 0; vnp != NULL; vnp = vnp->next, row_num++)
  {
    PopulateSingleBulkEditRowFromData (bulk_ed_rows + row_num, vnp->choice, vnp->data.ptrvalue, field_list, metadata); 
  }

  if (sort_column >= 0)
  {
    /* Now resort rows for column */
    sorted_rows = ResortBulkEditorRows (bulk_ed_rows, field_list, sort_column);
    bulk_ed_rows = FreeBulkEditorRows (bulk_ed_rows, field_list);
    bulk_ed_rows = sorted_rows;
  }
  return bulk_ed_rows;
}


static Boolean IsBulkEditorColumnEmpty (BulkEditorRowPtr row_list, Int4 column);

static Boolean IsBulkEditorRowColumnEmpty (BulkEditorRowPtr row, Int4 column)
{
  ValNodePtr vnp;
  Int4 pos;

  if (row == NULL) {
    return TRUE;
  }

  for (pos = 0, vnp = row->values_list;
       pos < column && vnp != NULL;
       pos++, vnp = vnp->next)
  {
  }
  if (pos == column && vnp != NULL && !StringHasNoText (vnp->data.ptrvalue)) 
  {
    return FALSE;
  } 
  else 
  {
    return IsBulkEditorColumnEmpty(row->subrows, column);
  }
}


static Boolean IsBulkEditorColumnEmpty (BulkEditorRowPtr row_list, Int4 column)
{
  Boolean rval = TRUE;
  Int4    i;

  if (row_list == NULL) 
  {
    return TRUE;
  }
  for (i = 0; row_list[i].object_list != NULL && rval; i++) 
  {
    rval = IsBulkEditorRowColumnEmpty (row_list + i, column);
  }
  return rval;
}


static Int4 
AddRowsToListBySelection 
(BulkEditorRowPtr dest,
 BulkEditorRowPtr src,
 BulkEdFieldPtr   field_list,
 Int4             last_inserted,
 Boolean          selected)
{
  Int4 row_num;

  if (dest == NULL || src == NULL) return last_inserted;

  for (row_num = 0; src[row_num].object_list != NULL; row_num++) {
    if (src[row_num].subrows == NULL) {
      if ((src[row_num].selected && selected)
          || (!src[row_num].selected && !selected)) {
        CopyToSingleBulkEditRow (dest + last_inserted, src + row_num, field_list);
        src[row_num].copied_to_meta = last_inserted;
        src[row_num].copied_to_sub = 0;
        last_inserted++;
      }
    } else {
      last_inserted = AddRowsToListBySelection(dest, src[row_num].subrows, field_list, last_inserted, selected);
    }
  }
  return last_inserted;
}

static BulkEditorRowPtr ResortBulkEditorRowsBySelected (BulkEditorRowPtr orig_rows, BulkEdFieldPtr field_list)
{
  Int4 num_individual_rows;
  Int4 last_inserted = 0;
  BulkEditorRowPtr new_rows = NULL;

  /* count individual rows (will need this many or fewer) */
  num_individual_rows = CountIndividualRows(orig_rows);

  /* set default copied_to values */
  SetDefaultCopiedToValues (orig_rows);
  
  new_rows = (BulkEditorRowPtr) MemNew (sizeof (BulkEditorRowData) * (num_individual_rows + 1));
  MemSet (new_rows, 0, sizeof (BulkEditorRowData) * (num_individual_rows + 1));

  /* add selected rows first */
  last_inserted = AddRowsToListBySelection(new_rows, orig_rows, field_list, last_inserted, TRUE);

  /* add unselected rows */
  last_inserted = AddRowsToListBySelection(new_rows, orig_rows, field_list, last_inserted, FALSE);

  return new_rows;     
}





typedef enum {
  eBulkApplyField = 1,
  eBulkEditField,
  eBulkConvertField,
  eBulkParseField,
  eBulkSwapField,
  eBulkRemoveField,
  eBulkCapitalizeField
} EBulkAction;

static Int4 GetFieldNumFromPopupValue (Int4 popup_val, BulkEdFieldPtr field_list, Boolean edit_only)
{
  Int4 i, pval = 1;

  for (i = 0; field_list[i].name != NULL; i++) {
    if (field_list[i].create_dlg_func != NULL || (!edit_only && field_list[i].display_func != NULL)) {
      if (pval == popup_val) {
        return i;
      } else {
        pval++;
      }
    }
  }
  return -1;
}


static PopuP MakeBulkEditorFieldListPopup (GrouP g, BulkEdFieldPtr field_list, Boolean edit_only)
{
  PopuP p;
  Int4  i;

  if (field_list == NULL) return NULL;

  p = PopupList (g, TRUE, NULL);
  for (i = 0; field_list[i].name != NULL; i++) {
    if (field_list[i].create_dlg_func != NULL || (!edit_only && field_list[i].display_func != NULL)) {
      PopupItem (p, field_list[i].name);
    }
  }
  return p;
}


static DialoG MakeBulkEditorFieldListDialog (GrouP g, BulkEdFieldPtr field_list, Boolean edit_only)
{
  ValNodeBlock block;
  Int4  i;

  if (field_list == NULL) return NULL;
  InitValNodeBlock (&block, NULL);

  for (i = 0; field_list[i].name != NULL; i++) {
    if (field_list[i].create_dlg_func != NULL || (!edit_only && field_list[i].display_func != NULL)) {
      ValNodeAddPointerToEnd (&block, i, StringSave (field_list[i].name));
    }
  }
  return ValNodeSelectionDialog (g, block.head, TALL_SELECTION_LIST, ValNodeStringName, ValNodeSimpleDataFree, ValNodeStringCopy, ValNodeChoiceMatch, "field", NULL, NULL, FALSE);
}


static void SetBulkEditorFieldListDialogValue (DialoG d, Int4 val)
{
  ValNode vn;

  MemSet (&vn, 0, sizeof (ValNode));
  vn.choice = val;
  PointerToDialog (d, &vn);
}


static void ResetBulkEditorFieldListDialogValue (DialoG d, BulkEdFieldPtr field_list)
{
  SetBulkEditorFieldListDialogValue(d, GetFieldNumFromPopupValue(1, field_list, TRUE));
}


static Int4 GetBulkEditorFieldListDialogValue (DialoG d)
{
  Int4 val = 0;
  ValNodePtr vnp;

  vnp = DialogToPointer (d);
  if (vnp != NULL) {
    val = vnp->choice;
    vnp = ValNodeFree (vnp);
  }
  return val;
}



typedef struct onefieldbulkedit
{
  Int4          field;
  EditApplyPtr  edit_apply;
  ChangeCasePtr capitalization;
} OneFieldBulkEditData, PNTR OneFieldBulkEditPtr;


static OneFieldBulkEditPtr OneFieldBulkEditNew (void)
{
  OneFieldBulkEditPtr ofp;

  ofp = (OneFieldBulkEditPtr) MemNew (sizeof (OneFieldBulkEditData));
  return ofp;
}


static OneFieldBulkEditPtr OneFieldBulkEditFree (OneFieldBulkEditPtr ofp)
{
  if (ofp != NULL) {
    ofp->edit_apply = EditApplyFree (ofp->edit_apply);
    ofp->capitalization = MemFree (ofp->capitalization);
    ofp = MemFree (ofp);
  }
  return ofp;
}


static Int4 GetDlgFieldNumFromMasterFieldNum (BulkEdFieldPtr master_field_list, BulkEdFieldPtr field_list, Int4 master_field)
{
  Int4 m = 0, f = 0;

  while (master_field_list[m].name != NULL && m < master_field) {
    if (StringCmp (master_field_list[m].name, field_list[f].name) == 0) {
      f++;
    }
    m++;
  }

  return f;
}


static Int4 GetMasterFieldNumFromDlgFieldNum (BulkEdFieldPtr master_field_list, BulkEdFieldPtr field_list, Int4 dlg_field)
{
  Int4 m = 0;

  while (master_field_list[m].name != NULL && StringCmp (master_field_list[m].name, field_list[dlg_field].name) != 0) {
    m++;
  }

  return m;

}


static void AdjustOneFieldForMaster (OneFieldBulkEditPtr ofp, BulkEdFieldPtr master_field_list, BulkEdFieldPtr field_list)
{
  if (ofp == NULL || master_field_list == NULL || field_list == NULL) {
    return;
  }
  
  ofp->field = GetDlgFieldNumFromMasterFieldNum (master_field_list, field_list, ofp->field);
}


typedef CharPtr  (*Nlm_GetAutopopulateTextProc) PROTO ((Pointer, Int4));

typedef struct onefieldbulkeditdlg
{
  DIALOG_MESSAGE_BLOCK
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
  BulkEdFieldPtr           field_list;
  DialoG                   edit_apply;
  DialoG                   capitalization;
  DialoG                   field_dlg;

  Nlm_GetAutopopulateTextProc autopopulate_func;
  Pointer                     autopopulate_data;
} OneFieldBulkEditDialogData, PNTR OneFieldBulkEditDialogPtr;


static void OneFieldBulkEditToDialog (DialoG d, Pointer data)
{
  OneFieldBulkEditDialogPtr dlg;
  OneFieldBulkEditPtr       dlg_data;

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

  dlg_data = (OneFieldBulkEditPtr) data;
  if (dlg_data == NULL) {
    ResetBulkEditorFieldListDialogValue (dlg->field_dlg, dlg->field_list);
    PointerToDialog (dlg->edit_apply, NULL);
    PointerToDialog (dlg->capitalization, NULL);
  } else {
    SetBulkEditorFieldListDialogValue (dlg->field_dlg, dlg_data->field);
    PointerToDialog (dlg->edit_apply, dlg_data->edit_apply);
    PointerToDialog (dlg->capitalization, dlg_data->capitalization);
  }
  if (dlg->change_notify != NULL) {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static Pointer OneFieldBulkEditDialogToPointer (DialoG d)
{
  OneFieldBulkEditDialogPtr dlg;
  OneFieldBulkEditPtr       dlg_data;

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

  dlg_data = (OneFieldBulkEditPtr) MemNew (sizeof (OneFieldBulkEditData));
  dlg_data->field = GetBulkEditorFieldListDialogValue (dlg->field_dlg);
  dlg_data->edit_apply = DialogToPointer (dlg->edit_apply);
  dlg_data->capitalization = DialogToPointer (dlg->capitalization);

  return dlg_data;
}


static void OneFieldBulkEditMsg (DialoG d, Int2 mssg)

{
  OneFieldBulkEditDialogPtr dlg;

  dlg = (OneFieldBulkEditDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (mssg == AECR_VIB_MSG_CLEAR_TEXT) {
      SendMessageToDialog (dlg->edit_apply, NUM_VIB_MSG + 1);
    }
  }
}


static void OneFieldAutopopulate(ButtoN b)
{
  OneFieldBulkEditDialogPtr dlg;
  CharPtr                   txt;
  EditApplyPtr  edit_apply;
  Int4          field;

  dlg = (OneFieldBulkEditDialogPtr) GetObjectExtra (b);
  if (dlg != NULL && dlg->autopopulate_func != NULL) {
    field = GetBulkEditorFieldListDialogValue (dlg->field_dlg);
    txt = (dlg->autopopulate_func) (dlg->autopopulate_data, field);
    edit_apply = DialogToPointer (dlg->edit_apply);
    if (edit_apply == NULL) {
      edit_apply = EditApplyNew();
    }
    edit_apply->find_txt = MemFree (edit_apply->find_txt);
    edit_apply->find_txt = StringSave (txt);
    edit_apply->apply_txt = MemFree (edit_apply->apply_txt);
    edit_apply->apply_txt = StringSave (txt);
    PointerToDialog (dlg->edit_apply, edit_apply);
    edit_apply = EditApplyFree (edit_apply);
    txt = MemFree (txt);
  }
}


static void OneFieldClearText(ButtoN b)
{
  OneFieldBulkEditDialogPtr dlg;
  EditApplyPtr  edit_apply;

  dlg = (OneFieldBulkEditDialogPtr) GetObjectExtra (b);
  if (dlg != NULL) {
    edit_apply = DialogToPointer (dlg->edit_apply);
    if (edit_apply != NULL) {
      edit_apply->find_txt = MemFree (edit_apply->find_txt);
      edit_apply->repl_txt = MemFree (edit_apply->repl_txt);
      edit_apply->apply_txt = MemFree (edit_apply->apply_txt);
      PointerToDialog (dlg->edit_apply, edit_apply);
      edit_apply = EditApplyFree (edit_apply);
    }
  }
}


static DialoG OneFieldBulkEditDialog
(GrouP                    h,
 EBulkAction              action_choice,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 BulkEdFieldPtr           field_list,
 Nlm_GetAutopopulateTextProc autopopulate_func,
 Pointer                     autopopulate_data
)

{
  OneFieldBulkEditDialogPtr dlg;
  GrouP                     p, g1, g2 = NULL;
  ButtoN                    b;
  
  dlg = (OneFieldBulkEditDialogPtr) MemNew (sizeof (OneFieldBulkEditDialogData));
  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 = OneFieldBulkEditToDialog;
  dlg->fromdialog = OneFieldBulkEditDialogToPointer;
  dlg->dialogmessage = OneFieldBulkEditMsg;
  dlg->testdialog = NULL;

  dlg->field_list = field_list;
  dlg->autopopulate_func = autopopulate_func;
  dlg->autopopulate_data = autopopulate_data;
  
  g1 = HiddenGroup (p, 2, 0, NULL);
  SetGroupSpacing (g1, 10, 10);
  dlg->field_dlg = MakeBulkEditorFieldListDialog (g1, field_list, TRUE);
  ResetBulkEditorFieldListDialogValue (dlg->field_dlg, dlg->field_list);

  if (action_choice == eBulkApplyField) 
  {
    dlg->edit_apply = EditApplyDialog (g1, eEditApplyChoice_Apply, NULL, NULL, NULL, NULL);
  } 
  else if (action_choice == eBulkEditField)
  {
    dlg->edit_apply = EditApplyDialog (g1, eEditApplyChoice_Edit, NULL, NULL, NULL, NULL);
  }
  else if (action_choice == eBulkCapitalizeField)
  {
    dlg->capitalization = ChangeCaseDialog (g1);
  }

  if (autopopulate_func != NULL) {
    g2 = HiddenGroup (p, 2, 0, NULL);
    SetGroupSpacing (g2, 10, 10);
    b = PushButton (g2, "Autopopulate", OneFieldAutopopulate);
    SetObjectExtra (b, dlg, NULL);
    b = PushButton (g2, "Clear Text", OneFieldClearText);
    SetObjectExtra (b, dlg, NULL);
  }
  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) g2, NULL);

  return (DialoG) p;
}


typedef struct twofieldbulkedit
{
  Int4 field_from;
  Int4 field_to;
  Boolean strip_name;
  Boolean leave_on_original;
  Boolean remove_parsed;
  TextPortionXPtr text_portion;
} TwoFieldBulkEditData, PNTR TwoFieldBulkEditPtr;


static TwoFieldBulkEditPtr TwoFieldBulkEditNew (void)
{
  TwoFieldBulkEditPtr tfp;

  tfp = (TwoFieldBulkEditPtr) MemNew (sizeof (TwoFieldBulkEditData));
  return tfp;
}


static TwoFieldBulkEditPtr TwoFieldBulkEditFree (TwoFieldBulkEditPtr tfp)
{
  if (tfp != NULL) {
    tfp->text_portion = TextPortionXFree (tfp->text_portion);
    tfp = MemFree (tfp);
  }
  return tfp;
}


static void AdjustTwoFieldsForMaster (TwoFieldBulkEditPtr tfp, BulkEdFieldPtr master_field_list, BulkEdFieldPtr field_list)
{
  if (tfp == NULL || master_field_list == NULL || field_list == NULL) {
    return;
  }
  
  tfp->field_to = GetDlgFieldNumFromMasterFieldNum (master_field_list, field_list, tfp->field_to);
  tfp->field_from = GetDlgFieldNumFromMasterFieldNum (master_field_list, field_list, tfp->field_from);
}


typedef struct twofieldbulkeditdlg
{
  DIALOG_MESSAGE_BLOCK
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
  BulkEdFieldPtr           field_list;
  ButtoN                   strip_name;
  ButtoN                   leave_on_original;
  ButtoN                   remove_parsed;
  DialoG                   text_portion;
  DialoG                   field_from;
  DialoG                   field_to;

  Nlm_GetAutopopulateTextProc autopopulate_func;
  Pointer                     autopopulate_data;
} TwoFieldBulkEditDialogData, PNTR TwoFieldBulkEditDialogPtr;


static void TwoFieldBulkEditToDialog (DialoG d, Pointer data)
{
  TwoFieldBulkEditDialogPtr dlg;
  TwoFieldBulkEditPtr       dlg_data;

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

  dlg_data = (TwoFieldBulkEditPtr) data;
  if (dlg_data == NULL) {
    ResetBulkEditorFieldListDialogValue (dlg->field_from, dlg->field_list);
    ResetBulkEditorFieldListDialogValue (dlg->field_to, dlg->field_list);
    if (dlg->leave_on_original != NULL) {
      SetStatus (dlg->leave_on_original, FALSE);
    }
    if (dlg->strip_name != NULL) {
      SetStatus (dlg->strip_name, FALSE);
    }
    if (dlg->remove_parsed != NULL) {
      SetStatus (dlg->remove_parsed, FALSE);
    }
    PointerToDialog (dlg->text_portion, NULL);
  } else {
    SetBulkEditorFieldListDialogValue (dlg->field_from, dlg_data->field_from);
    SetBulkEditorFieldListDialogValue (dlg->field_to, dlg_data->field_from);
    if (dlg->leave_on_original != NULL) {
      SetStatus (dlg->leave_on_original, dlg_data->leave_on_original);
    }
    if (dlg->strip_name != NULL) {
      SetStatus (dlg->strip_name, dlg_data->strip_name);
    }
    if (dlg->remove_parsed != NULL) {
      SetStatus (dlg->remove_parsed, dlg_data->remove_parsed);
    }
    PointerToDialog (dlg->text_portion, dlg_data->text_portion);
  }
  if (dlg->change_notify != NULL) {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static Pointer TwoFieldBulkEditDialogToPointer (DialoG d)
{
  TwoFieldBulkEditDialogPtr dlg;
  TwoFieldBulkEditPtr       dlg_data;

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

  dlg_data = (TwoFieldBulkEditPtr) MemNew (sizeof (TwoFieldBulkEditData));
  dlg_data->field_from = GetBulkEditorFieldListDialogValue (dlg->field_from);
  dlg_data->field_to = GetBulkEditorFieldListDialogValue (dlg->field_to);
  dlg_data->leave_on_original = dlg->leave_on_original == NULL ? FALSE : GetStatus (dlg->leave_on_original);
  dlg_data->strip_name = dlg->strip_name == NULL ? FALSE : GetStatus (dlg->strip_name);
  dlg_data->remove_parsed = dlg->remove_parsed == NULL ? FALSE : GetStatus (dlg->remove_parsed);
  dlg_data->text_portion = DialogToPointer (dlg->text_portion);

  return dlg_data;
}


static void TwoFieldBulkEditMsg (DialoG d, Int2 mssg)

{
  TwoFieldBulkEditDialogPtr dlg;

  dlg = (TwoFieldBulkEditDialogPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (mssg == AECR_VIB_MSG_CLEAR_TEXT) {
      SendMessageToDialog (dlg->text_portion, NUM_VIB_MSG + 1);
    }
  }
}


static void TwoFieldAutopopulate(ButtoN b)
{
  TwoFieldBulkEditDialogPtr dlg;
  CharPtr                   txt;
  TextPortionXPtr  text_portion;
  Int4             field;

  dlg = (TwoFieldBulkEditDialogPtr) GetObjectExtra (b);
  if (dlg != NULL && dlg->autopopulate_func != NULL) {
    field = GetBulkEditorFieldListDialogValue (dlg->field_from);
    txt = (dlg->autopopulate_func) (dlg->autopopulate_data, field);
    text_portion = DialogToPointer (dlg->text_portion);
    if (text_portion == NULL) {
      text_portion = TextPortionXNew();
    }
    text_portion->start_text = MemFree (text_portion->start_text);
    text_portion->start_text = StringSave (txt);
    text_portion->end_text = MemFree (text_portion->end_text);
    text_portion->end_text = StringSave (txt);
    PointerToDialog (dlg->text_portion, text_portion);
    text_portion = TextPortionXFree (text_portion);
    txt = MemFree (txt);
  }
}


static void TwoFieldClearText(ButtoN b)
{
  TwoFieldBulkEditDialogPtr dlg;
  TextPortionXPtr  text_portion;

  dlg = (TwoFieldBulkEditDialogPtr) GetObjectExtra (b);
  if (dlg != NULL) {
    text_portion = DialogToPointer (dlg->text_portion);
    if (text_portion != NULL) {
      text_portion->start_text = MemFree (text_portion->start_text);
      text_portion->end_text = MemFree (text_portion->end_text);
      PointerToDialog (dlg->text_portion, text_portion);
      text_portion = TextPortionXFree (text_portion);
    }
  }
}


static DialoG TwoFieldBulkEditDialog
(GrouP                    h,
 EBulkAction              action_choice,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata,
 BulkEdFieldPtr           field_list,
 Boolean                  strip_name,
 Nlm_GetAutopopulateTextProc autopopulate_func,
 Pointer                     autopopulate_data
)

{
  TwoFieldBulkEditDialogPtr dlg;
  GrouP            p, g1, g2, g3 = NULL;
  ButtoN           b;
  
  dlg = (TwoFieldBulkEditDialogPtr) MemNew (sizeof (TwoFieldBulkEditDialogData));
  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 = TwoFieldBulkEditToDialog;
  dlg->fromdialog = TwoFieldBulkEditDialogToPointer;
  dlg->dialogmessage = TwoFieldBulkEditMsg;
  dlg->testdialog = NULL;

  dlg->field_list = field_list;
  dlg->autopopulate_func = autopopulate_func;
  dlg->autopopulate_data = autopopulate_data;
  
  g1 = HiddenGroup (p, 2, 0, NULL);
  StaticPrompt (g1, "From", 0, dialogTextHeight, systemFont, 'l');
  StaticPrompt (g1, "To", 0, dialogTextHeight, systemFont, 'l');
  
  dlg->field_from = MakeBulkEditorFieldListDialog (g1, field_list, TRUE);
  ResetBulkEditorFieldListDialogValue (dlg->field_from, field_list);
  dlg->field_to = MakeBulkEditorFieldListDialog (g1, field_list, TRUE);
  ResetBulkEditorFieldListDialogValue (dlg->field_to, field_list);

  if (action_choice == eBulkParseField) 
  {
    dlg->text_portion = TextPortionXDialog(p);
  }

  g2 = HiddenGroup (p, 3, 0, NULL);

  if (action_choice == eBulkParseField)
  {
    dlg->remove_parsed = CheckBox (g2, "Remove from parsed field", NULL);

    if (autopopulate_func != NULL) {
      g3 = HiddenGroup (p, 2, 0, NULL);
      SetGroupSpacing (g3, 10, 10);
      b = PushButton (g3, "Autopopulate", TwoFieldAutopopulate);
      SetObjectExtra (b, dlg, NULL);
      b = PushButton (g3, "Clear Text", TwoFieldClearText);
      SetObjectExtra (b, dlg, NULL);
    }
  }

  if (action_choice == eBulkConvertField)
  {
    dlg->leave_on_original = CheckBox (g2, "Leave on original", NULL);
  }
  if (strip_name)
  {
    dlg->strip_name = CheckBox (g2, "Strip name from text", NULL);
  }

  AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE)g2, (HANDLE) dlg->text_portion, (HANDLE) g3, NULL);
  return (DialoG) p;
}


static void BulkDrawCollapseExpand (Boolean val, RectPtr r)
{
  RecT rct;

  if (r == NULL) return;

  rct = *r;
  rct.bottom = rct.top + stdLineHeight - 4;
  rct.right = rct.left + stdLineHeight - 4;
  InsetRect (&rct, 2, 2);
  FrameRect (&rct);
  
  Gray();
  if (val) {
    /* draw minus */
    MoveTo (rct.left, (rct.top + rct.bottom) / 2);
    LineTo (rct.right - 1, (rct.top + rct.bottom) / 2);
  } else {
    /* draw plus */
    MoveTo (rct.left, (rct.top + rct.bottom) / 2);
    LineTo (rct.right - 1, (rct.top + rct.bottom) / 2);
    MoveTo ((rct.left + rct.right) / 2, rct.top);
    LineTo ((rct.left + rct.right) / 2, rct.bottom);
  }
  Black();
}


static void BulkDrawSelect (Boolean val, RectPtr r)
{
  RecT rct;

  if (r == NULL) return;

  rct = *r;
  rct.bottom = rct.top + stdLineHeight - 4;
  rct.right = rct.left + stdLineHeight - 4;
  InsetRect (&rct, 2, 2);
  FrameRect (&rct);
  
  if (val) {
    /* draw X */
    MoveTo (rct.left, rct.top);
    LineTo (rct.right - 1, rct.bottom - 1);
    MoveTo (rct.right, rct.top);
    LineTo (rct.left, rct.bottom - 1);
  }
}


typedef struct bulkeditordlg {
  DIALOG_MESSAGE_BLOCK
  DoC               title_doc;
  DoC               doc;
  BulkEdFieldPtr    master_field_list;
  BulkEdFieldPtr    field_list;
  ColPtr            col_list;
  ParData           par_fmt;
  BulkEditorRowPtr  row_list;
  DialoG PNTR       editor_list;
  GrouP             ed_btn_grp;
  Int4              num_columns;
  Int4              num_master_columns;
  Int2              last_viewed_row;
  Int2              last_viewed_col;  /* this will always refer to the data column */
  Int2              last_sorted_col;  /* this will always refer to the data column */
  /* for columns shown in display when there are extra */
  Int4              display_offset;  
  Int4              columns_shown;

  /* These are for the bulk edit actions */
  PopuP             bulk_action;
  GrouP             action_pages[8];

  DialoG            bulk_apply;
  DialoG            bulk_edit;
  DialoG            bulk_convert;
  DialoG            bulk_parse;
  DialoG            bulk_swap;
  DialoG            bulk_remove;
  DialoG            bulk_capitalize;

  GrouP             sel_unsel_grp;
  PopuP             check_constraint_field;
  DialoG            check_constraint;
  PrompT            check_status;

  Boolean           any_editing;
  Boolean           collapse_by_default;
  ClickableCallback single_click_func;
  ClickableCallback double_click_func;
  Pointer           metadata;
  SeqEntryPtr       sep;
} BulkEditorDlgData, PNTR BulkEditorDlgPtr;


static void InitBulkEdTitleDoc (BulkEditorDlgPtr dlg, DoC doc)
{
  Int4       i, text_len = 9;
  CharPtr    line_text;

  if (dlg == NULL || doc == NULL) {
    return;
  }

  Reset (doc);
  for (i = 0; i < dlg->display_offset + dlg->columns_shown; i++) {
    if (i < dlg->display_offset) {
      continue;
    }
    text_len += StringLen (dlg->field_list[i].name) + 1;
  }

  line_text = (CharPtr) MemNew (text_len * sizeof (Char));
  line_text[0] = 0;
  /* add blank column for row selection/scrolling */
  if (dlg->display_offset > 0) {
    StringCat (line_text, "<");
  }
  StringCat (line_text, "\t");

  for (i = 0; i < dlg->display_offset + dlg->columns_shown - 1; i++) {
    if (i < dlg->display_offset) {
      continue;
    }
    if (i == dlg->last_sorted_col) {
      /* add blank column for expand/collapse */
      StringCat (line_text, "\t");
    }
    StringCat (line_text, dlg->field_list[i].name);
    StringCat (line_text, "\t");
  }
  if (i == dlg->last_sorted_col) {
    /* add blank column for expand/collapse */
    StringCat (line_text, "\t");
  }
  StringCat (line_text, dlg->field_list[i].name);
  /* add blank column at end */
  if (dlg->display_offset + dlg->columns_shown < dlg->num_columns) {
    StringCat (line_text, "\t>\n");
  } else {
    StringCat (line_text, "\t\n");
  }

  AppendText (doc, line_text, &(dlg->par_fmt), dlg->col_list, programFont);
  line_text = MemFree (line_text);
}


/* redraw, set scroll position */
static void UpdateBulkEdDisplay (BulkEditorDlgPtr dlg)
{
  Int4       i, text_len = 0, sub_num;
  CharPtr    line_text;
  BaR        sb;
  Int4       offset = 0;
  Int2       first_shown = 0;

  if (dlg == NULL) {
    return;
  }

  /* Get original scroll position */
  sb = GetSlateVScrollBar ((SlatE)dlg->doc);
  offset = 0;
  GetScrlParams4 (dlg->doc, &offset, &first_shown, NULL);

  Reset (dlg->doc);

  if (dlg->row_list == NULL) {
    return;
  }

  for (i = 0; dlg->row_list[i].object_list != NULL; i++) {
    line_text = GetTextForBulkEditorRow(dlg->row_list + i, dlg->field_list, dlg->last_sorted_col, dlg->display_offset, dlg->columns_shown);
    AppendText (dlg->doc, line_text, &(dlg->par_fmt), dlg->col_list, programFont);
    line_text = MemFree (line_text);
    if (dlg->row_list[i].subrows != NULL && dlg->row_list[i].expanded) {
      for (sub_num = 0; dlg->row_list[i].subrows[sub_num].object_list != NULL; sub_num++) {
        line_text = GetTextForBulkEditorRow(dlg->row_list[i].subrows + sub_num, dlg->field_list, dlg->last_sorted_col, dlg->display_offset, dlg->columns_shown);
          AppendText (dlg->doc, line_text, &(dlg->par_fmt), dlg->col_list, programFont);
        line_text = MemFree (line_text);
      }
    }
  }
  
  /* restore original scroll position */
  GetItemParams4 (dlg->doc, first_shown, &offset, NULL, NULL, NULL, NULL);
  offset = MIN (offset, GetBarMax (sb));
  SetScrlParams4 (dlg->doc, offset);
}


static void RedrawAfterColumnShift (BulkEditorDlgPtr dlg)
{
  RecT r;
  Int4 width = 0;

  ResetClip();
  /* reset column widths */
  dlg->col_list = MemFree (dlg->col_list);
  SelectFont (systemFont);
  dlg->col_list = AllocateDocColumnsForFieldList (dlg->field_list, &width, dlg->last_sorted_col, dlg->display_offset, dlg->columns_shown);

  /* set data */
  InitBulkEdTitleDoc (dlg, dlg->title_doc);
  UpdateBulkEdDisplay (dlg);
  /* redraw highlight etc. */
  ObjectRect (dlg->title_doc, &r);
  InvalRect (&r);  
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}


/* col is zero-based data column, already corrected for selection and expansion columns */
static void BulkEdDlgToField (Int2 row, Int2 col, BulkEditorDlgPtr dlg)
{
  Int4       i;
  Int4       bulk_row, sub_num;
  Int2       edit_col, last_edit_col;

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

  if (!DataPosFromBulkEdDlgRow (row, dlg->row_list, &bulk_row, &sub_num)) return;

  /* don't apply values if editing master row and column other than sort */
  if (sub_num < 0 && col != dlg->last_sorted_col && dlg->row_list[bulk_row].subrows != NULL) return;

  edit_col = GetMasterFieldNumFromDlgFieldNum (dlg->master_field_list, dlg->field_list, col);

  if (dlg->editor_list[edit_col] == NULL) return;

  if (sub_num < 0) {
    /* if applying to all, get confirmation */
    if (dlg->row_list[bulk_row].subrows != NULL) {
      /* if nothing changed, no need to ask, nothing to do */
      last_edit_col = GetMasterFieldNumFromDlgFieldNum (dlg->master_field_list, dlg->field_list, dlg->last_sorted_col);
      if (!AnyChangeInBulkFields(dlg->row_list + bulk_row, dlg->field_list, dlg->editor_list[last_edit_col], dlg->last_sorted_col)) {
        return;
      } else if (ANS_CANCEL == Message (MSG_OKC, "Apply value to all features in collapsible group?")) {
        return;
      }
    }
    ApplyValueToOneBulkEditorRow (dlg->row_list + bulk_row, col, dlg->field_list, dlg->editor_list[edit_col]);
    if (dlg->row_list[bulk_row].subrows != NULL) {
      for (i = 0; dlg->row_list[bulk_row].subrows[i].object_list != NULL; i++) {
        ApplyValueToOneBulkEditorRow (dlg->row_list[bulk_row].subrows + i, col, dlg->field_list, dlg->editor_list[edit_col]);
      }
    }
  } else {
    ApplyValueToOneBulkEditorRow (dlg->row_list[bulk_row].subrows + sub_num, col, dlg->field_list, dlg->editor_list[edit_col]);
  }
  UpdateBulkEdDisplay (dlg);
}


/* col is zero-based data column, already corrected for selection and expansion columns */
/* if col is -1, hide everything (sorted by selection) */
static void ShowBulkFieldEditor (BulkEditorDlgPtr dlg, Int4 bulk_row, Int4 sub_num, Int2 col)
{
  BulkEditorRowPtr berp;
  ValNodePtr       vnp;
  Int2             i;
  Int4             edit_col;

  for (i = 0; i < dlg->num_master_columns; i++) {
    SafeHide (dlg->editor_list[i]);
  }
  SafeHide (dlg->ed_btn_grp);

  if (col < 0) return;

  if (sub_num < 0) {
    berp = dlg->row_list + bulk_row;
  } else {
    berp = dlg->row_list[bulk_row].subrows + sub_num;
  }

  /* only show editor dialog if sorted column or not master row */
  if (col == dlg->last_sorted_col || berp->subrows == NULL) {
    vnp = GetBulkEditorRowValueByColumn (berp, col);

    edit_col = GetMasterFieldNumFromDlgFieldNum (dlg->master_field_list, dlg->field_list, col);

    if (vnp == NULL) {
      PointerToDialog (dlg->editor_list[edit_col], NULL);
    } else {
      PointerToDialog (dlg->editor_list[edit_col], vnp->data.ptrvalue);
    }
    if (dlg->editor_list[edit_col] != NULL) {
      Show (dlg->editor_list[edit_col]);
      Show (dlg->ed_btn_grp);
    }
  }
}


NLM_EXTERN void SetBulkEditorSortColumn (DialoG dialog, Int4 col)
{
  BulkEditorDlgPtr dlg;
  Int4             bulk_row_num = -1, bulk_sub_num = -1;
  RecT             r;
  Int4             offset = 0, i;
  BulkEditorRowPtr sorted_rows;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (dialog);
  if (dlg == NULL) return;

  if (col < -1 || col >= dlg->num_columns) return;
  ResetClip();

  /* save value of column listed */
  dlg->last_sorted_col = col;

  if (col < 0) {
    sorted_rows = ResortBulkEditorRowsBySelected (dlg->row_list, dlg->field_list);
  } else {
    sorted_rows = ResortBulkEditorRows (dlg->row_list, dlg->field_list, dlg->last_sorted_col);
    if (dlg->collapse_by_default) {
      CollapseAllBulkEditRows (sorted_rows);
    } else {
      ExpandAllBulkEditRows (sorted_rows);
    }
  }
  /* get new position from old row */
  if (DataPosFromBulkEdDlgRow (dlg->last_viewed_row, dlg->row_list, &bulk_row_num, &bulk_sub_num)) {
    if (bulk_sub_num > -1) {
      dlg->last_viewed_row = BulkEdDlgRowFromDataPos (dlg->row_list[bulk_row_num].subrows[bulk_sub_num].copied_to_meta,
                                                      dlg->row_list[bulk_row_num].subrows[bulk_sub_num].copied_to_sub,
                                                      sorted_rows);
    } else {
      dlg->last_viewed_row = BulkEdDlgRowFromDataPos (dlg->row_list[bulk_row_num].copied_to_meta,
                                                      dlg->row_list[bulk_row_num].copied_to_sub,
                                                      sorted_rows);
    }
    dlg->last_viewed_col = dlg->last_sorted_col;
  }

  dlg->row_list = FreeBulkEditorRows (dlg->row_list, dlg->field_list);
  dlg->row_list = sorted_rows;


  /* reformat the columns */
  dlg->col_list = AllocateDocColumnsForFieldList (dlg->field_list, NULL, dlg->last_sorted_col, dlg->display_offset, dlg->columns_shown);
  ResetClip();
  InitBulkEdTitleDoc (dlg, dlg->title_doc);
  ObjectRect (dlg->title_doc, &r);
  InvalRect (&r);  

  UpdateBulkEdDisplay (dlg);

  if (dlg->last_viewed_row > 0) {
    /* show appropriate editor for highlighted item */
    if (DataPosFromBulkEdDlgRow (dlg->last_viewed_row, dlg->row_list, &bulk_row_num, &bulk_sub_num)) {
      ShowBulkFieldEditor (dlg, bulk_row_num, bulk_sub_num, dlg->last_sorted_col);
    } else {
      dlg->last_viewed_row = 0;
      dlg->last_viewed_col = 0;
      for (i = 0; i < dlg->num_master_columns; i++) {
        SafeHide (dlg->editor_list[i]);
      }
      Hide (dlg->ed_btn_grp);
    }

    /* scroll to new position of last_viewed */
    GetItemParams4 (dlg->doc, dlg->last_viewed_row, &offset, NULL, NULL, NULL, NULL);
    SetScrlParams4 (dlg->doc, offset);
  }

  /* redraw highlight etc. */
  ObjectRect (dlg->title_doc, &r);
  InvalRect (&r);  
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}


/* clicking on a column in the title indicates that we should sort by this column. */
static void ClickBulkTitle (DoC d, PoinT pt)
{
  BulkEditorDlgPtr dlg;
  Int2             item;
  Int2             row;
  Int2             col;

  MapDocPoint (d, pt, &item, &row, &col, NULL);
  if (item < 1) {
    return;
  }
  dlg = (BulkEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  if (col == 1) {
    if (dlg->display_offset > 0) {
      dlg->display_offset --;
      RedrawAfterColumnShift (dlg);
      return;
    }
  } else if (dlg->col_list[col].last) {
    if (dlg->display_offset < dlg->num_columns - dlg->columns_shown) {
        dlg->display_offset ++;
        RedrawAfterColumnShift(dlg);
    }
    return;
  }


  if (col - 2 == dlg->last_sorted_col) {
    /* clicked on checkbox column */
    col = -1;
  } else {
    col = BulkEdDataColumnFromDocColumn(col, dlg->last_sorted_col, dlg->display_offset);
  }

  SetBulkEditorSortColumn (dlg->dialog, col);
}


static void UpdateCheckStatus (BulkEditorDlgPtr dlg)
{
  Int4 num_selected;
  CharPtr status_fmt = "%d feature%s currently checked";
  Char status_str[50];

  if (dlg == NULL) return;

  num_selected = CountBulkEditorRowsSelected (dlg->row_list);
  sprintf (status_str, status_fmt, num_selected, num_selected == 1 ? "" : "s");
  SetTitle (dlg->check_status, status_str);
}


/* when clicking on field, show editor (if available), change selection, collapse/expand,
 * set last viewed row, populate data from previously displayed editor 
 */
static void ClickBulkEdField (DoC d, PoinT pt)
{
  BulkEditorDlgPtr dlg;
  BulkEditorRowPtr berp;
  Int2            item;
  Int2            row;
  Int2            col;
  RecT            r;
  ValNodePtr      vnp;
  Int4            bulk_row = -1, sub_num = -1;
  Boolean         dblclick = dblClick;

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

  MapDocPoint (d, pt, &item, &row, &col, NULL);

  if (!DataPosFromBulkEdDlgRow (item, dlg->row_list, &bulk_row, &sub_num)) {
    return;
  }

  if (sub_num < 0) {
    berp = dlg->row_list + bulk_row;
  } else {
    berp = dlg->row_list[bulk_row].subrows + sub_num;
  }

  if (col == 1) {
    /* do selection */
    berp->selected = !berp->selected;
    if (berp->subrows != NULL) {
      SelectBulkEditorRows (berp->subrows, berp->selected);
    } else if (sub_num > -1) {
      /* this is a subrow - metarow can no longer be selected
       * if this is not selected.
       */
      if (dlg->row_list[bulk_row].selected && !berp->selected) {
        dlg->row_list[bulk_row].selected = FALSE;
      } else if (AllBulkEditorRowsSelected(dlg->row_list[bulk_row].subrows)) {
        /* all child rows are now selected, so select metarow */
        dlg->row_list[bulk_row].selected = TRUE;
      }
    }
    UpdateCheckStatus (dlg);
    ObjectRect (dlg->doc, &r);
    InvalRect (&r);  
    Update ();
    return;
  } else if (col == dlg->last_sorted_col + 2 - dlg->display_offset) {
    /* do expand/collapse */
    if (berp->subrows != NULL) {
      berp->expanded = !berp->expanded;
      UpdateBulkEdDisplay (dlg);
      ObjectRect (dlg->doc, &r);
      InvalRect (&r);  
      Update ();
    }
    return;
  } else  {
    /* adjust to account for checkbox column */
    col = BulkEdDataColumnFromDocColumn(col, dlg->last_sorted_col, dlg->display_offset);
  }
  if (item < 1 || col < 0 || col >= dlg->num_columns) return;
  /* took out this line - use Apply button instead, less confusing */
  /*BulkEdDlgToField (dlg->last_viewed_row, dlg->last_viewed_col, dlg); */
  ResetClip();

  ShowBulkFieldEditor (dlg, bulk_row, sub_num, col);

  if (dlg->field_list[col].release_cell_func != NULL) {
    vnp = GetBulkEditorRowValueByColumn (berp, col);
    if (vnp != NULL) {
      vnp->data.ptrvalue = (dlg->field_list[col].release_cell_func) (vnp->data.ptrvalue);
    }
  }
  dlg->last_viewed_row = item;
  dlg->last_viewed_col = col;

  /* redraw highlight etc. */
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();


  if (dblclick) {
    if (dlg->double_click_func != NULL) {
      (dlg->double_click_func) (berp->object_list, NULL);
    }
  } else {
    if (dlg->single_click_func != NULL) {
      (dlg->single_click_func) (berp->object_list, NULL);
    } 
  }
}


static void ApplyBulkEditingChange (ButtoN b)
{
  BulkEditorDlgPtr dlg;
  RecT r;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  BulkEdDlgToField (dlg->last_viewed_row, dlg->last_viewed_col, dlg);

  /* redraw highlight etc. */
  Select (dlg->dialog);
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();

}


static Boolean HighlightBulkEdField (DoC d, Int2 item, Int2 row, Int2 col)

{
  BulkEditorDlgPtr dlg;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return FALSE;

  if (col == 1 || col == dlg->last_sorted_col + 1) {
    /* don't highlight selection column or expansion column */
    return FALSE;
  } 

  col = BulkEdDataColumnFromDocColumn (col, dlg->last_sorted_col, dlg->display_offset);
  
  if (item == dlg->last_viewed_row && col == dlg->last_viewed_col) {
    return TRUE;
  } else {
    return FALSE;
  }
}

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

{
  Int2  lineHeight;
  RecT  rct;
  Int2  format_col, data_col;
  BulkEditorDlgPtr dlg;
  BulkEditorRowPtr berp;
  ValNodePtr       vnp;
  Int4             bulk_row_num = -1, bulk_sub_num = -1;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || r == NULL || item <= 0 || firstLine != 0) {
    return;
  }

  GetItemParams (d, item, NULL, NULL, NULL, &lineHeight, NULL);
  rct = *r;

  if (!DataPosFromBulkEdDlgRow (item, dlg->row_list, &bulk_row_num, &bulk_sub_num)) {
    return;
  }

  /* draw lines at top of each row after the first that does not have subrows */
  if (bulk_row_num > 0 && bulk_sub_num < 0) {
    MoveTo (rct.left, rct.top);
    LineTo (rct.right, rct.top);
  }
  
  if (bulk_sub_num < 0) {
    berp = dlg->row_list + bulk_row_num;
  } else {
    berp = dlg->row_list[bulk_row_num].subrows + bulk_sub_num;
  }
  
  /* for column 0, draw selection box */
  format_col = 0;
  rct.right = rct.left + dlg->col_list[format_col].pixWidth - 1;
  BulkDrawSelect (berp->selected, &rct);
  rct.left = rct.right + 1;
  format_col++; 
 
  for (data_col = 0, vnp = berp->values_list;
       data_col < dlg->num_columns && vnp != NULL; 
       data_col++, vnp = vnp->next) {

    if (data_col < dlg->display_offset) {
      continue;
    } else if (data_col >= dlg->display_offset + dlg->columns_shown) {
      break;
    }

    rct.right = rct.left + dlg->col_list[format_col].pixWidth - 1;
    if (data_col == dlg->last_sorted_col) {
      /* draw box if expandable */
      if (bulk_sub_num == -1
          && dlg->row_list[bulk_row_num].subrows != NULL) {
        BulkDrawCollapseExpand (dlg->row_list[bulk_row_num].expanded, &rct);
      }
      rct.left = rct.right + 1;
      format_col++;
      rct.right = rct.left + dlg->col_list[format_col].pixWidth - 1;
    }  
    if (dlg->field_list[data_col].draw_col_func != NULL) {
      (dlg->field_list[data_col].draw_col_func)(vnp->data.ptrvalue, &rct);
    }
    rct.left = rct.right + 1;
    format_col++;
  }
}


static CharPtr GetFirstBulkEditValue (Pointer data, Int4 col)
{
  BulkEditorDlgPtr dlg;
  CharPtr txt = NULL;
  Int4    i;

  if ((dlg = (BulkEditorDlgPtr) data) == NULL 
      || col < 0 || col >= dlg->num_master_columns 
      || dlg->master_field_list[col].get_func == NULL)
  {
    return NULL;
  }

  for (i = 0; dlg->row_list[i].object_list != NULL && txt == NULL; i++) 
  {
    txt = (dlg->master_field_list[col].get_func)(dlg->row_list[i].object_list->choice, dlg->row_list[i].object_list->data.ptrvalue, dlg->metadata);
    if (StringHasNoText (txt)) {
      txt = MemFree (txt);
    }
  }
  return txt;
}


static void CleanupBulkEditorDialog (GraphiC g, VoidPtr data)
{
  BulkEditorDlgPtr dlg;

  dlg = (BulkEditorDlgPtr) data;
  if (dlg != NULL) {
    dlg->row_list = FreeBulkEditorRows(dlg->row_list, dlg->field_list);
    /* field list has pointers to non-freeable data, just free container */
    dlg->field_list = MemFree (dlg->field_list);
    /* col_list is structures */
    dlg->col_list = MemFree (dlg->col_list);
  }
  StdCleanupExtraProc (g, data);
}

static void ApplyBulkEditorRowToObject (
  BulkEditorRowPtr berp, 
  ValNodePtr        target, 
  BulkEdFieldPtr   field_list, 
  Pointer          metadata, 
  LogInfoPtr       lip
)
{
  ValNodePtr vnp_val;
  Int4       i;
  Pointer    orig_val;
  CharPtr    orig_txt, new_txt, txt;

  if (berp == NULL || target == NULL || field_list == NULL) return;

  for (i = 0, vnp_val = berp->values_list;
       vnp_val != NULL;
       i++, vnp_val = vnp_val->next) {
    if (field_list[i].set_func != NULL) {
      if (lip != NULL) {
        orig_val = (field_list[i].get_func) (target->choice, target->data.ptrvalue, metadata);
        orig_txt = (field_list[i].display_func) (orig_val);
        new_txt = (field_list[i].display_func)(vnp_val->data.ptrvalue);
        if (StringCmp (orig_txt, new_txt) != 0) {
          txt = GetDiscrepancyItemText (target);
          fprintf (lip->fp, "Changed '%s' to '%s' for %s on %s\n",
                   orig_txt, new_txt, field_list[i].name, txt);
          txt = MemFree (txt);
          lip->data_in_log = TRUE;
        }
        orig_txt = MemFree (orig_txt);
        new_txt = MemFree (new_txt);
        field_list[i].free_func(orig_val);
      }
      (field_list[i].set_func) (target->data.ptrvalue, vnp_val->data.ptrvalue);
    }
  }
}

NLM_EXTERN void ApplyBulkEditorToObjectList (DialoG d, Boolean do_log)
{
  BulkEditorDlgPtr dlg;
  Int4             row, i;
  LogInfoPtr       lip = NULL;

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

  if (do_log) {
    lip = OpenLog ("Bulk Edit Changes");
  }

  for (row = 0; dlg->row_list[row].object_list != NULL; row++) {
    if (dlg->row_list[row].subrows == NULL) {
      ApplyBulkEditorRowToObject (dlg->row_list + row, 
                                  dlg->row_list[row].object_list, 
                                  dlg->field_list, 
                                  dlg->metadata,
                                  lip);
    } else {
      for (i = 0; dlg->row_list[row].subrows[i].object_list != NULL; i++) {
        ApplyBulkEditorRowToObject (dlg->row_list[row].subrows + i,
                                    dlg->row_list[row].subrows[i].object_list,
                                    dlg->field_list, 
                                    dlg->metadata,
                                    lip);
      }
    }
  }
  if (do_log) {
    CloseLog (lip);
    lip = FreeLog (lip);
  }
}


static void ExportBulkEditorRow (
  BulkEditorRowPtr berp, 
  ValNodePtr       target, 
  BulkEdFieldPtr   field_list, 
  Pointer          metadata,
  FILE *fp
)
{
  ValNodePtr vnp_val;
  Int4       i;
  Pointer    orig_val;
  CharPtr    orig_txt, new_txt;

  if (berp == NULL || target == NULL || field_list == NULL) return;

  /* first, print original values */
  for (i = 0, vnp_val = berp->values_list;
       vnp_val != NULL;
       i++, vnp_val = vnp_val->next) {
    orig_val = (field_list[i].get_func) (target->choice, target->data.ptrvalue, metadata);
    orig_txt = (field_list[i].display_func) (orig_val);
    fprintf (fp, "%s\t", orig_txt == NULL ? "" : orig_txt);
    orig_txt = MemFree (orig_txt);
    field_list[i].free_func(orig_val);
  }

  /* now print values that can be set */
  for (i = 0, vnp_val = berp->values_list;
       vnp_val != NULL;
       i++, vnp_val = vnp_val->next) {  
    if (field_list[i].set_str_func != NULL && field_list[i].set_func != NULL) {
      new_txt = (field_list[i].display_func)(vnp_val->data.ptrvalue);
      fprintf (fp, "%s\t", new_txt == NULL ? "" : new_txt);
      new_txt = MemFree (new_txt);
    }
  }
  fprintf (fp, "\n");
}


NLM_EXTERN void ExportBulkEditorTable (DialoG d)
{
  BulkEditorDlgPtr dlg;
  Int4             row, i;
  Char             path [PATH_MAX];
  FILE *fp;

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

  if (! GetOutputFileName (path, sizeof (path), NULL)) return;
  fp = FileOpen (path, "w");
  if (fp == NULL)
  {
    Message (MSG_ERROR, "Unable to open %s", path);
    return;
  }

  /* print header */
  /* first, print original values */
  for (i = 0; dlg->field_list[i].name != NULL; i++) {
    fprintf (fp, "Original %s\t", dlg->field_list[i].name);
  }
  /* now print values that can be changed */
  for (i = 0; dlg->field_list[i].name != NULL; i++) {
    if (dlg->field_list[i].set_str_func != NULL && dlg->field_list[i].set_func != NULL) {
      fprintf (fp, "New %s\t", dlg->field_list[i].name);
    }
  }
  fprintf (fp, "\n");

  /* now print table values */
  for (row = 0; dlg->row_list[row].object_list != NULL; row++) {
    if (dlg->row_list[row].subrows == NULL) {
      ExportBulkEditorRow (dlg->row_list + row, 
                                  dlg->row_list[row].object_list, 
                                  dlg->field_list, 
                                  dlg->metadata,
                                  fp);
    } else {
      for (i = 0; dlg->row_list[row].subrows[i].object_list != NULL; i++) {
        ExportBulkEditorRow (dlg->row_list[row].subrows + i,
                                    dlg->row_list[row].subrows[i].object_list,
                                    dlg->field_list, 
                                    dlg->metadata,
                                    fp);
      }
    }
  }
  FileClose (fp);
}


static void AddColumnValuesToRowList (BulkEditorRowPtr row_list, BulkEdFieldPtr master_field_list, Int4 master_pos, Int4 insert_pos, Pointer metadata)
{
  Int4 i, j;
  ValNodePtr vnp_prev, vnp;

  if (row_list == NULL || master_field_list == NULL) {
    return;
  }

  for (i = 0; row_list[i].object_list != NULL; i++) {
    vnp_prev = NULL;
    for (j = 0, vnp = row_list[i].values_list; j < insert_pos; j++, vnp = vnp->next) {
      vnp_prev = vnp;
    }
    vnp = ValNodeNew (NULL);
    vnp->choice = 0;
    if (row_list[i].subrows == NULL) {
      vnp->data.ptrvalue = master_field_list[master_pos].get_func (row_list[i].object_list->choice, row_list[i].object_list->data.ptrvalue, metadata);
    } 
    if (vnp_prev == NULL) {
      vnp->next = row_list[i].values_list;
      row_list[i].values_list = vnp;
    } else {
      vnp->next = vnp_prev->next;
      vnp_prev->next = vnp;
    }
    AddColumnValuesToRowList (row_list[i].subrows, master_field_list, master_pos, insert_pos, metadata);
  }
}


static Int4 AddColumnToRowList (BulkEditorRowPtr row_list, BulkEdFieldPtr master_field_list, BulkEdFieldPtr PNTR p_field_list, Int4 field, Int4 last_sort_col, Pointer metadata)
{
  Int4 i, m = 0, f = 0, num_columns = 1;
  BulkEdFieldPtr field_list, new_field_list;

  if (row_list == NULL || master_field_list == NULL || p_field_list == NULL || (field_list = *p_field_list) == NULL ||  field < 0) {
    return -1;
  }

  /* first, find insertion location */
  f = GetDlgFieldNumFromMasterFieldNum (master_field_list, *p_field_list, field);
  if (StringCmp (master_field_list[field].name, field_list[f].name) == 0) {
    /* already have column */
    return -1;
  }

  AddColumnValuesToRowList (row_list, master_field_list, field, f, metadata);

  /* insert column in field_list */
  for (i = 0; field_list[i].name != NULL; i++) {
    num_columns++;
  }

  num_columns++;
  new_field_list = (BulkEdFieldPtr) MemNew (sizeof (BulkEdFieldData) * num_columns);
  for (i = 0; i < f; i++) {
    new_field_list[i] = field_list[i];
  }
  new_field_list[i] = master_field_list[field];
  while (field_list[i].name != NULL) {
    new_field_list[i + 1] = field_list[i];
    i++;
  }
  new_field_list[i + 1] = field_list[i];
  field_list = MemFree (field_list);
  *p_field_list = new_field_list;

  AddSummaryInfoToMetaRows (row_list, last_sort_col);

  return f;
}


static void ChangeBulkEditorAction (PopuP p)
{
  BulkEditorDlgPtr dlg;
  Int4             page_num;
  Int4             num_pages;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (p);
  if (dlg == NULL || !dlg->any_editing) return;

  num_pages = sizeof (dlg->action_pages) / sizeof (GrouP);
  for (page_num = 0; page_num < num_pages; page_num++) {
    Hide (dlg->action_pages[page_num]);
  }
  page_num = GetValue (dlg->bulk_action) - 1;
  if (page_num > -1 && page_num < num_pages) {
    Show (dlg->action_pages[page_num]);
  }
}

static void RemoveColumnFromSelectedBulkEditRows (BulkEditorRowPtr row_list, Int4 col, BulkEdFieldPtr field_list, Boolean select_all)
{
  ValNodePtr vnp;

  if (row_list == NULL || col < 0 || field_list == NULL) return;
  while (row_list->object_list != NULL) {
    if (row_list->selected || select_all) {
      if (row_list->subrows == NULL || AllBulkEditorRowsSelected(row_list->subrows) || select_all) {
        vnp = GetBulkEditorRowValueByColumn (row_list, col);
        if (vnp != NULL) {
          field_list[col].free_func(vnp->data.ptrvalue);
          vnp->data.ptrvalue = NULL;
        }
      }
    }
    RemoveColumnFromSelectedBulkEditRows (row_list->subrows, col, field_list, select_all);
    row_list++;
  }
}

static void 
ApplyEditColumnFromSelectedBulkEditRows 
(BulkEditorRowPtr berp, 
 BulkEdFieldPtr   field_list,
 Int4             col,
 ApplyValuePtr    avp,
 Boolean select_all)
{
  ValNodePtr     vnp;

  if (berp == NULL) return;

  while (berp->object_list != NULL) {
    if (berp->selected || select_all) {
      if (berp->subrows == NULL || AllBulkEditorRowsSelected(berp->subrows) || select_all) {
        vnp = GetBulkEditorRowValueByColumn (berp, col);
        if (vnp != NULL) {
          vnp->data.ptrvalue = (field_list[col].set_str_func) (vnp->data.ptrvalue, avp);
        }
      }
    }
    ApplyEditColumnFromSelectedBulkEditRows (berp->subrows, field_list, col, avp, select_all);
    berp++;
  }
}


static void 
CapitalizeColumnsForSelectedBulkEditRows
(BulkEditorRowPtr berp, 
 BulkEdFieldPtr   field_list,
 Int4             col,
 ChangeCasePtr    capitalization,
 Boolean          select_all)
{
  ValNodePtr     vnp;
  CharPtr        val_str;
  ApplyValueData avd;
  SeqFeatPtr     sfp;
  SeqEntryPtr    sep, last_sep = NULL;
  ValNodePtr     orgname_list = NULL;

  if (berp == NULL) return;

  avd.field_list = NULL;
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;
  avd.etp = NULL;

  while (berp->object_list != NULL) {
    if (berp->selected || select_all) {
      if (berp->subrows == NULL || AllBulkEditorRowsSelected(berp->subrows) || select_all) {
        vnp = GetBulkEditorRowValueByColumn (berp, col);
        if (vnp != NULL) {
          val_str = (field_list[col].display_func)(vnp->data.ptrvalue);
          if (!StringHasNoText (val_str)) {
            if (berp->object_list->choice == OBJ_SEQFEAT && berp->object_list->data.ptrvalue != NULL) {
              sfp = (SeqFeatPtr) berp->object_list->data.ptrvalue;
              sep = GetTopSeqEntryForEntityID (sfp->idx.entityID);
              if (sep != last_sep) {
                orgname_list = ValNodeFree (orgname_list);
                /* get org names to use in fixes */
                VisitBioSourcesInSep (sep, &orgname_list, GetOrgNamesInRecordCallback);
                last_sep = sep;
              }
            }
            ChangeCase (&val_str, capitalization, orgname_list);
            avd.new_text = val_str;
            vnp->data.ptrvalue = (field_list[col].set_str_func) (vnp->data.ptrvalue, &avd);
          }
          val_str = MemFree (val_str);
        }
      }
    }
    CapitalizeColumnsForSelectedBulkEditRows (berp->subrows, field_list, col, capitalization, select_all);
    berp++;
  }
  orgname_list = ValNodeFree (orgname_list);
}

static void RemoveNameFromText (CharPtr orig, CharPtr remove)
{
  CharPtr cp, cp_dst;
  Int4    len;

  if (StringHasNoText (orig) || StringHasNoText (remove)) {
    return;
  }
  cp = StringISearch (orig, remove);
  if (cp != NULL) {
    len = StringLen (remove);
    cp_dst = cp;
    cp = cp + len;
    while (*cp != 0 && len > 0) {
      *cp_dst = *cp;
      cp_dst++;
      cp++;
      len--;
    }
    *cp_dst = 0;
  }
}

static void ConvertColumnsForSelectedBulkEditRows 
(BulkEditorRowPtr    berp,
 BulkEdFieldPtr      field_list,
 TwoFieldBulkEditPtr tfp,
 ExistingTextPtr     etp,
 Boolean             select_all)
{
  ValNodePtr vnp_from, vnp_to;
  CharPtr    val_from;
  ApplyValueData avd;
  if (berp == NULL || tfp == NULL) return;

  avd.field_list = NULL;
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;
  avd.etp = etp;

  while (berp->object_list != NULL) {
    if ((berp->selected || select_all) && berp->subrows == NULL) {
      vnp_from = GetBulkEditorRowValueByColumn (berp, tfp->field_from);
      vnp_to   = GetBulkEditorRowValueByColumn (berp, tfp->field_to);
      if (vnp_from != NULL && vnp_to != NULL) {
        val_from = (field_list[tfp->field_from].display_func)(vnp_from->data.ptrvalue);
        if (StringDoesHaveText (val_from)) {
          if (tfp->strip_name) {
            RemoveNameFromText (val_from, field_list[tfp->field_to].name);
          }

          avd.new_text = val_from;
          vnp_to->data.ptrvalue = (field_list[tfp->field_to].set_str_func) (vnp_to->data.ptrvalue, &avd);
          
          if (!tfp->leave_on_original) {
            (field_list[tfp->field_from].free_func)(vnp_from->data.ptrvalue);
            vnp_from->data.ptrvalue = NULL;
          }
      
        }
        val_from = MemFree (val_from);
      }
    }
    ConvertColumnsForSelectedBulkEditRows (berp->subrows, field_list, tfp, etp, select_all);
    berp++;
  }
}


static void ParseColumnsForSelectedBulkEditRows 
(BulkEditorRowPtr    berp,
 BulkEdFieldPtr      field_list,
 TwoFieldBulkEditPtr tfp,
 ExistingTextPtr     etp,
 Boolean             select_all)
{
  ValNodePtr vnp_from, vnp_to;
  CharPtr    val_from, val_to;
  ApplyValueData avd;
  if (berp == NULL || tfp == NULL) return;

  avd.field_list = NULL;
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;
  avd.etp = etp;

  while (berp->object_list != NULL) {
    if ((berp->selected || select_all) && berp->subrows == NULL) {
      vnp_from = GetBulkEditorRowValueByColumn (berp, tfp->field_from);
      vnp_to   = GetBulkEditorRowValueByColumn (berp, tfp->field_to);
      if (vnp_from != NULL && vnp_to != NULL) {
        val_from = (field_list[tfp->field_from].display_func)(vnp_from->data.ptrvalue);
        val_to = NULL;
        if (StringDoesHaveText (val_from)) {
          val_to = ReplaceStringForParse(val_from, tfp->text_portion);
          if (StringDoesHaveText (val_to)) {
            avd.new_text = val_to;
            vnp_to->data.ptrvalue = (field_list[tfp->field_to].set_str_func) (vnp_to->data.ptrvalue, &avd);
          }
          if (tfp->remove_parsed) {
            avd.new_text = val_from;
            avd.etp = NULL;
            vnp_from->data.ptrvalue = (field_list[tfp->field_from].set_str_func) (vnp_from->data.ptrvalue, &avd);
            avd.etp = etp;
          }      
        }
        val_from = MemFree (val_from);
        val_to = MemFree (val_to);
      }
    }
    ParseColumnsForSelectedBulkEditRows (berp->subrows, field_list, tfp, etp, select_all);
    berp++;
  }
}


static void SwapColumnsForSelectedBulkEditRows
(BulkEditorRowPtr berp,
 BulkEdFieldPtr   field_list,
 Int4             col_from,
 Int4             col_to,
 Boolean          select_all)
{
  ValNodePtr vnp_from, vnp_to;
  CharPtr    val_from, val_to;
  ApplyValueData avd;
  if (berp == NULL) return;

  avd.etp = NULL;
  avd.field_list = NULL;
  avd.text_to_replace = NULL;
  avd.where_to_replace = EditApplyFindLocation_anywhere;

  while (berp->object_list != NULL) {
    if ((berp->selected || select_all) && berp->subrows == NULL) {
      vnp_from = GetBulkEditorRowValueByColumn (berp, col_from);
      vnp_to   = GetBulkEditorRowValueByColumn (berp, col_to);
      if (vnp_from != NULL && vnp_to != NULL) {
        val_from = (field_list[col_from].display_func)(vnp_from->data.ptrvalue);
        val_to = (field_list[col_to].display_func)(vnp_to->data.ptrvalue);
        if (StringDoesHaveText (val_from) || StringDoesHaveText (val_to)) {
          avd.new_text = val_to;
          vnp_from->data.ptrvalue = (field_list[col_from].set_str_func) (vnp_from->data.ptrvalue, &avd);
      
          avd.new_text = val_from;
          vnp_to->data.ptrvalue = (field_list[col_to].set_str_func) (vnp_to->data.ptrvalue, &avd);
        }
        val_from = MemFree (val_from);
        val_to = MemFree (val_to);
      }
    }
    SwapColumnsForSelectedBulkEditRows (berp->subrows, field_list, col_from, col_to, select_all);
    berp++;
  }
}



static void 
AddSampleDataForBulkEditorSelectedFeatures 
(BulkEditorRowPtr berp,
 BulkEdFieldPtr   field_list,
 Int4             col,
 Int4             col_src,
 GetSamplePtr     gsp,
 TextPortionXPtr   text_portion,
 Boolean           select_all)
{
  CharPtr    str, str_src, str_parse;
  ValNodePtr vnp, vnp_src;
  Boolean    skip;

  if (berp == NULL || field_list == NULL || col < 0 || field_list[col].display_func == NULL || gsp == NULL) return;
  
  while (berp->object_list != NULL) {
    if (berp->subrows == NULL && (berp->selected || select_all)) {
      vnp = GetBulkEditorRowValueByColumn (berp, col);
      if (vnp != NULL) {
        str = (field_list[col]).display_func (vnp->data.ptrvalue);
        if (!StringHasNoText (str)) {
          skip = FALSE;
          /* only add values if col_src < 0 or data in col_src is not blank */
          if (col_src >= 0) {
            skip = TRUE;
            vnp_src = GetBulkEditorRowValueByColumn (berp, col_src);
            if (vnp_src != NULL) {
              str_src = (field_list[col_src]).display_func(vnp_src->data.ptrvalue);
              if (StringDoesHaveText (str_src)) {
                if (text_portion == NULL) {
                  skip = FALSE;
                } else {
                  str_parse = ReplaceStringForParse(str_src, text_portion);
                  if (StringDoesHaveText (str_parse)) {
                    skip = FALSE;
                  }
                  str_parse = MemFree (str_parse);
                }
              }
              str_src = MemFree (str_src);
            }
          }
          if (!skip) {
            gsp->num_found++;
            if (gsp->sample_text == NULL) {
              gsp->sample_text = str;
              str = NULL;
            } else if (StringCmp (str, gsp->sample_text) != 0) {
              gsp->all_same = FALSE;
            }
          }
        }
        str = MemFree (str);
      }
    }
    AddSampleDataForBulkEditorSelectedFeatures (berp->subrows, field_list, col, col_src, gsp, text_portion, select_all);
    berp++;
  }
}


static GetSamplePtr 
GetSampleDataForBulkEditorSelectedFeatures 
(BulkEditorRowPtr berp,
 BulkEdFieldPtr   field_list,
 Int4             col,
 Int4             col_src,
 TextPortionXPtr   text_portion,
 Boolean           select_all)
{
  GetSamplePtr gsp;

  if (berp == NULL || field_list == NULL || col < 0) return NULL;

  gsp = GetSampleNew ();
 
  AddSampleDataForBulkEditorSelectedFeatures(berp, field_list, col, col_src, gsp, text_portion, select_all);

  return gsp;
}


static void AdjustBulkEditPositions (BulkEditorDlgPtr dlg, Int4 added_col)
{
  if (added_col > -1) {
    if (added_col <= dlg->last_viewed_col) {
      dlg->last_viewed_col++;
    }
    if (added_col <= dlg->last_sorted_col) {
      dlg->last_sorted_col++;
    }
    dlg->num_columns++;
  }
}


static void ApplyBulkEditCommon (BulkEditorDlgPtr dlg, Boolean select_all)
{
  RecT             r;
  GetSamplePtr     gsp = NULL;
  ExistingTextPtr  etp = NULL;
  ApplyValueData   avd;
  OneFieldBulkEditPtr ofp = NULL;
  TwoFieldBulkEditPtr tfp = NULL;
  Int4                bulk_row_num, bulk_sub_num, i;
  Int4                added_col1 = -1, added_col2 = -1;
  Boolean             need_redraw = FALSE;

  if (dlg == NULL) return;

  switch (GetValue (dlg->bulk_action)) {
    case eBulkRemoveField:
      ofp = (OneFieldBulkEditPtr) DialogToPointer (dlg->bulk_remove);
      if (ofp != NULL) {
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), ofp->field, dlg->last_sorted_col, dlg->metadata);
        AdjustOneFieldForMaster (ofp, dlg->master_field_list, dlg->field_list);
        RemoveColumnFromSelectedBulkEditRows (dlg->row_list, ofp->field, dlg->field_list, select_all);
      }
      break;
    case eBulkApplyField:
      ofp = (OneFieldBulkEditPtr) DialogToPointer (dlg->bulk_apply);
      if (ofp != NULL) {
        /* add field to table, if it's not in the table yet */
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), ofp->field, dlg->last_sorted_col, dlg->metadata);
        AdjustOneFieldForMaster (ofp, dlg->master_field_list, dlg->field_list);

        /* warn about existing values */
        gsp = GetSampleDataForBulkEditorSelectedFeatures (dlg->row_list, dlg->field_list, ofp->field, -1, NULL, select_all);
        etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, FALSE);
        if (etp == NULL || etp->existing_text_choice != eExistingTextChoiceCancel) {
          avd.etp = etp;
          avd.field_list = NULL;
          AddEditApplyDataToApplyValue (AECR_APPLY, ofp->edit_apply, &avd);
          ApplyEditColumnFromSelectedBulkEditRows (dlg->row_list, dlg->field_list, ofp->field, &avd, select_all);
        }
      }
      break;
    case eBulkEditField:
      ofp = (OneFieldBulkEditPtr) DialogToPointer (dlg->bulk_edit);
      if (ofp != NULL) {
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), ofp->field, dlg->last_sorted_col, dlg->metadata);
        AdjustOneFieldForMaster (ofp, dlg->master_field_list, dlg->field_list);
        avd.etp = NULL;
        avd.field_list = NULL;
        AddEditApplyDataToApplyValue (AECR_EDIT, ofp->edit_apply, &avd);
        ApplyEditColumnFromSelectedBulkEditRows (dlg->row_list, dlg->field_list, ofp->field, &avd, select_all);
      }
      break;     
    case eBulkCapitalizeField:
      ofp = (OneFieldBulkEditPtr) DialogToPointer (dlg->bulk_capitalize);
      if (ofp != NULL && ofp->capitalization != NULL && ofp->capitalization->change != eChangeCaseNone) {
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), ofp->field, dlg->last_sorted_col, dlg->metadata);
        AdjustOneFieldForMaster (ofp, dlg->master_field_list, dlg->field_list);
        CapitalizeColumnsForSelectedBulkEditRows (dlg->row_list, dlg->field_list, ofp->field, ofp->capitalization, select_all);
      }
      break;
    case eBulkConvertField:
      tfp = (TwoFieldBulkEditPtr) DialogToPointer (dlg->bulk_convert);
      if (tfp != NULL) {
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), tfp->field_to, dlg->last_sorted_col, dlg->metadata);
        added_col2 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), tfp->field_from, dlg->last_sorted_col, dlg->metadata);
        AdjustTwoFieldsForMaster (tfp, dlg->master_field_list, dlg->field_list);

        gsp = GetSampleDataForBulkEditorSelectedFeatures (dlg->row_list, dlg->field_list, tfp->field_to, tfp->field_from, NULL, select_all);
        etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, FALSE);
        if (etp == NULL || etp->existing_text_choice != eExistingTextChoiceCancel) {
          ConvertColumnsForSelectedBulkEditRows (dlg->row_list, dlg->field_list, tfp, etp, select_all);
        }
      }
      break;
    case eBulkParseField:
      tfp = (TwoFieldBulkEditPtr) DialogToPointer (dlg->bulk_parse);
      if (tfp != NULL) {
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), tfp->field_to, dlg->last_sorted_col, dlg->metadata);
        added_col2 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), tfp->field_from, dlg->last_sorted_col, dlg->metadata);
        AdjustTwoFieldsForMaster (tfp, dlg->master_field_list, dlg->field_list);
        gsp = GetSampleDataForBulkEditorSelectedFeatures (dlg->row_list, dlg->field_list, tfp->field_to, tfp->field_from, tfp->text_portion, select_all);
        etp = GetExistingTextHandlerInfo (gsp == NULL ? 0 : gsp->num_found, FALSE);        
        if (etp == NULL || etp->existing_text_choice != eExistingTextChoiceCancel) {
          ParseColumnsForSelectedBulkEditRows (dlg->row_list, dlg->field_list, tfp, etp, select_all);
        }        
      }
      break;
    case eBulkSwapField:
      tfp = (TwoFieldBulkEditPtr) DialogToPointer (dlg->bulk_swap);
      if (tfp != NULL) {
        added_col1 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), tfp->field_to, dlg->last_sorted_col, dlg->metadata);
        added_col2 = AddColumnToRowList (dlg->row_list, dlg->master_field_list, &(dlg->field_list), tfp->field_from, dlg->last_sorted_col, dlg->metadata);
        AdjustTwoFieldsForMaster (tfp, dlg->master_field_list, dlg->field_list);
        SwapColumnsForSelectedBulkEditRows (dlg->row_list, dlg->field_list, tfp->field_from, tfp->field_to, select_all);
      }
      break;
  }

  ofp = OneFieldBulkEditFree (ofp);
  tfp = TwoFieldBulkEditFree (tfp);
  gsp = GetSampleFree (gsp);
  etp = MemFree (etp);

  AdjustBulkEditPositions (dlg, added_col1);
  AdjustBulkEditPositions (dlg, added_col2);

  AddSummaryInfoToMetaRows (dlg->row_list, dlg->last_sorted_col);

  /* update individual field editor */
  if (dlg->last_viewed_row > 0) {
    /* show appropriate editor for highlighted item */
    if (DataPosFromBulkEdDlgRow (dlg->last_viewed_row, dlg->row_list, &bulk_row_num, &bulk_sub_num)) {
      ShowBulkFieldEditor (dlg, bulk_row_num, bulk_sub_num, dlg->last_viewed_col);
    } else {
      dlg->last_viewed_row = 0;
      dlg->last_viewed_col = 0;
      for (i = 0; i < dlg->num_master_columns; i++) {
        SafeHide (dlg->editor_list[i]);
      }
      Hide (dlg->ed_btn_grp);
    }
  }

  if (added_col1 > -1 || added_col2 > -1) {
    RedrawAfterColumnShift (dlg);
  } else {
    UpdateBulkEdDisplay (dlg);
    ObjectRect (dlg->doc, &r);
    InvalRect (&r);  
    Update ();
  }
}


static void ApplyBulkEditAll (ButtoN b)
{
  BulkEditorDlgPtr dlg;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  ApplyBulkEditCommon (dlg, TRUE);
}

static void ApplyBulkEdit (ButtoN b)
{
  BulkEditorDlgPtr dlg;
  Boolean             select_all = FALSE;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  if (CountBulkEditorRowsSelected(dlg->row_list) == 0) {
    if (ANS_CANCEL == Message (MSG_OKC, "No items selected!  Apply to all?")) {
      return;
    } else {
      select_all = TRUE;
    }
  }

  ApplyBulkEditCommon (dlg, select_all);
}


extern void BulkEditorCheckAllDialog (DialoG d)
{
  BulkEditorDlgPtr dlg;
  RecT             r;

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

  SelectBulkEditorRows (dlg->row_list, TRUE);
  UpdateCheckStatus (dlg);
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}

static void BulkEditorCheckAll (ButtoN b)
{
  BulkEditorDlgPtr dlg;
  RecT             r;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  SelectBulkEditorRows (dlg->row_list, TRUE);
  UpdateCheckStatus (dlg);
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}


static void BulkEditorUncheckAll (ButtoN b)
{
  BulkEditorDlgPtr dlg;
  RecT             r;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  SelectBulkEditorRows (dlg->row_list, FALSE);
  UpdateCheckStatus (dlg);
  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}

static void BulkEditorStringConstraintCheck (ButtoN b)
{
  BulkEditorDlgPtr    dlg;
  RecT                r;
  Int4                field_num;
  StringConstraintXPtr scp;
  Boolean             val;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  field_num = GetFieldNumFromPopupValue(GetValue (dlg->check_constraint_field), dlg->field_list, FALSE);
  scp = DialogToPointer (dlg->check_constraint);

  if (GetValue (dlg->sel_unsel_grp) == 1) {
    val = TRUE;
  } else {
    val = FALSE;
  }

  SelectBulkEditorRowsByStringConstraint (dlg->row_list, dlg->field_list, field_num, scp, val);
  
  scp = StringConstraintXFree (scp);
  UpdateCheckStatus (dlg);

  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}


static void ObjectListToBulkEditorDialog (DialoG d, Pointer data)
{
  BulkEditorDlgPtr dlg;
  ValNodePtr       object_list;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  object_list = (ValNodePtr) data;
    
  dlg->row_list = FreeBulkEditorRows(dlg->row_list, dlg->field_list);
  dlg->row_list = MakeBulkEditorRowsFromObjectList (object_list, dlg->field_list, dlg->last_sorted_col, dlg->metadata);
  if (dlg->collapse_by_default) {
    CollapseAllBulkEditRows (dlg->row_list);
  } else {
    ExpandAllBulkEditRows (dlg->row_list);
  }
  ResetClip();
  UpdateBulkEdDisplay (dlg);
  UpdateCheckStatus (dlg);
}


static Pointer GetSelectedObjectsFromBulkEditorDialog (DialoG d)
{
  BulkEditorDlgPtr dlg;
  ValNodePtr       object_list = NULL;

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

  GetBulkEditorSelectedObjects (dlg->row_list, &object_list);
  return (Pointer) object_list;
}


static void CopyEditToClipboard (ButtoN b)
{
  BulkEditorDlgPtr dlg;
  CharPtr str;
  Int2    edit_col;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) {
    return;
  }
  if (dlg->last_viewed_col < 0 
    || dlg->last_viewed_col >= dlg->num_columns) {
    return;
  }
  edit_col = GetMasterFieldNumFromDlgFieldNum (dlg->master_field_list, dlg->field_list, dlg->last_viewed_col);
  if (dlg->editor_list[edit_col] == NULL) {
    return;
  }
  str = DialogToPointer (dlg->editor_list[edit_col]);
  if (!StringHasNoText (str)) {
    StringToClipboard (str);
  }
  str = MemFree (str);
}


static void CopyEditFromClipboard (ButtoN b)
{
  BulkEditorDlgPtr dlg;
  CharPtr str;
  Int2    edit_col;

  dlg = (BulkEditorDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) {
    return;
  }
  if (dlg->last_viewed_col < 0 
    || dlg->last_viewed_col >= dlg->num_columns) {
    return;
  }
  edit_col = GetMasterFieldNumFromDlgFieldNum (dlg->master_field_list, dlg->field_list, dlg->last_viewed_col);
  if (dlg->editor_list[edit_col] == NULL) {
    return;
  }
  str = ClipboardToString ();
  if (!StringHasNoText (str)) {
    PointerToDialog (dlg->editor_list[edit_col], str);
  }
  str = MemFree (str);
}


static BulkEdFieldPtr GetPopulatedFieldsFromMasterList (BulkEdFieldPtr master_field_list, BulkEditorRowPtr row_list)
{
  Int4 i, num_fields = 0;
  BoolPtr empties;
  Int4 num_full = 0;
  BulkEdFieldPtr pop_fields;

  for (i = 0; master_field_list[i].name != NULL; i++) {
    num_fields++;
  }

  empties = (BoolPtr) MemNew (sizeof (Boolean) * num_fields);
  for (i = 0; i < num_fields; i++) {
    if (IsBulkEditorColumnEmpty (row_list, i)) {
      empties[i] = TRUE;
    } else {
      empties[i] = FALSE;
      num_full++;
    }
  }

  pop_fields = (BulkEdFieldPtr) MemNew (sizeof (BulkEdFieldData) * (num_full + 1));
  num_full = 0;
  for (i = 0; i < num_fields; i++) {
    if (!empties[i]) {
      pop_fields[num_full++] = master_field_list[i];
    }
  }
  pop_fields[num_full] = master_field_list[num_fields];
  empties = MemFree (empties);
  return pop_fields;
}


static BulkEdFieldPtr CopyAllFieldsFromList (BulkEdFieldPtr master_field_list)
{
  Int4 i, num_fields = 0;
  Int4 num_full = 0;
  BulkEdFieldPtr pop_fields;

  for (i = 0; master_field_list[i].name != NULL; i++) {
    num_fields++;
  }


  pop_fields = (BulkEdFieldPtr) MemNew (sizeof (BulkEdFieldData) * (num_fields + 1));
  for (i = 0; i < num_fields; i++) {
    pop_fields[i] = master_field_list[i];
  }
  pop_fields[num_fields] = master_field_list[num_fields];
  return pop_fields;
}

static void FinishBulkEditorDialogSetup (GrouP p, BulkEditorDlgPtr dlg)
{
  Int4             width, num_columns = 0;
  Int4             i, page_num;
  GrouP            ed_grp, ed2_grp, check_grp, action_grp, page_grp, box_grp;
  GrouP            g1, g2, a, g_tmp, g_tmp2;
  ButtoN           b; 

  if (p == NULL || dlg == NULL) {
    return;
  }

  dlg->dialog = (DialoG) p;
  dlg->todialog = ObjectListToBulkEditorDialog; 
  dlg->fromdialog = GetSelectedObjectsFromBulkEditorDialog;
  dlg->testdialog = NULL;

  /* look for editing functions */
  dlg->num_master_columns = 0;
  dlg->any_editing = FALSE;
  for (i = 0; dlg->master_field_list[i].name != NULL; i++) {
    if (dlg->master_field_list[i].create_dlg_func != NULL) {
      dlg->any_editing = TRUE;
    }
    dlg->num_master_columns++;
  }
  /* count columns */
  for (i = 0; dlg->field_list[i].name != NULL; i++) {
    num_columns++;
  }

  dlg->num_columns = num_columns;
  dlg->columns_shown = num_columns;
  dlg->display_offset = 0;
  /* populate values list */

  SelectFont (systemFont);
  dlg->col_list = AllocateDocColumnsForFieldList (dlg->field_list, &width, dlg->last_sorted_col, dlg->display_offset, dlg->columns_shown);
  dlg->par_fmt.minLines = 1;
  dlg->par_fmt.minHeight = 1;

  dlg->title_doc = DocumentPanel (p, width, stdLineHeight);
  InitBulkEdTitleDoc (dlg, dlg->title_doc);
  SetObjectExtra (dlg->title_doc, dlg, NULL);
  SetDocProcs (dlg->title_doc, ClickBulkTitle, NULL, NULL, NULL);

  dlg->doc = DocumentPanel (p, width, 10 * stdLineHeight);
  SetObjectExtra (dlg->doc, dlg, NULL);
  SetDocProcs (dlg->doc, ClickBulkEdField, NULL, NULL, NULL);
  SetDocShade (dlg->doc, DrawBulkEdCell, NULL, HighlightBulkEdField, NULL);

  ed_grp = HiddenGroup (p, 4, 0, NULL);
  SetGroupSpacing (ed_grp, 10, 10);
  ed2_grp = HiddenGroup (ed_grp, 0, 0, NULL);
  dlg->editor_list = (DialoG PNTR) MemNew (sizeof(DialoG) * dlg->num_master_columns);
  for (i = 0; dlg->master_field_list[i].name != NULL; i++) {
    if (dlg->master_field_list[i].create_dlg_func == NULL) {
      dlg->editor_list[i] = NULL;
    } else {
      dlg->editor_list[i] = (dlg->master_field_list[i].create_dlg_func) (ed2_grp, dlg->master_field_list[i].name, dlg->sep);
      Hide (dlg->editor_list[i]);
    }
  }
  
  dlg->ed_btn_grp = HiddenGroup (ed_grp, 1, 0, NULL);
  SetGroupSpacing (dlg->ed_btn_grp, 10, 10);
  
  b = PushButton (dlg->ed_btn_grp, "Apply Change", ApplyBulkEditingChange);
  SetObjectExtra (b, dlg, NULL);
  g_tmp = HiddenGroup (dlg->ed_btn_grp, 2, 0, NULL);
  SetGroupSpacing (g_tmp, 10, 10);
  b = PushButton (g_tmp, "Copy to Clipboard", CopyEditToClipboard);
  SetObjectExtra (b, dlg, NULL);
  b = PushButton (g_tmp, "Copy from Clipboard", CopyEditFromClipboard);
  SetObjectExtra (b, dlg, NULL);
  Hide (dlg->ed_btn_grp);

  box_grp = HiddenGroup (p, 0, 2, NULL);

  /* group that contains controls for checking and unchecking features */
  check_grp = NormalGroup (box_grp, -1, 0, "Check Features", programFont, NULL);
  SetGroupSpacing (check_grp, 10, 10);
  g1 = HiddenGroup (check_grp, 3, 0, NULL);
  SetGroupSpacing (g1, 10, 10);
  b = PushButton (g1, "Check All Features", BulkEditorCheckAll);
  SetObjectExtra (b, dlg, NULL);
  b = PushButton (g1, "Uncheck All Features", BulkEditorUncheckAll);
  SetObjectExtra (b, dlg, NULL);

  g2 = HiddenGroup (check_grp, 4, 0, NULL);
  dlg->sel_unsel_grp = HiddenGroup (g2, 0, 2, NULL);
  RadioButton (dlg->sel_unsel_grp, "Check features where");
  RadioButton (dlg->sel_unsel_grp, "Uncheck features where");
  SetValue (dlg->sel_unsel_grp, 1);
  dlg->check_constraint_field = MakeBulkEditorFieldListPopup (g2, dlg->field_list, FALSE);
  dlg->check_constraint = StringConstraintDialogX (g2, NULL, FALSE);
  b = PushButton (g2, "Apply", BulkEditorStringConstraintCheck);
  SetObjectExtra (b, dlg, NULL);

  if (dlg->any_editing) {
    /* group that contains controls for applying actions to check features */
    action_grp = NormalGroup (box_grp, -1, 0, "Action for Checked Features", programFont, NULL);
    a = HiddenGroup (action_grp, 2, 0, NULL);
    SetGroupSpacing (a, 10, 10);
    dlg->bulk_action = PopupList (a, TRUE, ChangeBulkEditorAction);
    SetObjectExtra (dlg->bulk_action, dlg, NULL);
    PopupItem (dlg->bulk_action, "Apply");
    PopupItem (dlg->bulk_action, "Edit");
    PopupItem (dlg->bulk_action, "Convert");
    PopupItem (dlg->bulk_action, "Parse");
    PopupItem (dlg->bulk_action, "Swap");
    PopupItem (dlg->bulk_action, "Remove");
    PopupItem (dlg->bulk_action, "Change Case");
    SetValue (dlg->bulk_action, 2);
    page_grp = HiddenGroup (a, 0, 0, NULL);
    page_num = 0;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);
    dlg->bulk_apply = OneFieldBulkEditDialog (dlg->action_pages[page_num], eBulkApplyField, NULL, NULL, dlg->master_field_list, GetFirstBulkEditValue, dlg);

    page_num++;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);
    dlg->bulk_edit = OneFieldBulkEditDialog (dlg->action_pages[page_num], eBulkEditField, NULL, NULL, dlg->master_field_list, GetFirstBulkEditValue, dlg);

    page_num++;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);
    dlg->bulk_convert = TwoFieldBulkEditDialog (dlg->action_pages[page_num], eBulkConvertField, NULL, NULL, dlg->master_field_list, FALSE, NULL, NULL);

    page_num++;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);
    dlg->bulk_parse = TwoFieldBulkEditDialog (dlg->action_pages[page_num], eBulkParseField, NULL, NULL, dlg->master_field_list, FALSE, GetFirstBulkEditValue, dlg);

    page_num++;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);
    dlg->bulk_swap = TwoFieldBulkEditDialog (dlg->action_pages[page_num], eBulkSwapField, NULL, NULL, dlg->master_field_list, FALSE, NULL, NULL);

    page_num++;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);  
    dlg->bulk_remove = OneFieldBulkEditDialog (dlg->action_pages[page_num], eBulkRemoveField, NULL, NULL, dlg->master_field_list, NULL, NULL);

    page_num++;
    dlg->action_pages[page_num] = HiddenGroup (page_grp, 2, 0, NULL);  
    dlg->bulk_capitalize = OneFieldBulkEditDialog (dlg->action_pages[page_num], eBulkCapitalizeField, NULL, NULL, dlg->master_field_list, NULL, NULL);

    AlignObjects (ALIGN_CENTER, (HANDLE) dlg->action_pages[0], 
                                (HANDLE) dlg->action_pages[1], 
                                (HANDLE) dlg->action_pages[2], 
                                (HANDLE) dlg->action_pages[3], 
                                (HANDLE) dlg->action_pages[4], 
                                (HANDLE) dlg->action_pages[5], 
                                (HANDLE) dlg->action_pages[6], 
                                NULL);

    dlg->check_status = StaticPrompt (action_grp, "0 features are checked", 20 * stdCharWidth, dialogTextHeight, programFont, 'l');

    g_tmp2 = HiddenGroup (action_grp, 3, 0, NULL);
    SetGroupSpacing (g_tmp2, 10, 10);
    b = PushButton (g_tmp2, "Apply to All Features", ApplyBulkEditAll);
    SetObjectExtra (b, dlg, NULL);
    b = PushButton (g_tmp2, "Apply to Checked Features", ApplyBulkEdit);
    SetObjectExtra (b, dlg, NULL);

    AlignObjects (ALIGN_CENTER, (HANDLE) a, (HANDLE) dlg->check_status, (HANDLE) g_tmp2, NULL);
  }


  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->title_doc, (HANDLE) dlg->doc, (HANDLE) ed_grp, (HANDLE) box_grp, NULL);

  UpdateBulkEdDisplay (dlg);
  UpdateCheckStatus (dlg);
  ChangeBulkEditorAction (dlg->bulk_action);
}


NLM_EXTERN DialoG 
CreateBulkEditorDialogEx 
(GrouP               h,
 BulkEdFieldPtr      field_list,
 ValNodePtr          feat_list,
 SeqEntryPtr         sep, 
 Boolean             collapse_by_default,
 ClickableCallback   single_click_func,
 ClickableCallback   double_click_func,
 Pointer             metadata,
 Boolean             show_only_present)
{
  GrouP            p;
  BulkEditorDlgPtr dlg;

  p = HiddenGroup (h, -1, 0, NULL);
  dlg = (BulkEditorDlgPtr) MemNew (sizeof (BulkEditorDlgData));

  SetObjectExtra (p, dlg, CleanupBulkEditorDialog); /* need to cleanup values_list */

  dlg->collapse_by_default = collapse_by_default;
  dlg->single_click_func = single_click_func;
  dlg->double_click_func = double_click_func;
  dlg->master_field_list = field_list;
  dlg->metadata = metadata;
  dlg->sep = sep;

  dlg->last_viewed_row = 0;
  dlg->last_viewed_col = 0;
  dlg->last_sorted_col = 0;
  dlg->row_list = MakeBulkEditorRowsFromObjectList(feat_list, dlg->master_field_list, dlg->last_sorted_col, dlg->metadata);

  /* need master list and actual list */
  if (show_only_present) {
    dlg->field_list = GetPopulatedFieldsFromMasterList (dlg->master_field_list, dlg->row_list);
    dlg->row_list = FreeBulkEditorRows(dlg->row_list, dlg->master_field_list);
    dlg->row_list = MakeBulkEditorRowsFromObjectList(feat_list, dlg->field_list, dlg->last_sorted_col, dlg->metadata);
  } else {
    dlg->field_list = CopyAllFieldsFromList (dlg->master_field_list);
  }

  if (dlg->collapse_by_default) {
    CollapseAllBulkEditRows (dlg->row_list);
  } else {
    ExpandAllBulkEditRows (dlg->row_list);
  }

  FinishBulkEditorDialogSetup (p, dlg);

  return (DialoG) p;
}


NLM_EXTERN DialoG 
CreateBulkEditorDialog 
(GrouP               h,
 BulkEdFieldPtr      field_list,
 ValNodePtr          feat_list,
 SeqEntryPtr         sep, 
 Boolean             collapse_by_default,
 ClickableCallback   single_click_func,
 ClickableCallback   double_click_func)
{
  return CreateBulkEditorDialogEx (h, field_list, feat_list, sep, collapse_by_default,
                                   single_click_func, double_click_func, NULL, FALSE);
}


static Pointer BulkGetLocation (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SeqLocPtr slp;
  SeqFeatPtr sfp = (SeqFeatPtr) data;

  if (sfp == NULL) return NULL;
  slp = (SeqLocPtr) AsnIoMemCopy (sfp->location, (AsnReadFunc) SeqLocAsnRead, (AsnWriteFunc) SeqLocAsnWrite);
  return slp;
}

static void BulkSetLocation (Pointer target, Pointer data)
{
  SeqFeatPtr sfp = (SeqFeatPtr) target;
  SeqLocPtr slp = (SeqLocPtr) data;
  if (sfp == NULL) return;
  sfp->location = SeqLocFree (sfp->location);
  sfp->location = (SeqLocPtr) AsnIoMemCopy (slp, (AsnReadFunc) SeqLocAsnRead, (AsnWriteFunc) SeqLocAsnWrite);
}

static Int4 BulkFormatLocation (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = MAX (15, StringLen (name)) * stdCharWidth;
  col->pixInset = 1;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}

static void BulkFreeLocation (Pointer data)
{
  SeqLocPtr slp = (SeqLocPtr) data;

  slp = SeqLocFree (slp);
}

static CharPtr BulkDisplayLocation (Pointer data)
{
  SeqLocPtr slp = (SeqLocPtr) data;

  return SeqLocPrint (slp);
}

static DialoG BulkNucLocationDialog (GrouP g, CharPtr name, SeqEntryPtr sep)
{
  return CreateIntervalEditorDialog (g, NULL, 4, 2, sep, TRUE, FALSE);
}

static Pointer BulkCopyLocation (Pointer data)
{
  SeqLocPtr orig = (SeqLocPtr) data;
  SeqLocPtr new_loc;

  if (orig == NULL) return NULL;
  new_loc = (SeqLocPtr) AsnIoMemCopy ((Pointer) orig,
                          (AsnReadFunc) SeqLocAsnRead,
                          (AsnWriteFunc) SeqLocAsnWrite);

  return new_loc;
}

NLM_EXTERN Int4 BulkFormatSimpleText (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = (MAX (15, StringLen (name)) + 3) * MaxCharWidth();
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}

NLM_EXTERN void BulkFreeSimpleText (Pointer data)
{
  CharPtr str = (CharPtr) data;

  str = MemFree (str);
}

NLM_EXTERN CharPtr BulkDisplaySimpleText (Pointer data)
{
  CharPtr str = (CharPtr) data;
  if (StringHasNoText (str)) {
    return NULL;
  } else {
    return StringSave ((CharPtr) data);
  }
}

typedef struct bulksimpletext {
  DIALOG_MESSAGE_BLOCK
  TexT txt;
} BulkSimpleTextData, PNTR BulkSimpleTextPtr;

static void StringToBulkSimpleTextDialog (DialoG d, Pointer data)
{
  BulkSimpleTextPtr dlg;
  CharPtr           str;

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

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

static Pointer BulkSimpleTextDialogToString (DialoG d)
{
  BulkSimpleTextPtr dlg;

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

  return (Pointer) SaveStringFromText (dlg->txt);
}


static DialoG BulkSimpleTextDialogEx (GrouP g, CharPtr name, SeqEntryPtr sep, Boolean use_scroll)
{
  BulkSimpleTextPtr dlg;
  GrouP           p;

  p = HiddenGroup (g, 4, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (BulkSimpleTextPtr) MemNew (sizeof(BulkSimpleTextData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToBulkSimpleTextDialog;
  dlg->fromdialog = BulkSimpleTextDialogToString;
  dlg->testdialog = NULL;

  StaticPrompt (p, name, 0, dialogTextHeight, programFont, 'l');
  if (use_scroll) {
    dlg->txt = ScrollText (p, 20, 4, programFont, TRUE, NULL);
  } else {
    dlg->txt = DialogText (p, "", 20, NULL);
  }

  return (DialoG) p;
}

NLM_EXTERN DialoG BulkSimpleTextDialog (GrouP g, CharPtr name, SeqEntryPtr sep)
{
  return BulkSimpleTextDialogEx (g, name, sep, FALSE);
}

static DialoG BulkSimpleTextScrollDialog (GrouP g, CharPtr name, SeqEntryPtr sep)
{
  return BulkSimpleTextDialogEx (g, name, sep, TRUE);
}

NLM_EXTERN Pointer BulkSimpleTextCopy (Pointer data)
{
  CharPtr orig = (CharPtr) data;
  CharPtr new_str = StringSave (orig);
  return new_str;
}

static Int4 BulkFormatTrueFalse (ColPtr col, CharPtr name)
{
  size_t box_width = stdLineHeight - 3;
  if (col == NULL) return 0;

  col->pixWidth = MAX (box_width, (StringLen (name) + 2) * MaxCharWidth());
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 0;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}

static void BulkDrawTrueFalse (Pointer p, RectPtr r)
{
  RecT rct;

  if (r == NULL) return;

  rct = *r;
  rct.bottom = rct.top + stdLineHeight - 4;
  rct.right = rct.left + stdLineHeight - 4;
  Gray();
  InsetRect (&rct, 2, 2);
  FrameRect (&rct);
  
  if (p != NULL) {
    MoveTo (rct.left, rct.top);
    LineTo (rct.right - 1, rct.bottom - 1);
    MoveTo (rct.left, rct.bottom - 1);
    LineTo (rct.right - 1, rct.top);
  }
  Black();
}

static Pointer BulkReleaseTrueFalse (Pointer p)
{
  if (p == NULL) {
    p = (Pointer) StringSave ("TRUE");
  } else {
    p = MemFree (p);
  }
  return p;
}


static void BulkSetPseudo (Pointer target, Pointer val)
{
  SeqFeatPtr sfp = (SeqFeatPtr) target;
  if (sfp == NULL) return;
  if (StringHasNoText ((CharPtr)val)) {
    sfp->pseudo = FALSE;
  } else {
    sfp->pseudo = TRUE;
  }
}

static Pointer BulkGetPseudo (Pointer data)
{
  SeqFeatPtr sfp = (SeqFeatPtr) data;
  if (sfp == NULL || ! sfp->pseudo) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}

static void BulkSetComment (Pointer target, Pointer data)
{
  CharPtr str = (CharPtr) data;
  SeqFeatPtr sfp = (SeqFeatPtr) target;
  if (sfp == NULL) return;

  sfp->comment = MemFree (sfp->comment);
  if (str != NULL) {
    sfp->comment = StringSave (str);
  }
}

static Pointer BulkGetComment (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SeqFeatPtr sfp = (SeqFeatPtr) data;
  if (sfp == NULL || StringHasNoText (sfp->comment)) {
    return NULL;
  } else {
    return (Pointer) StringSave (sfp->comment);
  }
}


static Pointer BulkCDSGetProtein (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SeqFeatXrefPtr xref;
  ProtRefPtr     prp = NULL;
  BioseqPtr      prot_bsp;
  SeqFeatPtr     prot_feat;
  SeqMgrFeatContext fcontext;
  ValNodePtr        prot_name_list = NULL, vnp;
  SeqFeatPtr        sfp = (SeqFeatPtr) data;

  if (sfp == NULL) return NULL;

  xref = sfp->xref;
  while (xref != NULL && xref->data.choice != SEQFEAT_PROT) {
    xref = xref->next;
  }
  if (xref != NULL) {
    prp = xref->data.value.ptrvalue;
  }

  if (prp == NULL) {
    if (sfp->data.choice == SEQFEAT_PROT) {
      prp = (ProtRefPtr) sfp->data.value.ptrvalue;
    } else if (sfp->data.choice == SEQFEAT_CDREGION) {
      prot_bsp = BioseqFindFromSeqLoc (sfp->product);
      prot_feat = SeqMgrGetNextFeature (prot_bsp, NULL, SEQFEAT_PROT, FEATDEF_PROT, &fcontext);
      if (prot_feat != NULL) {
        prp = (ProtRefPtr) prot_feat->data.value.ptrvalue;
      }
    }
  }

  if (prp != NULL) {
    for (vnp = prp->name; vnp != NULL; vnp = vnp->next) {
      ValNodeAddPointer (&prot_name_list, vnp->choice, StringSave (vnp->data.ptrvalue));
    }
  }
    
  return prot_name_list;
}

static Pointer BulkCDSCopyProtein (Pointer data)
{
  ValNodePtr        new_prot_name_list = NULL, prot_name_list, vnp;
 
  prot_name_list = (ValNodePtr) data;
  
  for (vnp = prot_name_list; vnp != NULL; vnp = vnp->next) {
    ValNodeAddPointer (&new_prot_name_list, vnp->choice, StringSave (vnp->data.ptrvalue));
  }
  return new_prot_name_list;
}  

static Int4 BulkCDSFormatProtein (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = MAX (15, StringLen (name)) * MaxCharWidth();
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}

static CharPtr BulkCDSDisplayProtein (Pointer data)
{
  ValNodePtr prot_name_list, vnp;
  CharPtr    prot_text;
  Int4       text_len = 1;

  if (data == NULL) return NULL;
  prot_name_list = (ValNodePtr) data;
  for (vnp = prot_name_list; vnp != NULL; vnp = vnp->next) {
    text_len += StringLen (vnp->data.ptrvalue) + 1;
  }
  prot_text = (CharPtr) MemNew (sizeof (Char) * text_len);
  prot_text[0] = 0;
  for (vnp = prot_name_list; vnp != NULL; vnp = vnp->next) {
    StringCat (prot_text, vnp->data.ptrvalue);
    if (vnp->next != NULL) {
      StringCat (prot_text, ";");
    }
  }
  return prot_text;
}


static Pointer BulkCDSSetProteinString (Pointer curr_val, ApplyValuePtr avp)
{
  CharPtr        curr_val_str;
  CharPtr        new_str, name_str, cp;
  ValNodePtr     name_list;

  name_list = (ValNodePtr) curr_val;
  curr_val_str = BulkCDSDisplayProtein (name_list);
  name_list = ValNodeFreeData (name_list);

  new_str = HandleApplyValue (curr_val_str, avp);

  name_str = new_str;
  cp = StringChr (name_str, ';');
  while (cp != NULL) {
    *cp = 0;
    ValNodeAddPointer (&name_list, 0, StringSave (name_str));
    *cp = ';';
    name_str = cp + 1;
    cp = StringChr (name_str, ';');
  }
  ValNodeAddPointer (&name_list, 0, StringSave (name_str));
  new_str = MemFree (new_str);

  return name_list;
}


static void BulkCDSFreeProtein (Pointer data)
{
  ValNodePtr prot_name_list = (ValNodePtr) data;
 
  ValNodeFreeData (prot_name_list);
}

static DialoG BulkCDSProteinDialog (GrouP g, CharPtr name, SeqEntryPtr sep)
{
  return CreateVisibleStringDialog (g, 3, -1, 25);
}

static void BulkCDSSetProteinDesc (Pointer target, Pointer data)
{
  SeqFeatXrefPtr xref;
  ProtRefPtr     prp = NULL;
  BioseqPtr      prot_bsp;
  SeqFeatPtr     prot_feat;
  SeqMgrFeatContext fcontext;
  CharPtr           prot_desc;
  SeqFeatPtr sfp = (SeqFeatPtr) target;
  if (sfp == NULL) return;

  prot_desc = (CharPtr) data;

  xref = sfp->xref;
  while (xref != NULL && xref->data.choice != SEQFEAT_PROT) {
    xref = xref->next;
  }
  if (xref != NULL) {
    prp = xref->data.value.ptrvalue;
  }

  if (prp == NULL) {
    if (sfp->data.choice == SEQFEAT_PROT) {
      prp = (ProtRefPtr) sfp->data.value.ptrvalue;
    } else if (sfp->data.choice == SEQFEAT_CDREGION) {
      prot_bsp = BioseqFindFromSeqLoc (sfp->product);
      prot_feat = SeqMgrGetNextFeature (prot_bsp, NULL, SEQFEAT_PROT, FEATDEF_PROT, &fcontext);
      if (prot_feat == NULL && !StringHasNoText (prot_desc)) {
        prot_feat = CreateNewFeatureOnBioseq (prot_bsp, SEQFEAT_PROT, NULL);
        prp = ProtRefNew ();
        prot_feat->data.value.ptrvalue = prp;
          SeqMgrIndexFeatures (prot_bsp->idx.entityID, NULL);
      }
      if (prot_feat != NULL) {
        prp = (ProtRefPtr) prot_feat->data.value.ptrvalue;
      }
    }
  }

  if (prp == NULL && !StringHasNoText (prot_desc)) {
    xref = SeqFeatXrefNew ();
    prp = ProtRefNew ();
    xref->data.choice = SEQFEAT_PROT;
    xref->data.value.ptrvalue = prp;
    xref->next = sfp->xref;
    sfp->xref = xref;
  }
  if (prp != NULL) {
    prp->desc = StringSave (prot_desc);
  }

}


static Pointer BulkCDSGetProteinDesc (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SeqFeatXrefPtr xref;
  ProtRefPtr     prp = NULL;
  BioseqPtr      prot_bsp;
  SeqFeatPtr     prot_feat;
  SeqMgrFeatContext fcontext;
  CharPtr           prot_desc = NULL;
  SeqFeatPtr sfp = (SeqFeatPtr) data;

  if (sfp == NULL) return NULL;

  xref = sfp->xref;
  while (xref != NULL && xref->data.choice != SEQFEAT_PROT) {
    xref = xref->next;
  }
  if (xref != NULL) {
    prp = xref->data.value.ptrvalue;
  }

  if (prp == NULL) {
    if (sfp->data.choice == SEQFEAT_PROT) {
      prp = (ProtRefPtr) sfp->data.value.ptrvalue;
    } else if (sfp->data.choice == SEQFEAT_CDREGION) {
      prot_bsp = BioseqFindFromSeqLoc (sfp->product);
      prot_feat = SeqMgrGetNextFeature (prot_bsp, NULL, SEQFEAT_PROT, FEATDEF_PROT, &fcontext);
      if (prot_feat != NULL) {
        prp = (ProtRefPtr) prot_feat->data.value.ptrvalue;
      }
    }
  }

  if (prp != NULL) {
    prot_desc = StringSave (prp->desc);
  }
    
  return prot_desc;
}


static void BulkGeneSetLocus (Pointer target, Pointer data)
{
  CharPtr    str = (CharPtr) data;
  GeneRefPtr grp;
  SeqFeatPtr sfp = (SeqFeatPtr) target;

  if (sfp == NULL || sfp->idx.subtype != FEATDEF_GENE) return;

  grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  if (grp == NULL) {
    grp = GeneRefNew();
    sfp->data.value.ptrvalue = grp;
  }
  grp->locus = MemFree (grp->locus);
  if (str != NULL) {
    grp->locus = StringSave (str);
  }
}

static Pointer BulkGeneGetLocus (Uint1 data_choice, Pointer data, Pointer metadata)
{
  GeneRefPtr grp; 
  SeqFeatPtr sfp = (SeqFeatPtr) data;

  if (sfp == NULL || sfp->idx.subtype != FEATDEF_GENE || sfp->data.value.ptrvalue == NULL) {
    return NULL;
  }
  grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  if (StringHasNoText (grp->locus)) {
    return NULL;
  } else {
    return (Pointer) StringSave (grp->locus);
  }
}

static void BulkGeneSetLocusTag (Pointer target, Pointer data)
{
  CharPtr    str = (CharPtr) data;
  GeneRefPtr grp;
  SeqFeatPtr sfp = (SeqFeatPtr) target;

  if (sfp == NULL || sfp->idx.subtype != FEATDEF_GENE) return;

  grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  if (grp == NULL) {
    grp = GeneRefNew();
    sfp->data.value.ptrvalue = grp;
  }
  grp->locus_tag = MemFree (grp->locus_tag);
  if (str != NULL) {
    grp->locus_tag = StringSave (str);
  }
}

static Pointer BulkGeneGetLocusTag (Uint1 data_choice, Pointer data, Pointer metadata)
{
  GeneRefPtr grp;
  SeqFeatPtr sfp = (SeqFeatPtr) data;

  if (sfp == NULL || sfp->idx.subtype != FEATDEF_GENE || sfp->data.value.ptrvalue == NULL) {
    return NULL;
  }
  grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  if (StringHasNoText (grp->locus_tag)) {
    return NULL;
  } else {
    return (Pointer) StringSave (grp->locus_tag);
  }
}

static void BulkGeneSetDescription (Pointer target, Pointer data)
{
  CharPtr    str = (CharPtr) data;
  GeneRefPtr grp;
  SeqFeatPtr sfp = (SeqFeatPtr) target;

  if (sfp == NULL || sfp->idx.subtype != FEATDEF_GENE) return;

  grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  if (grp == NULL) {
    grp = GeneRefNew();
    sfp->data.value.ptrvalue = grp;
  }
  grp->desc = MemFree (grp->desc);
  if (str != NULL) {
    grp->desc = StringSave (str);
  }
}

static Pointer BulkGeneGetDescription (Uint1 data_choice, Pointer data, Pointer metadata)
{
  GeneRefPtr grp;
  SeqFeatPtr sfp = (SeqFeatPtr) data;

  if (sfp == NULL || sfp->idx.subtype != FEATDEF_GENE || sfp->data.value.ptrvalue == NULL) {
    return NULL;
  }
  grp = (GeneRefPtr) sfp->data.value.ptrvalue;
  if (StringHasNoText (grp->desc)) {
    return NULL;
  } else {
    return (Pointer) StringSave (grp->desc);
  }
}


static void BulkRNASetProduct (Pointer target, Pointer data)
{
  RnaRefPtr  rrp;
  CharPtr    rna_product;
  SeqFeatPtr sfp = (SeqFeatPtr) target;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA) return;

  rna_product = (CharPtr) data;

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (rrp == NULL && !StringHasNoText (rna_product)) {
    rrp = RnaRefNew ();
    rrp->ext.choice = 1;
    sfp->data.value.ptrvalue = rrp;
  }
  if (rrp == NULL) return;
  if (rrp->ext.choice == 0 && !StringHasNoText (rna_product)) {
    rrp->ext.choice = 1;
  } else if (rrp->ext.choice == 1 && StringHasNoText (rna_product)) {
    rrp->ext.choice = 0;
    rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
  }
 
  if (rrp->ext.choice == 1) {    
    rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
    rrp->ext.value.ptrvalue = StringSave (rna_product);
  }
}

static Pointer BulkRNAGetProduct (Uint1 data_choice, Pointer data, Pointer metadata)
{
  RnaRefPtr  rrp;
  CharPtr    rna_product = NULL;
  SeqFeatPtr sfp = (SeqFeatPtr) data;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA) return NULL;

  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (rrp != NULL) {
    if (rrp->ext.choice == 1) {
      rna_product = StringSave( rrp->ext.value.ptrvalue);
    }
  }

    
  return rna_product;
}

static Boolean IsBulkEditableRNA (SeqFeatPtr sfp)
{
  RnaRefPtr rrp;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA) {
    return FALSE;
  }
  rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
  if (rrp != NULL && rrp->ext.choice != 1 && rrp->ext.choice != 0) {
    return FALSE;
  } else {
    return TRUE;
  }
}


/* template for setting simple text strings */
NLM_EXTERN Pointer BulkSetSimpleTextString (Pointer curr_val, ApplyValuePtr avp)
{
  CharPtr        curr_val_str;

  curr_val_str = (CharPtr)curr_val;
  return HandleApplyValue (curr_val_str, avp);
}

/* for now, disallow pseudo editing and display.  To add back, put in this line:
  { "pseudo", BulkSetPseudo, BulkGetPseudo, NULL, NULL, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, BulkReleaseTrueFalse },
*/
/* for now, disallow location editing (but continue display).  To add back, use BulkNucLocationDialog in dialog. */
static BulkEdFieldData bulk_cds_fields[] = {
  { "protein name                     ", BulkCDSSetProtein, BulkCDSSetProteinString, BulkCDSGetProtein, BulkCDSDisplayProtein, BulkCDSFreeProtein, BulkCDSProteinDialog, BulkCDSFormatProtein, NULL, NULL, BulkCDSCopyProtein },
  { "protein description", BulkCDSSetProteinDesc, BulkSetSimpleTextString, BulkCDSGetProteinDesc, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "location", BulkSetLocation, NULL, BulkGetLocation, BulkDisplayLocation, BulkFreeLocation, NULL, BulkFormatLocation, NULL, NULL, BulkCopyLocation },
  { "comment", BulkSetComment, BulkSetSimpleTextString, BulkGetComment, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextScrollDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

static BulkEdFieldData bulk_gene_fields[] = {
  { "locus", BulkGeneSetLocus, BulkSetSimpleTextString, BulkGeneGetLocus, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "locus_tag", BulkGeneSetLocusTag, BulkSetSimpleTextString, BulkGeneGetLocusTag, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "description", BulkGeneSetDescription, BulkSetSimpleTextString, BulkGeneGetDescription, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "location", BulkSetLocation, NULL, BulkGetLocation, BulkDisplayLocation, BulkFreeLocation, NULL, BulkFormatLocation, NULL, NULL, BulkCopyLocation },
  { "comment", BulkSetComment, BulkSetSimpleTextString, BulkGetComment, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextScrollDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

static BulkEdFieldData bulk_rna_fields[] = {
  { "product", BulkRNASetProduct, BulkSetSimpleTextString, BulkRNAGetProduct, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "location", BulkSetLocation, NULL, BulkGetLocation, BulkDisplayLocation, BulkFreeLocation, NULL, BulkFormatLocation, NULL, NULL, BulkCopyLocation },
  { "comment", BulkSetComment, BulkSetSimpleTextString, BulkGetComment, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextScrollDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};


static BulkEdFieldPtr GetBulkEditorFieldListForFeature (SeqFeatPtr sfp)
{
  if (sfp == NULL) {
    return NULL;
  } else if (sfp->data.choice == SEQFEAT_CDREGION) {
    return bulk_cds_fields;
  } else if (sfp->data.choice == SEQFEAT_GENE) {
    return bulk_gene_fields;
  } else if (sfp->data.choice == SEQFEAT_RNA && IsBulkEditableRNA(sfp)) {
    /* Note - using FEATDEF_rRNA to represent all editable rRNA features */
    return bulk_rna_fields;
  } else {
    return NULL;
  }
}


static void BulkSrcSetTaxNameDesc (Pointer target, Pointer data)
{
  CharPtr      taxname;
  SeqDescrPtr  sdp = (SeqDescrPtr) target;
  BioSourcePtr biop;

  if (sdp == NULL || sdp->choice != Seq_descr_source || sdp->data.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sdp->data.ptrvalue;

  taxname = (CharPtr) data;

  if (biop->org == NULL) {
    biop->org = OrgRefNew();
  }
  SetTaxNameAndRemoveTaxRef (biop->org, StringSave (taxname)); 
}


NLM_EXTERN BioSourcePtr GetBioSourceFromObject (Uint1 choice, Pointer data);

static Pointer BulkSrcGetTaxNameDesc (Uint1 data_choice, Pointer data, Pointer metadata)
{
  CharPtr      taxname = NULL;
  BioSourcePtr biop = NULL;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  if (biop->org != NULL) {
    taxname = StringSave (biop->org->taxname);
  }

  return taxname;
}


static void BulkSrcSetTaxNameFeat (Pointer target, Pointer data)
{
  CharPtr      taxname;
  SeqFeatPtr   sfp = (SeqFeatPtr) target;
  BioSourcePtr biop;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || sfp->data.value.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sfp->data.value.ptrvalue;

  taxname = (CharPtr) data;

  if (biop->org == NULL) {
    biop->org = OrgRefNew();
  }
  SetTaxNameAndRemoveTaxRef (biop->org, StringSave (taxname)); 
}


static Pointer BulkSrcGetTaxNameFeat (Uint1 data_choice, Pointer data, Pointer metadata)
{
  CharPtr      taxname = NULL;
  SeqFeatPtr   sfp = (SeqFeatPtr) data;
  BioSourcePtr biop;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_BIOSRC || sfp->data.value.ptrvalue == NULL) return NULL;

  biop = (BioSourcePtr) sfp->data.value.ptrvalue;

  if (biop->org != NULL) {
    taxname = StringSave (biop->org->taxname);
  }

  return taxname;
}


static Pointer BulkSrcGetSubSrcModDesc (Uint1 data_choice, Pointer data, Uint2 mod_type)
{
  CharPtr      val = NULL;
  BioSourcePtr biop = NULL;
  SubSourcePtr ssp;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  ssp = biop->subtype;
  while (ssp != NULL && ssp->subtype != mod_type) {
    ssp = ssp->next;
  }
  if (ssp != NULL) {
    val = StringSave (ssp->name);
  }

  return val;
}


static void BulkSrcSetSubSrcModDesc (Pointer target, Pointer data, Uint2 mod_type)
{
  CharPtr      val;
  SeqDescrPtr  sdp = (SeqDescrPtr) target;
  BioSourcePtr biop;
  SubSourcePtr ssp, ssp_last = NULL;

  if (sdp == NULL || sdp->choice != Seq_descr_source || sdp->data.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sdp->data.ptrvalue;

  val = (CharPtr) data;

  ssp = biop->subtype;
  while (ssp != NULL && ssp->subtype != mod_type) {
    ssp_last = ssp;
    ssp = ssp->next;
  }
  if (ssp == NULL) {
    ssp = SubSourceNew();
    ssp->subtype = mod_type;
    if (ssp_last == NULL) {
      biop->subtype = ssp;
    } else {
      ssp_last->next = ssp;
    }
  }
  ssp->name = MemFree (ssp->name);
  ssp->name = StringSave (val);
}


#define BULK_SRC_GET_AND_SET_SUBSRC(Name,Type) \
static Pointer BulkSrcGet##Name (Uint1 data_choice, Pointer data, Pointer metadata) \
{ \
  return BulkSrcGetSubSrcModDesc(data_choice, data, SUBSRC_##Type); \
} \
static void BulkSrcSet##Name (Pointer target, Pointer data) \
{ \
  BulkSrcSetSubSrcModDesc(target, data, SUBSRC_##Type); \
}


static Pointer BulkSrcGetSubSrcModDescTF (Uint1 data_choice, Pointer data, Uint2 mod_type)
{
  CharPtr      val = NULL;
  BioSourcePtr biop = NULL;
  SubSourcePtr ssp;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  ssp = biop->subtype;
  while (ssp != NULL && ssp->subtype != mod_type) {
    ssp = ssp->next;
  }
  if (ssp != NULL) {
    val = StringSave ("TRUE");
  }

  return val;
}


static void BulkSrcSetSubSrcModDescTF (Pointer target, Pointer data, Uint2 mod_type)
{
  CharPtr      val;
  SeqDescrPtr  sdp = (SeqDescrPtr) target;
  BioSourcePtr biop;
  SubSourcePtr ssp, ssp_last = NULL;

  if (sdp == NULL || sdp->choice != Seq_descr_source || sdp->data.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sdp->data.ptrvalue;

  val = (CharPtr) data;

  ssp = biop->subtype;
  while (ssp != NULL && ssp->subtype != mod_type) {
    ssp_last = ssp;
    ssp = ssp->next;
  }
  if (StringHasNoText ((CharPtr)val)) {
    if (ssp != NULL) {
      if (ssp_last == NULL) {
        biop->subtype = ssp->next;
      } else {
        ssp_last->next = ssp->next;
      }
      ssp->next = NULL;
      ssp = SubSourceFree (ssp);
    }
  } else {
    if (ssp == NULL) {
      ssp = SubSourceNew();
      ssp->subtype = mod_type;
      if (ssp_last == NULL) {
        biop->subtype = ssp;
      } else {
        ssp_last->next = ssp;
      }
    }
    ssp->name = MemFree (ssp->name);
    ssp->name = StringSave (" ");
  }
}


#define BULK_SRC_GET_AND_SET_SUBSRC_TF(Name,Type) \
static Pointer BulkSrcGet##Name (Uint1 data_choice, Pointer data, Pointer metadata) \
{ \
  return BulkSrcGetSubSrcModDescTF(data_choice, data, SUBSRC_##Type); \
} \
static void BulkSrcSet##Name (Pointer target, Pointer data) \
{ \
  BulkSrcSetSubSrcModDescTF(target, data, SUBSRC_##Type); \
}


static Pointer BulkSrcGetOrgModDesc (Uint1 data_choice, Pointer data, Uint2 mod_type)
{
  CharPtr      val = NULL;
  BioSourcePtr biop = NULL;
  OrgModPtr    mod;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL || biop->org == NULL || biop->org->orgname == NULL) return NULL;

  mod = biop->org->orgname->mod;
  while (mod != NULL && mod->subtype != mod_type) {
    mod = mod->next;
  }
  if (mod != NULL) {
    val = StringSave (mod->subname);
  }

  return val;
}


static void BulkSrcSetOrgModDesc (Pointer target, Pointer data, Uint2 mod_type)
{
  CharPtr      val;
  SeqDescrPtr  sdp = (SeqDescrPtr) target;
  BioSourcePtr biop;
  OrgModPtr    mod, mod_last = NULL;

  if (sdp == NULL || sdp->choice != Seq_descr_source || sdp->data.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sdp->data.ptrvalue;

  val = (CharPtr) data;

  if (biop->org == NULL) {
    biop->org = OrgRefNew ();
  }
  if (biop->org->orgname == NULL) {
    biop->org->orgname = OrgNameNew ();
  }

  mod = biop->org->orgname->mod;
  while (mod != NULL && mod->subtype != mod_type) {
    mod_last = mod;
    mod = mod->next;
  }
  if (mod == NULL) {
    mod = OrgModNew();
    mod->subtype = mod_type;
    if (mod_last == NULL) {
      biop->org->orgname->mod = mod;
    } else {
      mod_last->next = mod;
    }
  }
  mod->subname = MemFree (mod->subname);
  mod->subname = StringSave (val);
}


#define BULK_SRC_GET_AND_SET_ORGMOD(Name,Type) \
static Pointer BulkSrcGet##Name (Uint1 data_choice, Pointer data, Pointer metadata) \
{ \
  return BulkSrcGetOrgModDesc(data_choice, data, ORGMOD_##Type); \
} \
static void BulkSrcSet##Name (Pointer target, Pointer data) \
{ \
  BulkSrcSetOrgModDesc(target, data, ORGMOD_##Type); \
}


static Pointer BulkSrcGetOrgModDescTF (Uint1 data_choice, Pointer data, Uint2 mod_type)
{
  CharPtr      val = NULL;
  BioSourcePtr biop = NULL;
  OrgModPtr    mod;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL || biop->org == NULL || biop->org->orgname == NULL) return NULL;

  mod = biop->org->orgname->mod;
  while (mod != NULL && mod->subtype != mod_type) {
    mod = mod->next;
  }
  if (mod != NULL) {
    val = StringSave ("TRUE");
  }

  return val;
}


static void BulkSrcSetOrgModDescTF (Pointer target, Pointer data, Uint2 mod_type)
{
  CharPtr      val;
  SeqDescrPtr  sdp = (SeqDescrPtr) target;
  BioSourcePtr biop;
  OrgModPtr    mod, mod_last = NULL;

  if (sdp == NULL || sdp->choice != Seq_descr_source || sdp->data.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sdp->data.ptrvalue;
  if (biop == NULL) return;

  val = (CharPtr) data;

  if (biop->org == NULL) {
    biop->org = OrgRefNew ();
  }
  if (biop->org->orgname == NULL) {
    biop->org->orgname = OrgNameNew ();
  }

  mod = biop->org->orgname->mod;
  while (mod != NULL && mod->subtype != mod_type) {
    mod = mod->next;
  }

  if (StringHasNoText ((CharPtr)val)) {
    /* remove */
    if (mod != NULL) {
      if (mod_last == NULL) {
        biop->org->orgname->mod = mod->next;
      } else {
        mod_last->next = mod->next;
      }
      mod->next = NULL;
      mod = OrgModFree (mod);
    }
  } else {
    /* set */
    if (mod == NULL) {
      mod = OrgModNew();
      mod->subtype = mod_type;
      if (mod_last == NULL) {
        biop->org->orgname->mod = mod;
      } else {
        mod_last->next = mod;
      }
    }
    mod->subname = MemFree (mod->subname);
    mod->subname = StringSave (" ");
  }
}


#define BULK_SRC_GET_AND_SET_ORGMOD_TF(Name,Type) \
static Pointer BulkSrcGet##Name (Uint1 data_choice, Pointer data, Pointer metadata) \
{ \
  return BulkSrcGetOrgModDescTF(data_choice, data, SUBSRC_##Type); \
} \
static void BulkSrcSet##Name (Pointer target, Pointer data) \
{ \
  BulkSrcSetOrgModDescTF(target, data, SUBSRC_##Type); \
}


BULK_SRC_GET_AND_SET_ORGMOD(Acronym,acronym)
BULK_SRC_GET_AND_SET_ORGMOD(Anamorph,anamorph)
BULK_SRC_GET_AND_SET_ORGMOD(Authority,authority)
BULK_SRC_GET_AND_SET_ORGMOD(Bio_material,bio_material)
BULK_SRC_GET_AND_SET_ORGMOD(Biotype,biotype)
BULK_SRC_GET_AND_SET_ORGMOD(Biovar,biovar)
BULK_SRC_GET_AND_SET_ORGMOD(Breed,breed)
BULK_SRC_GET_AND_SET_ORGMOD(Chemovar,chemovar)
BULK_SRC_GET_AND_SET_ORGMOD(Common,common)
BULK_SRC_GET_AND_SET_ORGMOD(Cultivar,cultivar)
BULK_SRC_GET_AND_SET_ORGMOD(Culture_collection,culture_collection)
BULK_SRC_GET_AND_SET_ORGMOD(Ecotype,ecotype)
BULK_SRC_GET_AND_SET_ORGMOD(Forma,forma)
BULK_SRC_GET_AND_SET_ORGMOD(Forma_specialis,forma_specialis)
BULK_SRC_GET_AND_SET_ORGMOD(Group,group)
BULK_SRC_GET_AND_SET_ORGMOD(Host,nat_host)
BULK_SRC_GET_AND_SET_ORGMOD(Isolate,isolate)
BULK_SRC_GET_AND_SET_ORGMOD(Metagenome_source,metagenome_source)
BULK_SRC_GET_AND_SET_ORGMOD(Pathovar,pathovar)
BULK_SRC_GET_AND_SET_ORGMOD(Serogroup,serogroup)
BULK_SRC_GET_AND_SET_ORGMOD(Serotype,serotype)
BULK_SRC_GET_AND_SET_ORGMOD(Serovar,serovar)
BULK_SRC_GET_AND_SET_ORGMOD(Specimen_voucher,specimen_voucher)
BULK_SRC_GET_AND_SET_ORGMOD(Strain,strain)
BULK_SRC_GET_AND_SET_ORGMOD(Subgroup,subgroup)
BULK_SRC_GET_AND_SET_ORGMOD(Sub_species,sub_species)
BULK_SRC_GET_AND_SET_ORGMOD(Substrain,substrain)
BULK_SRC_GET_AND_SET_ORGMOD(Subtype,subtype)
BULK_SRC_GET_AND_SET_ORGMOD(Synonym,synonym)
BULK_SRC_GET_AND_SET_ORGMOD(Teleomorph,teleomorph)
BULK_SRC_GET_AND_SET_ORGMOD(Type,type)
BULK_SRC_GET_AND_SET_ORGMOD(Variety,variety)
BULK_SRC_GET_AND_SET_ORGMOD(OrgModNote,other)
BULK_SRC_GET_AND_SET_SUBSRC(Cell_line,cell_line)
BULK_SRC_GET_AND_SET_SUBSRC(Cell_type,cell_type)
BULK_SRC_GET_AND_SET_SUBSRC(Chromosome,chromosome)
BULK_SRC_GET_AND_SET_SUBSRC(Clone,clone)
BULK_SRC_GET_AND_SET_SUBSRC(Clone_lib,clone_lib)
BULK_SRC_GET_AND_SET_SUBSRC(Collected_by,collected_by)
BULK_SRC_GET_AND_SET_SUBSRC(Collection_date,collection_date)
BULK_SRC_GET_AND_SET_SUBSRC(Country,country)
BULK_SRC_GET_AND_SET_SUBSRC(Dev_stage,dev_stage)
BULK_SRC_GET_AND_SET_SUBSRC(Endogenous_virus_name,endogenous_virus_name)
BULK_SRC_GET_AND_SET_SUBSRC_TF(Environmental_sample,environmental_sample)
BULK_SRC_GET_AND_SET_SUBSRC(Frequency,frequency)
BULK_SRC_GET_AND_SET_SUBSRC(Genotype,genotype)
BULK_SRC_GET_AND_SET_SUBSRC_TF(Germline,germline)
BULK_SRC_GET_AND_SET_SUBSRC(Haplogroup,haplogroup)
BULK_SRC_GET_AND_SET_SUBSRC(Haplotype,haplotype)
BULK_SRC_GET_AND_SET_SUBSRC(Identified_by,identified_by)
BULK_SRC_GET_AND_SET_SUBSRC(Isolation_source,isolation_source)
BULK_SRC_GET_AND_SET_SUBSRC(Lab_host,lab_host)
BULK_SRC_GET_AND_SET_SUBSRC(Lat_Lon,lat_lon)
BULK_SRC_GET_AND_SET_SUBSRC(Linkage_group,linkage_group)
BULK_SRC_GET_AND_SET_SUBSRC(Map,map)
BULK_SRC_GET_AND_SET_SUBSRC(Mating_type,mating_type)
BULK_SRC_GET_AND_SET_SUBSRC_TF(Metagenomic,metagenomic)
BULK_SRC_GET_AND_SET_SUBSRC(Plasmid_name,plasmid_name)
BULK_SRC_GET_AND_SET_SUBSRC(Pop_variant,pop_variant)
BULK_SRC_GET_AND_SET_SUBSRC_TF(Rearranged,rearranged)
BULK_SRC_GET_AND_SET_SUBSRC(Segment,segment)
BULK_SRC_GET_AND_SET_SUBSRC(Sex,sex)
BULK_SRC_GET_AND_SET_SUBSRC(Subclone,subclone)
BULK_SRC_GET_AND_SET_SUBSRC(Tissue_lib,tissue_lib)
BULK_SRC_GET_AND_SET_SUBSRC(Tissue_type,tissue_type)
BULK_SRC_GET_AND_SET_SUBSRC_TF(Transgenic,transgenic)
BULK_SRC_GET_AND_SET_SUBSRC(SubSrcNote,other)


#define BULK_SRC_SUBSRC_FIELD_TF(Name) \
 BulkSrcSet##Name, BulkSetSimpleTextString, BulkSrcGet##Name, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy


static ValNodePtr StringsToDbxrefList (ValNodePtr strings)
{
  ValNodePtr dbxref_list = NULL, vnp;
  DbtagPtr dbtag;

  for (vnp = strings; vnp != NULL; vnp = vnp->next)
  {
    dbtag = DbtagNew();
    SetDbtagString (dbtag, vnp->data.ptrvalue, ExistingTextOption_replace_old);
    ValNodeAddPointer (&dbxref_list, vnp->choice, dbtag);
  }
  return dbxref_list;
}


static ValNodePtr DbxrefListToStrings (ValNodePtr dbxref_list)
{
  ValNodePtr strings = NULL, vnp;
  DbtagPtr dbtag;

  for (vnp = dbxref_list; vnp != NULL; vnp = vnp->next)
  {
    dbtag = (DbtagPtr) vnp->data.ptrvalue;
    ValNodeAddPointer (&strings, vnp->choice, GetDbtagString (dbtag));
  }
  return strings;
}


static ValNodePtr FreeDbxrefList (ValNodePtr list)
{
  ValNodePtr vnp;
 
  for (vnp = list; vnp != NULL; vnp = vnp->next) {
    vnp->data.ptrvalue = DbtagFree (vnp->data.ptrvalue);
  }
  list = ValNodeFree (list);
  return list;
}




static Pointer BulkSrcGetDbxref (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SeqDescrPtr  sdp = (SeqDescrPtr) data;
  BioSourcePtr biop;
  ValNodePtr   dbxref_list = NULL;

  if (sdp == NULL || sdp->choice != Seq_descr_source 
      || (biop = (BioSourcePtr)sdp->data.ptrvalue) == NULL
      || biop->org == NULL) 
  {
    return NULL;
  }

  dbxref_list = DbxrefListToStrings (biop->org->db);
    
  return dbxref_list;
}


static void BulkSrcSetDbxref (Pointer target, Pointer data)
{
  ValNodePtr   dbxref_list;
  SeqDescrPtr  sdp = (SeqDescrPtr) target;
  BioSourcePtr biop;

  if (sdp == NULL || sdp->choice != Seq_descr_source || sdp->data.ptrvalue == NULL) return;

  biop = (BioSourcePtr) sdp->data.ptrvalue;
  if (biop == NULL) return;
  dbxref_list = (ValNodePtr) data;
  if (biop->org == NULL && dbxref_list == NULL) {
    return;
  }
  if (biop->org == NULL) {
    biop->org = OrgRefNew();
  }

  /* erase previous dbxrefs */
  biop->org->db = FreeDbxrefList (biop->org->db);
  biop->org->db = StringsToDbxrefList (dbxref_list);
}


static Int4 BulkSrcFormatDbxref (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = MAX (15, StringLen (name)) * MaxCharWidth();
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}


static CharPtr BulkSrcDisplayDbxref (Pointer data)
{
  ValNodePtr dbxref_list, vnp;
  CharPtr    dbxref_text;
  Int4       text_len = 1;

  if (data == NULL) return NULL;
  dbxref_list = (ValNodePtr) data;
  for (vnp = dbxref_list; vnp != NULL; vnp = vnp->next) {
    text_len += StringLen (vnp->data.ptrvalue) + 1;
  }
  dbxref_text = (CharPtr) MemNew (sizeof (Char) * text_len);
  dbxref_text[0] = 0;
  for (vnp = dbxref_list; vnp != NULL; vnp = vnp->next) {
    StringCat (dbxref_text, vnp->data.ptrvalue);
    if (vnp->next != NULL) {
      StringCat (dbxref_text, ";");
    }
  }
  return dbxref_text;
}


static Pointer BulkSrcSetDbxrefString (Pointer curr_val, ApplyValuePtr avp)
{
  CharPtr        curr_val_str;
  CharPtr        new_str, name_str, cp;
  ValNodePtr     name_list;

  name_list = (ValNodePtr) curr_val;
  curr_val_str = BulkSrcDisplayDbxref (name_list);
  name_list = ValNodeFreeData (name_list);

  new_str = HandleApplyValue (curr_val_str, avp);

  name_str = new_str;
  cp = StringChr (name_str, ';');
  while (cp != NULL) {
    *cp = 0;
    ValNodeAddPointer (&name_list, 0, StringSave (name_str));
    *cp = ';';
    name_str = cp + 1;
    cp = StringChr (name_str, ';');
  }
  ValNodeAddPointer (&name_list, 0, StringSave (name_str));
  new_str = MemFree (new_str);

  return name_list;
}


static void BulkSrcFreeDbxref (Pointer data)
{
  ValNodePtr prot_name_list = (ValNodePtr) data;
 
  ValNodeFreeData (prot_name_list);
}


static Pointer BulkSrcCopyDbxref (Pointer data)
{
  ValNodePtr new_list = NULL, list, vnp;
 
  list = (ValNodePtr) data;
  
  for (vnp = list; vnp != NULL; vnp = vnp->next) {
    ValNodeAddPointer (&new_list, vnp->choice, StringSave (vnp->data.ptrvalue));
  }
  return new_list;
}  


static DialoG BulkSrcDbxrefDialog (GrouP g, CharPtr name, SeqEntryPtr sep)
{
  return CreateVisibleStringDialog (g, 3, -1, 25);
}


static BulkEdFieldData bulk_src_desc_fields[] = {
  { "tax name", BulkSrcSetTaxNameDesc, BulkSetSimpleTextString, BulkSrcGetTaxNameDesc, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "acronym", BulkSrcSetAcronym, BulkSetSimpleTextString, BulkSrcGetAcronym, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "anamorph", BulkSrcSetAnamorph, BulkSetSimpleTextString, BulkSrcGetAnamorph, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "authority", BulkSrcSetAuthority, BulkSetSimpleTextString, BulkSrcGetAuthority, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "biotype", BulkSrcSetBiotype, BulkSetSimpleTextString, BulkSrcGetBiotype, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "biovar", BulkSrcSetBiovar, BulkSetSimpleTextString, BulkSrcGetBiovar, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "bio_material", BulkSrcSetBio_material, BulkSetSimpleTextString, BulkSrcGetBio_material, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "breed", BulkSrcSetBreed, BulkSetSimpleTextString, BulkSrcGetBreed, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "cell_line", BulkSrcSetCell_line, BulkSetSimpleTextString, BulkSrcGetCell_line, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "cell_type", BulkSrcSetCell_type, BulkSetSimpleTextString, BulkSrcGetCell_type, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "chemovar", BulkSrcSetChemovar, BulkSetSimpleTextString, BulkSrcGetChemovar, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "chromosome", BulkSrcSetChromosome, BulkSetSimpleTextString, BulkSrcGetChromosome, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "clone", BulkSrcSetClone, BulkSetSimpleTextString, BulkSrcGetClone, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "clone_lib", BulkSrcSetClone_lib, BulkSetSimpleTextString, BulkSrcGetClone_lib, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "collected_by", BulkSrcSetCollected_by, BulkSetSimpleTextString, BulkSrcGetCollected_by, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "collection_date", BulkSrcSetCollection_date, BulkSetSimpleTextString, BulkSrcGetCollection_date, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "common", BulkSrcSetCommon, BulkSetSimpleTextString, BulkSrcGetCommon, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "country", BulkSrcSetCountry, BulkSetSimpleTextString, BulkSrcGetCountry, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "cultivar", BulkSrcSetCultivar, BulkSetSimpleTextString, BulkSrcGetCultivar, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "culture_collection", BulkSrcSetCulture_collection, BulkSetSimpleTextString, BulkSrcGetCulture_collection, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "dbxref", BulkSrcSetDbxref, BulkSrcSetDbxrefString, BulkSrcGetDbxref, BulkSrcDisplayDbxref, BulkSrcFreeDbxref, BulkSrcDbxrefDialog, BulkSrcFormatDbxref, NULL, NULL, BulkSrcCopyDbxref },
  { "dev_stage", BulkSrcSetDev_stage, BulkSetSimpleTextString, BulkSrcGetDev_stage, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "ecotype", BulkSrcSetEcotype, BulkSetSimpleTextString, BulkSrcGetEcotype, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "endogenous_virus_name", BulkSrcSetEndogenous_virus_name, BulkSetSimpleTextString, BulkSrcGetEndogenous_virus_name, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "environmental-sample", BULK_SRC_SUBSRC_FIELD_TF(Environmental_sample) },
  { "forma", BulkSrcSetForma, BulkSetSimpleTextString, BulkSrcGetForma, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "forma_specialis", BulkSrcSetForma_specialis, BulkSetSimpleTextString, BulkSrcGetForma_specialis, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "frequency", BulkSrcSetFrequency, BulkSetSimpleTextString, BulkSrcGetFrequency, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "genotype", BulkSrcSetGenotype, BulkSetSimpleTextString, BulkSrcGetGenotype, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "germline", BULK_SRC_SUBSRC_FIELD_TF(Germline) },
  { "group", BulkSrcSetGroup, BulkSetSimpleTextString, BulkSrcGetGroup, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "haplogroup", BulkSrcSetHaplogroup, BulkSetSimpleTextString, BulkSrcGetHaplogroup, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "haplotype", BulkSrcSetHaplotype, BulkSetSimpleTextString, BulkSrcGetHaplotype, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "host", BulkSrcSetHost, BulkSetSimpleTextString, BulkSrcGetHost, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "identified_by", BulkSrcSetIdentified_by, BulkSetSimpleTextString, BulkSrcGetIdentified_by, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "isolate", BulkSrcSetIsolate, BulkSetSimpleTextString, BulkSrcGetIsolate, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "isolation_source", BulkSrcSetIsolation_source, BulkSetSimpleTextString, BulkSrcGetIsolation_source, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "lab_host", BulkSrcSetLab_host, BulkSetSimpleTextString, BulkSrcGetLab_host, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "lat_Lon", BulkSrcSetLat_Lon, BulkSetSimpleTextString, BulkSrcGetLat_Lon, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "linkage_group", BulkSrcSetLinkage_group, BulkSetSimpleTextString, BulkSrcGetLinkage_group, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "map", BulkSrcSetMap, BulkSetSimpleTextString, BulkSrcGetMap, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "mating_type", BulkSrcSetMating_type, BulkSetSimpleTextString, BulkSrcGetMating_type, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "metagenome_source", BulkSrcSetMetagenome_source, BulkSetSimpleTextString, BulkSrcGetMetagenome_source, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "metagenomic", BULK_SRC_SUBSRC_FIELD_TF(Metagenomic) },
  { "note (orgmod)", BulkSrcSetOrgModNote, BulkSetSimpleTextString, BulkSrcGetOrgModNote, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "note (subsrc)", BulkSrcSetSubSrcNote, BulkSetSimpleTextString, BulkSrcGetSubSrcNote, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "pathovar", BulkSrcSetPathovar, BulkSetSimpleTextString, BulkSrcGetPathovar, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "plasmid_name", BulkSrcSetPlasmid_name, BulkSetSimpleTextString, BulkSrcGetPlasmid_name, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "pop_variant", BulkSrcSetPop_variant, BulkSetSimpleTextString, BulkSrcGetPop_variant, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "rearranged", BULK_SRC_SUBSRC_FIELD_TF(Rearranged) } ,
  { "segment", BulkSrcSetSegment, BulkSetSimpleTextString, BulkSrcGetSegment, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "serogroup", BulkSrcSetSerogroup, BulkSetSimpleTextString, BulkSrcGetSerogroup, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "serotype", BulkSrcSetSerotype, BulkSetSimpleTextString, BulkSrcGetSerotype, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "serovar", BulkSrcSetSerovar, BulkSetSimpleTextString, BulkSrcGetSerovar, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "sex", BulkSrcSetSex, BulkSetSimpleTextString, BulkSrcGetSex, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "specimen_voucher", BulkSrcSetSpecimen_voucher, BulkSetSimpleTextString, BulkSrcGetSpecimen_voucher, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "strain", BulkSrcSetStrain, BulkSetSimpleTextString, BulkSrcGetStrain, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "subclone", BulkSrcSetSubclone, BulkSetSimpleTextString, BulkSrcGetSubclone, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "subgroup", BulkSrcSetSubgroup, BulkSetSimpleTextString, BulkSrcGetSubgroup, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "substrain", BulkSrcSetSubstrain, BulkSetSimpleTextString, BulkSrcGetSubstrain, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "subtype", BulkSrcSetSubtype, BulkSetSimpleTextString, BulkSrcGetSubtype, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "sub_species", BulkSrcSetSub_species, BulkSetSimpleTextString, BulkSrcGetSub_species, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "synonym", BulkSrcSetSynonym, BulkSetSimpleTextString, BulkSrcGetSynonym, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "teleomorph", BulkSrcSetTeleomorph, BulkSetSimpleTextString, BulkSrcGetTeleomorph, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "tissue_lib", BulkSrcSetTissue_lib, BulkSetSimpleTextString, BulkSrcGetTissue_lib, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "tissue_type", BulkSrcSetTissue_type, BulkSetSimpleTextString, BulkSrcGetTissue_type, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "transgenic", BULK_SRC_SUBSRC_FIELD_TF(Transgenic) },
  { "type", BulkSrcSetType, BulkSetSimpleTextString, BulkSrcGetType, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "variety", BulkSrcSetVariety, BulkSetSimpleTextString, BulkSrcGetVariety, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};


static BulkEdFieldData bulk_src_feat_fields[] = {
  { "tax name", BulkSrcSetTaxNameFeat, BulkSetSimpleTextString, BulkSrcGetTaxNameFeat, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

typedef struct bulkeditor {
  FORM_MESSAGE_BLOCK
  DialoG         bulk_ed;
  ButtoN         do_log;
  BulkEdFieldPtr field_list;
} BulkEditorData, PNTR BulkEditorPtr;

typedef struct collectfeat {
  Uint1 featdef;
  Uint1 featchoice;
  ValNodePtr feat_list;
} CollectFeatData, PNTR CollectFeatPtr;

static void CollectFeaturesForBulkEditor (BioseqPtr bsp, Pointer data)
{
  CollectFeatPtr cfp;
  SeqFeatPtr     sfp;
  SeqMgrFeatContext fcontext;

  if (bsp == NULL || data == NULL) return;
  cfp = (CollectFeatPtr) data;

  for (sfp = SeqMgrGetNextFeature (bsp, NULL, cfp->featchoice, cfp->featdef, &fcontext);
       sfp != NULL;
       sfp = SeqMgrGetNextFeature (bsp, sfp, cfp->featchoice, cfp->featdef, &fcontext)) {
    ValNodeAddPointer (&(cfp->feat_list), OBJ_SEQFEAT, sfp);
  }
}

static void AcceptBulkEditor (ButtoN b)
{
  BulkEditorPtr      bep;

  bep = (BulkEditorPtr) GetObjectExtra (b);
  if (bep == NULL) return;

  WatchCursor();
  Update();

  ApplyBulkEditorToObjectList (bep->bulk_ed, bep->do_log == NULL ? FALSE : GetStatus (bep->do_log));

  ObjMgrSetDirtyFlag (bep->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bep->input_entityID, 0, 0);
  ArrowCursor ();
  Update ();
  Remove (bep->form);
}

extern Uint1 GetSubtypeForBulkEdit (ValNodePtr feat_list)
{
  ValNodePtr vnp_check;
  SeqFeatPtr sfp;
  Uint1      subtype = FEATDEF_BAD;

  for (vnp_check = feat_list; vnp_check != NULL; vnp_check = vnp_check->next) {
    if (vnp_check->choice != OBJ_SEQFEAT) continue;
    sfp = vnp_check->data.ptrvalue;
    if (sfp->data.choice == SEQFEAT_RNA) {
      /* note - using FEATDEF_rRNA to represent all editable RNA features */
      if (!IsBulkEditableRNA(sfp)) {
        return FEATDEF_BAD;
      } else if (subtype == FEATDEF_BAD) {
        subtype = FEATDEF_rRNA;
      } else if (subtype != FEATDEF_rRNA) {
        return FEATDEF_BAD;
      }
    } else if (subtype == FEATDEF_BAD) {
      subtype = sfp->idx.subtype;
    } else if (subtype != sfp->idx.subtype) {
      return FEATDEF_BAD;
    }
  }
  return subtype;
}

static void ExportBulkEditorTableBtn (ButtoN b)
{
  BulkEditorPtr bep;

  bep = (BulkEditorPtr) GetObjectExtra (b);
  if (bep == NULL) {
    return;
  }

  ExportBulkEditorTable (bep->bulk_ed);

}


extern void BulkEditorFeatList (Uint2 entityID, ValNodePtr feat_list)
{
  GrouP              c, g;
  ButtoN             b;
  BulkEditorPtr      bep;
  GrouP              h;
  WindoW             w;
  SeqEntryPtr        sep;
  BulkEdFieldPtr     field_list = NULL;
  Uint1              subtype = 0;
  CharPtr            title = "Bulk Editor";

  if (feat_list == NULL) {
    Message (MSG_ERROR, "No features found!");
    return;
  }
  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  sep = GetTopSeqEntryForEntityID (entityID);

  subtype = GetSubtypeForBulkEdit (feat_list);
  if (subtype == FEATDEF_BAD) {
    Message (MSG_ERROR, "Can't bulk edit a list of mixed feature types!");
    return;
  }
  if (subtype == FEATDEF_CDS) {
    title = "Bulk CDS Editor";
    field_list = bulk_cds_fields;
  } else if (subtype == FEATDEF_GENE) {
    title = "Bulk Gene Editor";
    field_list = bulk_gene_fields;
  } else if (subtype == FEATDEF_rRNA) {
    title = "Bulk RNA Editor";
    /* Note - using FEATDEF_rRNA to represent all editable rRNA features */
    field_list = bulk_rna_fields;
  } else if (subtype == FEATDEF_BIOSRC) {
    title = "Bulk Src Feat Editor";
    field_list = bulk_src_feat_fields;
  } else {
    Message (MSG_ERROR, "No bulk editor for this feature type!");
    return;
  }

  bep = (BulkEditorPtr) MemNew (sizeof (BulkEditorData));
  if (bep == NULL)
    return;

  bep->field_list = field_list;

  w = FixedWindow (-50, -33, -10, -10, title,
           StdCloseWindowProc);
  SetObjectExtra (w, bep, StdCleanupFormProc); 
  bep->form = (ForM) w;
  bep->input_entityID = entityID;

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

  /* want: Document with columns for pseudo (when appropriate), location, fields */
  /* when user clicks on field, should be able to edit contents */
  
  bep->bulk_ed = CreateBulkEditorDialog (h, bep->field_list, feat_list, sep, TRUE, NULL, NULL);

  if (subtype == FEATDEF_CDS) {
    bep->do_log = CheckBox (h, "Log Changes", NULL);
  } else {
    bep->do_log = NULL;
  }

  g = HiddenGroup (h, 2, 0, NULL);
  b = PushButton (g, "Export Table", ExportBulkEditorTableBtn);
  SetObjectExtra (b, bep, NULL);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 3, 0, NULL);
  b = PushButton (c, "Accept", AcceptBulkEditor);
  SetObjectExtra (b, bep, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);

  /* Line things up nicely */

  AlignObjects (ALIGN_CENTER, (HANDLE) bep->bulk_ed,
                              (HANDLE) c, (HANDLE) g, 
                              (HANDLE) bep->do_log, NULL);


  /* Display the window now */

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


extern Uint1 GetSubtypeForBulkDescrEdit (ValNodePtr descr_list)
{
  ValNodePtr vnp_check;
  SeqDescrPtr sdp;
  Uint1      subtype = 0;

  for (vnp_check = descr_list; vnp_check != NULL; vnp_check = vnp_check->next) {
    if (vnp_check->choice != OBJ_SEQDESC) continue;
    sdp = vnp_check->data.ptrvalue;
    if (subtype == 0) {
      subtype = sdp->choice;
    } else if (sdp->choice != subtype) {
      return 0;
    }
  }
  return subtype;
}


extern void BulkEditorDescrList (Uint2 entityID, ValNodePtr descr_list)
{
  GrouP              c;
  ButtoN             b;
  BulkEditorPtr      bep;
  GrouP              h;
  WindoW             w;
  SeqEntryPtr        sep;
  BulkEdFieldPtr     field_list = NULL;
  Uint1              subtype = 0;

  if (descr_list == NULL) {
    Message (MSG_ERROR, "No descriptors found!");
    return;
  }
  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  sep = GetTopSeqEntryForEntityID (entityID);

  subtype = GetSubtypeForBulkDescrEdit (descr_list);
  if (subtype == 0) {
    Message (MSG_ERROR, "Can't bulk edit a list of mixed descriptor types!");
    return;
  }
  if (subtype == Seq_descr_source) {
    field_list = bulk_src_desc_fields;
  } else {
    Message (MSG_ERROR, "No bulk editor for this descriptor type!");
    return;
  }

  bep = (BulkEditorPtr) MemNew (sizeof (BulkEditorData));
  if (bep == NULL)
    return;

  bep->field_list = field_list;

  w = FixedWindow (-50, -33, -10, -10, "Bulk Src Editor",
           StdCloseWindowProc);
  SetObjectExtra (w, bep, StdCleanupFormProc); 
  bep->form = (ForM) w;
  bep->input_entityID = entityID;

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

  /* want: Document with columns for pseudo (when appropriate), location, fields */
  /* when user clicks on field, should be able to edit contents */
  
  bep->bulk_ed = CreateBulkEditorDialogEx (h, bep->field_list, descr_list, sep, TRUE, NULL, NULL, NULL, subtype == Seq_descr_source);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 3, 0, NULL);
  b = PushButton (c, "Accept", AcceptBulkEditor);
  SetObjectExtra (b, bep, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);

  /* Line things up nicely */

  AlignObjects (ALIGN_CENTER, (HANDLE) bep->bulk_ed,
                              (HANDLE) c, NULL);


  /* Display the window now */

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


NLM_EXTERN void BulkEditorObjectList (Uint2 entityID, CharPtr title, ValNodePtr feat_list, BulkEdFieldPtr field_list)
{
  GrouP              c;
  ButtoN             b;
  BulkEditorPtr      bep;
  GrouP              h;
  WindoW             w;
  SeqEntryPtr        sep;
  Uint1              subtype = 0;

  if (feat_list == NULL) {
    Message (MSG_ERROR, "No features found!");
    return;
  }
  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  sep = GetTopSeqEntryForEntityID (entityID);

  bep = (BulkEditorPtr) MemNew (sizeof (BulkEditorData));
  if (bep == NULL)
    return;

  bep->field_list = field_list;

  w = FixedWindow (-50, -33, -10, -10, title,
           StdCloseWindowProc);
  SetObjectExtra (w, bep, StdCleanupFormProc); 
  bep->form = (ForM) w;
  bep->input_entityID = entityID;

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

  /* want: Document with columns for pseudo (when appropriate), location, fields */
  /* when user clicks on field, should be able to edit contents */
  
  bep->bulk_ed = CreateBulkEditorDialog (h, bep->field_list, feat_list, sep, TRUE, NULL, NULL);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 3, 0, NULL);
  b = PushButton (c, "Accept", AcceptBulkEditor);
  SetObjectExtra (b, bep, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);

  /* Line things up nicely */

  AlignObjects (ALIGN_CENTER, (HANDLE) bep->bulk_ed,
                              (HANDLE) c, NULL);


  /* Display the window now */

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


static void BulkEditorBaseForm (BaseFormPtr bfp, Uint1 featchoice, Uint1 featdef)
{
  SeqEntryPtr        sep;
  CollectFeatData    cfd;
  BulkEdFieldPtr     field_list = NULL;

  if (bfp == NULL) return;

  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);

  if (featchoice == SEQFEAT_CDREGION || featdef == FEATDEF_CDS) {
    field_list = bulk_cds_fields;
  } else if (featchoice == SEQFEAT_GENE || featdef == FEATDEF_GENE) {
    field_list = bulk_gene_fields;
  } else if (featchoice == SEQFEAT_RNA) {
    field_list = bulk_rna_fields;
  } else {
    Message (MSG_ERROR, "No bulk editor for this feature type!");
    return;
  }

  cfd.featchoice = featchoice;
  cfd.featdef = featdef;
  cfd.feat_list = NULL;
  VisitBioseqsInSep (sep, &cfd, CollectFeaturesForBulkEditor);

  if (cfd.feat_list == NULL) {
    Message (MSG_ERROR, "No features found!");
    return;
  }

  BulkEditorFeatList (bfp->input_entityID, cfd.feat_list);
}

static void BulkEditor (IteM i, Uint1 featchoice, Uint1 featdef)
{
  BaseFormPtr        bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif

  BulkEditorBaseForm(bfp, featchoice, featdef);
}

NLM_EXTERN void BulkEditCDS (IteM i)
{
  BulkEditor (i, SEQFEAT_CDREGION, FEATDEF_CDS);
}


NLM_EXTERN void BulkEditCDSBaseForm (BaseFormPtr bfp)
{  
  BulkEditorBaseForm (bfp, SEQFEAT_CDREGION, FEATDEF_CDS);
}


extern void BulkEditGene (IteM i)
{
  BulkEditor (i, SEQFEAT_GENE, FEATDEF_GENE);
}

extern void BulkEditRNA (IteM i)
{
  BulkEditor (i, SEQFEAT_RNA, 0);
}


static void GetBulkEditSourceCallback (SeqDescrPtr sdp, Pointer data)
{
  if (sdp != NULL && sdp->choice == Seq_descr_source && data != NULL) {
    ValNodeAddPointer ((ValNodePtr PNTR) data, OBJ_SEQDESC, sdp);
  }
}


NLM_EXTERN void BulkEditSourceBaseForm (BaseFormPtr bfp)
{
  SeqEntryPtr  sep;
  ValNodePtr   list = NULL;

  if (bfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;
  VisitDescriptorsInSep (sep, &list, GetBulkEditSourceCallback);

  BulkEditorDescrList (bfp->input_entityID, list);
}


extern void BulkEditSource (IteM i)
{
  BaseFormPtr  bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  BulkEditSourceBaseForm (bfp);
}


static Pointer GetBarcodeTestBarcodeID (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || res->bsp == NULL) {
    return NULL;
  } else {
    return BarcodeTestBarcodeIdString (res->bsp);
  }
}

  
static Pointer GetBarcodeTestGenbankID (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || res->bsp == NULL) {
    return NULL;
  } else {
    return BarcodeTestGenbankIdString (res->bsp);
  }
}


static Pointer GetBarcodeTestLengthResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_Length]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestPrimersResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_Primers]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestCountryResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_Country]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestSpecimenVoucherResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_SpecimenVoucher]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestStructuredSpecimenVoucherResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_StructuredSpecimenVoucher]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestCollectionDateResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_CollectionDate]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestOrderAssignmentResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_OrderAssignment]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestLowTraceResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_LowTrace]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestFrameShiftResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res == NULL || !res->failed_tests[eBarcodeTest_FrameShift]) {
    return NULL;
  } else {
    return StringSave ("TRUE");
  }
}


static Pointer GetBarcodeTestPercentNsResult (Uint1 data_choice, Pointer data, Pointer metadata)
{
  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  Char txt[5];
  if (res == NULL || !res->failed_tests[eBarcodeTest_PercentN]) {
    return NULL;
  } else {
    sprintf (txt, "%.1f", res->n_percent);
    return StringSave (txt);
  }
}


static Pointer HasBarcodeKeyword (Uint1 data_choice, Pointer data, Pointer metadata)
{
  Boolean rval = FALSE;

  BarcodeTestResultsPtr res = (BarcodeTestResultsPtr) data;
  if (res != NULL && res->bsp != NULL) {
    rval = BioseqHasBarcodeKeyword(res->bsp);
  }
  if (rval) {
    return StringSave ("TRUE");
  } else {
    return NULL;
  }
}



static Int4 BarcodeFormatBarcodeID (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = MAX (21, StringLen (name)) * MaxCharWidth();
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}


static Int4 BarcodeFormatGenbankID (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = MAX (10, StringLen (name)) * MaxCharWidth();
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}


static Int4 BarcodeFormatPercentN (ColPtr col, CharPtr name)
{
  if (col == NULL) return 0;

  col->pixWidth = MAX (5, StringLen (name)) * MaxCharWidth();
  col->pixInset = 0;
  col->charWidth = 0;
  col->charInset = 0;
  col->font = NULL;
  col->just = 'l';
  col->wrap = 1;
  col->bar = 0;
  col->underline = 0;
  col->left = 0;
  return col->pixWidth;
}


static BulkEdFieldData barcode_test_fields[] = {
  { "Barcode ID", NULL, NULL, GetBarcodeTestBarcodeID, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BarcodeFormatBarcodeID, NULL, NULL, BulkSimpleTextCopy },
  { "Genbank Accession", NULL, NULL, GetBarcodeTestGenbankID, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BarcodeFormatGenbankID, NULL, NULL, BulkSimpleTextCopy },
  { "Length", NULL, NULL, GetBarcodeTestLengthResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Primers", NULL, NULL, GetBarcodeTestPrimersResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Country", NULL, NULL, GetBarcodeTestCountryResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Voucher", NULL, NULL, GetBarcodeTestSpecimenVoucherResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Structured Voucher", NULL, NULL, GetBarcodeTestStructuredSpecimenVoucherResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Percent Ns", NULL, NULL, GetBarcodeTestPercentNsResult, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BarcodeFormatPercentN, NULL, NULL, BulkSimpleTextCopy }, 
  { "Collection Date", NULL, NULL, GetBarcodeTestCollectionDateResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Order Assignment", NULL, NULL, GetBarcodeTestOrderAssignmentResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Low Trace", NULL, NULL, GetBarcodeTestLowTraceResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Frame Shift", NULL, NULL, GetBarcodeTestFrameShiftResult, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { "Has BARCODE Keyword", NULL, NULL, HasBarcodeKeyword, NULL, BulkFreeSimpleText, NULL, BulkFormatTrueFalse, BulkDrawTrueFalse, NULL, BulkSimpleTextCopy }, 
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

static void NavigateToBarcodeTestResultBioseq (ValNodePtr object, Pointer userdata)
{
  BaseFormPtr           bfp;
  BarcodeTestResultsPtr res;

  if (object == NULL) return;

  res = (BarcodeTestResultsPtr) object->data.ptrvalue;

  if (res == NULL || res->bsp == NULL) return;
  bfp = GetBaseFormForEntityID (res->bsp->idx.entityID);
  if (bfp != NULL) {
    Select (bfp->form);
    SetBioseqViewTargetByBioseq (bfp, res->bsp);
  }
}


extern DialoG BarcodeTestResultsDisplay (GrouP h, BarcodeTestConfigPtr cfg)
{
  return CreateBulkEditorDialog (h, barcode_test_fields, NULL, NULL, FALSE, NavigateToBarcodeTestResultBioseq, NULL);
}

static Pointer GetCurrentLatLon (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SubSourcePtr bad_ssp;
  BioSourcePtr biop;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  bad_ssp = FindBadLatLon (biop);
  if (bad_ssp == NULL) 
  {
    return NULL;
  }
  else
  {
    return StringSave (bad_ssp->name);
  }
}


static Pointer GetCorrectedLatLon (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SubSourcePtr bad_ssp;
  CharPtr      fix;
  BioSourcePtr biop;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  bad_ssp = FindBadLatLon (biop);
  if (bad_ssp == NULL) 
  {
    return NULL;
  }
  else
  {
    fix = FixLatLonFormat (bad_ssp->name);
    if (fix == NULL)
    {
      return StringSave ("Unable to autocorrect");
    }
    else
    {
      return fix;
    }
  }
}


static BulkEdFieldData latlon_fields[] = {
  { "Current Lat-lon", NULL, NULL, GetCurrentLatLon, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Suggested Correction", NULL, NULL, GetCorrectedLatLon, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

extern DialoG LatLonTestResultsDisplay (GrouP h)
{
  return CreateBulkEditorDialog (h, latlon_fields, NULL, NULL, FALSE, ScrollToDiscrepancyItem, EditDiscrepancyItem);
}


static BulkEdFieldData trim_sequence_fields[] = {
  { "Current PercentN", NULL, NULL, GetCurrentPercentN, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Bases to Remove", NULL, NULL, GetTrimRemove, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Remaining Bases", NULL, NULL, GetTrimRemaining, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

extern DialoG TrimSequenceResultsDisplay (GrouP h)
{
  return CreateBulkEditorDialog (h, trim_sequence_fields, NULL, NULL, FALSE, ScrollToTrimSequenceItem, NULL);
}


static void DefaultCheckTrimSequenceResults (BulkEditorRowPtr berp)
{
  ValNodePtr vnp;

  if (berp == NULL) {
    return;
  }
  while (berp->object_list != NULL) {
    if (berp->subrows == NULL) {
      vnp = berp->object_list;
      if (vnp != NULL) {
        if (TrimShouldDelete (vnp->choice, vnp->data.ptrvalue, NULL)) {
          berp->selected = FALSE;
        } else {
          berp->selected = TRUE;
        }
      }
    } else {
      DefaultCheckTrimSequenceResults (berp->subrows);
      berp->selected = AllBulkEditorRowsSelected (berp->subrows);
    }

    berp++;
  }
}


extern void DefaultCheckTrimSequenceResultsDisplay (DialoG d)
{
  BulkEditorDlgPtr    dlg;
  RecT                r;

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

  DefaultCheckTrimSequenceResults (dlg->row_list);
    
  UpdateCheckStatus (dlg);

  ObjectRect (dlg->doc, &r);
  InvalRect (&r);  
  Update ();
}


static BulkEdFieldData trim_sequence_end_fields[] = {
  { "Accession", NULL, NULL, GetTrimAccession, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Remove from 5'", NULL, NULL, GetTrim5, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Remove from 3'", NULL, NULL, GetTrim3, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

extern DialoG TrimSequenceEndDisplay (GrouP h)
{
  return CreateBulkEditorDialog (h, trim_sequence_end_fields, NULL, NULL, FALSE, ScrollToTrimSequenceItem, NULL);
}



static Pointer GetTaxName (Uint1 data_choice, Pointer data, Pointer metadata)
{
  TaxFixItemPtr t;

  if ((t = (TaxFixItemPtr) data) == NULL || t->orig_org == NULL || t->orig_org->taxname == NULL) {
    return NULL;
  } else {
    return StringSave (t->orig_org->taxname);
  }
}


extern Boolean IsTaxNameBad (OrgRefPtr org)
{
  Boolean rval = TRUE;
  T3DataPtr        tdp;
  Taxon3RequestPtr t3rq;
  Taxon3ReplyPtr   t3ry;
  T3ReplyPtr       trp;
  ValNodePtr       val;
  T3StatusFlagsPtr tfp;

  if (org == NULL) {
    return TRUE;
  }
  t3rq = CreateTaxon3Request (0, NULL, org);
  if (t3rq != NULL) {
    t3ry = Tax3SynchronousQuery (t3rq);
    Taxon3RequestFree (t3rq);
    if (t3ry != NULL) {
      for (trp = t3ry->reply; trp != NULL; trp = trp->next) {
        if (trp->choice == T3Reply_data) {
          tdp = (T3DataPtr) trp->data.ptrvalue;
          if (tdp != NULL) {
            for (tfp = tdp->status; tfp != NULL; tfp = tfp->next) {
              if (StringICmp (tfp->property, "is_species_level") == 0) {
                val = tfp->Value_value;
                if (val != NULL && val->choice == Value_value_bool) {
                  if (val->data.intvalue != 0) {
                    rval = FALSE;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  return rval;
}


static Pointer GetTaxNameCorrection (Uint1 data_choice, Pointer data, Pointer metadata)
{
  TaxFixItemPtr t;

  if ((t = (TaxFixItemPtr) data) == NULL || t->suggested_fix == NULL) {
    return NULL;
  } else {
    return StringSave (t->suggested_fix);
  }
}


static void TaxFixSetSuggestedFix (Pointer target, Pointer data)
{
  TaxFixItemPtr  t;

  t = (TaxFixItemPtr) target;
  t->suggested_fix = MemFree (t->suggested_fix);
  t->suggested_fix = StringSave((CharPtr) data);
}


static void ScrollToTaxFixItem (ValNodePtr vnp, Pointer userdata)
{
  TaxFixItemPtr t;
  ValNode vn;

  if (vnp != NULL && (t = (TaxFixItemPtr) vnp->data.ptrvalue) != NULL) {
    vn.choice = t->data_choice;
    vn.data.ptrvalue = t->data;
    vn.next = NULL;
    ScrollToDiscrepancyItem (&vn, NULL);
  }
}


static void EditTaxFixItem(ValNodePtr vnp, Pointer userdata)
{
  TaxFixItemPtr t;
  ValNode vn;

  if (vnp != NULL && (t = (TaxFixItemPtr) vnp->data.ptrvalue) != NULL) {
    vn.choice = t->data_choice;
    vn.data.ptrvalue = t->data;
    vn.next = NULL;
    EditDiscrepancyItem (&vn, NULL);
  }
}


static BulkEdFieldData taxfix_fields[] = {
  { "Tax Name", NULL, NULL, GetTaxName, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Suggested Correction", TaxFixSetSuggestedFix, BulkSetSimpleTextString, GetTaxNameCorrection, BulkDisplaySimpleText, BulkFreeSimpleText, BulkSimpleTextDialog, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

extern DialoG TaxFixDisplay (GrouP h)
{
  return CreateBulkEditorDialog (h, taxfix_fields, NULL, NULL, TRUE, ScrollToTaxFixItem, EditTaxFixItem);
}


static CharPtr GetSubSource (Uint1 data_choice, Pointer data, Uint1 subtype)
{
  SubSourcePtr ssp;
  BioSourcePtr biop;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  for (ssp = biop->subtype; ssp != NULL && ssp->subtype != subtype; ssp = ssp->next)
  {}

  if (ssp == NULL) 
  {
    return NULL;
  }
  else
  {
    return StringSave (ssp->name);
  }
}


static Pointer GetCountry (Uint1 data_choice, Pointer data, Pointer metadata)
{
  return GetSubSource (data_choice, data, SUBSRC_country);
}

static Pointer GetLatLon (Uint1 data_choice, Pointer data, Pointer metadata)
{
  return GetSubSource (data_choice, data, SUBSRC_lat_lon);
}


extern Pointer GetLatLonCountryCorrection (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SubSourcePtr ssp;
  BioSourcePtr biop;
  CharPtr      country = NULL, cp;
  FloatHi      lat, lon;
  Boolean      found_lat_lon = FALSE;
  CharPtr      msg = NULL;
  CharPtr      guess_fmt = "Lat_lon does not map to '%s', but may be in '%s'";
  CharPtr      guess;

  biop = GetBioSourceFromObject (data_choice, data);
  if (biop == NULL) return NULL;

  for (ssp = biop->subtype; ssp != NULL && (country == NULL || !found_lat_lon); ssp = ssp->next)
  {
    if (ssp->subtype == SUBSRC_country && !StringHasNoText (ssp->name))
    {
      country = StringSave (ssp->name);
    }
    else if (ssp->subtype == SUBSRC_lat_lon)
    {
      if (ParseLatLon (ssp->name, &lat, &lon))
      {
        found_lat_lon = TRUE;
      }
    }
  }

  cp = StringChr (country, ':');
  if (cp != NULL) 
  {
    *cp = 0;
  }

  if (country == NULL && !found_lat_lon)
  {
    msg = StringSave ("Country and lat-lon not specified");
  }
  else if (country == NULL) 
  {
    msg = StringSave ("Country not specified");
  }
  else if (!found_lat_lon)
  {
    msg = StringSave ("Lat-lon not specified.");
  }
  else if (!IsCountryInLatLonList (country)) 
  {
    msg = StringSave ("Country not in lat-lon list");
  }
  else if (TestLatLonForCountry (country, lat, lon))
  {
    msg = StringSave ("No conflict");
  }
  else if (TestLatLonForCountry (country, -lat, lon)) 
  {
    if (lat < 0.0) {
      msg = StringSave ("Latitude should be set to N (northern hemisphere)");
    } else {
      msg = StringSave ("Latitude should be set to S (southern hemisphere)");
    }
  } else if (TestLatLonForCountry (country, lat, -lon)) {
    if (lon < 0.0) {
      msg = StringSave ("Longitude should be set to E (eastern hemisphere)");
    } else {
      msg = StringSave ("Longitude should be set to W (western hemisphere)");
    }
  } else if (TestLatLonForCountry (country, lon, lat)) {
    msg = StringSave ("Latitude and longitude values appear to be exchanged");
  } else {
    guess = GuessCountryForLatLon (lat, lon);
    if (guess == NULL)
    {
      msg = StringSave ("Lat-lon does not map to country");
    }
    else
    {
      msg = (CharPtr) MemNew (sizeof (Char) * (StringLen (guess_fmt) + StringLen (country) + StringLen (guess)));
      sprintf (msg, guess_fmt, country, guess);
    }
  }
  return msg;
}

static BulkEdFieldData latloncountry_fields[] = {
  { "Lat-lon", NULL, NULL, GetLatLon, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Country", NULL, NULL, GetCountry, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Suggested Correction", NULL, NULL, GetLatLonCountryCorrection, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

extern DialoG LatLonCountryResultsDisplay (GrouP h)
{
  return CreateBulkEditorDialog (h, latloncountry_fields, NULL, NULL, FALSE, ScrollToDiscrepancyItem, EditDiscrepancyItem);
}


static Pointer GetCurrentSpecificHost (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SpecificHostFixPtr s = (SpecificHostFixPtr) data;

  if (s == NULL || StringHasNoText (s->bad_specific_host))
  {
    return NULL;
  } 
  else
  {
    return StringSave (s->bad_specific_host);
  }
}


static Pointer GetCorrectedSpecificHost (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SpecificHostFixPtr s = (SpecificHostFixPtr) data;
  CharPtr            fmt = "Unable to suggest correction for '%s'";
  CharPtr            rval = NULL;

  if (s == NULL)
  {
    return NULL;
  } 
  else if (StringHasNoText (s->new_taxname))
  {
    if (StringHasNoText (s->old_taxname)) {
      rval = StringSave ("Unable to suggest correction");
    } else {
      rval = (CharPtr) MemNew (sizeof (Char) * (StringLen (fmt) + StringLen (s->old_taxname)));
      sprintf (rval, fmt, s->old_taxname);
    }
  }
  else
  {
    rval = StringSave (s->bad_specific_host);
    FindReplaceString (&rval, s->old_taxname, s->new_taxname, TRUE, TRUE);
  }
  return rval;
}


static Pointer GetSpecificHostCorrectionCategory  (Uint1 data_choice, Pointer data, Pointer metadata)
{
  SpecificHostFixPtr s = (SpecificHostFixPtr) data;
  CharPtr            fmt = "Unable to suggest correction for '%s'";
  CharPtr            rval = NULL;

  if (s == NULL)
  {
    return NULL;
  } 
  else 
  {
    switch (s->fix_type) {
      case eSpecificHostFix_unrecognized:
        rval = StringSave ("Unrecognized");
        break;
      case eSpecificHostFix_spelling:
        rval = StringSave ("Spelling");
        break;
      case eSpecificHostFix_capitalization:
        rval = StringSave ("Capitalization");
        break;
      case eSpecificHostFix_truncation:
        rval = StringSave ("Truncation");
        break;
      case eSpecificHostFix_replacement:
        rval = StringSave ("Replacement");
        break;
      case eSpecificHostFix_ambiguous:
        rval = StringSave ("Ambiguous");
        break;
    }
  }
  return rval;
}


static void ScrollToSpecificHostFix (ValNodePtr vnp, Pointer userdata)
{
  SpecificHostFixPtr s;
  
  if (vnp == NULL || vnp->data.ptrvalue == NULL) return;

  s = (SpecificHostFixPtr) vnp->data.ptrvalue;
  ScrollToDiscrepancyItem (s->feat_or_desc, NULL);
}


static void EditSpecificHostFix (ValNodePtr vnp, Pointer userdata)
{
  SpecificHostFixPtr s;
  
  if (vnp == NULL || vnp->data.ptrvalue == NULL) return;

  s = (SpecificHostFixPtr) vnp->data.ptrvalue;
  EditDiscrepancyItem (s->feat_or_desc, NULL);
}


static BulkEdFieldData specifichost_fields[] = {
  { "Current Specific-host", NULL, NULL, GetCurrentSpecificHost, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Suggested Correction", NULL, NULL, GetCorrectedSpecificHost, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { "Category", NULL, NULL, GetSpecificHostCorrectionCategory, BulkDisplaySimpleText, BulkFreeSimpleText, NULL, BulkFormatSimpleText, NULL, NULL, BulkSimpleTextCopy },
  { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};

extern DialoG SpecificHostResultsDisplay (GrouP h)
{
  return CreateBulkEditorDialog (h, specifichost_fields, NULL, NULL, FALSE, ScrollToSpecificHostFix, EditSpecificHostFix);
}


