/*   sequin6.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:  sequin6.c
*
* Author:  Jonathan Kans
*
* Version Creation Date:   11/12/97
*
* $Revision: 6.426 $
*
* File Description: 
*
* Modifications:  
* --------------------------------------------------------------------------
* Date     Name        Description of modification
* -------  ----------  -----------------------------------------------------
*
*
* ==========================================================================
*/

#include "sequin.h"
#include <ncbilang.h>
#include <gather.h>
#include <asn2gnbp.h>
#include <bspview.h>
#include <import.h>
#include <objsub.h>
#include <explore.h>
#include <subutil.h>
#include <gbftdef.h>
#include <edutil.h>
#include <salpanel.h>
#include <seqpanel.h>
#include <biosrc.h>
#include <vsm.h>
#include <actutils.h>
#include <findrepl.h>
#define NLM_GENERATED_CODE_PROTO
#include <objmacro.h>
#include <macrodlg.h>
#include <macroapi.h>

#define NUMBER_OF_SUFFIXES    7

static ENUM_ALIST(name_suffix_alist)
  {" ",    0},
  {"Jr.",  1},
  {"Sr.",  2},
  {"II",   4},
  {"III",  5},
  {"IV",   6},
  {"V",    7},
  {"VI",   8},
END_ENUM_ALIST


#define PUBLICATION_PUBLISHED_FIELD 1
#define PUBLICATION_INPRESS_FIELD   2
#define PUBLICATION_UNPUB_FIELD     3


#define GENE_LOCUS_FIELD        1
#define GENE_DESCRIPTION_FIELD  2
#define GENE_ALLELE_FIELD       3
#define GENE_MAPLOC_FIELD       4
#define GENE_LOCUS_TAG_FIELD    5
#define GENE_SYNONYM_FIELD      6
#define GENE_COMMENT_FIELD      7

static ENUM_ALIST(gene_field_alist)
  {" ",                    0},
  {"Gene locus",           GENE_LOCUS_FIELD},
  {"Gene description",     GENE_DESCRIPTION_FIELD},
  {"Gene allele",          GENE_ALLELE_FIELD},
  {"Gene maploc",          GENE_MAPLOC_FIELD},
  {"Locus_tag",            GENE_LOCUS_TAG_FIELD},
  {"Gene synonym",         GENE_SYNONYM_FIELD},
  {"Gene comment",         GENE_COMMENT_FIELD},
END_ENUM_ALIST

#define CDS_COMMENT   1
#define CDS_GENE_XREF 2
#define CDS_DB_XREF   3

static ENUM_ALIST(cds_field_alist)
  {" ",                    0},
  {"CDS comment",          CDS_COMMENT},
  {"gene xref",            CDS_GENE_XREF},
  {"db_xref",              CDS_DB_XREF},
END_ENUM_ALIST

#define PROT_NAME_FIELD        1
#define PROT_DESCRIPTION_FIELD 2
#define PROT_ECNUM_FIELD       3
#define PROT_ACTIVITY_FIELD    4
#define PROT_COMMENT_FIELD     5

static ENUM_ALIST(prot_field_alist)
  {" ",                    0},
  {"Protein name",         PROT_NAME_FIELD},
  {"Protein description",  PROT_DESCRIPTION_FIELD},
  {"Protein E.C. number",  PROT_ECNUM_FIELD},
  {"Protein activity",     PROT_ACTIVITY_FIELD},
  {"Protein comment",      PROT_COMMENT_FIELD},
END_ENUM_ALIST

#define RNA_NAME_FIELD       1
#define RNA_COMMENT_FIELD    2
#define RNA_GENE_XREF_FIELD  3

static ENUM_ALIST(rnax_field_alist)
  {" ",                    0},
  {"RNA Name",             RNA_NAME_FIELD},
  {"RNA Comment",          RNA_COMMENT_FIELD},
  {"gene xref",            RNA_GENE_XREF_FIELD},
END_ENUM_ALIST

#define ORGREF_SCI_NAME_FIELD    1
#define ORGREF_COMMON_NAME_FIELD 2
#define ORGREF_LINEAGE_FIELD     3
#define ORGREF_DIVISION_FIELD    4

static ENUM_ALIST(orgref_field_alist)
  {" ",                    0},
  {"Scientific Name",      ORGREF_SCI_NAME_FIELD},
  {"Common Name",          ORGREF_COMMON_NAME_FIELD},
  {"Lineage",              ORGREF_LINEAGE_FIELD},
  {"Division",             ORGREF_DIVISION_FIELD},
END_ENUM_ALIST

#define IMPORT_GBQUAL_FIELD  1
#define IMPORT_COMMENT_FIELD 2

static ENUM_ALIST(impfeat_field_alist)
  {" ",        0},
  {"GBQual",   IMPORT_GBQUAL_FIELD},
  {"Comment",  IMPORT_COMMENT_FIELD},
END_ENUM_ALIST


#define NUM_SUBTARGET_POPUPS 12


static DialoG ImpFeatSelectDialog (GrouP g, Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  EnumFieldAssocPtr list, ap;
  ValNodePtr   choice_list = NULL;

  list = import_featdef_alist (FALSE, FALSE, FALSE);
  SortEnumFieldAssocPtrArray (list, CompareImpFeatEnumFieldAssoc);
  for (ap = list; ap->name != NULL; ap++) {
    if (ap == list) {
      ValNodeAddPointer (&choice_list, 0, StringSave ("All import features"));
    } else {
      ValNodeAddPointer (&choice_list, 0, StringSave (ap->name));
    }
  }
  return ValNodeSelectionDialog (g, choice_list, TALL_SELECTION_LIST,
                                                ValNodeStringName,
                                ValNodeSimpleDataFree, ValNodeStringCopy,
                                ValNodeChoiceMatch, "Import Feature Type",
                                change_notify, change_userdata, FALSE);
}

typedef enum {
  eFieldTypeGene = 0,
  eFieldTypeCDS,
  eFieldTypeProtein,
  eFieldTypeRNA,
  eFieldTypeBioSource,
  eFieldTypeOrgModSubSource,
  eFieldTypeImport,
  eFieldTypeDefline,
  eFieldTypeCommentDescriptor,
  eFieldTypeFeatureNote,
  eFieldTypePublication,
  eFieldType_Max
} SegregateFieldType;

static CharPtr field_type_names[] = {
  "Gene",
  "CDS",
  "Protein",
  "RNA",
  "BioSource",
  "OrgMod and SubSource",
  "Import Feature",
  "DefLine",
  "Comment Descriptor",
  "Feature Note",
  "Publication" };


typedef struct fieldsubfield {
  Int4 field;
  Int4 subfield;
  CharPtr impfeat_key;
  ValNodePtr subfield_list;
} FieldSubfieldData, PNTR FieldSubfieldPtr;


static FieldSubfieldPtr FieldSubfieldFree (FieldSubfieldPtr f)
{
  if (f != NULL) {
    f->subfield_list = ValNodeFree (f->subfield_list);
    f->impfeat_key = MemFree (f->impfeat_key);
    f = MemFree (f);
  }
  return f;
}


typedef struct fieldsubfielddlg {
  DIALOG_MESSAGE_BLOCK
  PopuP field_list;
  DialoG subfield_dlg[eFieldType_Max];
  DialoG impfeat_type;
  Boolean allowed_fields[eFieldType_Max];
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} FieldSubfieldDlgData, PNTR FieldSubfieldDlgPtr;


static Int4 FieldNumFromListVal (Int4 list_val, BoolPtr allowed_fields)
{
  Int4 i;
  Int4 field_num = -1;

  if (list_val < 1) return -1;

  for (i = 0; i < eFieldType_Max && field_num < 0; i++) {
    if (allowed_fields[i]) {
      if (list_val - 1 == i) {
        field_num = i;
      }
    } else {
      list_val++;
    }
  }
  return field_num;
}


static Int4 ListValFromFieldNum (Int4 field_num, BoolPtr allowed_fields)
{
  Int4 list_val = 0, i = 0;

  if (field_num < 0) return 0;

  while (i <= field_num) {
    if (allowed_fields[i]) {
      list_val++;
    }
    i++;
  }
  return list_val;
}


static void ChangeFieldType (PopuP p)
{
  FieldSubfieldDlgPtr dlg;
  Int4                list_val, field_num, i;

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

  list_val = GetValue (dlg->field_list);
  field_num = FieldNumFromListVal (list_val, dlg->allowed_fields);

  for (i = 0; i < eFieldType_Max; i++) {
    if (dlg->subfield_dlg[i] == NULL) continue;
    if (i == field_num) {
      Show (dlg->subfield_dlg[i]);
    } else {
      Hide (dlg->subfield_dlg[i]);
    }
  }
  if (field_num == eFieldTypeImport) {
    Show (dlg->impfeat_type);
  } else {
    Hide (dlg->impfeat_type);
  }
  if (dlg->change_notify != NULL) {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static void FieldSubfieldToDialog (DialoG d, Pointer data)
{
  FieldSubfieldDlgPtr dlg;
  FieldSubfieldPtr    f;
  Int4                list_val;
  ValNode             vn;

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

  f = (FieldSubfieldPtr) data;
  if (f == NULL || f->field < 0) {
    SetValue (dlg->field_list, 0);
  } else {
    list_val = ListValFromFieldNum (f->field, dlg->allowed_fields);
    SetValue (dlg->field_list, list_val);
    if (dlg->subfield_dlg[f->field] != NULL) {    
      if (list_val == eFieldTypeFeatureNote) {
        PointerToDialog (dlg->subfield_dlg[eFieldTypeFeatureNote], f->subfield_list);
      } else {
        vn.choice = f->subfield;
        vn.data.ptrvalue = NULL;
        vn.next = NULL;
        PointerToDialog (dlg->subfield_dlg[f->field], &vn);
        if (f->field == eFieldTypeImport) {
          vn.choice = 0;
          vn.data.ptrvalue = f->impfeat_key;
          PointerToDialog (dlg->impfeat_type, &vn);
        }
      }
    }
  }
  ChangeFieldType (dlg->field_list);
}


static Pointer DialogToFieldSubfield (DialoG d)
{
  FieldSubfieldDlgPtr dlg;
  FieldSubfieldPtr f;
  Int4             val;
  ValNodePtr       vnp;
  SourceQualDescPtr sqdp;

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

  f = (FieldSubfieldPtr) MemNew (sizeof (FieldSubfieldData));
  f->field = -1;
  f->subfield = -1;
  f->subfield_list = NULL;
  f->impfeat_key = NULL;

  val = GetValue (dlg->field_list);
  if (val > 0) {
    f->field = FieldNumFromListVal (val, dlg->allowed_fields);    
    if (dlg->subfield_dlg[f->field] == NULL) {
      f->subfield = 0;
    } else {
      if (f->field == eFieldTypeFeatureNote) {
        f->subfield_list = DialogToPointer (dlg->subfield_dlg[f->field]);
      } else {
        vnp = DialogToPointer (dlg->subfield_dlg[f->field]);
        if (vnp != NULL) {
          if (f->field == eFieldTypeOrgModSubSource) {
            sqdp = (SourceQualDescPtr) vnp->data.ptrvalue;
            if (sqdp != NULL) {
              f->subfield = sqdp->subtype;
              if (!sqdp->isOrgMod) {
                f->subfield += 1000;
              }
            }
          } else {
            f->subfield = vnp->choice;
            vnp = ValNodeFree (vnp);
          }
        }
        if (f->field == eFieldTypeImport) {
          vnp = DialogToPointer (dlg->impfeat_type);
          if (vnp != NULL) {    
            f->impfeat_key = StringSave (vnp->data.ptrvalue);
            vnp = ValNodeFree (vnp);
          }
        }
      }
    }
  }

  return f;
}


static ValNodePtr TestFieldSubfieldDialog (DialoG d)
{
  ValNodePtr          err_list = NULL;
  FieldSubfieldPtr    f;
  
  f = DialogToPointer (d);
  if (f == NULL || f->field < 0 
      || (f->subfield < 0 && f->subfield_list == NULL)
      || (f->field == eFieldTypeImport && f->impfeat_key == NULL)) {
    ValNodeAddPointer (&err_list, 0, "Must choose field");
  }
  f = FieldSubfieldFree (f);
  return err_list;  
}


static DialoG 
CreateFieldSubfieldDlg 
(GrouP h,
 BoolPtr allowed_fields,
 Nlm_ChangeNotifyProc     change_notify,
 Pointer                  change_userdata)
{
  FieldSubfieldDlgPtr dlg;
  GrouP               p, g, g2;
  Int4                i;
  ValNode             vn;
  
  dlg = (FieldSubfieldDlgPtr) MemNew (sizeof (FieldSubfieldDlgData));
  if (dlg == NULL)
  {
    return NULL;
  }
  
  p = HiddenGroup (h, 2, 0, NULL);
  SetObjectExtra (p, dlg, StdCleanupExtraProc);

  dlg->dialog = (DialoG) p;
  dlg->todialog = FieldSubfieldToDialog;
  dlg->fromdialog = DialogToFieldSubfield;
  dlg->testdialog = TestFieldSubfieldDialog;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  if (allowed_fields == NULL) {
    MemSet (dlg->allowed_fields, TRUE, sizeof (dlg->allowed_fields));
  } else {
    for (i = 0; i < eFieldType_Max; i++) {      
      dlg->allowed_fields[i] = allowed_fields[i];
    }
  }

  dlg->field_list = PopupList (p, TRUE, ChangeFieldType);
  SetObjectExtra (dlg->field_list, dlg, NULL);
  for (i = 0; i < eFieldType_Max; i++) {
    if (dlg->allowed_fields[i]) {
      PopupItem (dlg->field_list, field_type_names[i]);
    }
  }

  g = HiddenGroup (p, 0, 0, NULL);
  dlg->subfield_dlg[eFieldTypeGene] = EnumAssocSelectionDialog (g, gene_field_alist, 
                                                                NULL, FALSE, 
                                                                dlg->change_notify, 
                                                                dlg->change_userdata);
  dlg->subfield_dlg[eFieldTypeCDS] = EnumAssocSelectionDialog (g, cds_field_alist,
                                                                NULL, FALSE, 
                                                                dlg->change_notify, 
                                                                dlg->change_userdata);
  dlg->subfield_dlg[eFieldTypeProtein] = EnumAssocSelectionDialog (g, prot_field_alist,
                                                                NULL, FALSE, 
                                                                dlg->change_notify, 
                                                                dlg->change_userdata);
  dlg->subfield_dlg[eFieldTypeRNA] = EnumAssocSelectionDialog (g, rnax_field_alist,
                                                                NULL, FALSE, 
                                                                dlg->change_notify, 
                                                                dlg->change_userdata);
  dlg->subfield_dlg[eFieldTypeBioSource] = EnumAssocSelectionDialog (g, orgref_field_alist,
                                                                NULL, FALSE, 
                                                                dlg->change_notify, 
                                                                dlg->change_userdata);
  /* Set default BioSource field to scientific name */
  vn.choice = ORGREF_SCI_NAME_FIELD;
  vn.next = NULL;
  vn.data.ptrvalue = NULL;
  PointerToDialog (dlg->subfield_dlg[eFieldTypeBioSource], &vn);

  dlg->subfield_dlg[eFieldTypeOrgModSubSource] = SourceQualTypeSelectionDialog (g, FALSE,
                                                                                dlg->change_notify,
                                                                                dlg->change_userdata);
                                                                                  
  if (dlg->allowed_fields[eFieldTypeImport]) {
    g2 = HiddenGroup (g, 2, 0, NULL);
    dlg->impfeat_type = ImpFeatSelectDialog (g2, change_notify, change_userdata);
    Hide (dlg->impfeat_type);

    dlg->subfield_dlg[eFieldTypeImport] = EnumAssocSelectionDialog (g2, impfeat_field_alist,
                                                                NULL, FALSE, 
                                                                dlg->change_notify, 
                                                                dlg->change_userdata);
  }
  dlg->subfield_dlg[eFieldTypeFeatureNote] = FeatureSelectionDialog (g, TRUE, dlg->change_notify, dlg->change_userdata);

  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->subfield_dlg[eFieldTypeGene],
                              (HANDLE) dlg->subfield_dlg[eFieldTypeCDS],
                              (HANDLE) dlg->subfield_dlg[eFieldTypeProtein],
                              (HANDLE) dlg->subfield_dlg[eFieldTypeRNA],
                              (HANDLE) dlg->subfield_dlg[eFieldTypeBioSource],
                              (HANDLE) dlg->subfield_dlg[eFieldTypeOrgModSubSource],
                              (HANDLE) dlg->subfield_dlg[eFieldTypeFeatureNote],
                              (HANDLE) g2,
                              NULL);

  for (i = 0; i < eFieldType_Max; i++) {
    Hide (dlg->subfield_dlg[i]);
  }
  return (DialoG) p;
}


typedef struct convertformdata {
  FEATURE_FORM_BLOCK

  TexT               deleteText;
  CharPtr            deleteStr;
  GrouP              deleteLevel;
  Int2               deleteLevelInt;
  DialoG             target_dlg;
  Int2               impfeat_type;
  ButtoN             accept;
  ButtoN             leaveDlgUp;
  Int2               type;
  Int2               subtype;

  DialoG             textportion_dlg;
  TextPortionXPtr     textportion;
  Boolean            remove_inside;

  CharPtr            foundstr;
  GrouP              repeat_remove_grp;

  Boolean            isDirty;
  Boolean            repeat_remove;
  GrouP              ifNotFoundGroup;
  Int2               ifNotFound;
  BioseqSetPtr       target_set;
  Nlm_ChangeNotifyProc set_accept_proc;
  ValNodePtr         id_list;

} ConvertFormData, PNTR ConvertFormPtr;

static ConvertFormPtr ConvertFormNew (void)
{
  ConvertFormPtr cfp;

  cfp = (ConvertFormPtr) MemNew (sizeof (ConvertFormData));
  if (cfp == NULL) return NULL;
  MemSet (cfp, 0, sizeof (ConvertFormData));
  return cfp;
}


/* Values for ifNotFound field */

#define DO_NOTHING       2
#define REMOVE_ALL_TEXT  3


/*-------------------------------------------------------------------------*/
/*                                                                         */
/* SaveStringFromTextNoStripSpaces () -                                    */
/*                                                                         */
/*-------------------------------------------------------------------------*/

static CharPtr SaveStringFromTextNoStripSpaces (TexT t)

{
  size_t   len;
  CharPtr  str;

  len = TextLength (t);
  if (len > 0) {
    str = (CharPtr) MemNew(len + 1);
    if (str != NULL) {
      GetTitle (t, str, len + 1);
      return str;
    } else {
      return NULL;
    }
  } else {
    return NULL;
  }
}

static void ConvertFormTextCallback (TexT t)
{
  ConvertFormPtr cfp;

  cfp = (ConvertFormPtr) GetObjectExtra (t);
  if (cfp != NULL && cfp->set_accept_proc != NULL) {
    (cfp->set_accept_proc) (cfp);
  }
}


static void ChangeTargetFields (Pointer data)

{
  ConvertFormPtr  cfp;

  cfp = (ConvertFormPtr) data;
  if (cfp == NULL) return;

  if (cfp->set_accept_proc != NULL) {
    cfp->set_accept_proc (cfp);
  }
}


/*=========================================================================*/
/*                                                                         */
/* CheckForString () -- Searches for a given string with another string.   */
/*                                                                         */
/*=========================================================================*/

static Boolean CheckForString (CharPtr searchStr,
			       CharPtr sourceStr)
{
  if (NULL == SearchForString (sourceStr, searchStr, FALSE, FALSE))
    return FALSE;
  else
    return TRUE;
}

/*=========================================================================*/
/*                                                                         */
/* GeneRefPtr () -- Search a GeneRef feature for a given string.           */
/*                                                                         */
/*=========================================================================*/

static Boolean SearchGeneRef (GeneRefPtr     grp,
			      SeqFeatPtr     sfp,
			      ConvertFormPtr cfp)
{
  ValNodePtr vnp;

  /* Check parameters */

  if ((NULL == grp) || (NULL == sfp))
    return FALSE;

  /* Check each text field for the given string */

  switch (cfp->subtype) {
  case 1 :
    return CheckForString (cfp->deleteStr, grp->locus);
    break;
  case 2 :
    return CheckForString (cfp->deleteStr, grp->desc);
    break;
  case 3 :
    return CheckForString (cfp->deleteStr, grp->allele);
    break;
  case 4 :
    return CheckForString (cfp->deleteStr, grp->maploc);
    break;
  case 5 :
    return CheckForString (cfp->deleteStr, grp->locus_tag);
    break;
  case 6 :
    for (vnp = grp->syn; vnp != NULL; vnp = vnp->next) {
      if (TRUE == CheckForString (cfp->deleteStr,
				  vnp->data.ptrvalue))
	return TRUE;
    }
    return FALSE;
    break;
  case 7 :
    return CheckForString (cfp->deleteStr, sfp->comment);
    break;
  default :
    break;
  }

  return FALSE;
}

/*=========================================================================*/
/*                                                                         */
/* SearchCDSFeat () -- Search a CDS feature for a given string.            */
/*                                                                         */
/*=========================================================================*/

static Boolean SearchCDSFeat (SeqFeatPtr     sfp,
			      ConvertFormPtr cfp)
{

  /* Check parameters */

  if (NULL == sfp)
    return FALSE;

  /* Check each text field for the given string */

  switch (cfp->subtype) {
  case 1 :
    return CheckForString (cfp->deleteStr, sfp->comment);
    break;
  case 2 :
  default :
    break;
  }

  /* If no match found, return FALSE */

  return FALSE;
}

/*=========================================================================*/
/*                                                                         */
/* SearchRnaRef () -- Search an RnaRef feature for a given string.         */
/*                                                                         */
/*=========================================================================*/

static Boolean SearchRnaRef (RnaRefPtr      rrp,
			     SeqFeatPtr     sfp,
			     ConvertFormPtr cfp)
{
  /* Check parameters */

  if ((NULL == rrp) || (NULL == sfp))
    return FALSE;

  /* Check each text field for the given string */

  switch (cfp->subtype) {
  case 1 :
    if ((0 == rrp->ext.choice) || (1 == rrp->ext.choice)) {
      return CheckForString (cfp->deleteStr, rrp->ext.value.ptrvalue);
    }
    break;
  case 2 :
    return CheckForString (cfp->deleteStr, sfp->comment);
    break;
  case 3 :
  default :
    break;
  }

  /* If no match found, return FALSE */

  return FALSE;
}

/*=========================================================================*/
/*                                                                         */
/* SearchProtRef () -- Search a ProtRef feature for a given string.        */
/*                                                                         */
/*=========================================================================*/

static Boolean SearchProtRef (ProtRefPtr     prp,
			      SeqFeatPtr     sfp,
			      ConvertFormPtr cfp)
{
  ValNodePtr vnp;

  /* Check parameters */

  if (NULL == prp)
    return FALSE;

  /* Check each text field for the given string */

  switch (cfp->subtype) {

    /* Search the name field */  

  case 1 :
    for (vnp = prp->name; vnp != NULL; vnp = vnp->next) {
      if (TRUE == CheckForString (cfp->deleteStr,
				  (CharPtr) vnp->data.ptrvalue))
	return TRUE;
    }
    break;

    /* Search the desc field */

  case 2 :
    return CheckForString (cfp->deleteStr, prp->desc);
    break;

    /* Search the ec field */

  case 3 :
    for (vnp = prp->ec; vnp != NULL; vnp = vnp->next) {
      if (TRUE == CheckForString (cfp->deleteStr, vnp->data.ptrvalue))
	return TRUE;
    }
    break;

    /* Search the activity field */

  case 4 :
    for (vnp = prp->activity; vnp != NULL; vnp = vnp->next) {
      if (TRUE == CheckForString (cfp->deleteStr, vnp->data.ptrvalue))
	return TRUE;
    }
    break;

    /* Search the comment field */

  case 5 :
    return CheckForString (cfp->deleteStr, sfp->comment);
    break;

    /* Default check */

  default :
    break;
  }

  /* If we made it this far no match was found */

  return FALSE;
}

/*=========================================================================*/
/*                                                                         */
/* SearchImpFeat () -- Search an ImpFeat feature for a given string.       */
/*                                                                         */
/*=========================================================================*/

static Boolean SearchImpFeat (SeqFeatPtr     sfp,
			      ConvertFormPtr cfp)
{
  GBQualPtr       gbqp;

  /* Check parameters */

  if (NULL == sfp)
    return FALSE;

  switch (cfp->subtype) {

    /* Search the GB Quals */

  case IMPORT_GBQUAL_FIELD :
    gbqp = sfp->qual;
    while (NULL != gbqp) {
      if (NULL != gbqp->val)
	if (TRUE == CheckForString (cfp->deleteStr, gbqp->val))
	  return TRUE;
      gbqp = gbqp->next;
    }
    return FALSE;
    break;

    /* Search the comment field */

  case IMPORT_COMMENT_FIELD :
    return CheckForString (cfp->deleteStr, sfp->comment);
    break;
  default :
    break;
  }

  return FALSE;
}


static Boolean DoesBioSourceContainText 
(BioSourcePtr biop,
 ConvertFormPtr cfp)
{
  OrgRefPtr     orp;
  Boolean       found;
  OrgNamePtr    onp;

  if (biop == NULL || cfp == NULL) return FALSE;

  found = FALSE;

  orp = biop->org;
  if (orp == NULL) return FALSE;
  switch (cfp->subtype) {
    case ORGREF_SCI_NAME_FIELD :
      found = CheckForString (cfp->deleteStr, orp->taxname);
      break;
    case ORGREF_COMMON_NAME_FIELD :
	  found = CheckForString (cfp->deleteStr, orp->common);
      break;
    case ORGREF_LINEAGE_FIELD :
      onp = orp->orgname;
      if (onp == NULL) {
        onp = OrgNameNew ();
        orp->orgname = onp;
      }
      if (onp != NULL)
	    found = CheckForString (cfp->deleteStr, onp->lineage);
	  else
	    found = FALSE;
      break;
    case ORGREF_DIVISION_FIELD :
      onp = orp->orgname;
      if (onp == NULL) {
        onp = OrgNameNew ();
        orp->orgname = onp;
      }
      if (onp != NULL)
        found = CheckForString (cfp->deleteStr, onp->div);
	  else
	    found = FALSE;
      break;
    default :
      break;
  }
  return found;
}

static Boolean DoSubSourcesContainText 
(BioSourcePtr biop,
 ConvertFormPtr cfp)
{
  OrgRefPtr     orp;
  OrgModPtr     mod;
  SubSourcePtr  ssp;
  OrgNamePtr    onp;
  Boolean found = FALSE;

  if (biop == NULL || cfp == NULL) return FALSE;
  if (cfp->subtype < 1000) {
	orp = biop->org;
	if (orp == NULL) {
	  orp = OrgRefNew ();
	  biop->org = orp;
	}
	if (orp != NULL) {
	  onp = orp->orgname;
	  if (onp == NULL) {
	    onp = OrgNameNew ();
	    orp->orgname = onp;
	  }
	  if (onp != NULL) {
	    mod = onp->mod;
	    while (mod != NULL && mod->subtype != cfp->subtype) {
	      mod = mod->next;
	    }
	    if (mod != NULL)
	      found = CheckForString (cfp->deleteStr, mod->subname);
	    else
	      found = FALSE;
	  }
	  else
	    found = FALSE;
	}
  } else {
	ssp = biop->subtype;
	while (ssp != NULL && ssp->subtype != (cfp->subtype - 1000)) {
	  ssp = ssp->next;
	}
	while (ssp != NULL) {
	  if (ssp->subtype == (cfp->subtype - 1000)) {
	    found = CheckForString (cfp->deleteStr, ssp->name);
	    if (found)
	      break;
	  }
	  ssp = ssp->next;
	}
	if (NULL == ssp)
	  found = FALSE;
  }
  return found;
}


typedef Boolean (LIBCALLBACK *wantSegregateNucProtSetFunction) 
( BioseqSetPtr bssp,
  Pointer      userdata);

typedef Boolean (LIBCALLBACK *wantSegregateSequenceFunction) 
( BioseqPtr bsp,
  Pointer   userdata);
  
/*=========================================================================*/
/*                                                                         */
/* SegregateItemsRecursor () - Given a functions for determining which bioseqs     */
/*                     meet conditions, segregate bioseqs into separate    */
/*                     sets.                                               */
/*                                                                         */
/*=========================================================================*/

static void SegregateItemsRecursor 
(SeqEntryPtr                     seqlist,
 BioseqSetPtr                    set1,
 BioseqSetPtr                    set2,
 wantSegregateNucProtSetFunction do_np,
 wantSegregateSequenceFunction   do_seq,
 Pointer                         userdata
 )
{
  
  BioseqPtr         bsp;
  BioseqSetPtr      this_bssp;
  SeqEntryPtr       this_list;
  SeqEntryPtr       sep, next_sep;
  SeqEntryPtr       set1last, set2last;


  if (set1 == NULL || set2 == NULL || seqlist == NULL)
    return;

  set1last = set1->seq_set;
  while (set1last != NULL && set1last->next != NULL) {
    set1last = set1last->next;
  }
  set2last = set2->seq_set;
  while (set2last != NULL && set2last->next != NULL) {
    set2last = set2last->next;
  }

  sep = seqlist;
  while (sep != NULL) {
    next_sep = sep->next;
    if (IS_Bioseq_set (sep)) {
      this_bssp = (BioseqSetPtr) sep->data.ptrvalue;
      if (this_bssp->_class == BioseqseqSet_class_nuc_prot) {
        if (do_np != NULL && do_np (this_bssp, userdata)) {
          if (set2last == NULL) {
            set2->seq_set = sep;
          } else {
            set2last->next = sep;
          }
          set2last = sep;
        } else {
          if (set1last == NULL) {
            set1->seq_set = sep;
          } else {
            set1last->next = sep;
          }
          set1last = sep;
        }
        sep->next = NULL;
      } else {
        /* copy class types from this set if class types are not set */
        if (set1->_class == BioseqseqSet_class_genbank 
            && set2->_class == BioseqseqSet_class_genbank) {
          set1->_class = this_bssp->_class;
          set2->_class = this_bssp->_class;
        }
        /* copy descriptors from this set */
        if (this_bssp != set1) {
          ValNodeLink (&(set1->descr),
                       AsnIoMemCopy ((Pointer) this_bssp->descr,
                                     (AsnReadFunc) SeqDescrAsnRead,
                                     (AsnWriteFunc) SeqDescrAsnWrite));
        }
        if (this_bssp != set2) {
          ValNodeLink (&(set2->descr),
                       AsnIoMemCopy ((Pointer) this_bssp->descr,
                                     (AsnReadFunc) SeqDescrAsnRead,
                                     (AsnWriteFunc) SeqDescrAsnWrite));
        }
        if (this_bssp != set1 && this_bssp != set2) {
          this_bssp->descr = SeqDescrFree (this_bssp->descr);
        }
        
        this_list = this_bssp->seq_set;
        this_bssp->seq_set = NULL;
        SegregateItemsRecursor (this_list, set1, set2, do_np, do_seq, userdata);
      }
    } else if (IS_Bioseq (sep)) {
      bsp = (BioseqPtr) sep->data.ptrvalue;
      if (do_seq != NULL && do_seq (bsp, userdata)) {
        if (set2last == NULL) {
          set2->seq_set = sep;
        } else {
          set2last->next = sep;
        }
        set2last = sep;
      } else {
        if (set1last == NULL) {
          set1->seq_set = sep;
        } else {
          set1last->next = sep;
        }
        set1last = sep;
      }
      sep->next = NULL;
    }
    sep = next_sep;
  }
}


static Boolean DoFeaturesInAnnotContainText (SeqAnnotPtr sap, ConvertFormPtr cfp)
{
  SeqFeatPtr     sfp;
  GeneRefPtr     grp;
  ProtRefPtr     prp;
  RnaRefPtr      rrp;
  Boolean        found = FALSE;
  ValNodePtr     vnp;
  FieldSubfieldPtr f;
  
  if (sap == NULL || cfp == NULL) return FALSE;
  
  /* Search the requested item for the given string */

  while (sap != NULL && !found) {
    if (sap->type == 1) {
      sfp = (SeqFeatPtr) sap->data;
      while (sfp != NULL && !found) {
        if (sfp->data.choice == SEQFEAT_GENE && cfp->type == eFieldTypeGene) {
          grp = (GeneRefPtr) sfp->data.value.ptrvalue;
	      found = SearchGeneRef (grp, sfp, cfp);
        } else if (sfp->data.choice == SEQFEAT_CDREGION && cfp->type == eFieldTypeCDS) {
	      found = SearchCDSFeat (sfp, cfp);
        } else if (sfp->data.choice == SEQFEAT_PROT && cfp->type == eFieldTypeProtein) {
          prp = (ProtRefPtr) sfp->data.value.ptrvalue;
	      found = SearchProtRef (prp, sfp, cfp);
        } else if (sfp->data.choice == SEQFEAT_RNA && cfp->type == eFieldTypeRNA) {
          rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
	      found = SearchRnaRef (rrp, sfp, cfp);
        } else if (sfp->data.choice == SEQFEAT_IMP &&
		      cfp->type == eFieldTypeImport) {
	      found = SearchImpFeat (sfp, cfp);
        } else if (cfp->type == eFieldTypeFeatureNote) {
          f = DialogToPointer (cfp->target_dlg);
          if (f != NULL) {
            for (vnp = f->subfield_list; vnp != NULL; vnp = vnp->next) {
              if (vnp->choice == sfp->idx.subtype 
                  && CheckForString (cfp->deleteStr, sfp->comment)) {
                found = TRUE;
              }
            }
            f = FieldSubfieldFree (f);
          }
        }
        sfp = sfp->next;
      }
    }
    sap = sap->next;
  }
  return found;  
}

static Boolean DoFeaturesContainText_Callback 
(BioseqPtr      bsp,
 ConvertFormPtr cfp)
{
  
  SeqAnnotPtr    sap;

  /* Check parameters */

  if (bsp == NULL || cfp == NULL)
    return FALSE;

  /* Get the list of annotations */

  sap = bsp->annot;

  /* Search the requested item for the given string */

  return DoFeaturesInAnnotContainText (sap, cfp);
}

typedef struct objstringdata 
{
  CharPtr match;
  Boolean found;	
} ObjStringData, PNTR ObjStringPtr;

static void LIBCALLBACK AsnWriteRemoveForDCallBack (AsnExpOptStructPtr pAEOS)

{
  CharPtr        pchFind;
  CharPtr        pchSource;
  ObjStringPtr   osp;

  osp = (ObjStringPtr) pAEOS->data;
  if (ISA_STRINGTYPE (AsnFindBaseIsa (pAEOS->atp))) {
	pchSource = (CharPtr) pAEOS->dvp->ptrvalue;
	pchFind = osp->match;
	if (StringSearch (pchSource, pchFind) != NULL) {
	  osp->found = TRUE;
	}
  }
}

static Boolean ObjectHasSubstring (ObjMgrTypePtr omtp, AsnIoPtr aip, Pointer ptr, ObjStringPtr osp)

{
  osp->found = FALSE;
  (omtp->asnwrite) (ptr, aip, NULL);
  return osp->found;
}

static Uint1 GetPubStatus (PubdescPtr pdp)
{
  ValNodePtr vnp;
  CitGenPtr  cgp;
  CitArtPtr  cap;
  CitJourPtr cjp;
  CitBookPtr cbp;
  CitSubPtr  csp;
  MedlineEntryPtr mlp;
  ImprintPtr ip = NULL;
  Uint1      status = 255; /* 255 is currently not a valid status */
  
  if (pdp == NULL) return status;
  
  for (vnp = pdp->pub; vnp != NULL && ip == NULL; vnp = vnp->next)
  {
  	switch (vnp->choice)
  	{
  	  case PUB_Gen:
        cgp = (CitGenPtr) vnp->data.ptrvalue;
  	    if (cgp != NULL && StringICmp (cgp->cit, "Unpublished"))
  	    {
  	  	  return PUB_STATUS_UNPUBLISHED;
  	    }
  	    break;
  	  case PUB_Article:
  	  case PUB_Medline:
  	    if (vnp->choice == PUB_Article)
  	    {
  	      cap = (CitArtPtr) vnp->data.ptrvalue;
  	    }
  	    else
  	    {
  	      cap = NULL;
  	      mlp = (MedlineEntryPtr) vnp->data.ptrvalue;
  	      if (mlp != NULL)
  	      {
  	        cap = mlp->cit;
  	      }
  	    }
  	    if (cap != NULL && cap->from == 1)
  	    {
  	      cjp = (CitJourPtr) cap->fromptr;
  	      if (cjp != NULL)
  	      {
  	      	ip = cjp->imp;
  	      }
  	    }
  	    break;
  	  case PUB_Man:
      case PUB_Book:
        cbp = (CitBookPtr) vnp->data.ptrvalue;
        if (cbp != NULL)
        {
          ip = cbp->imp;
        }
        break;
  	  case PUB_Sub:
  	    csp = (CitSubPtr) vnp->data.ptrvalue;
  	    if (csp != NULL)
  	    {
  	      ip = csp->imp;
  	    }
  	    break; 
  	}
  }
  if (ip != NULL)
  {
  	status = ip->prepub;
  }
  return status;  
}

static Boolean DoesPubStatusMatch (PubdescPtr pdp, ConvertFormPtr cfp)
{
  Uint1 pub_status;
  
  if (pdp == NULL || cfp == NULL) return FALSE;
  if (cfp->subtype == 0) return TRUE;
  
  pub_status = GetPubStatus (pdp);
  
  if (cfp->subtype == PUBLICATION_PUBLISHED_FIELD 
      && pub_status == PUB_STATUS_PUBLISHED)
  {
  	return TRUE;
  }
  else if (cfp->subtype == PUBLICATION_INPRESS_FIELD
      && pub_status == PUB_STATUS_IN_PRESS)
  {
  	return TRUE;
  }
  else if (cfp->subtype == PUBLICATION_UNPUB_FIELD
      && pub_status == PUB_STATUS_UNPUBLISHED)
  {
  	return TRUE;
  }
  else
  {
  	return FALSE;
  }
}

static Boolean DoesSequenceHavePubWithText (BioseqPtr bsp, ConvertFormPtr cfp)
{
  AsnExpOptPtr      aeop;
  AsnIoPtr          aip;
  ObjStringData     osd;
  SeqMgrDescContext dcontext;
  SeqDescrPtr       sdp;
  SeqMgrFeatContext fcontext;
  SeqFeatPtr        sfp;
  Boolean           rval = FALSE;
  ObjMgrPtr         omp;
  ObjMgrTypePtr     omtp;
  PubdescPtr        pdp;

  if (bsp == NULL || cfp == NULL) return FALSE;
  omp = ObjMgrGet ();
  if (omp == NULL) return FALSE;
  omtp = ObjMgrTypeFind (omp, OBJ_SEQDESC, NULL, NULL);
  if (omtp == NULL) return FALSE;
  
  aip = AsnIoNullOpen ();
  aeop = AsnExpOptNew (aip, NULL, NULL, AsnWriteRemoveForDCallBack);
  if (aeop != NULL) {
    aeop->user_data = (Pointer) &osd;
  }
  osd.match = cfp->deleteStr;

  /* look for publication descriptors */
  sdp = SeqMgrGetNextDescriptor (bsp, NULL, Seq_descr_pub, &dcontext);
  while (sdp != NULL && !rval) {
    if (ObjectHasSubstring (omtp, aip, (Pointer) sdp, &osd)) {
      pdp = (PubdescPtr) sdp->data.ptrvalue;
      if (DoesPubStatusMatch (pdp, cfp))
      {
        rval = TRUE;
      }
    }
    sdp = SeqMgrGetNextDescriptor (bsp, sdp, Seq_descr_pub, &dcontext);
  }
  
  if (!rval)
  { 
    omtp = ObjMgrTypeFind (omp, OBJ_SEQFEAT, NULL, NULL);
    if (omtp != NULL) 
    {	
      /* look for publication features */
      sfp = SeqMgrGetNextFeature (bsp, NULL, 0, FEATDEF_PUB, &fcontext);
      while (sfp != NULL && !rval) 
      {
        if (ObjectHasSubstring (omtp, aip, (Pointer) sfp, &osd))
        {
          pdp = (PubdescPtr) sfp->data.value.ptrvalue;
          if (DoesPubStatusMatch (pdp, cfp))
          {
            rval = TRUE;
          }
        }
        sfp = SeqMgrGetNextFeature (bsp, sfp, 0, FEATDEF_PUB, &fcontext);
      }
    }
  }
  
  AsnIoClose (aip); 
  return rval;
}

static Boolean DoesNucProtSetHavePubWithText (BioseqSetPtr bssp, ConvertFormPtr cfp)
{
  AsnExpOptPtr      aeop;
  AsnIoPtr          aip;
  ObjStringData     osd;
  SeqDescrPtr       sdp;
  Boolean           rval = FALSE;
  ObjMgrPtr         omp;
  ObjMgrTypePtr     omtp;
  PubdescPtr        pdp;
  
  if (bssp == NULL || cfp == NULL) return FALSE;  
  omp = ObjMgrGet ();
  if (omp == NULL) return FALSE;
  omtp = ObjMgrTypeFind (omp, OBJ_SEQDESC, NULL, NULL);
  if (omtp == NULL) return FALSE;

  aip = AsnIoNullOpen ();
  aeop = AsnExpOptNew (aip, NULL, NULL, AsnWriteRemoveForDCallBack);
  if (aeop != NULL) {
    aeop->user_data = (Pointer) &osd;
  }
  osd.match = cfp->deleteStr;

  /* look for publication descriptors */
  sdp = bssp->descr;
  while (sdp != NULL && !rval) {
    if (sdp->choice == Seq_descr_pub && ObjectHasSubstring (omtp, aip, (Pointer) sdp, &osd)) {
      pdp = (PubdescPtr) sdp->data.ptrvalue;
      if (DoesPubStatusMatch (pdp, cfp))
      {
        rval = TRUE;
      }
    }
    sdp = sdp->next;
  }
  
  AsnIoClose (aip); 
  return rval;
}

static Boolean DoesSimpleDescriptorForSequenceContainText (BioseqPtr bsp, CharPtr checkStr, Uint1 desc_choice)
{
  SeqMgrDescContext dcontext;
  SeqDescrPtr       sdp;
  Boolean           found = FALSE;

  for (sdp = SeqMgrGetNextDescriptor (bsp, NULL, desc_choice, &dcontext);
       sdp != NULL && !found;
       sdp = SeqMgrGetNextDescriptor (bsp, sdp, desc_choice, &dcontext))
  {
    found = CheckForString (checkStr, sdp->data.ptrvalue);
  }
  return found;
}


static Boolean LIBCALLBACK DoesSequenceContainText (BioseqPtr bsp, Pointer userdata)
{
  BioSourcePtr biop;
  ConvertFormPtr cfp;
  Boolean      found = FALSE;
  SeqDescrPtr       sdp;
  SeqMgrDescContext descContext;

  cfp = (ConvertFormPtr) userdata;
  if (bsp == NULL || cfp == NULL) return FALSE;

  switch (cfp->type) {
    case eFieldTypeGene :
    case eFieldTypeCDS :
    case eFieldTypeProtein :
    case eFieldTypeRNA :
    case eFieldTypeImport :
    case eFieldTypeFeatureNote :
      found = DoFeaturesContainText_Callback (bsp, cfp);
      break;
    case eFieldTypeBioSource :
    case eFieldTypeOrgModSubSource :
      sdp = SeqMgrGetNextDescriptor (bsp, NULL, 0, &descContext);
      while (NULL != sdp && ! found) {
        if (Seq_descr_source == sdp->choice 
            && (biop = sdp->data.ptrvalue) != NULL) {
          if (cfp->type == eFieldTypeBioSource) {
            found = DoesBioSourceContainText (biop, cfp);
          } else if (cfp->type == eFieldTypeOrgModSubSource) {
            found = DoSubSourcesContainText (biop, cfp);
          }
        }
        sdp = SeqMgrGetNextDescriptor (bsp, sdp, 0, &descContext);
      }
      break;
    case eFieldTypeDefline :
      found = DoesSimpleDescriptorForSequenceContainText (bsp, cfp->deleteStr, Seq_descr_title);
      break;
    case eFieldTypeCommentDescriptor:
      found = DoesSimpleDescriptorForSequenceContainText (bsp, cfp->deleteStr, Seq_descr_comment);
      break;
  	case eFieldTypePublication :
  	  found = DoesSequenceHavePubWithText (bsp, cfp);
  	  break;
    default:
      break;
  }
  return found;
}

static Boolean LIBCALLBACK  DoesNucProtSetContainText (BioseqSetPtr bssp, Pointer userdata)
{
  SeqEntryPtr sep;
  BioseqPtr   bsp;
  ConvertFormPtr cfp;

  if (bssp == NULL) return FALSE;
  cfp = (ConvertFormPtr) userdata;
  if (cfp == NULL) return FALSE;
  if (cfp->type == eFieldTypePublication && DoesNucProtSetHavePubWithText (bssp, cfp))
  {
  	return TRUE;
  }
  if (DoFeaturesInAnnotContainText (bssp->annot, cfp))
  {
  	return TRUE;
  }

  for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
    if (IS_Bioseq (sep)) {
      bsp = (BioseqPtr) sep->data.ptrvalue;
      if (DoesSequenceContainText (bsp, (Pointer)cfp)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

static void SegregateItemsByText 
(SeqEntryPtr                     seqlist,
 ConvertFormPtr                  cfp,
 BioseqSetPtr                    set1,
 BioseqSetPtr                    set2)
{
  SegregateItemsRecursor (seqlist, set1, set2, 
                  DoesNucProtSetContainText,
                  DoesSequenceContainText, (Pointer)cfp);
}

typedef void (LIBCALLBACK *segregateFunction) (
  SeqEntryPtr  seqlist,
  Pointer      userdata,
  BioseqSetPtr set1,
  BioseqSetPtr set2
);


static void CreateSetsForSegregate (BioseqSetPtr bssp, BioseqSetPtr PNTR pNewSet1, BioseqSetPtr PNTR pNewSet2)
{
  BioseqSetPtr parent_set;
  SeqEntryPtr  tmp1, tmp2, last_sep;
  BioseqSetPtr newset1, newset2;
  
  if (bssp == NULL || pNewSet1 == NULL || pNewSet2 == NULL) return;
  
  parent_set = (BioseqSetPtr)(bssp->idx.parentptr);

  if (parent_set == NULL || parent_set->seq_set == NULL) {
    /* this set has no parent, so make it the parent set, class GenBank,
     * and create two new sets using the original set class as members of this set
     */
    newset1 = BioseqSetNew ();
    if (newset1 == NULL) return;
    newset2 = BioseqSetNew ();
    if (newset2 == NULL) return;
    newset1->_class = bssp->_class;
    newset2->_class = bssp->_class;
    tmp1 = SeqEntryNew ();
    if (tmp1 == NULL) return;
    tmp1->choice = 2;
    tmp1->data.ptrvalue = (Pointer) newset1;
    tmp2 = SeqEntryNew ();
    if (tmp2 == NULL) return;
    tmp2->choice = 2;
    tmp2->data.ptrvalue = (Pointer) newset2;
    bssp->seq_set = tmp1;
    tmp1->next = tmp2;
    bssp->_class = BioseqseqSet_class_genbank;
    /* Propagate descriptors down */
    ValNodeLink (&(newset1->descr),
                 AsnIoMemCopy ((Pointer) bssp->descr,
                               (AsnReadFunc) SeqDescrAsnRead,
                               (AsnWriteFunc) SeqDescrAsnWrite));
    ValNodeLink (&(newset2->descr),
                 AsnIoMemCopy ((Pointer) bssp->descr,
                               (AsnReadFunc) SeqDescrAsnRead,
                               (AsnWriteFunc) SeqDescrAsnWrite));
    bssp->descr = SeqDescrFree (bssp->descr);
  } else {
    last_sep = parent_set->seq_set;
    newset1 = bssp;
    newset2 = BioseqSetNew ();
    if (newset2 == NULL) return;
    newset2->_class = newset1->_class;
    tmp1 = SeqEntryNew ();
    if (tmp1 == NULL) return;
    tmp1->choice = 2;
    tmp1->data.ptrvalue = (Pointer) newset2;
    while (last_sep != NULL && last_sep->next != NULL) {
      last_sep = last_sep->next;
    }
    if (last_sep == NULL) return;
    last_sep->next = tmp1;
    /* copy descriptors horizontally */
    ValNodeLink (&(newset2->descr),
                 AsnIoMemCopy ((Pointer) bssp->descr,
                               (AsnReadFunc) SeqDescrAsnRead,
                               (AsnWriteFunc) SeqDescrAsnWrite));
  }
  *pNewSet1 = newset1;
  *pNewSet2 = newset2;
}  
  

static void SegregateItemsGenericCallback
(SeqEntryPtr       sep,
 BioseqSetPtr      bssp,
 Uint2             entityID,
 Pointer           userdata,
 segregateFunction seg_func)
{
  ObjMgrDataPtr     omdptop;
  ObjMgrData        omdata;
  BioseqSetPtr      newset1;
  BioseqSetPtr      newset2;
  Uint2             parenttype;
  Pointer           parentptr;
  SeqEntryPtr       seqlist;

  if (sep == NULL || seg_func == NULL) return;
  /* Display the 'working' cursor */

  WatchCursor ();
  Update ();


  SaveSeqEntryObjMgrData (sep, &omdptop, &omdata);
  GetSeqEntryParent (sep, &parentptr, &parenttype);

  seqlist = bssp->seq_set;
  bssp->seq_set = NULL;

  CreateSetsForSegregate (bssp, &newset1, &newset2);

  /* Do the search and move sequences */
  (*seg_func)(seqlist, userdata, newset1, newset2);

  /* Remove the window and update things */
  SeqMgrLinkSeqEntry (sep, parenttype, parentptr);
  RestoreSeqEntryObjMgrData (sep, omdptop, &omdata); 
  ObjMgrSetDirtyFlag (entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, entityID, 0, 0);

  ArrowCursor ();
  Update ();
  
  /* Return successfully */
  return;
  
}
 

static Boolean OkToSegregate (OMProcControlPtr ompcp)
{
  if (ompcp == NULL
      || ompcp->input_itemtype != OBJ_BIOSEQSET 
      || ompcp->input_data == NULL) {
    Message (MSG_ERROR, "You must select a set to segregate!");
    return FALSE;
  } else {
    return TRUE;
  }
}


/*=========================================================================*/
/*                                                                         */
/* SegregateByText_Callback () - Finds and deletes all items of a selected */
/*                            type that contain a given text phrase.       */
/*                                                                         */
/*=========================================================================*/

static void SegregateByText_Callback (ButtoN b)
{
  ConvertFormPtr    cfp;
  SeqEntryPtr       sep;
  BioseqSetPtr      bssp;
  SeqEntryPtr       seqlist;
  BioseqSetPtr      newset1, newset2;
  ObjMgrDataPtr     omdptop;
  ObjMgrData        omdata;
  Uint2             parenttype;
  Pointer           parentptr;
  Boolean           leaveDlgUp = FALSE;
  FieldSubfieldPtr  f;

  /* Check the initial conditions and get the sequence */
  cfp = (ConvertFormPtr) GetObjectExtra (b);
  if (cfp == NULL) {
    return;
  }
  if (cfp->input_entityID == 0 || cfp->target_set == NULL) {
    Remove (cfp->form);
    return;
  }

  sep = GetTopSeqEntryForEntityID (cfp->input_entityID); 
  if (sep == NULL) {
    Remove (cfp->form);
    return;
  }
  
  if (GetStatus (cfp->leaveDlgUp))
  {
  	leaveDlgUp = TRUE;
  }

  SaveSeqEntryObjMgrData (sep, &omdptop, &omdata);
  GetSeqEntryParent (sep, &parentptr, &parenttype);

  bssp = cfp->target_set;
  seqlist = bssp->seq_set;
  bssp->seq_set = NULL;

  CreateSetsForSegregate (bssp, &newset1, &newset2);

  /* Get the string to search for */

  cfp->deleteStr = SaveStringFromTextNoStripSpaces (cfp->deleteText);
  if (StringHasNoText (cfp->deleteStr)){ 
    if (!leaveDlgUp)
    {
      Remove (cfp->form);
    }
    return;
  }

  /* Get the type of items to search */
  f = DialogToPointer (cfp->target_dlg);
  if (f == NULL || f->field < 0 || (f->subfield < 0 && f->subfield_list == NULL)) {
    f = FieldSubfieldFree (f);
    if (!leaveDlgUp)
    {
      Remove (cfp->form);
    }
    return;
  }
  
  cfp->type = f->field;
  cfp->subtype = f->subfield;
  f = FieldSubfieldFree (f);

  /* Display the 'working' cursor */

  WatchCursor ();
  Update ();

  /* Do the search and move sequences */
  SegregateItemsByText (seqlist, cfp, newset1, newset2);

  /* Remove the window and update things */
  SeqMgrLinkSeqEntry (sep, parenttype, parentptr);
  RestoreSeqEntryObjMgrData (sep, omdptop, &omdata); 
  ObjMgrSetDirtyFlag (cfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, cfp->input_entityID, 0, 0);

  ArrowCursor ();
  Update ();
  if (!leaveDlgUp)
  {
    Remove (cfp->form);
  }

  /* Return successfully */
  return;
}




/*-------------------------------------------------------------------------*/
/*                                                                         */
/* SetSegregateAcceptButton () -- Enable/Disable the Accept button depending  */
/*                             on the condition of other window objects.   */
/*                                                                         */
/*-------------------------------------------------------------------------*/

static void SetSegregateAcceptButton (Pointer data)

{
  ConvertFormPtr  cfp;
  FieldSubfieldPtr f;

  cfp = (ConvertFormPtr) data;
  if (cfp == NULL)
    return;

  /* Disable if a target has not been selected */
  f = DialogToPointer (cfp->target_dlg);
  if (f == NULL || f->field < 0 || (f->subfield < 0 && f->subfield_list == NULL)) {
    SafeDisable (cfp->accept);
    f = FieldSubfieldFree (f);
    return;
  }
  f = FieldSubfieldFree (f);

  if (cfp->deleteText != NULL) {
    /* Disable if there is no delete string */ 
    if (TextHasNoText (cfp->deleteText)) {
      SafeDisable (cfp->accept);
      return;
    }
  }

  /* If we made it to here, then we passed all */
  /* test and can enable the accept button.    */

  SafeEnable (cfp->accept);
}

static void CleanupSegregatePage (GraphiC g, VoidPtr data)

{
  ConvertFormPtr  cfp;

  cfp = (ConvertFormPtr) data;
  if (cfp != NULL) {
  }
  StdCleanupFormProc (g, data);
}

/*=========================================================================*/
/*                                                                         */
/* CreateSegregateByTextWindow () - Creates and then displays the window   */
/*                        for getting segregate by text info from the user.*/
/*                                                                         */
/*=========================================================================*/

extern Int2 LIBCALLBACK CreateSegregateByTextWindow (Pointer data)
{
  Boolean            allowed[eFieldType_Max];
  GrouP              c;
  ConvertFormPtr     cfp;
  GrouP              g;
  GrouP              h;
  OMProcControlPtr   ompcp;
  GrouP              p;
  StdEditorProcsPtr  sepp;
  WindoW             w;

  /* Check parameters and get a pointer to the current data */

  ompcp = (OMProcControlPtr) data;
  if (!OkToSegregate(ompcp)) {
    return OM_MSG_RET_ERROR;
  }

  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  cfp = ConvertFormNew ();
  if (cfp == NULL)
    return OM_MSG_RET_ERROR;
  cfp->target_set = (BioseqSetPtr)ompcp->input_data;
  cfp->set_accept_proc = SetSegregateAcceptButton;

  w = FixedWindow (-50, -33, -10, -10, "Segregate By Text String",
		   StdCloseWindowProc);
  SetObjectExtra (w, cfp, CleanupSegregatePage);
  cfp->form = (ForM) w;

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

  cfp->input_entityID = ompcp->input_entityID;
  cfp->input_itemID = ompcp->input_itemID;
  cfp->input_itemtype = ompcp->input_itemtype;

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

  /* Add the popup lists */

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

  g = HiddenGroup (h, 3, 0, NULL);

  StaticPrompt (g, "Segregate sequences with the string", 0, dialogTextHeight,
		programFont, 'l');
  cfp->deleteText = DialogText (g, "", 10, ConvertFormTextCallback);
  SetObjectExtra (cfp->deleteText, cfp, NULL);

  p = HiddenGroup (h, 6, 0, NULL);
  StaticPrompt (p, "Find string in", 0, popupMenuHeight, programFont, 'l');
  MemSet (allowed, TRUE, sizeof (Boolean) * eFieldType_Max);
  allowed[eFieldTypeCommentDescriptor] = TRUE;

  cfp->target_dlg = CreateFieldSubfieldDlg (p, allowed, ChangeTargetFields, cfp);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 4, 0, NULL);
  cfp->accept = DefaultButton (c, "Accept", SegregateByText_Callback);
  SetObjectExtra (cfp->accept, cfp, NULL);
  Disable (cfp->accept);
  PushButton (c, "Cancel", StdCancelButtonProc);
  cfp->leaveDlgUp = CheckBox (c, "Leave Dialog Up", NULL);

  /* Line things up nicely */

  AlignObjects (ALIGN_LEFT, (HANDLE) p, (HANDLE) c, (HANDLE) h, NULL);

  /* Display the window now */

  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (cfp->accept);
  Update ();
  return OM_MSG_RET_OK;
}


typedef struct segregatefeatdata {
  FEATURE_FORM_BLOCK

  PopuP        type_popup;
  ValNodePtr   type_list;
  ButtoN       accept;
  
  BioseqSetPtr target_set;
  Uint2        segregate_type;
  Boolean      is_feat;
} SegregateFeatData, PNTR SegregateFeatPtr;

static Boolean LIBCALLBACK DoesSequenceContainFeatureType (BioseqPtr bsp, Pointer userdata)
{
  SeqMgrFeatContext context;
  SeqFeatPtr        feat;
  SegregateFeatPtr  sfp;
  
  sfp = (SegregateFeatPtr) userdata;
  if (sfp == NULL || bsp == NULL) return FALSE;
  
  feat = NULL;
  while ((feat = SeqMgrGetNextFeature (bsp, feat, 0, 0, &context)) != NULL)
  {
  	if (feat->idx.subtype == sfp->segregate_type)
  	{
  	  return TRUE;
  	}
  }
  return FALSE;
}

static Boolean LIBCALLBACK DoesNucProtSetContainFeatureType (BioseqSetPtr bssp, Pointer userdata)
{
  SeqEntryPtr sep;
  BioseqPtr   bsp;
  SegregateFeatPtr  sfp;
  
  sfp = (SegregateFeatPtr) userdata;
  if (sfp == NULL || bssp == NULL) return FALSE;

  for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
    if (IS_Bioseq (sep)) {
      bsp = (BioseqPtr) sep->data.ptrvalue;
      if (DoesSequenceContainFeatureType (bsp, sfp)) {
        return TRUE;
      }
    }
  }
  return FALSE;	
}

static Boolean LIBCALLBACK DoesSequenceContainDescriptorType (BioseqPtr bsp, Pointer userdata)
{
  SeqMgrDescContext context;
  SeqDescPtr        desc;
  SegregateFeatPtr  sfp;
  
  sfp = (SegregateFeatPtr) userdata;
  if (sfp == NULL || bsp == NULL) return FALSE;
  
  if((desc = SeqMgrGetNextDescriptor (bsp, NULL, sfp->segregate_type, &context)) != NULL)
  {
	return TRUE;
  }
  return FALSE;
}

typedef struct checkdescdata {
  Uint2   segregate_type;
  Boolean found;
} CheckDescData, PNTR CheckDescPtr;

static void DoesSetContainDescriptorType_Callback (SeqDescPtr sdp, Pointer userdata)
{
  CheckDescPtr p;
  
  if (sdp == NULL || userdata == NULL) return;
  p = (CheckDescPtr) userdata;
  if (p->found) return;
  if (sdp->choice == p->segregate_type) p->found = TRUE;
}

static Boolean LIBCALLBACK DoesNucProtSetContainDescriptorType (BioseqSetPtr bssp, Pointer userdata)
{
  CheckDescData d;
  SegregateFeatPtr  sfp;
  
  sfp = (SegregateFeatPtr) userdata;
  if (sfp == NULL || bssp == NULL) return FALSE;

  d.found = FALSE;
  d.segregate_type = sfp->segregate_type;
  VisitDescriptorsInSet (bssp, &d, DoesSetContainDescriptorType_Callback);
  return d.found;	
}

/*=========================================================================*/
/*                                                                         */
/* SegregateItemsByFeature () - Given a feature type, move bioseqs         */
/*                        containing those features to a new popset.       */
/*                                                                         */
/*=========================================================================*/

static void SegregateItemsByFeatureOrDescriptor 
(SeqEntryPtr      seqlist,
 SegregateFeatPtr sfp,
 BioseqSetPtr     set1,
 BioseqSetPtr     set2)
{
  

  if (sfp == NULL || set1 == NULL || set2 == NULL || seqlist == NULL)
    return;
  
  if (sfp->is_feat)
  {
  	SegregateItemsRecursor (seqlist, set1, set2, 
  	                               DoesNucProtSetContainFeatureType,
  	                               DoesSequenceContainFeatureType,
  	                               (Pointer) sfp);
  } 
  else 
  {
  	SegregateItemsRecursor (seqlist, set1, set2, 
  	                               DoesNucProtSetContainDescriptorType,
  	                               DoesSequenceContainDescriptorType,
  	                               (Pointer) sfp);
  }
  
}


/*=========================================================================*/
/*                                                                         */
/* SegregateByFeatureOrDescriptor_Callback () - Segregates sequences that  */
/*                            contain a selected feature.                  */
/*                                                                         */
/*=========================================================================*/

static void SegregateByFeatureOrDescriptor_Callback (ButtoN b)
{
  SegregateFeatPtr  sfp;
  SeqEntryPtr       sep;
  UIEnum            val;
  BioseqSetPtr      bssp;
  SeqEntryPtr       seqlist;
  BioseqSetPtr      newset1, newset2;
  ObjMgrDataPtr     omdptop;
  ObjMgrData        omdata;
  Uint2             parenttype;
  Pointer           parentptr;
  ValNodePtr        vnp;

  /* Check the initial conditions and get the sequence */
  sfp = (SegregateFeatPtr) GetObjectExtra (b);
  if (sfp == NULL) {
    return;
  }
  if (sfp->input_entityID == 0 || sfp->target_set == NULL) {
    Remove (sfp->form);
    return;
  }

  sep = GetTopSeqEntryForEntityID (sfp->input_entityID); 
  if (sep == NULL) {
    Remove (sfp->form);
    return;
  }

  SaveSeqEntryObjMgrData (sep, &omdptop, &omdata);
  GetSeqEntryParent (sep, &parentptr, &parenttype);

  bssp = sfp->target_set;
  seqlist = bssp->seq_set;
  bssp->seq_set = NULL;

  CreateSetsForSegregate (bssp, &newset1, &newset2);

  /* Get the feature to look for */
  val = GetValue (sfp->type_popup);
  for (vnp = sfp->type_list; vnp != NULL && val > 1; vnp = vnp->next, val--)
  {  	
  }
  if (vnp == NULL || val != 1)
  {
    Remove (sfp->form);
    return;
  }
  sfp->segregate_type = vnp->choice;
  
  /* Display the 'working' cursor */

  WatchCursor ();
  Update ();

  /* Do the search and move sequences */
  SegregateItemsByFeatureOrDescriptor (seqlist, sfp, newset1, newset2);

  /* Remove the window and update things */
  SeqMgrLinkSeqEntry (sep, parenttype, parentptr);
  RestoreSeqEntryObjMgrData (sep, omdptop, &omdata); 
  ObjMgrSetDirtyFlag (sfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, sfp->input_entityID, 0, 0);

  ArrowCursor ();
  Update ();
  Remove (sfp->form);

  /* Return successfully */
  return;
}


/*=========================================================================*/
/*                                                                         */
/* CreateSegregateByFeatureWindow () - Creates and then displays the window*/
/*                        for getting segregate by text info from the user.*/
/*                                                                         */
/*=========================================================================*/

static Int2 LIBCALLBACK CreateSegregateByFeatureOrDescriptorWindow (Pointer data, Boolean is_feat)
{
  GrouP              c;
  SegregateFeatPtr   sfp;
  GrouP              g;
  GrouP              h;
  OMProcControlPtr   ompcp;
  StdEditorProcsPtr  sepp;
  WindoW             w;
  ValNodePtr         vnp;

  /* Check parameters and get a pointer to the current data */

  ompcp = (OMProcControlPtr) data;
  if (!OkToSegregate(ompcp)) {
    return OM_MSG_RET_ERROR;
  }

  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  sfp = (SegregateFeatPtr) MemNew (sizeof (SegregateFeatData));
  if (sfp == NULL)
    return OM_MSG_RET_ERROR;
  sfp->is_feat = is_feat;

  if (sfp->is_feat)
  {
    w = FixedWindow (-50, -33, -10, -10, "Segregate By Feature",
	                 StdCloseWindowProc);
  }
  else
  {
    w = FixedWindow (-50, -33, -10, -10, "Segregate By Descriptor",
	                 StdCloseWindowProc);
  }
		   
  SetObjectExtra (w, sfp, StdCleanupFormProc);
  sfp->form = (ForM) w;

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

  sfp->input_entityID = ompcp->input_entityID;
  sfp->input_itemID = ompcp->input_itemID;
  sfp->input_itemtype = ompcp->input_itemtype;
  sfp->target_set = (BioseqSetPtr)ompcp->input_data;

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

  /* Add the popup lists */

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

  g = HiddenGroup (h, 3, 0, NULL);

  if (sfp->is_feat)
  {
    StaticPrompt (g, "Segregate sequences with the feature", 0, dialogTextHeight,
	              programFont, 'l');
    sfp->type_list = BuildFeatureValNodeList (TRUE, NULL, 0, TRUE, FALSE);
  }
  else
  {
    StaticPrompt (g, "Segregate sequences with the descriptor", 0, dialogTextHeight,
	              programFont, 'l');
	sfp->type_list = BuildDescriptorValNodeList ();
  }

  sfp->type_popup = PopupList (g, TRUE, NULL);
  SetObjectExtra (sfp->type_popup, sfp, NULL);
  for (vnp = sfp->type_list; vnp != NULL; vnp = vnp->next)
  {
    PopupItem (sfp->type_popup, (CharPtr) vnp->data.ptrvalue);
  }
  SetValue (sfp->type_popup, 1);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 4, 0, NULL);
  sfp->accept = DefaultButton (c, "Accept", SegregateByFeatureOrDescriptor_Callback);
  SetObjectExtra (sfp->accept, sfp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);

  /* Line things up nicely */

  AlignObjects (ALIGN_LEFT, (HANDLE) g, (HANDLE) c, (HANDLE) h, NULL);

  /* Display the window now */

  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (sfp->accept);
  Update ();
  return OM_MSG_RET_OK;
}

extern Int2 LIBCALLBACK CreateSegregateByFeatureWindow (Pointer data)
{
  return CreateSegregateByFeatureOrDescriptorWindow (data, TRUE);
}

extern Int2 LIBCALLBACK CreateSegregateByDescriptorWindow (Pointer data)
{
  return CreateSegregateByFeatureOrDescriptorWindow (data, FALSE);
}

typedef struct segregatemoltypedata
{
  FEATURE_FORM_BLOCK

  PopuP        type_popup;
  ButtoN       use_mol_type;
  PopuP        class_popup;
  ButtoN       use_mol_class;
  ButtoN       accept;
  Uint1        moltype;
  Uint1        molclass;
  BioseqSetPtr target_set;
} SegregateMolTypeData, PNTR SegregateMolTypePtr;

static ENUM_ALIST(molinfo_biomol_alist)
  {"Genomic DNA or RNA",     1},
  {"Precursor RNA",          2},
  {"mRNA [cDNA]",            3},
  {"Ribosomal RNA",          4},
  {"Transfer RNA",           5},
  {"Peptide",                8},
  {"Other-Genetic",          9},
  {"Genomic-mRNA",          10},
  {"cRNA",                  11},
  {"Transcribed RNA",       13},
  {"Other",                255},
END_ENUM_ALIST

static ENUM_ALIST(mol_class_alist)
{"DNA",             Seq_mol_dna},
{"RNA",             Seq_mol_rna},
{"Protein",         Seq_mol_aa},
{"Nucleotide",      Seq_mol_na},
{"Other",           Seq_mol_other},
END_ENUM_ALIST

static Boolean LIBCALLBACK DoesSequenceHaveMoleculeType (BioseqPtr bsp, Pointer userdata)
{
  SegregateMolTypePtr  smp;
  ValNodePtr           sdp;
  MolInfoPtr           mip;
  
  smp = (SegregateMolTypePtr) userdata;	
  if (bsp == NULL || smp == NULL) return FALSE;
  
  if (GetStatus (smp->use_mol_class) && bsp->mol != smp->molclass)
  {
    return FALSE;
  }

  if (GetStatus (smp->use_mol_type))
  {
    sdp = bsp->descr;
    while (sdp != NULL)
    {
      if (sdp->choice == Seq_descr_molinfo && sdp->data.ptrvalue != NULL) 
      {
        mip = (MolInfoPtr) sdp->data.ptrvalue;
        if (mip->biomol == smp->moltype)
        {
      	  return TRUE;
        }
      }
      sdp = sdp->next;
    }
    return FALSE;
  }
  return TRUE;
}

typedef struct lookformoltype
{
  Boolean found;
  Uint1   moltype;  
  Uint1   molclass;
} LookForMolTypeData, PNTR LookForMolTypePtr;

static void DoesNucProtSetHaveMoleculeTypeCallback (SeqDescPtr sdp, Pointer userdata)
{
  LookForMolTypePtr l;
  MolInfoPtr           mip;
  
  if (sdp == NULL || userdata == NULL) return;
  l = (LookForMolTypePtr) userdata;
  
  if (sdp->choice == Seq_descr_molinfo && sdp->data.ptrvalue != NULL)
  {
  	mip = (MolInfoPtr) sdp->data.ptrvalue;
  	if (mip->biomol == l->moltype)
  	{
  	  l->found = TRUE;
  	}
  }
}

typedef struct lookformolclass
{
  Boolean found;
  Uint1   molclass;
} LookForMolClassData, PNTR LookForMolClassPtr;

static void FindMolClassCallback (BioseqPtr bsp, Pointer userdata)
{
  LookForMolClassPtr lcp;
  
  if (bsp == NULL || userdata == NULL) return;
  lcp = (LookForMolClassPtr) userdata;
  
  if (bsp->mol == lcp->molclass)
  {
    lcp->found = TRUE;
  }
}

static Boolean LIBCALLBACK DoesNucProtSetHaveMoleculeType (BioseqSetPtr bssp, Pointer userdata)
{
  LookForMolTypeData   lm;
  LookForMolClassData  lc;
  SegregateMolTypePtr  smp;

  if (bssp == NULL || userdata == NULL) return FALSE;
  smp = (SegregateMolTypePtr) userdata;
  
  if (GetStatus (smp->use_mol_class))
  {
    lc.found = FALSE;
    lc.molclass = smp->molclass;
    VisitBioseqsInSet (bssp, &lc, FindMolClassCallback);
    if (!lc.found) return FALSE;
  }
  
  if (GetStatus (smp->use_mol_type))
  {
    lm.moltype = smp->moltype;
    lm.found = FALSE;
    VisitDescriptorsInSet (bssp, &lm, DoesNucProtSetHaveMoleculeTypeCallback);
    if (!lm.found) return FALSE;
  }
  return TRUE;
}

static void LIBCALLBACK SegregateByMoleculeType 
(SeqEntryPtr  seqlist,
 Pointer      userdata,
 BioseqSetPtr set1,
 BioseqSetPtr set2)
{
  SegregateItemsRecursor (seqlist, set1, set2, 
                          DoesNucProtSetHaveMoleculeType,
                          DoesSequenceHaveMoleculeType, userdata);
}


/*=========================================================================*/
/*                                                                         */
/* SegregateByMoleculeType_Callback () - Segregates sequences that  */
/*                            have a selected molecule type.                  */
/*                                                                         */
/*=========================================================================*/

static void SegregateByMoleculeType_Callback (ButtoN b)
{
  SegregateMolTypePtr  smp;
  SeqEntryPtr          sep;
  UIEnum               val;

  /* Check the initial conditions and get the sequence */
  smp = (SegregateMolTypePtr) GetObjectExtra (b);
  if (smp == NULL) {
    return;
  }
  if (smp->input_entityID == 0 || smp->target_set == NULL) {
    Remove (smp->form);
    return;
  }

  sep = GetTopSeqEntryForEntityID (smp->input_entityID); 
  if (sep == NULL) {
    Remove (smp->form);
    return;
  }
  
  if (!GetEnumPopup (smp->type_popup, molinfo_biomol_alist, &val)) 
  {
    return;
  }
  smp->moltype = val;
  if (!GetEnumPopup (smp->class_popup, mol_class_alist, &val)) 
  {
    return;
  }
  smp->molclass = val;

  SegregateItemsGenericCallback (sep, smp->target_set, smp->input_entityID,
                                 (Pointer) smp, SegregateByMoleculeType);

  Remove (smp->form);

  /* Return successfully */
  return;
}

static void EnableMolInfoPopups (ButtoN b)
{
  SegregateMolTypePtr smp;
  Boolean             ok_to_accept = FALSE;
  
  smp = (SegregateMolTypePtr) GetObjectExtra (b);
  if (smp == NULL) return;
  
  if (GetStatus (smp->use_mol_type))
  {
    Enable (smp->type_popup);
    ok_to_accept = TRUE;
  }
  else
  {
    Disable (smp->type_popup);
  }
  if (GetStatus (smp->use_mol_class))
  {
    Enable (smp->class_popup);
    ok_to_accept = TRUE;
  }
  else
  {
    Disable (smp->class_popup);
  }  
  if (ok_to_accept)
  {
    Enable (smp->accept);
  }
  else
  {
    Disable (smp->accept);
  }
}

/*=========================================================================*/
/*                                                                         */
/* CreateSegregateByFeatureWindow () - Creates and then displays the window*/
/*                        for getting segregate by text info from the user.*/
/*                                                                         */
/*=========================================================================*/

extern Int2 LIBCALLBACK CreateSegregateByMoleculeTypeWindow (Pointer data)
{
  GrouP               c;
  SegregateMolTypePtr smp;
  GrouP               g, k;
  GrouP               h;
  OMProcControlPtr    ompcp;
  StdEditorProcsPtr   sepp;
  WindoW              w;

  /* Check parameters and get a pointer to the current data */

  ompcp = (OMProcControlPtr) data;
  if (!OkToSegregate(ompcp)) {
    return OM_MSG_RET_ERROR;
  }

  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  smp = (SegregateMolTypePtr) MemNew (sizeof (SegregateMolTypeData));
  if (smp == NULL)
    return OM_MSG_RET_ERROR;
  
  w = FixedWindow (-50, -33, -10, -10, "Segregate By Molecule Type",
	               StdCloseWindowProc);
		   
  SetObjectExtra (w, smp, StdCleanupFormProc);
  smp->form = (ForM) w;

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

  smp->input_entityID = ompcp->input_entityID;
  smp->input_itemID = ompcp->input_itemID;
  smp->input_itemtype = ompcp->input_itemtype;
  smp->target_set = (BioseqSetPtr)ompcp->input_data;

  /* Add the popup lists */

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

  g = HiddenGroup (h, 1, 0, NULL);

  StaticPrompt (g, "Segregate sequences with:", 0, dialogTextHeight,
	              programFont, 'l');
	k = HiddenGroup (g, 2, 0, NULL);
	smp->use_mol_type = CheckBox (k, "Molecule Type", EnableMolInfoPopups);
  SetObjectExtra (smp->use_mol_type, smp, NULL);
  smp->type_popup = PopupList (k, TRUE, NULL);
  InitEnumPopup (smp->type_popup, molinfo_biomol_alist, NULL);
  SetValue (smp->type_popup, 1);
  SetStatus (smp->use_mol_type, FALSE);
  Disable (smp->type_popup);
	k = HiddenGroup (g, 2, 0, NULL);
  smp->use_mol_class = CheckBox (k, "Molecule Class", EnableMolInfoPopups);
  SetObjectExtra (smp->use_mol_class, smp, NULL);
  smp->class_popup = PopupList (k, TRUE, NULL);
  InitEnumPopup (smp->class_popup, mol_class_alist, NULL);
  SetValue (smp->class_popup, 1);
  SetStatus (smp->use_mol_class, FALSE);
  Disable (smp->class_popup);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 4, 0, NULL);
  smp->accept = DefaultButton (c, "Accept", SegregateByMoleculeType_Callback);
  SetObjectExtra (smp->accept, smp, NULL);
  Disable (smp->accept);
  PushButton (c, "Cancel", StdCancelButtonProc);

  /* Line things up nicely */

  AlignObjects (ALIGN_LEFT, (HANDLE) g, (HANDLE) c, (HANDLE) h, NULL);

  /* Display the window now */

  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (smp->accept);
  Update ();
  return OM_MSG_RET_OK;
}

static Boolean LIBCALLBACK DoesSequenceHaveID (BioseqPtr bsp, Pointer userdata)
{
  ConvertFormPtr cfp;
  Boolean      found = FALSE;
  ValNodePtr     vnp;

  cfp = (ConvertFormPtr) userdata;
  if (bsp == NULL || cfp == NULL) return FALSE;

  for (vnp = cfp->id_list; vnp != NULL && !found; vnp = vnp->next) {
    if (SeqIdIn (vnp->data.ptrvalue, bsp->id)) {
      found = TRUE;
    }
  }
  return found;
}

static Boolean LIBCALLBACK  DoesNucProtSetContainID (BioseqSetPtr bssp, Pointer userdata)
{
  SeqEntryPtr sep;
  BioseqPtr   bsp;
  ConvertFormPtr cfp;

  if (bssp == NULL) return FALSE;
  cfp = (ConvertFormPtr) userdata;
  if (cfp == NULL) return FALSE;

  for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
    if (IS_Bioseq (sep)) {
      bsp = (BioseqPtr) sep->data.ptrvalue;
      if (DoesSequenceHaveID (bsp, (Pointer)cfp)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

static void SegregateItemsById 
(SeqEntryPtr                     seqlist,
 ConvertFormPtr                  cfp,
 BioseqSetPtr                    set1,
 BioseqSetPtr                    set2)
{
  SegregateItemsRecursor (seqlist, set1, set2, 
                  DoesNucProtSetContainID,
                  DoesSequenceHaveID, (Pointer)cfp);
}

static void SetSegregateByIDAcceptButton (Pointer data)

{
  ConvertFormPtr  cfp;
  CharPtr         id_str;

  cfp = (ConvertFormPtr) data;
  if (cfp == NULL)
    return;

  id_str = SaveStringFromText (cfp->deleteText);
  if (StringHasNoText (id_str)) {
    SafeDisable (cfp->accept);
  } else {
    SafeEnable (cfp->accept);
  }
}

/*=========================================================================*/
/*                                                                         */
/* SegregateByID_Callback () - Finds and deletes all items of a selected */
/*                            type that contain a given text phrase.       */
/*                                                                         */
/*=========================================================================*/

static void SegregateById_Callback (ButtoN b)
{
  ConvertFormPtr    cfp;
  SeqEntryPtr       sep;
  BioseqSetPtr      bssp;
  SeqEntryPtr       seqlist;
  BioseqSetPtr      newset1, newset2;
  ObjMgrDataPtr     omdptop;
  ObjMgrData        omdata;
  Uint2             parenttype;
  Pointer           parentptr;
  Boolean           leaveDlgUp = FALSE;
  CharPtr           id_str;

  /* Check the initial conditions and get the sequence */
  cfp = (ConvertFormPtr) GetObjectExtra (b);
  if (cfp == NULL) {
    return;
  }
  if (cfp->input_entityID == 0 || cfp->target_set == NULL) {
    Remove (cfp->form);
    return;
  }

  sep = GetTopSeqEntryForEntityID (cfp->input_entityID); 
  if (sep == NULL) {
    Remove (cfp->form);
    return;
  }
  
  if (GetStatus (cfp->leaveDlgUp))
  {
  	leaveDlgUp = TRUE;
  }

  /* Get the string to search for */
  id_str = SaveStringFromText(cfp->deleteText);
  if (StringHasNoText (id_str)){ 
    if (!leaveDlgUp)
    {
      Remove (cfp->form);
    }
    id_str = MemFree (id_str);
    return;
  }
  
  cfp->id_list = ParseAccessionNumberListFromString (id_str, SeqMgrGetSeqEntryForData (cfp->target_set));
  id_str = MemFree (id_str);
  if (cfp->id_list == NULL) {
    Message (MSG_ERROR, "No IDs specified!");
    return;
  } 

  SaveSeqEntryObjMgrData (sep, &omdptop, &omdata);
  GetSeqEntryParent (sep, &parentptr, &parenttype);

  bssp = cfp->target_set;
  seqlist = bssp->seq_set;
  bssp->seq_set = NULL;

  CreateSetsForSegregate (bssp, &newset1, &newset2);

  /* Display the 'working' cursor */

  WatchCursor ();
  Update ();

  /* Do the search and move sequences */
  SegregateItemsById (seqlist, cfp, newset1, newset2);

  cfp->id_list = FreeSeqIdList (cfp->id_list);
  /* Remove the window and update things */
  SeqMgrLinkSeqEntry (sep, parenttype, parentptr);
  RestoreSeqEntryObjMgrData (sep, omdptop, &omdata); 
  ObjMgrSetDirtyFlag (cfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, cfp->input_entityID, 0, 0);

  ArrowCursor ();
  Update ();
  if (!leaveDlgUp)
  {
    Remove (cfp->form);
  }

  /* Return successfully */
  return;
}

/*=========================================================================*/
/*                                                                         */
/* CreateSegregateByIdWindow () - Creates and then displays the window   */
/*                        for getting segregate by ID info from the user.*/
/*                                                                         */
/*=========================================================================*/

extern Int2 LIBCALLBACK CreateSegregateByIdWindow (Pointer data)
{
  GrouP              c;
  ConvertFormPtr     cfp;
  GrouP              g;
  GrouP              h;
  OMProcControlPtr   ompcp;
  StdEditorProcsPtr  sepp;
  WindoW             w;

  /* Check parameters and get a pointer to the current data */

  ompcp = (OMProcControlPtr) data;
  if (!OkToSegregate(ompcp)) {
    return OM_MSG_RET_ERROR;
  }

  /* Create a new window, and a struct */
  /* to pass around the data in.       */

  cfp = ConvertFormNew ();
  if (cfp == NULL)
    return OM_MSG_RET_ERROR;
  cfp->target_set = (BioseqSetPtr)ompcp->input_data;
  cfp->set_accept_proc = SetSegregateByIDAcceptButton;

  w = FixedWindow (-50, -33, -10, -10, "Segregate By ID",
		   StdCloseWindowProc);
  SetObjectExtra (w, cfp, CleanupSegregatePage);
  cfp->form = (ForM) w;

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

  cfp->input_entityID = ompcp->input_entityID;
  cfp->input_itemID = ompcp->input_itemID;
  cfp->input_itemtype = ompcp->input_itemtype;

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

  /* Add the popup lists */

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

  g = HiddenGroup (h, 3, 0, NULL);

  StaticPrompt (g, "Segregate sequences with the following IDs", 0, dialogTextHeight,
		programFont, 'l');
  cfp->deleteText = DialogText (g, "", 10, ConvertFormTextCallback);
  SetObjectExtra (cfp->deleteText, cfp, NULL);

  /* Add Accept and Cancel buttons */

  c = HiddenGroup (h, 4, 0, NULL);
  cfp->accept = DefaultButton (c, "Accept", SegregateById_Callback);
  SetObjectExtra (cfp->accept, cfp, NULL);
  Disable (cfp->accept);
  PushButton (c, "Cancel", StdCancelButtonProc);
  cfp->leaveDlgUp = CheckBox (c, "Leave Dialog Up", NULL);

  /* Line things up nicely */

  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) c, NULL);

  /* Display the window now */

  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (cfp->accept);
  Update ();
  return OM_MSG_RET_OK;
}


static void SearchAndExciseTextInside (CharPtr PNTR strptr, ConvertFormPtr cfp)

{
  CharPtr  lft = NULL;
  CharPtr  rgt = NULL;
  CharPtr  string;
  CharPtr  next_delete = NULL;
  Int4     strLen;

  if (strptr == NULL || cfp == NULL || cfp->textportion == NULL) return;
  string = *strptr;
  if (string == NULL) return;

  FindTextPortionXInString (string, cfp->textportion, &lft, &strLen);
  if (lft == NULL) return;
  rgt = lft + strLen;
  next_delete = lft;
  if (lft < rgt) {  /* No text to delete */
    while (*rgt != 0) {
      *lft = *rgt;
      lft++;
      rgt++;
    }
    *lft = '\0';
  }
  if (cfp->repeat_remove && next_delete != NULL && *next_delete != 0) {
    SearchAndExciseTextInside (&next_delete, cfp);
  }
}


/*---------------------------------------------------------------------*/
/*                                                                     */
/* RemOutTxt_SearchAndExcise () -- Removes all text from a string that */
/*                                 does not match a specified substring*/
/*                                                                     */
/*---------------------------------------------------------------------*/

static void SearchAndExciseTextOutside (CharPtr sourceStr, ConvertFormPtr cfp)
{
  CharPtr  leftEnd = NULL;
  CharPtr  rightEnd = NULL;
  Int4     strLen;
  Int4     i;

  /* Check parameters */

  if (cfp == NULL || cfp->textportion == NULL || sourceStr == NULL)
  {
    return;
  }

  FindTextPortionXInString (sourceStr, cfp->textportion, &leftEnd, &strLen);

  if (leftEnd != NULL) 
  {
    rightEnd = leftEnd + strLen;
  }

  if (NULL == leftEnd)  /* String not found */
  {
    if (DO_NOTHING == cfp->ifNotFound)
    {
    	return;
    }
    else
    {
      sourceStr [0] = '\0';
      return;
    }
  }
  else
  {
    /* End the original string at rightEnd and  */
    /* then move everything starting at leftEnd */
    /* to the beginning of the original.        */

    rightEnd[0] = '\0';
    if (leftEnd == sourceStr)
      return;

    strLen = StringLen (leftEnd);

    for (i = 0; i <= strLen; i++)
      sourceStr[i] = leftEnd[i];

    sourceStr[i] = '\0';
  }
}


static void SearchAndExciseText (CharPtr str, ConvertFormPtr cfp)
{
  if (str == NULL || cfp == NULL) return;
  if (cfp->remove_inside) 
  {
    SearchAndExciseTextInside (&str, cfp);
  } 
  else
  {
    SearchAndExciseTextOutside (str, cfp);
  }
}


static void RemoveAFeatureText (SeqFeatPtr sfp, Pointer mydata)
{
  ConvertFormPtr  cfp;
  GBQualPtr       gbqp;
  GeneRefPtr      grp;
  ProtRefPtr      prp;
  RnaRefPtr       rrp;
  CharPtr         str;
  ValNodePtr      vnp;

  if (sfp == NULL) return;
  cfp = (ConvertFormPtr) mydata;
  if (cfp == NULL) return;

  if (sfp->data.choice == SEQFEAT_GENE && cfp->type == eFieldTypeGene) {
    grp = (GeneRefPtr) sfp->data.value.ptrvalue;
    if (grp != NULL) {
      switch (cfp->subtype) {
        case 1 :
          SearchAndExciseText (grp->locus, cfp);
          break;
        case 2 :
          SearchAndExciseText (grp->desc, cfp);
          break;
        case 3 :
          SearchAndExciseText (grp->allele, cfp);
          break;
        case 4 :
          SearchAndExciseText (grp->maploc, cfp);
          break;
        case 5 :
          SearchAndExciseText (grp->locus_tag, cfp);
          break;
        case 6 :
          for (vnp = grp->syn; vnp != NULL; vnp = vnp->next) {
            str = (CharPtr) vnp->data.ptrvalue;
            SearchAndExciseText (str, cfp);
          }
          break;
        case 7 :
          SearchAndExciseText (sfp->comment, cfp);
          break;
        default :
          break;
      }
    }
  } else if (sfp->data.choice == SEQFEAT_CDREGION && cfp->type == eFieldTypeCDS) {
    switch (cfp->subtype) {
      case CDS_COMMENT :
        SearchAndExciseText (sfp->comment, cfp);
        break;
      case CDS_GENE_XREF :
        break;
      default :
        break;
    }
  } else if (sfp->data.choice == SEQFEAT_PROT && cfp->type == eFieldTypeProtein) {
    prp = (ProtRefPtr) sfp->data.value.ptrvalue;
    if (prp != NULL) {
      switch (cfp->subtype) {
        case 1 :
          for (vnp = prp->name; vnp != NULL; vnp = vnp->next) {
            str = (CharPtr) vnp->data.ptrvalue;
            SearchAndExciseText (str, cfp);
          }
          break;
        case 2 :
          SearchAndExciseText (prp->desc, cfp);
          break;
        case 3 :
          for (vnp = prp->ec; vnp != NULL; vnp = vnp->next) {
            str = (CharPtr) vnp->data.ptrvalue;
            SearchAndExciseText (str, cfp);
          }
          break;
        case 4 :
          for (vnp = prp->activity; vnp != NULL; vnp = vnp->next) {
            str = (CharPtr) vnp->data.ptrvalue;
            SearchAndExciseText (str, cfp);
          }
          break;
        case 5 :
          SearchAndExciseText (sfp->comment, cfp);
          break;
        default :
          break;
      }
    }
  } else if (sfp->data.choice == SEQFEAT_RNA && cfp->type == eFieldTypeRNA) {
    rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
    if (rrp != NULL) {
      switch (cfp->subtype) {
        case 1 :
          if (rrp->ext.choice == 0 || rrp->ext.choice == 1) {
            rrp->ext.choice = 1;
            str = (CharPtr) rrp->ext.value.ptrvalue;
            SearchAndExciseText (str, cfp);
          }
          break;
        case 2 :
          SearchAndExciseText (sfp->comment, cfp);
          break;
        case 3 :
        default :
          break;
      }
    }
  } else if (sfp->data.choice == SEQFEAT_IMP && cfp->type == eFieldTypeImport) {
    switch (cfp->subtype) {
      case IMPORT_GBQUAL_FIELD :
        gbqp = sfp->qual;
        while (NULL != gbqp)
        {
          if (NULL != gbqp->val)
            SearchAndExciseText (gbqp->val, cfp);
          gbqp = gbqp->next;
        }
        break;
      case IMPORT_COMMENT_FIELD :
        SearchAndExciseText (sfp->comment, cfp);
        break;
      default :
        break;
    }
	}
}


static void RemoveOrgModText (BioSourcePtr biop, ConvertFormPtr cfp)
{
  OrgModPtr mod, prev_mod = NULL, next_mod;

  if (biop == NULL || biop->org == NULL || biop->org->orgname == NULL || cfp == NULL) return;

  mod = biop->org->orgname->mod;
  while (mod != NULL) {
    next_mod = mod->next;
    if (mod->subtype == cfp->subtype) {
	    SearchAndExciseText (mod->subname, cfp);
      if (StringHasNoText (mod->subname) && !IsNonTextModifier (GetOrgModQualName (mod->subtype))) {
        if (prev_mod == NULL) {
          biop->org->orgname->mod = mod->next;
        } else {
          prev_mod->next = mod->next;
        }
        mod->next = NULL;
        mod = OrgModFree (mod);
      } else {
        prev_mod = mod;
      }
    } else {
      prev_mod = mod;
    }
    mod = mod->next;
  }
}


static void RemoveSubSourceText (BioSourcePtr biop, ConvertFormPtr cfp)
{
  SubSourcePtr ssp, next_ssp, prev_ssp = NULL;

  if (biop == NULL || cfp == NULL) return;

  ssp = biop->subtype;
  while (ssp != NULL) {
    next_ssp = ssp->next;
    if (ssp->subtype == (cfp->subtype - 1000)) {
	    SearchAndExciseText (ssp->name, cfp);
      if (StringHasNoText (ssp->name) && !IsNonTextModifier (GetSubsourceQualName (ssp->subtype))) {
        if (prev_ssp == NULL) {
          biop->subtype = ssp->next;
        } else {
          prev_ssp->next = ssp->next;
        }
        ssp->next = NULL;
        ssp = SubSourceFree (ssp);
      } else {
        prev_ssp = ssp;
      }
    } else {
      prev_ssp = ssp;
    }
	  ssp = next_ssp;
  }
}

static void RemoveASourceText (BioSourcePtr biop, Pointer data)
{
  ConvertFormPtr cfp;
  OrgRefPtr      orp;
  CharPtr        tmp_str;

  cfp = (ConvertFormPtr) data;
  if (biop == NULL || cfp == NULL) return;
  switch (cfp->type) {
    case eFieldTypeBioSource :
      orp = biop->org;
      if (orp == NULL) return;
      switch (cfp->subtype) {
        case ORGREF_SCI_NAME_FIELD :
          tmp_str = StringSave (orp->taxname);
          SearchAndExciseText (tmp_str, cfp);
          if (StringCmp (tmp_str, orp->taxname) == 0) {
            /* no change, no need to remove taxref */
            tmp_str = MemFree (tmp_str);
          } else {
            SetTaxNameAndRemoveTaxRef (orp, tmp_str);
          }
          break;
        case ORGREF_COMMON_NAME_FIELD :
          SearchAndExciseText (orp->common, cfp);
          break;
        case ORGREF_LINEAGE_FIELD :
          if (orp->orgname != NULL) {
            SearchAndExciseText (orp->orgname->lineage, cfp);
          }
          break;
        case ORGREF_DIVISION_FIELD :
          if (orp->orgname != NULL) {
            SearchAndExciseText (orp->orgname->div, cfp);
          }
          break;
        default:
          break;
      }
      break;
    case eFieldTypeOrgModSubSource :
      if (cfp->subtype < 1000) {  /* Orgmod Note */
        RemoveOrgModText (biop, cfp);
      } else {  /* subsource note */
        RemoveSubSourceText (biop, cfp);
      }
      break;
    default:
      break;
  }
}


static void RemoveDescriptorTextCallback (SeqDescrPtr sdp, Pointer userdata)
{
  ConvertFormPtr    cfp;
  CharPtr           title;
  ObjValNodePtr     ovp;

  cfp = (ConvertFormPtr) userdata;

  if (sdp == NULL || cfp == NULL) return;

  if (cfp->type == eFieldTypeDefline) {
    if (sdp->choice != Seq_descr_title) {
      return;
    }
  } else if (cfp->type == eFieldTypeCommentDescriptor) {
    if (sdp->choice != Seq_descr_comment) {
      return;
    }
  } else {
    return;
  }

  title = (CharPtr) sdp->data.ptrvalue;
  SearchAndExciseText (title, cfp);
  
  if (StringHasNoText (title) && (sdp->extended > 0)) {
    ovp = (ObjValNodePtr) sdp;
    ovp->idx.deleteme = TRUE;
    cfp->isDirty = TRUE;
  }
}

  
static void DoRemoveText (ConvertFormPtr cfp)

{
  SeqEntryPtr     sep;
  FieldSubfieldPtr f;

  /* Get the current sequence and associated data */

  if (cfp == NULL || cfp->input_entityID == 0)
    return;

  sep = GetTopSeqEntryForEntityID (cfp->input_entityID);
  if (sep == NULL)
    return;

  /* Get the feature and subfeature types */
  f = DialogToPointer (cfp->target_dlg);
  if (f == NULL || f->field < 0 || (f->subfield < 0 && f->subfield_list == NULL)) {
    f = FieldSubfieldFree (f);
    return;
  }

  cfp->type = f->field;
  cfp->subtype = f->subfield;

  f = FieldSubfieldFree (f);

  /* Hide the window and set the 'working' cursor */

  Hide (cfp->form);
  WatchCursor ();
  Update ();


  /* get information about text to remove */
  cfp->textportion = (TextPortionXPtr) DialogToPointer (cfp->textportion_dlg);
  
  if (GetValue (cfp->repeat_remove_grp) == 2) {
    cfp->repeat_remove = TRUE;
  } else {
    cfp->repeat_remove = FALSE;
  }

  /* Do actual work of removing text */
  cfp->isDirty = FALSE;
  if (cfp->type == eFieldTypeDefline || cfp->type == eFieldTypeCommentDescriptor) {
    VisitDescriptorsInSep (sep, cfp, RemoveDescriptorTextCallback);
  }
  else if (cfp->type == eFieldTypeBioSource || cfp->type == eFieldTypeOrgModSubSource) {
    VisitBioSourcesInSep (sep, cfp, RemoveASourceText);
  }
  else {
    VisitFeaturesInSep (sep, cfp, RemoveAFeatureText);
  }
  
  /* Clean up and exit */

  cfp->textportion = TextPortionXFree (cfp->textportion);

  if (cfp->isDirty) {
    DeleteMarkedObjects (cfp->input_entityID, 0, NULL);
  }

  ObjMgrSetDirtyFlag (cfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, cfp->input_entityID, 0, 0);
  if (GetStatus (cfp->leaveDlgUp))
  {
  	Show (cfp->form);
  }
  else
  {
    Remove (cfp->form);  	
  }
  ArrowCursor ();
  Update ();
}

static void ConvertMessageProc (ForM f, Int2 mssg)

{
  StdEditorProcsPtr  sepp;

  sepp = (StdEditorProcsPtr) GetAppProperty ("StdEditorForm");
  if (sepp != NULL) {
    if (sepp->handleMessages != NULL) {
      sepp->handleMessages (f, mssg);
    }
  }
}


/* gather information specific ro remove inside text, then call common removetext function */
static void DoRemoveInsideText (ButtoN b)
{
  ConvertFormPtr cfp;

  cfp = (ConvertFormPtr) GetObjectExtra (b);
  if (cfp == NULL) return;

  if (GetValue (cfp->repeat_remove_grp) == 2) {
    cfp->repeat_remove = TRUE;
  } else {
    cfp->repeat_remove = FALSE;
  }

  DoRemoveText (cfp);
}


/*-------------------------------------------------------------------------*/
/*                                                                         */
/* SetRemoveTextAcceptButton () -- Enable/Disable the Accept button depending */
/*                              on the condition of other window objects.  */
/*                                                                         */
/*-------------------------------------------------------------------------*/

static void SetRemoveTextAcceptButton (Pointer data)

{
  ConvertFormPtr   cfp;
  FieldSubfieldPtr f;
  Boolean          ok_to_accept = FALSE;
  TextPortionXPtr   tp;

  cfp = (ConvertFormPtr) data;
  if (cfp == NULL) return;

  f = DialogToPointer (cfp->target_dlg);
  if (f != NULL && f->field >= 0 && (f->subfield >= 0 || f->subfield_list != NULL)) {
    if (f->field == eFieldTypeFeatureNote) {
      if (f->subfield_list != NULL) {
        ok_to_accept = TRUE;
      }
    } else { 
      ok_to_accept = TRUE;
    }
  }
  f = FieldSubfieldFree (f);
  if (ok_to_accept) {
    tp = (TextPortionXPtr) DialogToPointer (cfp->textportion_dlg);
    if (tp == NULL || (StringHasNoText (tp->start_text) && StringHasNoText (tp->end_text))) {
      ok_to_accept = FALSE;        
    }
    tp = TextPortionXFree (tp);
  }
  if (ok_to_accept) {
    SafeEnable (cfp->accept);
  } else {
    SafeDisable (cfp->accept);
  }
  f = FieldSubfieldFree (f);
}



extern void RemoveTextInsideStringBaseForm (BaseFormPtr bfp)
{
  GrouP              c;
  ConvertFormPtr     cfp;
  GrouP              h;
  PrompT             ppt, ppt2;
  SeqEntryPtr        sep;
  StdEditorProcsPtr  sepp;
  WindoW             w;
  Boolean            allowed[eFieldType_Max];

  if (bfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;
  cfp = ConvertFormNew ();
  if (cfp == NULL) return;

  cfp->set_accept_proc = SetRemoveTextAcceptButton;
  cfp->remove_inside = TRUE;

  w = FixedWindow (-50, -33, -10, -10, "Remove Text Inside String", StdCloseWindowProc);
  SetObjectExtra (w, cfp, StdCleanupFormProc);
  cfp->form = (ForM) w;
  cfp->formmessage = ConvertMessageProc;

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

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

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

  ppt = StaticPrompt (h, "Remove Text", 0, popupMenuHeight, programFont, 'c');
  cfp->textportion_dlg = TextPortionXDialogEx (h, TRUE, SetRemoveTextAcceptButton, cfp);
  
  cfp->repeat_remove_grp = HiddenGroup (h, 2, 0, NULL);
  RadioButton (cfp->repeat_remove_grp, "Remove first instance in each string");
  RadioButton (cfp->repeat_remove_grp, "Remove all instances");
  SetValue (cfp->repeat_remove_grp, 1);

  ppt2 = StaticPrompt (h, "Perform excision in", 0, popupMenuHeight, programFont, 'c');
  
  MemSet (allowed, TRUE, sizeof (Boolean) * eFieldType_Max);
  allowed[eFieldTypeCommentDescriptor] = TRUE;
  allowed[eFieldTypeFeatureNote] = FALSE;
  allowed[eFieldTypePublication] = FALSE;
  cfp->target_dlg = CreateFieldSubfieldDlg (h, allowed, ChangeTargetFields, cfp);

  c = HiddenGroup (h, 4, 0, NULL);
  cfp->accept = DefaultButton (c, "Accept", DoRemoveInsideText);
  SetObjectExtra (cfp->accept, cfp, NULL);
  Disable (cfp->accept);
  PushButton (c, "Cancel", StdCancelButtonProc);
  cfp->leaveDlgUp = CheckBox (c, "Leave Dialog Up", NULL);


  AlignObjects (ALIGN_CENTER, (HANDLE) ppt, (HANDLE) cfp->textportion_dlg, (HANDLE) cfp->repeat_remove_grp, (HANDLE) ppt2, (HANDLE) cfp->target_dlg, (HANDLE) c, NULL);
  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (cfp->textportion_dlg);
  Update ();
}


extern void RemoveTextInsideString (IteM i)
{
  BaseFormPtr        bfp;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  RemoveTextInsideStringBaseForm (bfp);
}


/* AAForCodon is extern in seqport.c */
/* NLM_EXTERN Uint1 AAForCodon (Uint1Ptr codon, CharPtr codes); */

/*
static Boolean CorrectStartCodonCallback (GatherContextPtr gcp)

{
  Uint1           aa;
  Boolean         bad_base;
  CodeBreakPtr    cbp;
  Uint1           codon [3];
  CharPtr         codes;
  CdRegionPtr     crp;
  GeneticCodePtr  gc;
  Int2            i;
  Uint1           residue;
  SeqEntryPtr     sep;
  SeqFeatPtr      sfp;
  SeqLocPtr       slp;
  SeqPntPtr       spntp;
  SeqPortPtr      spp;
  ValNodePtr      vnp;

  if (gcp == NULL) return TRUE;
  sep = (SeqEntryPtr) gcp->userdata;
  if (sep == NULL ) return TRUE;
  if (gcp->thistype != OBJ_SEQFEAT) return TRUE;
  sfp = (SeqFeatPtr) gcp->thisitem;
  if (sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION) return TRUE;
  crp = (CdRegionPtr) sfp->data.value.ptrvalue;
  if (crp == NULL) return TRUE;

  if (crp->code_break != NULL) return TRUE;

	gc = NULL;
	if (crp->genetic_code != NULL)
	{
		vnp = (ValNodePtr)(crp->genetic_code->data.ptrvalue);
		while ((vnp != NULL) && (gcp == NULL))
		{
			switch (vnp->choice)
			{
			case 1:
				gc = GeneticCodeFind(0, (CharPtr)vnp->data.ptrvalue);
				break;
			case 2:
				gc = GeneticCodeFind(vnp->data.intvalue, NULL);
				break;
			case 3:
			case 6:
			case 4:
			case 5:
			case 7:
			case 8:
			default:
				break;
			}
			vnp = vnp->next;
		}
	}
	if (gc == NULL)
		gc = GeneticCodeFind(1, NULL);
	if (gc == NULL) return TRUE;

	codes = NULL;
	for (vnp = (ValNodePtr)gc->data.ptrvalue; vnp != NULL; vnp = vnp->next)
	{
		if (vnp->choice == 3)
			codes = (CharPtr)vnp->data.ptrvalue;
	}
	if (codes == NULL) return TRUE;

  if (crp->frame == 2 || crp->frame == 3) return TRUE;

  spp = SeqPortNewByLoc (sfp->location, Seq_code_ncbi4na);
  if (spp == NULL) return TRUE;
  bad_base = FALSE;
  for (i = 0; i < 3; i++) {
    residue = SeqPortGetResidue (spp);
    if (residue == SEQPORT_EOF)
      break;
    if (residue == INVALID_RESIDUE)
      bad_base = TRUE;
    codon[i] = residue;
  }
  SeqPortFree (spp);
  if (i != 3 || bad_base) return TRUE;
  aa = AAForCodon (codon, codes);

  spp = SeqPortNewByLoc (sfp->product, Seq_code_ncbieaa);
  if (spp == NULL) return TRUE;
  residue = SeqPortGetResidue (spp);
  SeqPortFree (spp);
  if (residue == SEQPORT_EOF || residue == INVALID_RESIDUE) return TRUE;

  if (residue != aa) {
    cbp = CodeBreakNew ();
    if (cbp != NULL) {
      spntp = SeqPntNew ();
      slp = ValNodeNew (NULL);
      slp->choice = SEQLOC_PNT;
      slp->data.ptrvalue = (Pointer) spntp;
      spntp->point = (Int4) 0;
      spntp->id = SeqIdStripLocus (SeqIdDup (SeqLocId (sfp->product)));
      cbp->loc = slp;
      slp = aaLoc_to_dnaLoc (sfp, cbp->loc);
      cbp->loc = SeqLocFree (cbp->loc);
      cbp->loc = slp;
      cbp->aa.value.intvalue = aa;
      cbp->aa.choice = 1;
      cbp->next = crp->code_break;
      crp->code_break = cbp;
    }
  }

  return TRUE;
}

static void CorrectStartCodon (SeqEntryPtr sep, Uint2 entityID)

{
  BioseqSetPtr  bssp;
  GatherScope   gs;

  if (sep == NULL) return;
  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL && (bssp->_class == 7 || bssp->_class == 13 ||
                         bssp->_class == 14 || bssp->_class == 15)) {
      for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
        CorrectStartCodon (sep, entityID);
      }
      return;
    }
  }
  MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
  gs.seglevels = 1;
  gs.get_feats_location = FALSE;
  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;
  gs.scope = sep;
  GatherEntity (entityID, (Pointer) sep, CorrectStartCodonCallback, &gs);
}

extern void CorrectCDSStartCodon (IteM i);
extern void CorrectCDSStartCodon (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;
  CorrectStartCodon (sep, bfp->input_entityID);
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}
*/


typedef struct applyformdata {
  FEATURE_FORM_BLOCK

  Int2           type;
  Int2           errcount;
  ValNodePtr     ambigList;
  Boolean        noLeft;
  Boolean        noRight;
  ButtoN         applyToParts;
  ButtoN         partial5;
  ButtoN         partial3;
  TexT           onlyThisPart;
  GrouP          all_or_some_grp;
  TexT           accession_list_txt;
  
  DialoG         feature_details_dlg;
  
  BatchApplyFeatureDetailsPtr feature_details_data;
  
  GrouP          strand_group;
  GrouP          use_whole_interval;
  TexT           left_end;
  TexT           right_end;
  ButtoN         add_to_seq_with_like_feature;
  ButtoN         also_add_mRNA_btn;
  ButtoN         accept;
  ButtoN         leaveDlgUp;
  
  GetSamplePtr   gsp;
  ExistingTextPtr etp;
} ApplyFormData, PNTR ApplyFormPtr;

typedef struct alreadyhas {
  Boolean        rsult;
  Uint1          featchoice;
  Uint1          descchoice;
  Uint1          rnatype;
} AlreadyHas, PNTR AlreadyHasPtr;

static Boolean SeeIfAlreadyHasGatherFunc (GatherContextPtr gcp)

{
  AlreadyHasPtr  ahp;
  RnaRefPtr      rrp;
  ValNodePtr     sdp;
  SeqFeatPtr     sfp;

  if (gcp == NULL) return TRUE;

  ahp = (AlreadyHasPtr) gcp->userdata;
  if (ahp == NULL ) return TRUE;

  if (gcp->thistype == OBJ_SEQFEAT && ahp->featchoice != 0) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp != NULL && sfp->data.choice == ahp->featchoice && sfp->data.value.ptrvalue != NULL) {
      if (sfp->data.choice == SEQFEAT_RNA) {
        rrp = (RnaRefPtr) sfp->data.value.ptrvalue;
        if (rrp->type != ahp->rnatype) return TRUE;
      }
      ahp->rsult = TRUE;
      return FALSE;
    }
  } else if (gcp->thistype == OBJ_SEQDESC && ahp->descchoice != 0) {
    sdp = (ValNodePtr) gcp->thisitem;
    if (sdp != NULL && sdp->choice == ahp->descchoice && sdp->data.ptrvalue != NULL) {
      ahp->rsult = TRUE;
      return FALSE;
    }
  }
  return TRUE;
}

static Boolean AlreadyHasFeatOrDesc (SeqEntryPtr sep, Uint1 featchoice, Uint1 descchoice, Uint1 rnatype)

{
  AlreadyHas   ah;
  BioseqPtr    bsp;
  GatherScope  gs;
  SeqEntryPtr  nsep;
  SeqIdPtr     sip;
  SeqLocPtr    slp;

  ah.rsult = FALSE;
  ah.featchoice = featchoice;
  ah.descchoice = descchoice;
  ah.rnatype = rnatype;
  if (sep == NULL) return FALSE;
  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_SEQDESC] = FALSE;
  gs.ignore[OBJ_SEQANNOT] = FALSE;
  gs.scope = sep;
  if (descchoice != 0) {
    nsep = FindNucSeqEntry (sep);
    if (nsep != NULL && IS_Bioseq (nsep)) {
      bsp = (BioseqPtr) nsep->data.ptrvalue;
      if (bsp != NULL) {
        slp = ValNodeNew (NULL);
        slp->choice = SEQLOC_WHOLE;
        sip = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (bsp->id, 0)));
        slp->data.ptrvalue = sip;
        gs.target = slp;
      }
    }
  }
  GatherSeqEntry (sep, (Pointer) (&ah), SeeIfAlreadyHasGatherFunc, &gs);
  gs.target = SeqLocFree (gs.target);
  return ah.rsult;
}


typedef struct hasimpfeatdata {
    Boolean has_it;
    CharPtr key;

} HasImpFeatData, PNTR HasImpFeatPtr;


static void AlreadyHasImpFeatCallback(SeqFeatPtr sfp, Pointer data)
{
    ImpFeatPtr imp;
    HasImpFeatPtr h;

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


static Boolean AlreadyHasImpFeat(SeqEntryPtr sep, CharPtr key)
{
    HasImpFeatData h;

    h.has_it = FALSE;
    h.key = key;

    VisitFeaturesInSep (sep, &h, AlreadyHasImpFeatCallback);
    return h.has_it;
}


static void setSeqFeatStrand(SeqFeatPtr sfp, Uint1 strand)
{
  SeqIntPtr          sqip;

  if (sfp == NULL) return;
  if (sfp->location == NULL) return;
  if (sfp->location->choice != SEQLOC_INT) return;
  sqip = (SeqIntPtr) sfp->location->data.ptrvalue;
  if (sqip != NULL)
  {
    sqip->strand = Seq_strand_minus;
  }
}

/* must be called after partials and strand are set*/
static void AdjustSeqLocForApply (SeqFeatPtr sfp, ApplyFormPtr afp)
{
  BioseqPtr bsp;
  SeqLocPtr slp;
  SeqIntPtr sip;
  CharPtr   num_str;
  Int4      from;
  Int4      to;
  Int4      tmp;
  Boolean   partial3, partial5;
  Boolean   is_caret = FALSE;
  Uint1     strand;
  
  if (sfp == NULL || afp == NULL) return;
  
  bsp = BioseqFindFromSeqLoc (sfp->location);
  if (bsp == NULL) return;
  
  num_str = SaveStringFromText (afp->left_end);
  if (num_str == NULL) return;
  from = atoi (num_str);
  tmp = StringLen (num_str);
  if (tmp > 1 && num_str[tmp - 1] == '^')
  {
    is_caret = TRUE;
  } 
  num_str = MemFree (num_str);
  num_str = SaveStringFromText (afp->right_end);
  if (num_str == NULL) return;
  if (num_str[0] == '^') {
    is_caret = TRUE;
    to = atoi (num_str + 1);
  } else {
    to = atoi (num_str);
  }
  num_str = MemFree (num_str);
  if (from > to)
  {
  	tmp = from;
  	from = to;
  	to = tmp;
  }
  if (from < 1)
  {
  	from = 1;
  }
  if (to < 1)
  {
    if (to == 0 && is_caret) {
      to = from + 1;
    } else {
  	  to = 1;
  	}
  }
  CheckSeqLocForPartial (sfp->location, &partial5, &partial3);
  strand = SeqLocStrand (sfp->location);
  if (to > bsp->length)
  {
  	to = bsp->length;
  	partial3 = TRUE;
  }

  if (is_caret && to != from + 1) {
    is_caret = FALSE;
  }

  slp = NULL;
  if (is_caret) {
    AddSeqLocPoint (&slp, SeqIdStripLocus (SeqIdDup (SeqIdFindBest (bsp->id, 0))),
                    from, FALSE, TRUE, strand);
  } else {
    slp = ValNodeNew (NULL);
    if (slp != NULL) {
      sip = SeqIntNew ();
      if (sip != NULL) {
        sip->from = from - 1;
        sip->to = to - 1;
        sip->strand = strand;
        sip->id = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (bsp->id, 0)));
        slp->choice = SEQLOC_INT;
        slp->data.ptrvalue = (Pointer) sip;
        SetSeqLocPartial (slp, partial5, partial3);
      }
    }
  }
  
  sfp->location = SeqLocFree (sfp->location);
  sfp->location = slp;
  
  if (partial5 || partial3)
  {
  	sfp->partial = TRUE;
  }
  
}

static void SetApplyFeatureLocation (SeqFeatPtr sfp, ApplyFormPtr afp)
{
  if (sfp == NULL || afp == NULL)
  {
    return;
  }
  
  if (GetValue (afp->strand_group) == 2) 
  {
    /* reverse strand direction - strand direction is plus by default */
    setSeqFeatStrand (sfp, Seq_strand_minus);
  }

  SetSeqLocPartial (sfp->location, afp->noLeft, afp->noRight);

  if (afp->use_whole_interval != NULL
      && GetValue (afp->use_whole_interval) != 1)
  {
    /* adjust location to match coordinates from user */
    AdjustSeqLocForApply (sfp, afp);
  }      
    
  sfp->partial = (afp->noLeft || afp->noRight);
}

static void AddGeneXrefToFeat (SeqFeatPtr sfp, CharPtr str)
{
  SeqFeatXrefPtr    xref;
  GeneRefPtr        grp;
  
  if (sfp == NULL || StringHasNoText (str)) return;
  
  /* add gene xref to feature */
  xref = SeqFeatXrefNew ();
  if (xref != NULL)
  {
    grp = CreateNewGeneRef (str, NULL, NULL, FALSE);
    if (grp != NULL) 
    {
      xref->data.choice = SEQFEAT_GENE;
      xref->data.value.ptrvalue = grp;
      xref->next = sfp->xref;
      sfp->xref = xref;
    }
  }
}

static SeqFeatPtr ApplyGene (CharPtr str, ApplyFormPtr afp, SeqEntryPtr gene_sep, SeqFeatPtr sfp)
{
  GeneRefPtr        grp;
  SeqFeatPtr        gene_sfp;
  SeqFeatXrefPtr    xref;
  SeqMgrFeatContext fcontext;
  BioseqPtr         bsp = NULL;
  SeqFeatPtr        other_feat;
  SeqFeatPtr        overlap_gene;
  Boolean           added_xrefs = FALSE;
  SeqFeatPtr        misc_feat = NULL;
  SeqLocPtr         overlap_loc;
  CharPtr           gene_desc = NULL;

  if (afp == NULL || gene_sep == NULL 
	  || (StringHasNoText (str) 
	      && (afp->feature_details_data == NULL 
		      || StringHasNoText (afp->feature_details_data->geneDesc))))
  {
    return NULL;
  }

  if (afp != NULL && afp->feature_details_data != NULL)
  {
    gene_desc = afp->feature_details_data->geneDesc;
  }

  /* we need a location to use when we're checking for feature-stealing genes */
  if (sfp != NULL)
  {
    overlap_loc = sfp->location;
  }
  else
  {
    misc_feat = CreateNewFeature (gene_sep, NULL, SEQFEAT_COMMENT, NULL);
    if (NULL == misc_feat)
    return NULL;
    
    SetApplyFeatureLocation (misc_feat, afp);

    overlap_loc = misc_feat->location;
  }
  
  /* first, add gene xrefs to all features on bioseq that are contained in the location */
  /* maintain list of features that had xrefs before, should not remove them later */
  if (IS_Bioseq (gene_sep))
  {
    bsp = (BioseqPtr) gene_sep->data.ptrvalue;
  }
  else if (sfp != NULL)
  {
    bsp = BioseqFindFromSeqLoc (sfp->location);
  }
  if (bsp != NULL)
  {
    other_feat = SeqMgrGetNextFeature (bsp, NULL, 0, 0, &fcontext);
    while (other_feat != NULL)
    {
      if (other_feat != sfp && other_feat->data.choice != SEQFEAT_GENE)
      {
        for (xref = other_feat->xref;
             xref != NULL && xref->data.choice != SEQFEAT_GENE;
             xref = xref->next)
        {}
        if (xref == NULL
            && SeqLocCompare (other_feat->location, overlap_loc) == SLC_A_EQ_B)
        {
          overlap_gene = SeqMgrGetOverlappingGene (other_feat->location, &fcontext);
          if (overlap_gene != NULL)
          {
            AddGeneXrefToFeat (other_feat, fcontext.label);
            added_xrefs = TRUE;
          }
        }
      }
      other_feat = SeqMgrGetNextFeature (bsp, other_feat, 0, 0, &fcontext);
    }   
  }
  
  if (misc_feat != NULL)
  {
    misc_feat->idx.deleteme = TRUE;
    DeleteMarkedObjects (0, OBJ_SEQENTRY, gene_sep);
  }
  
  grp = CreateNewGeneRef (str, NULL, gene_desc, FALSE);
  if (NULL == grp)
    return NULL;

  gene_sfp = CreateNewFeature (gene_sep, NULL, SEQFEAT_GENE, NULL);
  if (NULL == gene_sfp)
    return NULL;

  gene_sfp->data.value.ptrvalue = (Pointer) grp;
  
  SetApplyFeatureLocation (gene_sfp, afp);

  if (added_xrefs && sfp != NULL)
  {
    /* add gene xref to feature */
    AddGeneXrefToFeat (sfp, str);
  }
  
  return gene_sfp;
}


typedef struct adjustfeatforgapdialog {
  DIALOG_MESSAGE_BLOCK
  DialoG feature_select;
  ButtoN unknown_gaps;
  ButtoN known_gaps;
  GrouP  partial_grp;
  ButtoN trim_ends;
  ButtoN split_internal;
  ButtoN split_in_intron;

  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} AdjustFeatForGapDialogData, PNTR AdjustFeatForGapDialogPtr;


static void AdjustFeaturesForGapToDialog (DialoG d, Pointer udata)
{
  AdjustFeatForGapDialogPtr dlg;
  AdjustFeatForGapPtr       data;

  dlg = (AdjustFeatForGapDialogPtr) GetObjectExtra (d);
  data = (AdjustFeatForGapPtr) udata;

  if (dlg == NULL) return;
 
  if (data == NULL) {
    PointerToDialog (dlg->feature_select, NULL);
    SetStatus (dlg->unknown_gaps, FALSE);
    SetStatus (dlg->known_gaps, FALSE);
    SetStatus (dlg->trim_ends, FALSE);
    SetStatus (dlg->split_internal, FALSE);
    SetStatus (dlg->split_in_intron, FALSE);
    Disable (dlg->split_in_intron);
    SetValue (dlg->partial_grp, 2);
  } else {
    PointerToDialog (dlg->feature_select, data->feature_list);
    SetStatus (dlg->unknown_gaps, data->options & eAdjustFeatForGap_unknown_gaps);
    SetStatus (dlg->known_gaps, data->options & eAdjustFeatForGap_known_gaps);
    SetStatus (dlg->split_internal, data->options & eAdjustFeatForGap_split_internal);
    SetStatus (dlg->split_in_intron, data->options & eAdjustFeatForGap_split_in_intron);
    if (!(data->options & eAdjustFeatForGap_split_internal)) {
      Disable (dlg->split_in_intron);
    }
    SetStatus (dlg->trim_ends, data->options & eAdjustFeatForGap_trim_ends);

    if ((data->options & eAdjustFeatForGap_make_partial) && (data->options & eAdjustFeatForGap_partial_for_pseudo)) {
      SetValue (dlg->partial_grp, 1);
    } else if ((data->options & eAdjustFeatForGap_make_partial) && !(data->options & eAdjustFeatForGap_partial_for_pseudo)) {
      SetValue (dlg->partial_grp, 2);
    } else {
      SetValue (dlg->partial_grp, 3);
    }
  }    
}


static Pointer AdjustFeaturesForGapToPointer (DialoG d)
{
  AdjustFeatForGapDialogPtr dlg;
  AdjustFeatForGapPtr       data;
  Int4                      grp_val;

  dlg = (AdjustFeatForGapDialogPtr) GetObjectExtra (d);

  if (dlg == NULL) return NULL;

  data = (AdjustFeatForGapPtr) MemNew (sizeof (AdjustFeatForGapData));

  /* get list of feature types to ask on */
  data->feature_list = (ValNodePtr) DialogToPointer (dlg->feature_select);
  data->features_in_gap = NULL;

  if (GetStatus (dlg->unknown_gaps)) {
    data->options |= eAdjustFeatForGap_unknown_gaps;
  }
  if (GetStatus (dlg->known_gaps)) {
    data->options |= eAdjustFeatForGap_known_gaps;
  }

  grp_val = GetValue (dlg->partial_grp);
  switch (grp_val) {
    case 1:
      data->options |= eAdjustFeatForGap_make_partial | eAdjustFeatForGap_partial_for_pseudo;
      break;
    case 2:
      data->options |= eAdjustFeatForGap_make_partial;
      break;
    case 3:
      /* both false */
      break;
  }
  
  if (GetStatus (dlg->split_internal)) {
    data->options |= eAdjustFeatForGap_split_internal;
    if (GetStatus (dlg->split_in_intron)) {
      data->options |= eAdjustFeatForGap_split_in_intron;
    }
  }
  if (GetStatus (dlg->trim_ends)) {
    data->options |= eAdjustFeatForGap_trim_ends;
  }

  return (Pointer) data;
}


static ValNodePtr TestAdjustForGapsDialog (DialoG d)
{
  AdjustFeatForGapDialogPtr dlg;
  AdjustFeatForGapPtr       data;
  ValNodePtr                err_list = NULL;
  
  dlg = (AdjustFeatForGapDialogPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    ValNodeAddPointer (&err_list, 0, "No dialog");
    return err_list;
  }

  data = (AdjustFeatForGapPtr) DialogToPointer (d);
  if (data == NULL) 
  {
    ValNodeAddPointer (&err_list, 0, "No data");
    return err_list;
  }
    
  if (data->feature_list == NULL) 
  {
    ValNodeAddPointer (&err_list, 0, "No features");
  }

  if (!(data->options & eAdjustFeatForGap_unknown_gaps) && !(data->options & eAdjustFeatForGap_known_gaps))
  {
    ValNodeAddPointer (&err_list, 0, "No gaps");
  }

  if (!(data->options & eAdjustFeatForGap_split_internal) && !(data->options & eAdjustFeatForGap_trim_ends))
  {
    ValNodeAddPointer (&err_list, 0, "No action");
  }

  data = AdjustFeatForGapFree (data);

  return err_list;    
}


static void AdjustFeaturesForGapsChangeNotifyButton (ButtoN b)
{
  AdjustFeatForGapDialogPtr dlg;

  dlg = (AdjustFeatForGapDialogPtr) GetObjectExtra (b);
  if (GetStatus (dlg->split_internal)) {
    Enable (dlg->split_in_intron);
  } else {
    Disable (dlg->split_in_intron);
  }
  if (dlg != NULL && dlg->change_notify != NULL) {
    (dlg->change_notify) (dlg->change_userdata);
  }
}


static void AdjustFeaturesForGapsChangeNotifyGroup (GrouP g)
{
  AdjustFeatForGapDialogPtr dlg;

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


static DialoG AdjustFeaturesForGapDialog (GrouP h, Uint2 entityID, Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  AdjustFeatForGapDialogPtr dlg;
  GrouP                     p, g, g2;
  SeqEntryPtr               sep;

  dlg = (AdjustFeatForGapDialogPtr) MemNew (sizeof (AdjustFeatForGapDialogData));
  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 = AdjustFeaturesForGapToDialog;
  dlg->fromdialog = AdjustFeaturesForGapToPointer;
  dlg->dialogmessage = NULL;
  dlg->testdialog = TestAdjustForGapsDialog;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  
  sep = GetTopSeqEntryForEntityID (entityID);
  dlg->feature_select = FeatureSelectionDialogEx (p, TRUE, sep,
                                                  change_notify, 
                                                  change_userdata);

  g = HiddenGroup (p, 2, 0, NULL);
  dlg->unknown_gaps = CheckBox (g, "Unknown length gaps", AdjustFeaturesForGapsChangeNotifyButton);
  SetObjectExtra (dlg->unknown_gaps, dlg, NULL);
  dlg->known_gaps = CheckBox (g, "Known length gaps", AdjustFeaturesForGapsChangeNotifyButton);
  SetObjectExtra (dlg->known_gaps, dlg, NULL);

  dlg->partial_grp = NormalGroup (p, 3, 0, "Make truncated ends partial", programFont, AdjustFeaturesForGapsChangeNotifyGroup);
  SetObjectExtra (dlg->partial_grp, dlg, NULL);
  RadioButton (dlg->partial_grp, "Always");
  RadioButton (dlg->partial_grp, "Unless pseudo");
  RadioButton (dlg->partial_grp, "Never");
  SetValue (dlg->partial_grp, 2);

  g2 = HiddenGroup (p, 3, 0, NULL);
  dlg->trim_ends = CheckBox (g2, "Trim ends in gaps", AdjustFeaturesForGapsChangeNotifyButton);
  SetObjectExtra (dlg->trim_ends, dlg, NULL);
  dlg->split_internal = CheckBox (g2, "Split for internal gaps", AdjustFeaturesForGapsChangeNotifyButton);
  SetObjectExtra (dlg->split_internal, dlg, NULL);
  dlg->split_in_intron = CheckBox (g2, "(Even when gaps are in introns)", AdjustFeaturesForGapsChangeNotifyButton);
  SetObjectExtra (dlg->split_in_intron, dlg, NULL);
  Disable (dlg->split_in_intron);

  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->feature_select, (HANDLE) g, (HANDLE) dlg->partial_grp, (HANDLE) g2, NULL);
  return (DialoG) p;
}

typedef struct adjustfeatforgapform {
  FORM_MESSAGE_BLOCK

  DialoG dlg;
  DialoG clickable_list;
  ButtoN accept_btn;
  
  ValNodePtr feat_list;
} AdjustFeatforGapFormData, PNTR AdjustFeatForGapFormPtr;


typedef struct featforadjust {
  AdjustFeatForGapPtr afgp;
  ValNodePtr          features_to_adjust;
  ValNodePtr          features_contain_gap;
  ValNodePtr          features_in_gap;  
} FeatForAdjustData, PNTR FeatForAdjustPtr;


static void FindFeaturesToBeAdjustedForGapsCallback (SeqFeatPtr sfp, Pointer data)
{
  FeatForAdjustPtr faap;
  BioseqPtr   gapped_bioseq;
  Boolean     terminal_gaps = FALSE;
  Boolean     entirely_in_gap = FALSE;
  Boolean     internal_gaps = FALSE;

  if (sfp == NULL || data == NULL) return;

  faap = (FeatForAdjustPtr) data;
  if (faap->afgp == NULL) return;

  if (!FeatureOkForFeatureList(sfp, faap->afgp->feature_list)) return;

  gapped_bioseq = BioseqFind (SeqLocId (sfp->location));

  LocationContainsGaps (sfp->location, gapped_bioseq, faap->afgp->options, &terminal_gaps, &internal_gaps, &entirely_in_gap);
  if (entirely_in_gap)
  {
    ValNodeAddPointer (&(faap->features_in_gap), OBJ_SEQFEAT, sfp);
  }
  
  if (internal_gaps)
  {
    ValNodeAddPointer (&(faap->features_contain_gap), OBJ_SEQFEAT, sfp);
  }

  if (((faap->afgp->options & eAdjustFeatForGap_split_internal) && internal_gaps) 
      || ((faap->afgp->options & eAdjustFeatForGap_trim_ends) && terminal_gaps)) 
  {
    ValNodeAddPointer (&(faap->features_to_adjust), OBJ_SEQFEAT, sfp);
  }
}


static void AdjustFeaturesForGapsChangeNotify (Pointer data)
{
  AdjustFeatForGapFormPtr dlg;
  FeatForAdjustData       ffad;
  ValNodePtr              err_list;
  SeqEntryPtr             sep;
  ClickableItemPtr        cip;

  dlg = (AdjustFeatForGapFormPtr) data;

  if (dlg == NULL) return;

  err_list = TestDialog (dlg->dlg);
  if (err_list == NULL) 
  {
    Enable (dlg->accept_btn);
  } 
  else
  {
    err_list = ValNodeFree (err_list);
    Disable (dlg->accept_btn);
  }

  /* clear clickable list display */
  PointerToDialog (dlg->clickable_list, NULL);
  /* re-create list of features */
  dlg->feat_list = FreeClickableList (dlg->feat_list);

  ffad.afgp = DialogToPointer (dlg->dlg);
  ffad.features_contain_gap = NULL;
  ffad.features_in_gap = NULL;
  ffad.features_to_adjust = NULL;

  if (ffad.afgp->feature_list != NULL)
  {
    sep = GetTopSeqEntryForEntityID (dlg->input_entityID);
    VisitFeaturesInSep (sep, &(ffad), FindFeaturesToBeAdjustedForGapsCallback);
  }

  if (ffad.features_to_adjust != NULL) {
    cip = NewClickableItem (0, "%d features will be adjusted", ffad.features_to_adjust);
    ValNodeAddPointer (&(dlg->feat_list), 0, cip);
  }
  if (ffad.features_in_gap != NULL) {
    cip = NewClickableItem (0, "%d features are completely contained in gaps and will be deleted", ffad.features_in_gap);
    ValNodeAddPointer (&(dlg->feat_list), 0, cip);
  }
  if (ffad.features_contain_gap != NULL) {
    cip = NewClickableItem (0, "%d features contain internal gaps", ffad.features_contain_gap);
    ValNodeAddPointer (&(dlg->feat_list), 0, cip);
  }

  /* pass list of features to clickable list display */
  PointerToDialog (dlg->clickable_list, dlg->feat_list);

}


static void DoAdjustFeaturesForGaps (ButtoN b)
{
  AdjustFeatForGapFormPtr dlg;
  AdjustFeatForGapPtr     data;
  SeqEntryPtr             sep;

  dlg = (AdjustFeatForGapFormPtr) GetObjectExtra (b);

  if (dlg == NULL) return;

  data = (AdjustFeatForGapPtr) DialogToPointer (dlg->dlg);

  sep = GetTopSeqEntryForEntityID (dlg->input_entityID);
  VisitFeaturesInSep (sep, data, AdjustFeatureForGapsCallback);

  /* remove features entirely in the gap */
  MarkFeaturesInGapsForDeletion (data);
  DeleteMarkedObjects (dlg->input_entityID, 0, NULL);
  RenormalizeNucProtSets (sep, TRUE);

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

  data = AdjustFeatForGapFree(data);
  
  Remove (dlg->form);  
}


static void MakeGapFeatureReport (ButtoN b)
{
  FILE                    *fp;
  Char                    path [PATH_MAX];
  AdjustFeatForGapFormPtr dlg;
  Int4                    num_disc = 0;
  Boolean                 show_all = FALSE;


  dlg = (AdjustFeatForGapFormPtr) GetObjectExtra (b);

  if (dlg == NULL) return;
  
  num_disc = CountChosenDiscrepancies (dlg->feat_list, FALSE);

  if (num_disc == 0) 
  {
    if (ANS_CANCEL == Message (MSG_OKC, "No items selected!  Export all?"))
    {
      return;
    }
    else
    {
      show_all = TRUE;
    }
  }
  
  path [0] = '\0';
  if (GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
    fp = FileOpen (path, "r");
    if (fp != NULL) {
      FileClose (fp);
    } else {
      FileCreate (path, "TEXT", "ttxt");
    }
#endif
    fp = FileOpen (path, "w");
    if (fp != NULL) {
      WriteClickableListReport (fp, dlg->feat_list, show_all, FALSE);
      FileClose (fp);
      return;
    }
  }
}


extern void AdjustFeaturesForGaps (IteM i)
{
  BaseFormPtr             bfp;
  AdjustFeatForGapFormPtr dlg;
  WindoW                  w;
  GrouP                   h, c;
  ButtoN                  b;

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

  dlg = (AdjustFeatForGapFormPtr) MemNew (sizeof (AdjustFeatforGapFormData));
  if (dlg == NULL) return;
    
  w = FixedWindow (-50, -33, -10, -10, "Adjust Features for Gaps", StdCloseWindowProc);
  SetObjectExtra (w, dlg, StdCleanupFormProc);
  dlg->form = (ForM) w;
  dlg->input_entityID = bfp->input_entityID;
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  
  dlg->clickable_list =  CreateClickableListDialogEx (h, "", "Features", NULL, NULL,
                                                      ScrollToDiscrepancyItem, EditDiscrepancyItem, NULL,
                                                      GetDiscrepancyItemText,
                                                      stdCharWidth * 15,
                                                      stdCharWidth * 30,
                                                      TRUE, TRUE);

  dlg->dlg = AdjustFeaturesForGapDialog (h, bfp->input_entityID, AdjustFeaturesForGapsChangeNotify, dlg);

  c = HiddenGroup (h, 3, 0, NULL);
  SetGroupSpacing (c, 10, 10);
  dlg->accept_btn = PushButton (c, "Accept", DoAdjustFeaturesForGaps);
  SetObjectExtra (dlg->accept_btn, dlg, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  b = PushButton (c, "Make Report", MakeGapFeatureReport);
  SetObjectExtra (b, dlg, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->clickable_list,
                              (HANDLE) dlg->dlg,
                              (HANDLE) c,
                              NULL);
  RealizeWindow (w);
  AdjustFeaturesForGapsChangeNotify (dlg);
  Show (w);
  Select (w);
}


static ValNodePtr FreeLocList(ValNodePtr vnp)
{
  ValNodePtr vnp_next;

  while (vnp != NULL) {
    vnp_next = vnp->next;
    vnp->next = NULL;
    vnp->data.ptrvalue = SeqLocFree(vnp->data.ptrvalue);
    vnp = ValNodeFree (vnp);
    vnp = vnp_next;
  }
  return vnp;
}


static Int4 FrameFromShiftAndStart(Int4 shift, Int4 start)
{
    Int4 rval = 0;
    switch (start) {
      case 0:
      case 1:
        switch (shift % 3) {
          case 0:
            rval = 1;
            break;
          case 1:
            rval = 3;
            break;
          case 2:
            rval = 2;
            break;
          default:
            break;
        }
        break;
      case 2:
        switch (shift % 3) {
          case 0:
            rval = 2;
            break;
          case 1:
            rval = 1;
            break;
          case 2:
            rval = 3;
            break;
          default:
            break;
        }
        break;
      case 3:
        switch (shift % 3) {
          case 0:
            rval = 3;
            break;
          case 1:
            rval = 2;
            break;
          case 2:
            rval = 1;
            break;
          default:
            break;
        }
        break;         
    }
    return rval;
}


static void TrimCodingRegionsFoNsCallback(BioseqPtr bsp, Pointer userdata)
{
  SeqMgrFeatContext  fcontext;
  SeqFeatPtr         cds, gene;
  SeqIdPtr           sip;
  SeqLocPtr          slp, sp, cut;
  Boolean            partial5, partial3, is_start;
  ValNodePtr         vnp, s_vnp, cut_list_5, cut_list_3;
  Int4               len, n_start_len, n_end_len, offset;
  CharPtr            bases;
  SeqIntPtr          sintp;
  Int4               start, stop;
  Uint1              strand;
  CdRegionPtr        crp;
  Boolean            any_cut;

  if (bsp == NULL) {
    return;
  }
  for (cds = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_CDREGION, 0, &fcontext);
       cds != NULL;
       cds = SeqMgrGetNextFeature (bsp, cds, SEQFEAT_CDREGION, 0, &fcontext))
  {
      gene = GetGeneForFeature(cds);
      CheckSeqLocForPartial (cds->location, &partial5, &partial3);

      /* trim leading Ns, adjust frame */
      cut_list_5 = NULL;
      cut_list_3 = NULL;
      is_start = TRUE;
      n_start_len = 0;
      n_end_len = 0;
      for (sp = SeqLocFindNext(cds->location, NULL);
           sp != NULL;
           sp = sp->next) {          
        sip = SeqLocId (sp);
        if (sip == NULL) {
          continue;
        }
        len = SeqLocLen(sp);
        bases = (CharPtr) MemNew (sizeof (Char) * len + 1);
        MemSet (bases, 0, len + 1);
        SeqPortStreamLoc (sp, STREAM_EXPAND_GAPS, (Pointer) bases, NULL);
        if (is_start) {
          offset = StrSpn(bases, "N");
          if (offset > 0) {
            /* add piece to cut_list_5 to be removed later */
            sintp = SeqIntNew();
            sintp->id = SeqIdDup(sip);
            start = SeqLocStart(sp);
            stop = SeqLocStop(sp);
            strand = SeqLocStrand(sp);
            if (strand == Seq_strand_minus) {
              sintp->to = stop;
              sintp->from = stop - offset + 1;
              sintp->strand = Seq_strand_minus;
            } else {
              sintp->from = start;
              sintp->to = start + offset - 1;
            }
            vnp = ValNodeNew(NULL);
            vnp->choice = SEQLOC_INT;
            vnp->data.ptrvalue = sintp;
            ValNodeAddPointer(&cut_list_5, 0, vnp);
            n_start_len += offset;
          }
          if (offset < len) {
            is_start = FALSE;
          }
        }

        if (!is_start) {
          /* look for Ns at the right end. Will remove prior list if this interval is not all Ns */
          offset = 0;
          while (offset < len && bases[len - offset - 1] == 'N') {
            offset++;
          }
          if (offset < len) {
            cut_list_3 = FreeLocList(cut_list_3);
            n_end_len = 0;
          }
          if (offset > 0) {
            /* add piece to cut_list_3 to be removed later */
            sintp = SeqIntNew();
            sintp->id = SeqIdDup(sip);
            start = SeqLocStart(sp);
            stop = SeqLocStop(sp);
            strand = SeqLocStrand(sp);
            if (strand == Seq_strand_minus) {
              sintp->from = start;
              sintp->to = start + offset - 1;
              sintp->strand = Seq_strand_minus;
            } else {
              sintp->to = stop;
              sintp->from = stop - offset + 1;
            }
            vnp = ValNodeNew(NULL);
            vnp->choice = SEQLOC_INT;
            vnp->data.ptrvalue = sintp;
            /* add cut to front of list so we can remove in reverse order */
            s_vnp = ValNodeNew (NULL);
            s_vnp->data.ptrvalue = vnp;
            s_vnp->next = cut_list_3;
            cut_list_3 = s_vnp;            
            n_end_len += offset;
          }
        }
        bases = MemFree (bases);
      }
      any_cut = FALSE;
      if (n_start_len > 0) {
        any_cut = TRUE;
        partial5 = TRUE;
        /* adjust cds frame */
        crp = (CdRegionPtr) cds->data.value.ptrvalue;  
        crp->frame = FrameFromShiftAndStart(n_start_len, crp->frame);
        /* trim from front */
        for (vnp = cut_list_5; vnp != NULL; vnp = vnp->next) {
          cds->location = SeqLocSubtract(cds->location, vnp->data.ptrvalue);
        }
      }
      if (n_end_len > 0) {
        any_cut = TRUE;
        partial3 = TRUE;
        /* trim from back */
        for (vnp = cut_list_3; vnp != NULL; vnp = vnp->next) {
          cds->location = SeqLocSubtract(cds->location, vnp->data.ptrvalue);
        }
      }
      if (cds->location == NULL) {
        cds->location = ValNodeNew(NULL);
        cds->location->choice = SEQLOC_NULL;
        cds->idx.deleteme = TRUE;
      } else if (any_cut) {
        SetSeqLocPartial (cds->location, partial5, partial3);
        cds->partial = partial5 || partial3;
        SetStringValue (&(cds->comment), "nucleotide span adjusted for sequencing gap", ExistingTextOption_append_semi);
        /* also adjust gene feature */
        if (gene) {
          gene->location = SeqLocFree(gene->location);
          gene->location = SeqLocMerge (bsp, cds->location, NULL, TRUE, FALSE, FALSE);
        }
      }
      cut_list_5 = FreeLocList(cut_list_5);
      cut_list_3 = FreeLocList(cut_list_3);
    }
  
}


extern void TrimCodingRegionsForNs (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;

  VisitBioseqsInSep (sep, NULL, TrimCodingRegionsFoNsCallback);
  DeleteMarkedObjects (bfp->input_entityID, 0, NULL);
  RetranslateCdRegionsEx (bfp->input_entityID, TRUE, TRUE);
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}


static ValNodePtr GapLocationsFromNs (BioseqPtr bsp, Int4Ptr gap_sizes)

{
  CharPtr     bases, txt;
  Char        ch;
  Int4        len;
  ValNodePtr  seq_ext;
  Boolean     unknown_greater_than_or_equal = FALSE;
  Boolean     known_greater_than_or_equal = FALSE;
  Int4        unknown_gap_size = 0;
  Int4        known_gap_size = 0;
  Int4        gap_len;
  Boolean     make_unknown_size;
  GapLocInfoPtr glip;
  ValNodePtr  result_list = NULL;
  Int4        start_pos = 0;
  

  if (bsp == NULL || bsp->repr != Seq_repr_raw || ISA_aa (bsp->mol)) 
  {
    return NULL;
  }
  
  if (gap_sizes == NULL)
  {
    known_greater_than_or_equal = TRUE;
  }
  else
  {
    unknown_gap_size = gap_sizes[0];
    known_gap_size = gap_sizes[1];
    if (unknown_gap_size < 0)
    {
      unknown_greater_than_or_equal = TRUE;
      unknown_gap_size = 0 - unknown_gap_size;
    }
    if (known_gap_size < 0)
    {
      known_greater_than_or_equal = TRUE;
      known_gap_size = 0 - known_gap_size;
    }
  }

  bases = GetSequenceByBsp (bsp);
  if (bases == NULL) return NULL;

  for (txt = bases, ch = *txt; ch != '\0'; txt++, ch = *txt) {
    if (ch == 'N') break;
  }
  if (ch != 'N') {
    MemFree (bases);
    return NULL;
  }

  seq_ext = NULL;
  len = 0;

  txt = bases;
  ch = *txt;

  gap_len = 0;
  while (ch != '\0') {

    if (ch == 'N') {
      gap_len = StringSpn (txt, "N");
      if (gap_len == unknown_gap_size
          || (gap_len > unknown_gap_size && unknown_greater_than_or_equal)
          || gap_len == known_gap_size
          || (gap_len > known_gap_size && known_greater_than_or_equal))
      {
        make_unknown_size = FALSE;
        if (gap_len == 0)
        {
          make_unknown_size = FALSE;
        }
        else if (gap_len == unknown_gap_size)
        {
          make_unknown_size = TRUE;
        }
        else if (gap_len == known_gap_size)
        {
          make_unknown_size = FALSE;
        }
        else if (gap_len > unknown_gap_size && unknown_greater_than_or_equal)
        {
          if (!known_greater_than_or_equal)
          {
          	make_unknown_size = TRUE;
          }
          else if (unknown_gap_size > known_gap_size)
          {
          	make_unknown_size = TRUE;
          }
          else if (gap_len < known_gap_size)
          {
          	make_unknown_size = TRUE;
          }
        }
        
        /* Add Location to List */
        glip = (GapLocInfoPtr) MemNew (sizeof (GapLocInfoData));
        if (glip != NULL)
        {
          glip->start_pos = start_pos;
          glip->is_known = ! make_unknown_size;
          glip->length = gap_len;
          glip->replace = TRUE;
          ValNodeAddPointer (&result_list, 0, glip);
        }
      }
      txt += gap_len;
      start_pos += gap_len;
      ch = *txt;
      gap_len = 0;
    } else {
      gap_len = StringCSpn (txt, "N");
      txt+=gap_len;
      start_pos += gap_len;
      ch = *txt;
      gap_len = 0;
    }
  }
  
  MemFree (bases);
  return result_list;
}

static SeqLocPtr 
RemoveGapLocationsFromSeqLoc 
(SeqLocPtr  slp, 
 ValNodePtr gap_locs, 
 BioseqPtr  bsp,
 BoolPtr    loc_changed, 
 Boolean    set_partial_ends,
 Boolean    reverse_list)
{
  ValNodePtr    gap_vnp;
  GapLocInfoPtr glip;
  SeqLocPtr   loc_list = NULL, prev_loc = NULL;
  SeqIdPtr    sip, before_sip, after_sip;
  Boolean     changed, partial5, partial3;
  SeqLocPtr   before = NULL, after = NULL;

  if (loc_changed != NULL)
  {
    *loc_changed = FALSE;
  }
  
  if (slp == NULL || gap_locs == NULL || bsp == NULL)
  {
    return slp;
  }
  
  sip = SeqLocId (slp);
  if (sip == NULL)
  {
    return slp;
  }
  
  CheckSeqLocForPartial (slp, &partial5, &partial3);
  
  before = SeqLocCopy (slp);
  loc_list = before;
  
  for (gap_vnp = gap_locs;
       gap_vnp != NULL;
       gap_vnp = gap_vnp->next)
  {
    glip = (GapLocInfoPtr) gap_vnp->data.ptrvalue;
    if (glip == NULL || glip->is_known)
    {
      continue;
    }
    if (GapInLocation (glip->start_pos, glip->length, before))
    {
      if (loc_changed != NULL)
      {
        *loc_changed = TRUE;
      }
      /* we make a copy of the original location */
      after = SeqLocCopy (before);
      
      /* note - we need to use duplicates of the SeqID returned by
       * SeqLocId, just in case the first location in a mixed location 
       * is deleted, which would free the result from SeqLocId 
       */
      after_sip = SeqIdDup (SeqLocId (after));
      before_sip = SeqIdDup (SeqLocId (before));
      /* in the "after" location, we free everything before the 
       * end of the gap.
       */
      after = SeqLocDeleteEx (after, after_sip,
                              0, glip->start_pos + glip->length - 1,
                              FALSE, &changed, &partial5, &partial3);
                              
      /* in the "before" location, we free everything after the
       * beginning of the gap.
       */
      before = SeqLocDeleteEx (before, before_sip, 
                               glip->start_pos, bsp->length,
                               FALSE, &changed, &partial5, &partial3);

      SetPartialsAfterSplittingAtGap(before, after, set_partial_ends, partial5, partial3);
        
      /* we're done with these IDs now */                  
      after_sip = SeqIdFree (after_sip);
      before_sip = SeqIdFree (before_sip);                          
                          
      if (before == NULL)
      {
        if (prev_loc == NULL)
        {
          loc_list = after;
        }
        else
        {
          prev_loc->next = after;
        }
      }
      else
      {
        before->next = after;
        prev_loc = before;
      }        
      before = after;
    }
  }

  slp = SeqLocFree (slp);

  if (reverse_list) {
    ValNodeReverse (&loc_list);
  }

  return loc_list;  
}


static void ClearGeneXrefsAndFeatIDs (SeqFeatPtr sfp)

{
  SeqFeatXrefPtr       curr, next;
  SeqFeatXrefPtr PNTR  last;

  if (sfp == NULL) return;

  ClearFeatIDs (sfp);
  ClearFeatIDXrefs (sfp);

  last = (SeqFeatXrefPtr PNTR) &(sfp->xref);
  curr = sfp->xref;
  while (curr != NULL) {
    next = curr->next;
    if (curr->data.choice == SEQFEAT_GENE) {
      *last = next;
      curr->next = NULL;
      SeqFeatXrefFree (curr);
    } else {
      last = &(curr->next);
    }
    curr = next;
  }
}

static void ClearIDsOnNewProtFeats (SeqFeatPtr sfp, Pointer userdata)

{
  ClearGeneXrefsAndFeatIDs (sfp);
}

static void AppendFirstToGeneralID (SeqIdPtr sip, Pointer userdata)

{
  DbtagPtr     dbt, gnl;
  ObjectIdPtr  oipd, oipg;
  size_t       len;
  CharPtr      str;

  if (sip == NULL || sip->choice != SEQID_GENERAL) return;

  dbt = (DbtagPtr) userdata;
  gnl = (DbtagPtr) sip->data.ptrvalue;
  if (dbt == NULL || gnl == NULL) return;
  if (StringCmp (gnl->db, dbt->db) != 0) return;

  oipd = dbt->tag;
  oipg = gnl->tag;
  if (oipd == NULL || oipg == NULL) return;

  if (oipd->str != NULL && oipg->str != NULL) {
    if (StringCmp (oipd->str, oipg->str) != 0) return;
    len = StringLen (oipd->str) + 5;
    str = (CharPtr) MemNew (sizeof (Char) * len);
    if (str != NULL) {
      StringCpy (str, oipd->str);
      StringCat (str, "_1");
      oipg->str = MemFree (oipg->str);
      oipg->str = str;
    }
  }
}

static void AppendFirstToFeat (SeqFeatPtr sfp, Pointer userdata)

{
  if (sfp == NULL || userdata == NULL) return;
  VisitSeqIdsInSeqFeat (sfp, userdata, AppendFirstToGeneralID);
}

static BioseqPtr 
SqnAddProteinSequenceCopy 
(BioseqPtr  protbsp, 
 BioseqPtr  featbsp,
 SeqFeatPtr new_sfp,
 Int4       count,
 Uint2      entityID)
{
  Char        str [128], sfx [16];
  SeqIdPtr    new_id = NULL;
  BioseqPtr   new_protbsp;
  SeqEntryPtr prot_sep, parent_sep;
  SeqFeatPtr  prot_sfp, prot_cpy;
  SeqAnnotPtr sap;
  SeqIdPtr    sip;
  SeqLocPtr   slp;
  DbtagPtr    dbt;

  if (protbsp == NULL || featbsp == NULL || new_sfp == NULL)
  {
    return NULL;
  }
  
  parent_sep = GetBestTopParentForData (entityID, featbsp);
  if (parent_sep == NULL 
      || !IS_Bioseq_set (parent_sep) 
      || parent_sep->data.ptrvalue == NULL)
  {
    return NULL;
  }

  str [0] = '\0';
  for (sip = protbsp->id; sip != NULL; sip = sip->next) {
    if (sip->choice != SEQID_GENERAL) continue;
    dbt = (DbtagPtr) sip->data.ptrvalue;
    if (dbt == NULL) continue;
    if (IsSkippableDbtag (dbt)) continue;
    SeqIdWrite (sip, str, PRINTID_FASTA_GENERAL, sizeof (str) - 1);
    if (count > 0) {
      sprintf (sfx, "_%ld", (long) count);
      StringCat (str, sfx);
      new_id = MakeSeqID (str);
    }
  }

  if (StringHasNoText (str)) {
    SeqIdWrite (protbsp->id, str, PRINTID_REPORT, sizeof (str) - 1);  
    if (count > 0) {
      sprintf (sfx, "_%ld", (long) count);
      StringCat (str, sfx);
      new_id = MakeSeqID (str);
    }
  }

  if (new_id == NULL) {
    new_id = MakeUniqueSeqID (str);
  }
  new_protbsp = BioseqCopyEx (new_id, protbsp, 0, protbsp->length - 1,
                                      Seq_strand_plus, TRUE);
  ValNodeLink (&(new_protbsp->descr),
               AsnIoMemCopy ((Pointer) protbsp->descr,
                             (AsnReadFunc) SeqDescrAsnRead,
                             (AsnWriteFunc) SeqDescrAsnWrite));
                                      
  prot_sep = SeqEntryNew ();
  if (prot_sep != NULL)
  {
    prot_sep->choice = 1;
    prot_sep->data.ptrvalue = new_protbsp;
    SeqMgrSeqEntry (SM_BIOSEQ, (Pointer) new_protbsp, prot_sep);
    AddSeqEntryToSeqEntry (parent_sep, prot_sep, TRUE);
  }
  
  SeqMgrAddToBioseqIndex (new_protbsp);           
  new_sfp->product = SeqLocWholeNew (new_protbsp); 

  if (new_protbsp->annot == NULL) {
    for (sap = protbsp->annot; sap != NULL; sap = sap->next) {
      if (sap->type == 1) {
        for (prot_sfp = sap->data; prot_sfp != NULL; prot_sfp = prot_sfp->next) {
          slp = SeqLocCopy (prot_sfp->location);
          slp = SeqLocReplaceID (slp, new_protbsp->id);
          prot_cpy = CreateNewFeatureOnBioseq (new_protbsp, SEQFEAT_PROT, slp);
          prot_cpy->data.value.ptrvalue = AsnIoMemCopy (prot_sfp->data.value.ptrvalue, (AsnReadFunc) ProtRefAsnRead, (AsnWriteFunc) ProtRefAsnWrite);
        }
      }
    }
  }
  SeqMgrIndexFeatures (entityID, NULL);
  return new_protbsp;
}

static void CreateMultipleFeaturesFromGappedLocs (SeqFeatPtr sfp, BioseqPtr bsp, BioseqPtr protbsp, Boolean partial5, Boolean partial3)
{
  SeqFeatPtr  new_sfp;
  Uint1       choice;
  Int4        count = 0;
  CdRegionPtr crp;
  GBQualPtr   gbq;
  GeneRefPtr  grp;
  size_t      len;
  RnaRefPtr   rrp;
  SeqLocPtr   slp, nxt;
  CharPtr     str;
  Char        tmp [16];
  BioseqPtr   new_protbsp = NULL;
  Uint2       entityID;
  Boolean     isFirst, isLast;

  if (sfp == NULL || sfp->location == NULL || sfp->location->next == NULL || bsp == NULL) {
    return;
  }
  entityID = bsp->idx.entityID;
  choice = sfp->data.choice;

  if (choice == SEQFEAT_CDREGION) {
    if (protbsp == NULL) return;
  }

  /* make new feature from each interval */
  isFirst = TRUE;
  isLast = FALSE;
  for (slp = sfp->location; slp != NULL; slp = slp->next) {
    count++;

    if (slp->next == NULL) {
      isLast = TRUE;
    }

    nxt = slp->next;
    slp->next = NULL;
    new_sfp = CreateNewFeatureOnBioseq (bsp, choice, slp);
    slp->next = nxt;
    if (new_sfp == NULL) return;

    if (choice == SEQFEAT_CDREGION) {
	    /* create copy of coding region data */
	    crp = (CdRegionPtr) AsnIoMemCopy ((CdRegionPtr) sfp->data.value.ptrvalue,
	                                       (AsnReadFunc) CdRegionAsnRead,
	                                       (AsnWriteFunc) CdRegionAsnWrite);
      new_sfp->data.value.ptrvalue = crp;
    } else if (choice == SEQFEAT_GENE) {
      /* create copy of gene data */
      grp = (GeneRefPtr) AsnIoMemCopy ((GeneRefPtr) sfp->data.value.ptrvalue,
                                       (AsnReadFunc) GeneRefAsnRead,
                                       (AsnWriteFunc) GeneRefAsnWrite);
      new_sfp->data.value.ptrvalue = grp;
    } else if (choice == SEQFEAT_RNA) {
      /* create copy of RNA data */
      rrp = (RnaRefPtr) AsnIoMemCopy ((RnaRefPtr) sfp->data.value.ptrvalue,
                                      (AsnReadFunc) RnaRefAsnRead,
                                      (AsnWriteFunc) RnaRefAsnWrite);
      new_sfp->data.value.ptrvalue = rrp;
    }

    new_sfp->comment = StringSave (sfp->comment);
    new_sfp->qual = (GBQualPtr) AsnIoMemCopy ((GBQualPtr) sfp->qual,
                                              (AsnReadFunc) GBQualAsnRead,
                                              (AsnWriteFunc) GBQualAsnWrite);

    for (gbq = new_sfp->qual; gbq != NULL; gbq = gbq->next) {
      if (StringICmp (gbq->qual, "protein_id") != 0 &&
          StringICmp (gbq->qual, "orig_protein_id") != 0 &&
          StringICmp (gbq->qual, "transcript_id") != 0 &&
          StringICmp (gbq->qual, "orig_transcript_id") != 0) {
        continue;
      }
      if (StringNICmp (gbq->val, "gnl|", 4) != 0) continue;
      len = StringLen (gbq->val) + 16;
      str = (CharPtr) MemNew (sizeof (Char) * len);
      if (str != NULL) {
        sprintf (tmp, "_%ld", (long) count);
        StringCpy (str, gbq->val);
        StringCat (str, tmp);
        gbq->val = MemFree (gbq->val);
        gbq->val = str;
      }
    }

    /* set partials on new feature */
    if (isFirst) {
      SetSeqLocPartial (new_sfp->location, partial5, TRUE);
    } else if (isLast) {
      SetSeqLocPartial (new_sfp->location, TRUE, partial3);
    } else {
      SetSeqLocPartial (new_sfp->location, TRUE, TRUE);
    }

    if (choice == SEQFEAT_CDREGION) {
      new_protbsp = SqnAddProteinSequenceCopy (protbsp, bsp, new_sfp, count, entityID);
      if (new_protbsp == NULL) return;

      VisitFeaturesOnBsp (new_protbsp, NULL, ClearIDsOnNewProtFeats);                                          

      /* adjust frame */
      AdjustFrame (sfp, new_protbsp);

      /* retranslate coding region */
      SeqEdTranslateOneCDS (new_sfp, bsp, entityID, Sequin_GlobalAlign2Seq);
      
      /* set partials on product */
      if (isFirst) {
        SetProductSequencePartials (new_protbsp, partial5, TRUE);
      } else if (isLast) {
        SetProductSequencePartials (new_protbsp, TRUE, partial3);
      } else {
        SetProductSequencePartials (new_protbsp, TRUE, TRUE);
      }
    }

    isFirst = FALSE;
  }

  /* delete originals */
  sfp->idx.deleteme = TRUE;
  if (protbsp != NULL) {
    protbsp->idx.deleteme = TRUE;
  }
}

static void AdjustCodingRegionLocationsForGapLocations (BioseqPtr bsp, ValNodePtr gap_list)

{
  SeqFeatPtr         cds, gene, mrna;
  Uint2              entityID;
  SeqMgrFeatContext  fcontext, mcontext;
  Boolean            loc_changed, partial5, partial3, reverse_list;
  BioseqPtr          protbsp;
  
  if (bsp == NULL || gap_list == NULL) return;

  entityID = bsp->idx.entityID;

  for (cds = SeqMgrGetNextFeature (bsp, NULL, SEQFEAT_CDREGION, 0, &fcontext);
       cds != NULL;
       cds = SeqMgrGetNextFeature (bsp, cds, SEQFEAT_CDREGION, 0, &fcontext))
  {
    loc_changed = FALSE;
    gene = GetGeneForFeature (cds);
    mrna = SeqMgrGetOverlappingmRNA (cds->location, &mcontext);

    CheckSeqLocForPartial (cds->location, &partial5, &partial3);
    reverse_list = (Boolean) (SeqLocStrand (cds->location) == Seq_strand_minus);
    cds->location = RemoveGapLocationsFromSeqLoc (cds->location, gap_list, bsp, &loc_changed, TRUE, reverse_list);
    if (!loc_changed) {
        continue;
    }

    ClearGeneXrefsAndFeatIDs (cds);
    AddCDSGapComment (cds);
    protbsp = BioseqFindFromSeqLoc (cds->product);
    CreateMultipleFeaturesFromGappedLocs(cds, bsp, protbsp, partial5, partial3);

    if (gene != NULL) {
      ClearGeneXrefsAndFeatIDs (gene);
      /*
      CheckSeqLocForPartial (gene->location, &partial5, &partial3);
      reverse_list = (Boolean) (SeqLocStrand (gene->location) == Seq_strand_minus);
      gene->location = RemoveGapLocationsFromSeqLoc (gene->location, gap_list, bsp, &loc_changed, FALSE, reverse_list);
      CreateMultipleFeaturesFromGappedLocs(gene, bsp, NULL, partial5, partial3);
      */
    }
    if (mrna != NULL) {
      ClearGeneXrefsAndFeatIDs (mrna);
      CheckSeqLocForPartial (mrna->location, &partial5, &partial3);
      reverse_list = (Boolean) (SeqLocStrand (mrna->location) == Seq_strand_minus);
      mrna->location = RemoveGapLocationsFromSeqLoc (mrna->location, gap_list, bsp, &loc_changed, FALSE, reverse_list);
      CreateMultipleFeaturesFromGappedLocs(mrna, bsp, NULL, partial5, partial3);
    }
  }

  DeleteMarkedObjects (entityID, 0, NULL);
  SeqMgrClearFeatureIndexes (entityID, NULL);
  SeqMgrIndexFeatures (entityID, NULL);
}

extern void 
PrepareCodingRegionLocationsForDeltaConversionCallback
(BioseqPtr bsp, Pointer userdata)
{
  Int4Ptr gap_sizes;
  ValNodePtr gap_locations;
  
  if (bsp == NULL)
  {
    return;
  }
  
  gap_sizes = (Int4Ptr) userdata;
  
  gap_locations = GapLocationsFromNs (bsp, gap_sizes);
  
  AdjustCodingRegionLocationsForGapLocations (bsp, gap_locations);
  
  gap_locations = ValNodeFreeData (gap_locations);
}

static Int4 
CalculateGapCoverage 
(BioseqPtr         bsp, 
 SeqLocPtr         slp, 
 SeqMgrFeatContext context,
 Boolean           include_terminal_gaps)
{
  DeltaSeqPtr       dsp;
  SeqLocPtr         loc;
  SeqLitPtr         slip;
  Int4              seq_offset = 0;
  Int4              covered = 0;
  Int4              k, start, stop;

  if (slp == NULL
      || bsp == NULL || !ISA_na (bsp->mol) 
      || bsp->repr != Seq_repr_delta || bsp->seq_ext_type != 4)
  {
    return 0;
  }
  
  dsp = (DeltaSeqPtr) bsp->seq_ext;
  while (dsp != NULL && seq_offset < context.right)
  {
    if (dsp->choice == 1)
    {
      loc = (SeqLocPtr) dsp->data.ptrvalue;
      if (loc != NULL)
      {
        seq_offset += SeqLocLen (loc);
      }
    }
    else if (dsp->choice == 2)
    {
      slip = (SeqLitPtr) dsp->data.ptrvalue;
      if (slip != NULL)
      {
        if (IsDeltaSeqKnownGap (dsp) && !DoesDeltaSeqHaveGapTypeOrLinkage(dsp))
        {        
          if (seq_offset <= context.left && seq_offset + slip->length >= context.right)
          {
            /* gap covers entire location */
            return SeqLocLen (slp);
          }
          else if (include_terminal_gaps || 
                   (seq_offset > context.left 
                    && seq_offset + slip->length < context.right))
          {
            /* we only count internal gaps */
            for (k = 0; k < 2 * context.numivals; k += 2) 
            {
              start = context.ivals [k];
              stop = context.ivals [k + 1];
              if (seq_offset <= start && seq_offset + slip->length > stop)
              {
                /* gap covers entire interval */
                covered += stop - start;
              }
              else if (seq_offset > start && seq_offset + slip->length < stop)
              {
                /* interval covers entire gap */
                covered += slip->length;
              }
              else if (seq_offset < start && seq_offset + slip->length > start)
              {
                /* gap covers left end of interval */
                covered += seq_offset + slip->length - start;
              }
              else if (seq_offset < stop && seq_offset + slip->length > stop)
              {
                /* gap covers right end of interval */
                covered += stop - seq_offset;
              }
            }
          }
        }
        seq_offset += slip->length;
      }
    }
    dsp = dsp->next;
  }

  return covered;
  
}


static Int4 TrailingNLength (CharPtr seq)
{
  Int4 seq_len = StringLen (seq);
  Int4 trailing_ns = 0;
  CharPtr cp;

  if (seq == NULL) {
    return 0;
  }
  cp = seq + seq_len - 1;
  while (cp > seq && *cp == 'N') {
    cp--;
    trailing_ns++;
  }
  if (*cp == 'N') {
    trailing_ns++;
  }
  return trailing_ns;
}


static SeqLocPtr 
RemoveTerminalNsFromLocation
(SeqLocPtr slp)
{
  SeqLocPtr         loc = NULL, slp_copy;
  Int4              start, stop;
  Boolean           first = TRUE;
  BioseqPtr         bsp;
  SeqIdPtr          sip;
  Boolean           changed = FALSE, partial5, partial3, tmp5, tmp3;
  CharPtr           buffer = NULL;
  Int4              buflen = 0;
  Int4              leading_ns;
  Int4              trailing_n_start = -1, this_ns;
  Int4              loc_len;
  Uint1             strand;

  if (slp == NULL)
  {
    return NULL;
  }
  
  bsp = BioseqFindFromSeqLoc (slp);
  if (bsp == NULL) {
    return NULL;
  }
  CheckSeqLocForPartial (slp, &partial5, &partial3);
  
  slp_copy = (SeqLocPtr) AsnIoMemCopy (slp, 
                                       (AsnReadFunc) SeqLocAsnRead,
                                       (AsnWriteFunc) SeqLocAsnWrite);
  if (!ISA_na (bsp->mol))
  {
    return slp_copy;
  }

  while ((loc = SeqLocFindNext (slp, loc)) != NULL) {
    start = SeqLocStart (loc);
    stop = SeqLocStop (loc);
    strand = SeqLocStrand (loc);
    loc_len = stop - start + 1;
    if (buflen < loc_len + 1) {
      buffer = MemFree (buffer);
      buflen = loc_len + 1;
      buffer = (CharPtr) MemNew (sizeof (Char) * buflen);
    }
    SeqPortStreamInt (bsp, start, stop, strand, EXPAND_GAPS_TO_DASHES, (Pointer) (buffer), NULL);
    leading_ns = 0;
    if (first) {
      leading_ns = StringSpn (buffer, "N");
      if (leading_ns > 0) {
        sip = SeqIdDup (SeqLocId (slp_copy));
        tmp5 = FALSE;
        tmp3 = FALSE;
        if (strand == Seq_strand_minus) {
          slp_copy = SeqLocDeleteEx (slp_copy, sip,
                                     stop - leading_ns + 1, stop,
                                     FALSE, &changed, &tmp5, &tmp3);
        } else {
          slp_copy = SeqLocDeleteEx (slp_copy, sip,
                            start, start + leading_ns - 1,
                            FALSE, &changed, &tmp5, &tmp3);
        }
        partial5 |= tmp5;
        partial3 |= tmp3;
        sip = SeqIdFree (sip);
      }
    }

    if (!first || leading_ns != loc_len) {
      this_ns = TrailingNLength (buffer);
      if (this_ns > 0) {
        if (this_ns == loc_len && trailing_n_start > -1) {
          /* add to existing trailing Ns */
        } else {
          if (strand == Seq_strand_minus) {
            trailing_n_start = start + this_ns - 1;
          } else {
            trailing_n_start = stop - this_ns + 1;
          }
        }
      }
    } else {
      trailing_n_start = -1;
    }

    if (first && leading_ns != loc_len) {
      first = FALSE;
    }
  }

  if (trailing_n_start > -1) {
    sip = SeqIdDup (SeqLocId (slp_copy));
    tmp5 = FALSE;
    tmp3 = FALSE;
    if (strand == Seq_strand_minus) {
      slp_copy = SeqLocDeleteEx (slp_copy, sip,
                              start, trailing_n_start,
                              FALSE, &changed, &tmp5, &tmp3);
    } else {
      slp_copy = SeqLocDeleteEx (slp_copy, sip,
                              trailing_n_start, stop,
                              FALSE, &changed, &tmp5, &tmp3);
    }
    partial5 |= tmp5;
    partial3 |= tmp3;
    sip = SeqIdFree (sip);
  }

  if (slp_copy != NULL)  
  {
    SetSeqLocPartial (slp_copy, partial5, partial3);
  }

  buffer = MemFree (buffer);

  return slp_copy;
}


typedef struct cdstomiscfeatform
{
  FEATURE_FORM_BLOCK
  
  GrouP  all_or_percent_grp;
  DialoG string_constraint_dlg;
  
  Boolean             convert_all;
  FilterSetPtr        fsp;
  StringConstraintXPtr scp;
  BioseqPtr           bsp;
} CDSToMiscFeatFormData, PNTR CDSToMiscFeatFormPtr;

static void ConvertCDSToMiscFeatFeatureCallback (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  CDSToMiscFeatFormPtr cmffp;
  Int4                 orig_len, covered_len;
  SeqMgrFeatContext    context;
  SeqEntryPtr          oldscope;
  CDStoMiscFeatData    cmfd;

  cmffp = (CDSToMiscFeatFormPtr) userdata;
  
  if (sfp == NULL || cmffp == NULL || cmffp->bsp == NULL)
  {
    return;
  }
  
  sfp = SeqMgrGetDesiredFeature (sfp->idx.entityID, cmffp->bsp, sfp->idx.itemID, 0, sfp, &context);
  if (sfp == NULL)
  {
    return;
  }
  
  oldscope = SeqEntrySetScope (NULL);

  orig_len = SeqLocLen (sfp->location);
  covered_len = CalculateGapCoverage (cmffp->bsp, sfp->location, context, cmffp->convert_all);
  if (covered_len >= orig_len / 2 || (cmffp->convert_all && covered_len > 0))
  {
    cmfd.must_have_stops = FALSE;
    cmfd.viral = FALSE;
    cmfd.opts = NULL;
    ConvertCDSToMiscFeat (sfp, &cmfd);
  }
  
  SeqEntrySetScope (oldscope);
}

static void ConvertGappedCodingRegionsToMiscFeatBioseqCallback (BioseqPtr bsp, Pointer userdata)
{
  CDSToMiscFeatFormPtr cmffp;
  SeqEntryPtr          sep;
  
  if (bsp == NULL || !ISA_na (bsp->mol) 
      || bsp->repr != Seq_repr_delta || bsp->seq_ext_type != 4
      || userdata == NULL)
  {
    return;
  }
  
  sep = SeqMgrGetSeqEntryForData (bsp);
  
  cmffp = (CDSToMiscFeatFormPtr) userdata;
  cmffp->bsp = bsp;
  
  OperateOnSeqEntryConstrainedObjects (sep, cmffp->fsp, 
                                       ConvertCDSToMiscFeatFeatureCallback,
                                       NULL, SEQFEAT_CDREGION, FEATDEF_CDS, 0, cmffp);

}

static void AcceptCDSToMiscFeat (ButtoN b)
{
  CDSToMiscFeatFormPtr cmffp;
  SeqEntryPtr          sep;
  
  cmffp = (CDSToMiscFeatFormPtr) GetObjectExtra (b);
  if (cmffp == NULL)
  {
    return;
  }
  
  if (GetValue (cmffp->all_or_percent_grp) == 2)
  {
    cmffp->convert_all = TRUE;
  }
  else
  {
    cmffp->convert_all = FALSE;
  }
  
  cmffp->fsp = FilterSetNew();
  cmffp->fsp->scp = DialogToPointer (cmffp->string_constraint_dlg);

  sep = GetTopSeqEntryForEntityID (cmffp->input_entityID);
  if (sep == NULL) return;

  VisitBioseqsInSep (sep, cmffp, ConvertGappedCodingRegionsToMiscFeatBioseqCallback);
  DeleteMarkedObjects (cmffp->input_entityID, 0, NULL);
  RenormalizeNucProtSets (sep, TRUE);
  ObjMgrSetDirtyFlag (cmffp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, cmffp->input_entityID, 0, 0);
  Remove (cmffp->form);  
}

extern void ConvertCodingRegionsWithInternalKnownGapToMiscFeat (IteM i)
{
  BaseFormPtr          bfp;
  CDSToMiscFeatFormPtr cmffp;
  WindoW               w;
  GrouP                h, c;
  ButtoN               b;

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

  cmffp = (CDSToMiscFeatFormPtr) MemNew (sizeof (CDSToMiscFeatFormData));
  if (cmffp == NULL) return;
    
  w = FixedWindow (-50, -33, -10, -10, "Convert Coding Regions With Gaps to Misc_Feat", StdCloseWindowProc);
  SetObjectExtra (w, cmffp, StdCleanupFormProc);
  cmffp->form = (ForM) w;
  cmffp->input_entityID = bfp->input_entityID;
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  
  cmffp->all_or_percent_grp = HiddenGroup (h, 0, 2, NULL);
  SetGroupSpacing (cmffp->all_or_percent_grp, 10, 10);
  RadioButton (cmffp->all_or_percent_grp, "Convert only when internal gap covers 50% or more of the coding region");
  RadioButton (cmffp->all_or_percent_grp, "Convert all coding regions with gaps (both terminal and internal)");
  SetValue (cmffp->all_or_percent_grp, 1);
  
  cmffp->string_constraint_dlg = StringConstraintDialogX (h, "Where feature text", FALSE);

  c = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (c, 10, 10);
  b = PushButton (c, "Accept", AcceptCDSToMiscFeat);
  SetObjectExtra (b, cmffp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  AlignObjects (ALIGN_CENTER, (HANDLE) cmffp->all_or_percent_grp,
                              (HANDLE) cmffp->string_constraint_dlg,
                              (HANDLE) c,
                              NULL);
  RealizeWindow (w);
  Show (w);
  Select (w);
}


/*---------------------------------------------------------------------*/
/*                                                                     */
/* Apply_AddCDS () --                                                  */
/*                                                                     */
/*---------------------------------------------------------------------*/

static void Apply_AddCDS (Uint2        entityID,
			  SeqEntryPtr  sep,
			  SeqEntryPtr  nsep,
			  ApplyFormPtr afp,
			  Boolean      suppressDups)
{
  ByteStorePtr       bs;
  BioseqPtr          bsp;
  Char               ch;
  CdRegionPtr        crp;
  RnaRefPtr          rrp;
  ValNodePtr         descr;
  Int2               genCode;
  Int2               i;
  MolInfoPtr         mip;
  SeqEntryPtr        old;
  CharPtr            prot;
  ProtRefPtr         prp;
  SeqEntryPtr        psep;
  CharPtr            ptr;
  SeqFeatPtr         sfp;
  SeqIdPtr           sip;
  Char               str [128];
  ValNodePtr         vnp;
  SeqEntryPtr        parent_sep;
  SeqEntryPtr        gene_sep;
  SeqFeatPtr         prot_sfp, mRNA_sfp;

  /* If necessary then check for duplication before adding */

  if (suppressDups &&
      entityID > 0 &&
      AlreadyHasFeatOrDesc (sep, SEQFEAT_CDREGION, 0, 0))
    return;

  /* determine the parent of this sequence (for use when segmented) */
  parent_sep = NULL;
  if (IS_Bioseq (sep))
  {
    parent_sep = GetBestTopParentForData (entityID, sep->data.ptrvalue);
  }
  if (parent_sep == NULL)
  {
    parent_sep = sep;
  }

  /*Create a new CDS feature */

  genCode = SeqEntryToGeneticCode (parent_sep, NULL, NULL, 0);
  crp = CreateNewCdRgn (1, FALSE, genCode);
  if (NULL == crp)
    return;
  
  sfp = CreateNewFeature (nsep, NULL, SEQFEAT_CDREGION, NULL); 	

  if (NULL == sfp)
    return;
  
  sfp->data.value.ptrvalue = (Pointer) crp;
  
  /* set the comment */
  if (afp->feature_details_data->featcomment != NULL) {
      sfp->comment = StringSave (afp->feature_details_data->featcomment);
  }

  /* adjust the location of the new feature */
  SetApplyFeatureLocation (sfp, afp);
  
  /* Fill in the fields of the new CDS feature */
  if (afp->feature_details_data->reading_frame < 1 
      || afp->feature_details_data->reading_frame > 3)
  {
    if (!SetBestFrameByLocation (sfp)) {
      str [0] = '\0';
      if (IS_Bioseq (nsep)) {
        bsp = (BioseqPtr) nsep->data.ptrvalue;
        if (bsp != NULL) {
          sip = SeqIdFindBest (bsp->id, 0);
          SeqIdWrite (sip, str, PRINTID_REPORT, sizeof (str) - 1);
        }
      }
      (afp->errcount)++;
      ValNodeCopyStr (&(afp->ambigList), 0, str);
    }
  }
  else
  {
    crp->frame = afp->feature_details_data->reading_frame;
  }

  /* Create corresponding protein sequence data for the CDS */

  bs = ProteinFromCdRegionEx (sfp, TRUE, FALSE);
  if (NULL == bs)
    return;

  prot = BSMerge (bs, NULL);
  bs = BSFree (bs);
  if (NULL == prot)
    return;

  ptr = prot;
  ch = *ptr;
  while (ch != '\0') {
    *ptr = TO_UPPER (ch);
    ptr++;
    ch = *ptr;
  }
  i = (Int2) StringLen (prot);
  if (i > 0 && prot [i - 1] == '*') {
    prot [i - 1] = '\0';
  }
  bs = BSNew (1000);
  if (bs != NULL) {
    ptr = prot;
    BSWrite (bs, (VoidPtr) ptr, (Int4) StringLen (ptr));
  }

  /* Create the product protein Bioseq */
  
  bsp = BioseqNew ();
  if (NULL == bsp)
    return;
  
  bsp->repr = Seq_repr_raw;
  bsp->mol = Seq_mol_aa;
  bsp->seq_data_type = Seq_code_ncbieaa;
  bsp->seq_data = (SeqDataPtr) bs;
  bsp->length = BSLen (bs);
  bs = NULL;
  old = SeqEntrySetScope (sep);
  bsp->id = MakeNewProteinSeqId (sfp->location, NULL);
  SeqMgrAddToBioseqIndex (bsp);
  SeqEntrySetScope (old);
  
  /* Create a new SeqEntry for the Prot Bioseq */
  
  psep = SeqEntryNew ();
  if (NULL == psep)
    return;
  
  psep->choice = 1;
  psep->data.ptrvalue = (Pointer) bsp;
  SeqMgrSeqEntry (SM_BIOSEQ, (Pointer) bsp, psep);
  
  /* Add a descriptor to the protein Bioseq */
  
  mip = MolInfoNew ();
  if (NULL == mip)
    return;
  
  mip->biomol = 8;
  mip->tech = 8;
  if (afp->noLeft && afp->noRight) {
    mip->completeness = 5;
  } else if (afp->noLeft) {
    mip->completeness = 3;
  } else if (afp->noRight) {
    mip->completeness = 4;
  }
  vnp = CreateNewDescriptor (psep, Seq_descr_molinfo);
  if (NULL == vnp)
    return;
  
  vnp->data.ptrvalue = (Pointer) mip;
  
  /**/
  
  descr = ExtractBioSourceAndPubs (parent_sep);

  AddSeqEntryToSeqEntry (parent_sep, psep, TRUE);
  nsep = FindNucSeqEntry (parent_sep);
  ReplaceBioSourceAndPubs (parent_sep, descr);
  SetSeqFeatProduct (sfp, bsp);
  
  /* create a full-length protein feature for the new protein sequence */
  if (! StringHasNoText (afp->feature_details_data->protName) 
      && ! StringHasNoText (afp->feature_details_data->protDesc))
  {
    prp = CreateNewProtRef (afp->feature_details_data->protName, 
                            afp->feature_details_data->protDesc, 
                            NULL, NULL);
  }
  else if (!StringHasNoText (afp->feature_details_data->protName))
  {
    prp = CreateNewProtRef (afp->feature_details_data->protName, NULL, NULL, NULL);
  }
  else if (!StringHasNoText (afp->feature_details_data->protDesc))
  {
    prp = CreateNewProtRef (NULL, afp->feature_details_data->protDesc, NULL, NULL);
  }
  else
  { 
    prp = ProtRefNew ();
  }
  
  if (prp != NULL) {
    prot_sfp = CreateNewFeature (psep, NULL, SEQFEAT_PROT, NULL);
    if (prot_sfp != NULL) {
      prot_sfp->data.value.ptrvalue = (Pointer) prp;
      SetSeqLocPartial (prot_sfp->location, afp->noLeft, afp->noRight);
      prot_sfp->partial = (afp->noLeft || afp->noRight);
    }
  }
  
  /* after the feature has been created, then adjust it for gaps */
  /* Note - this step may result in multiple coding regions being created. */
  AdjustCDSLocationsForUnknownGapsCallback (sfp, NULL);

  /* if requested, also add mRNA feature */
  if (afp->also_add_mRNA_btn != NULL && GetStatus(afp->also_add_mRNA_btn)) {
    mRNA_sfp = CreateNewFeature (nsep, NULL, SEQFEAT_RNA, NULL); 
    rrp = RnaRefNew ();
    if (rrp != NULL) {
      rrp->type = 2;
      if (!StringHasNoText(afp->feature_details_data->protName)) {
          rrp->ext.choice = 1;
          rrp->ext.value.ptrvalue = StringSave (afp->feature_details_data->protName);
      }    
    }
	mRNA_sfp->data.value.ptrvalue = rrp;
    SetApplyFeatureLocation (mRNA_sfp, afp);
    AdjustCDSLocationsForUnknownGapsCallback (mRNA_sfp, NULL);
  }

  /* Create a Gene ref feature on the nuc seq or segment */
  /* we can only create a feature where the sep->choice is 1 */
  if (sep->choice == 1)
  {
    gene_sep = sep;
  }
  else
  {
    gene_sep = nsep;
  }

  if (! StringHasNoText (afp->feature_details_data->geneName)) {
    if (entityID > 0 
        && suppressDups
        && AlreadyHasFeatOrDesc (gene_sep, SEQFEAT_GENE, 0, 0))
    {
      return;
    }

    ApplyGene (afp->feature_details_data->geneName, afp, gene_sep, sfp);
  }
}

static void CheckTitle (SeqEntryPtr sep, GetSamplePtr gsp)
{
  BioseqPtr         bsp;
  BioseqSetPtr      bssp = NULL, part_set;
  SeqDescrPtr       sdp;
  
  if (sep == NULL || sep->data.ptrvalue == NULL
      || gsp == NULL)
  {
    return;
  }
  
  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL 
        && bssp->_class == BioseqseqSet_class_nuc_prot 
        && bssp->seq_set != NULL) 
    {
      sep = bssp->seq_set;
    }
  }
  
  if (IS_Bioseq_set (sep))
  {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL 
        && bssp->_class == BioseqseqSet_class_segset 
        && bssp->seq_set != NULL) 
    {
      /* first do parts */
      if (bssp->seq_set->next != NULL 
          && IS_Bioseq_set (bssp->seq_set->next)
          && bssp->seq_set->next->data.ptrvalue != NULL)
      {
        part_set = (BioseqSetPtr) bssp->seq_set->next->data.ptrvalue;
        if (part_set->_class == BioseqseqSet_class_parts)
        {
          for (sep = part_set->seq_set; sep != NULL; sep = sep->next)
          {
            CheckTitle (sep, gsp);
          }
        }
      }
      
      /* now do master */
      sep = bssp->seq_set;
    }
  }  

  if (!IS_Bioseq (sep))
  {
    return;
  }
    
  bsp = (BioseqPtr) sep->data.ptrvalue;
  sdp = bsp->descr;
  while (sdp != NULL && sdp->choice != Seq_descr_title)
  {
    sdp = sdp->next;
  }
  if (sdp != NULL)
  {
    gsp->num_found ++;
    if (gsp->sample_text == NULL)
    {
      gsp->sample_text = StringSave (sdp->data.ptrvalue);
    }
    else if (StringCmp (gsp->sample_text, sdp->data.ptrvalue) != 0)
    {
      gsp->all_same = FALSE;
    }
  }
  
  
}

static void ApplyOneTitle (SeqEntryPtr sep, ExistingTextPtr etp, CharPtr defline)
{
  BioseqPtr         bsp;
  BioseqSetPtr      bssp, part_set;
  SeqDescrPtr       sdp;
  
  if (sep == NULL || sep->data.ptrvalue == NULL
      || StringHasNoText (defline))
  {
    return;
  }
  
  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL 
        && bssp->_class == BioseqseqSet_class_nuc_prot 
        && bssp->seq_set != NULL) 
    {
      sep = bssp->seq_set;
    }
  }
  
  if (IS_Bioseq_set (sep))
  {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL 
        && bssp->_class == BioseqseqSet_class_segset 
        && bssp->seq_set != NULL) 
    {
      /* first do parts */
      if (bssp->seq_set->next != NULL 
          && IS_Bioseq_set (bssp->seq_set->next)
          && bssp->seq_set->next->data.ptrvalue != NULL)
      {
        part_set = (BioseqSetPtr) bssp->seq_set->next->data.ptrvalue;
        if (part_set->_class == BioseqseqSet_class_parts)
        {
          for (sep = part_set->seq_set; sep != NULL; sep = sep->next)
          {
            ApplyOneTitle (sep, etp, defline);
          }
        }
      }
      
      /* now do master */
      sep = bssp->seq_set;
    }
  }    

  if (!IS_Bioseq (sep))
  {
    return;
  }
    
  bsp = (BioseqPtr) sep->data.ptrvalue;
  sdp = bsp->descr;
  while (sdp != NULL && sdp->choice != Seq_descr_title)
  {
    sdp = sdp->next;
  }

  if (sdp == NULL)
  {
    sdp = CreateNewDescriptor (sep, Seq_descr_title);
  }
  
  if (sdp != NULL) {
    sdp->data.ptrvalue = HandleExistingText (sdp->data.ptrvalue,
                                             StringSave (defline), etp);
  }
  RemoveAutodefObjects(sep);
}


/*---------------------------------------------------------------------*/
/*                                                                     */
/* RealApplyBioFeatToAll () --                                         */
/*                                                                     */
/*---------------------------------------------------------------------*/

static void RealApplyBioFeatToAll (Uint2        entityID,
				                           SeqEntryPtr  sep,
				                           SeqEntryPtr  nsep,
                                   ApplyFormPtr afp,
				                           Boolean      suppressDups)

{
  ImpFeatPtr         ifp;
  SeqFeatPtr         sfp = NULL;
  SeqFeatPtr         gene_sfp;
  Boolean            put_comment_on_gene = FALSE;

  /* Check parameters */

  if (sep == NULL || nsep == NULL || afp == NULL)
    return;

  /* Add a title feature */

  if (afp->type == CHECK_TITLE)
  {
    CheckTitle (sep, afp->gsp);
    return;
  }
  else if (afp->type == ADD_TITLE) 
  {
    if (entityID == 0 && SeqEntryGetTitle (sep) != NULL)
    {
      return;
    }
    ApplyOneTitle (sep, afp->etp, afp->feature_details_data->defline);
    return;
  }

  /* Add a CDS feature */

  else if (afp->type == ADD_CDS)
    Apply_AddCDS (entityID, sep, nsep, afp, suppressDups);

  /* Add an rRNA feature */

  else if (afp->type == ADD_RRNA) {
    if (suppressDups && entityID > 0 
        && AlreadyHasRNA (sep, afp->feature_details_data->rnaType)) {
      return;
    }
   
    sfp = CreateNewFeature (nsep, NULL, SEQFEAT_RNA, NULL);
    ApplyRnaTypeToSeqFeat (sfp, afp->feature_details_data->rnaType);
    if (! StringHasNoText (afp->feature_details_data->rnaName)) {
      ApplyProductToRNA (sfp, afp->feature_details_data->rnaName);
    }
    
    SetApplyFeatureLocation (sfp, afp);      
    AddToComment (sfp, afp->feature_details_data->featcomment);

    if (! StringHasNoText (afp->feature_details_data->geneName)) {
      if (entityID > 0 
        && suppressDups
	      && AlreadyHasFeatOrDesc (sep, SEQFEAT_GENE, 0, 0))
      {
	       return;        
      }
      ApplyGene (afp->feature_details_data->geneName, afp, nsep, sfp);

    }
  }

  /* Add an Import feature */

  else if (afp->type == ADD_IMP) {
    if (afp->feature_details_data->featdef_choice == FEATDEF_GENE)
    {
      put_comment_on_gene = TRUE;
    }
    else 
    {
      if (AlreadyHasImpFeat (sep, afp->feature_details_data->featdef_name) && suppressDups)
      {
	       return;        
      }

      ifp = ImpFeatNew ();
      if (ifp != NULL) {
        ifp->key = StringSave (afp->feature_details_data->featdef_name);
        sfp = CreateNewFeature (nsep, NULL, SEQFEAT_IMP, NULL);
        if (sfp != NULL) {
          sfp->data.value.ptrvalue = (Pointer) ifp;
          SetApplyFeatureLocation (sfp, afp);
          if (! StringHasNoText (afp->feature_details_data->featcomment)) 
          {
            sfp->comment = StringSave (afp->feature_details_data->featcomment);
          }
          sfp->qual = DialogToPointer (afp->gbquals);
        }
      }
    }
    if (! StringHasNoText (afp->feature_details_data->geneName) 
		|| ! StringHasNoText (afp->feature_details_data->geneDesc)) 
    {
      if (entityID > 0 
          && suppressDups
	        && AlreadyHasFeatOrDesc (sep, SEQFEAT_GENE, 0, 0)) 
      {
	      return;        
      }
      gene_sfp = ApplyGene (afp->feature_details_data->geneName, afp, nsep, sfp);

      if (gene_sfp != NULL && ! StringHasNoText (afp->feature_details_data->featcomment) && put_comment_on_gene)
      {
        gene_sfp->comment = StringSave (afp->feature_details_data->featcomment);
      }
    }
  }
}

/*---------------------------------------------------------------------*/
/*                                                                     */
/* ApplyBioFeatToRaw () --                                             */
/*                                                                     */
/*---------------------------------------------------------------------*/

static void ApplyBioFeatToRaw (Uint2        entityID,
			       SeqEntryPtr  parentSep,
			       SeqEntryPtr  sep,
			       ApplyFormPtr afp,
			       Int2         onlythis)

{
  BioseqPtr     bsp;
  BioseqSetPtr  bssp;
  Int2          count;
  SeqEntryPtr   nucSep;

  /* Check parameters */

  if (sep == NULL || afp == NULL)
    return;

  /* If it is a set then recurse until we get to the raw Bioseq */

  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL) {
      if (onlythis != 0 && bssp->_class == BioseqseqSet_class_parts) {
        for (sep = bssp->seq_set, count = 1;
             sep != NULL && count != onlythis;
             sep = sep->next, count++)
	  continue;
        if (sep != NULL) {
          ApplyBioFeatToRaw (entityID, parentSep, sep, afp, onlythis);
        }
      } else {
        for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
          ApplyBioFeatToRaw (entityID, parentSep, sep, afp, onlythis);
        }
      }
      return;
    }
  }

  /* Get the nucleotide Bioseq */

  nucSep = FindNucSeqEntry (sep);
  if (nucSep == NULL)
    return;

  bsp = (BioseqPtr) nucSep->data.ptrvalue;
  if (bsp == NULL)
    return;
  
  /* If we've got a raw Bioseq then do the apply */

  if (bsp->repr == Seq_repr_raw) {
    RealApplyBioFeatToAll (entityID, nucSep, nucSep, afp, FALSE);
    /*
    RealApplyBioFeatToAll (entityID, parentSep, nucSep, afp, FALSE);
    */
  }
}

/*---------------------------------------------------------------------*/
/*                                                                     */
/* ApplyBioFeatToAll () --                                             */
/*                                                                     */
/*---------------------------------------------------------------------*/

static void ApplyBioFeatToAll (Uint2        entityID,
			       SeqEntryPtr  sep,
			       ApplyFormPtr afp)

{
  BioseqSetPtr  bssp;
  SeqEntryPtr   nsep;
  Int2          onlythis;
  Char          str [32];
  Boolean       suppressDups = FALSE;

  /* Check parameters */

  if (sep == NULL || afp == NULL)
    return;
  
  if (afp->add_to_seq_with_like_feature != NULL)
  {
    suppressDups = ! GetStatus (afp->add_to_seq_with_like_feature);
  }

  /* If it is a set then recurse until we get to a Bioseq */

  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL) {
      if (bssp->_class == BioseqseqSet_class_genbank || IsPopPhyEtcSet (bssp->_class)) {
        for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
          ApplyBioFeatToAll (entityID, sep, afp);
        }
        return;
      } else if (bssp->_class == BioseqseqSet_class_gen_prod_set) {
        /* just the first item */
        ApplyBioFeatToAll (entityID, bssp->seq_set, afp);
        return;
      }
    }
  }

  /* Get the nucleotide Bioseq */

  nsep = FindNucSeqEntry (sep);
  if (nsep == NULL)
    return;

  /* Apply the feature */

  if (afp->applyToParts != NULL && GetStatus (afp->applyToParts)) {
    GetTitle (afp->onlyThisPart, str, sizeof (str));
    if (! StrToInt (str, &onlythis)) {
      onlythis = 0;
    }
    ApplyBioFeatToRaw (entityID, sep, sep, afp, onlythis);
  } else {
    RealApplyBioFeatToAll (entityID, sep, nsep, afp, suppressDups);
  }
}

/*---------------------------------------------------------------------*/
/*                                                                     */
/* ApplyBioFeatToAll () --                                             */
/*                                                                     */
/*---------------------------------------------------------------------*/

static void ApplyBioFeatToList (Uint2        entityID,
			       SeqEntryPtr  sep,
			       ApplyFormPtr afp,
			       ValNodePtr   id_list)

{
  BioseqPtr     bsp;
  SeqEntryPtr   nsep, oldscope;
  Int2          onlythis;
  Char          str [32];
  Boolean       suppressDups = FALSE;
  ValNodePtr    vnp;

  /* Check parameters */

  if (sep == NULL || afp == NULL || id_list == NULL)
    return;
  
  if (afp->add_to_seq_with_like_feature != NULL)
  {
    suppressDups = ! GetStatus (afp->add_to_seq_with_like_feature);
  }

  oldscope = SeqEntrySetScope (sep);
  for (vnp = id_list; vnp != NULL; vnp = vnp->next) {
     bsp = BioseqFind (vnp->data.ptrvalue);
     if (bsp != NULL) {
       sep = SeqMgrGetSeqEntryForData (bsp);
       
       /* Get the nucleotide Bioseq */
       nsep = FindNucSeqEntry (sep);
       if (nsep == NULL)
         continue;

       /* Apply the feature */

       if (afp->applyToParts != NULL && GetStatus (afp->applyToParts)) {
         GetTitle (afp->onlyThisPart, str, sizeof (str));
         if (! StrToInt (str, &onlythis)) {
           onlythis = 0;
         }
         ApplyBioFeatToRaw (entityID, sep, sep, afp, onlythis);
       } else {
         RealApplyBioFeatToAll (entityID, sep, nsep, afp, suppressDups);
       }
    }
  }
}


Int2 ApplyAnnotationToAll (Int2 type, SeqEntryPtr sep,
                           ButtoN partialLft, ButtoN partialRgt,
                           TexT geneName, TexT protName, 
                           TexT protDesc, TexT rnaName,
                           TexT featcomment, TexT defline)

{
  ApplyFormData  afd;
  RnaTypeData    rtd;

  MemSet ((Pointer) (&afd), 0, sizeof (ApplyFormData));
  afd.type = type;
  afd.errcount = 0;
  afd.ambigList = NULL;
  afd.partial5 = partialLft;
  afd.partial3 = partialRgt;
  afd.noLeft = GetStatus (afd.partial5);
  afd.noRight = GetStatus (afd.partial3);
  afd.feature_details_data = BatchApplyFeatureDetailsNew ();
  if (afd.feature_details_data == NULL)
  {
    return 1;
  }
  
  if (ADD_RRNA == type)
  {
    rtd.ncrna_class = NULL;
    rtd.rna_featdef = FEATDEF_rRNA;
    afd.feature_details_data->rnaType = &rtd;
  }

  if (geneName != NULL && ! TextHasNoText (geneName))
    afd.feature_details_data->geneName = SaveStringFromText (geneName);
  if (protName != NULL && ! TextHasNoText (protName))
    afd.feature_details_data->protName = SaveStringFromText (protName);
  if (protDesc != NULL && ! TextHasNoText (protDesc))
    afd.feature_details_data->protDesc = SaveStringFromText (protDesc);
  if (rnaName != NULL && ! TextHasNoText (rnaName))
    afd.feature_details_data->rnaName = SaveStringFromText (rnaName);
  if (featcomment != NULL && ! TextHasNoText (featcomment))
    afd.feature_details_data->featcomment = SaveStringFromText (featcomment);
  if (defline != NULL && ! TextHasNoText (defline))
    afd.feature_details_data->defline = SaveStringFromText (defline);
  ApplyBioFeatToAll (0, sep, &afd);
  if (afd.type == ADD_CDS) {
    return afd.errcount;
  }
  return 0;
}

static void CommonApplyToAllProcBfpInfo (Uint2 entityID,
                                         Uint4 itemID,
                                         Uint2 itemtype,
                                         Int2 type);

static void NowReadyToApplyToAll (ApplyFormPtr afp, DialoG gbquals)

{
  Uint2        parenttype;
  Pointer      parentptr;
  CharPtr      plural;
  SeqEntryPtr  sep;
  CharPtr      tmp;
  SeqEntryPtr  top;
  ValNodePtr   vnp;
  Char         path [PATH_MAX];
  FILE         *fp;
  ValNodePtr   id_list = NULL;

  if (afp == NULL) return;
  afp->gbquals = gbquals;
  sep = GetTopSeqEntryForEntityID (afp->input_entityID);
  if (sep == NULL) return;
  Hide (afp->form);
  WatchCursor ();
  Update ();
  afp->noLeft = GetStatus (afp->partial5);
  afp->noRight = GetStatus (afp->partial3);
  top = sep;
  if (afp->type == ADD_CDS) {
    GetSeqEntryParent (top, &parentptr, &parenttype);
  }

  if (afp->all_or_some_grp != NULL && GetValue (afp->all_or_some_grp) == 2) {  
    tmp = SaveStringFromText (afp->accession_list_txt);
    id_list = ParseAccessionNumberListFromString(tmp, sep);
    tmp = MemFree (tmp);
    if (id_list == NULL) {
      ArrowCursor();
      Update();
      Show (afp->form);
      return;
    }
    ApplyBioFeatToList (afp->input_entityID, sep, afp, id_list);
    id_list = FreeSeqIdList (id_list);
    tmp = MemFree (tmp);
  } else {
    ApplyBioFeatToAll (afp->input_entityID, sep, afp);
  }
  ArrowCursor ();
  Update ();
  if (afp->errcount > 0 && afp->type == ADD_CDS) {
    TmpNam (path);
    fp = FileOpen (path, "w");
    if (fp != NULL) {
      if (afp->errcount > 1) {
        plural = "records";
      } else {
        plural = "record";
      }
      fprintf (fp, "Possible ambiguous frames detected in %d %s\n",
               (int) afp->errcount, plural);
      for (vnp = afp->ambigList; vnp != NULL; vnp = vnp->next) {
        tmp = (CharPtr) vnp->data.ptrvalue;
        fprintf (fp, "%s\n", tmp);
      }
      FileClose (fp);
      LaunchGeneralTextViewer (path, "Ambiguous Frames");
      FileRemove (path);
    }
  }
  ObjMgrSetDirtyFlag (afp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, afp->input_entityID, 0, 0);
  if (GetStatus (afp->leaveDlgUp))
  {
    afp->ambigList = ValNodeFreeData (afp->ambigList);
    Show (afp->form);
  }
  else
  {
    Remove (afp->form);
  }
}

typedef struct qualsform {
  FEATURE_FORM_BLOCK

  ApplyFormPtr  afp;
} QualsForm, PNTR QualsFormPtr;

static void CallNowReady (ButtoN b)

{
  QualsFormPtr  qfp;

  qfp = (QualsFormPtr) GetObjectExtra (b);
  if (qfp == NULL) return;
  Hide (qfp->form);
  NowReadyToApplyToAll (qfp->afp, qfp->gbquals);
  Remove (qfp->form);
}


static void DoTheApplyToAllProc (ButtoN b)

{
  ApplyFormPtr       afp;
  CharPtr            name;
  QualsFormPtr       qfp;
  WindoW             w;

  afp = GetObjectExtra (b);
  if (afp == NULL) {
    Remove (ParentWindow (b));
    return;
  }

  afp->feature_details_data = DialogToPointer (afp->feature_details_dlg);
  
  if (afp->type == ADD_IMP) {
    /* if gene, do not collect quals */
    if (afp->feature_details_data->featdef_choice != FEATDEF_GENE)
    {      
      qfp = (QualsFormPtr) MemNew (sizeof (QualsForm));
      if (qfp != NULL) {
        Hide (afp->form);
        Update ();
        name = afp->feature_details_data->featdef_name;
        qfp->afp = afp;
        w = FixedWindow (-50, -33, -10, -10, "Qualifiers", StdCloseWindowProc);
        SetObjectExtra (w, qfp, StdCleanupFormProc);
        qfp->form = (ForM) w;
        CreateStandardEditMenu (w);
        qfp->gbquals = NewCreateImportFields (w, name, NULL, FALSE);
        b = PushButton (w, "Okay", CallNowReady);
        SetObjectExtra (b, qfp, NULL);
        AlignObjects (ALIGN_CENTER, (HANDLE) qfp->gbquals, (HANDLE) b, NULL);
        RealizeWindow (w);
        Show (w);
        Select (w);
      }
    } else {
      NowReadyToApplyToAll (afp, NULL);
    }
  }
  else if (afp->type == ADD_TITLE)
  {
    if (StringHasNoText(afp->feature_details_data->defline)) {
        /* do nothing, no defline text */
        ObjMgrSetDirtyFlag(afp->input_entityID, TRUE);
        ObjMgrSendMsg(OM_MSG_UPDATE, afp->input_entityID, 0, 0);
        Remove(afp->form);
    } else if (Message(MSG_OKC, "%s", kDoNotEditTitle) == ANS_CANCEL) {
        /* do nothing */
    } else {
        afp->gsp = GetSampleNew();
        afp->type = CHECK_TITLE;
        NowReadyToApplyToAll(afp, NULL);
        afp->etp = GetExistingTextHandlerInfo(afp->gsp == NULL ? 0 : afp->gsp->num_found, FALSE);
        afp->gsp = GetSampleFree(afp->gsp);
        afp->type = ADD_TITLE;
        if (afp->etp == NULL
            || afp->etp->existing_text_choice != eExistingTextChoiceCancel)
        {
            NowReadyToApplyToAll(afp, NULL);
        }
        afp->etp = MemFree(afp->etp);
    }
  } else {
    NowReadyToApplyToAll (afp, NULL);
  }
}

static void ApplyToPartsProc (ButtoN b)

{
  ApplyFormPtr  afp;

  afp = (ApplyFormPtr) GetObjectExtra (b);
  if (afp == NULL) return;
  if (GetStatus (b)) {
    SafeEnable (afp->onlyThisPart);
  } else {
    SafeDisable (afp->onlyThisPart);
  }
}

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

{
  BioseqSetPtr  bssp;
  BoolPtr       rsult;

  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL && bssp->_class == BioseqseqSet_class_parts) {
      rsult = (BoolPtr) mydata;
      if (rsult != NULL) {
        *rsult = TRUE;
      }
    }
  }
}

extern Boolean HasPartsSet (SeqEntryPtr sep);
extern Boolean HasPartsSet (SeqEntryPtr sep)

{
  Boolean  rsult = FALSE;

  SeqEntryExplore (sep, (Pointer) (&rsult), LookForParts);
  return rsult;
}

static void EnableApplyCdsCoords (GrouP g)
{
  ApplyFormPtr afp;
  
  afp = (ApplyFormPtr) GetObjectExtra (g);
  if (afp == NULL) return;
  if (GetValue (g) == 1)
  {
  	Disable (afp->left_end);
  	Disable (afp->right_end);
  }
  else
  {
  	Enable (afp->left_end);
  	Enable (afp->right_end);
  }
}

static void ApplyMessageProc (ForM f, Int2 mssg)

{
  ApplyFormPtr  afp;

  afp = (ApplyFormPtr) GetObjectExtra (f);
  if (afp != NULL) {
    if (afp->appmessage != NULL) {
      afp->appmessage (f, mssg);
    }
  }
}

static void CleanupApplyToAllForm (GraphiC g, VoidPtr data)

{
  ApplyFormPtr  afp;

  afp = (ApplyFormPtr) data;
  if (afp != NULL) {
    afp->feature_details_data = BatchApplyFeatureDetailsFree (afp->feature_details_data);
    ValNodeFreeData (afp->ambigList);
  }
  StdCleanupFormProc (g, data);
}

static void ChangeAllOrSome (GrouP g)
{
  ApplyFormPtr       afp;

  afp = (ApplyFormPtr) GetObjectExtra (g);
  if (afp != NULL) {
    if (GetValue(g) == 1) {
      Disable (afp->accession_list_txt);
    } else {
      Enable (afp->accession_list_txt);
    }
  }
}


static void EnableApplyFeatureAccept (Pointer data)
{
  ApplyFormPtr afp;

  afp = (ApplyFormPtr) data;
  if (afp == NULL) return;

  if (OkToAcceptBatchApplyFeatureDetails (afp->feature_details_dlg))
  {
    Enable (afp->accept);
  } 
  else
  {
    Disable (afp->accept);
  }
}


static void CommonApplyToAllProcBfpInfo (Uint2 entityID,
                                         Uint4 itemID,
                                         Uint2 itemtype,
                                         Int2 type)
{
  ApplyFormPtr       afp;
  GrouP              c;
  GrouP              g = NULL;
  GrouP              h;
  GrouP              r2, r3, r4;
  SeqEntryPtr        sep;
  StdEditorProcsPtr  sepp;
  WindoW             w;
  GrouP              x;
  GrouP              parts_group = NULL;
  GrouP              feature_details = NULL;
  GrouP              indexer_only_group = NULL;

  sep = GetTopSeqEntryForEntityID (entityID);
  if (sep == NULL) return;
  afp = (ApplyFormPtr) MemNew (sizeof (ApplyFormData));
  if (afp == NULL) return;
  w = FixedWindow (-50, -33, -10, -10, "Automatic Processing", StdCloseWindowProc);
  SetObjectExtra (w, afp, CleanupApplyToAllForm);
  afp->form = (ForM) w;
  afp->formmessage = ApplyMessageProc;

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

  afp->input_entityID = entityID;
  afp->input_itemID = itemID;
  afp->input_itemtype = itemtype;

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

  afp->type = type;
  afp->errcount = 0;

  if (HasPartsSet (sep)) {
    parts_group = HiddenGroup (h, 1, 0, NULL);
    afp->applyToParts = CheckBox (parts_group, "Apply to segmented parts, not segmented sequence", ApplyToPartsProc);
    SetObjectExtra (afp->applyToParts, afp, NULL);
    x = HiddenGroup (parts_group, 2, 0, NULL);
    StaticPrompt (x, "Apply only to particular numbered segment", 0, dialogTextHeight, programFont, 'l');
    afp->onlyThisPart = DialogText (x, "", 4, NULL);
    Disable (afp->onlyThisPart);
  }

  afp->strand_group = NULL;
  afp->add_to_seq_with_like_feature = NULL;
  afp->also_add_mRNA_btn = NULL;

  if (type == ADD_CDS || type == ADD_RRNA || type == ADD_IMP) {
    /* create group to hold feature details */
    feature_details = HiddenGroup (h, -1, 0, NULL);
    
    /* group for feature completeness */
    g = HiddenGroup (feature_details, 2, 0, NULL);
    afp->partial5 = CheckBox (g, "Incomplete at 5' end", NULL);
    afp->partial3 = CheckBox (g, "Incomplete at 3' end", NULL);
    
    /* group for strand */
    afp->strand_group = HiddenGroup (feature_details, 2, 0, NULL);
    SetObjectExtra (afp->strand_group, afp, NULL);
    RadioButton (afp->strand_group, "Plus Strand");
    RadioButton (afp->strand_group, "Minus Strand");
    SetValue (afp->strand_group, 1);
    
    /* coordinates */
    if (indexerVersion)
    {
      indexer_only_group = HiddenGroup (feature_details, -1, 0, NULL);
      r2 = HiddenGroup (indexer_only_group, 5, 0, NULL);
      afp->use_whole_interval = HiddenGroup (r2, 0, 2, EnableApplyCdsCoords);
      SetObjectExtra (afp->use_whole_interval, afp, NULL);
      RadioButton (afp->use_whole_interval, "Use Whole Sequence Interval");
      RadioButton (afp->use_whole_interval, "Use these coordinates:");
      r3 = HiddenGroup (r2, 0, 2, NULL);
      StaticPrompt (r3, "", 0, dialogTextHeight, programFont, 'l');
      r4 = HiddenGroup (r3, 4, 0, NULL);
      StaticPrompt (r4, "From", 0, dialogTextHeight, programFont, 'l');
      afp->left_end = DialogText (r4, "1", 5, NULL);
      StaticPrompt (r4, "To", 0, dialogTextHeight, programFont, 'l');
      afp->right_end = DialogText (r4, "1", 5, NULL);
      SetValue (afp->use_whole_interval, 1);
      Disable (afp->left_end);
      Disable (afp->right_end);
      
      /* apply to some sequences or all sequences */
      afp->all_or_some_grp = HiddenGroup (indexer_only_group, 1, 0, ChangeAllOrSome);
      SetObjectExtra (afp->all_or_some_grp, afp, NULL);
      RadioButton (afp->all_or_some_grp, "Apply to all sequences");
      RadioButton (afp->all_or_some_grp, "Apply to sequences in this list");
      afp->accession_list_txt = DialogText (afp->all_or_some_grp, "", 25, NULL);
      SetValue (afp->all_or_some_grp, 1);
      ChangeAllOrSome(afp->all_or_some_grp);
      
      /* add to features that already have same */
      if (type == ADD_CDS)
      {
        afp->add_to_seq_with_like_feature = CheckBox (indexer_only_group, "Also add to sequences that already have a CDS", NULL);
        afp->also_add_mRNA_btn = CheckBox (indexer_only_group, "Also add mRNA", NULL);
        SetStatus (afp->also_add_mRNA_btn, FALSE);
      }
      else if (type == ADD_RRNA)
      {
        afp->add_to_seq_with_like_feature = CheckBox (indexer_only_group, "Also add to sequences that already have an rRNA", NULL);
      }
      else if (type == ADD_IMP)
      {
        afp->add_to_seq_with_like_feature = CheckBox (indexer_only_group, "Also add to sequences that already have this feature", NULL);
      }
      SafeSetStatus (afp->add_to_seq_with_like_feature, TRUE);
      AlignObjects (ALIGN_CENTER, (HANDLE) r2,
                                   (HANDLE) afp->add_to_seq_with_like_feature,
                                   (HANDLE) afp->also_add_mRNA_btn,
                                   NULL);
    }
    else
    {
      afp->use_whole_interval = NULL;
      afp->all_or_some_grp = NULL;
      afp->accession_list_txt = NULL;
    }
    
    AlignObjects (ALIGN_CENTER, (HANDLE) g,
                                (HANDLE) afp->strand_group,
                                (HANDLE) indexer_only_group,
                                NULL);
  }
  afp->feature_details_dlg = BatchApplyFeatureDetailsDialog (h, type, EnableApplyFeatureAccept, afp);
        

  afp->gbquals = NULL;

  c = HiddenGroup (h, 4, 0, NULL);
  afp->accept = DefaultButton (c, "Accept", DoTheApplyToAllProc);
  SetObjectExtra (afp->accept, afp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  afp->leaveDlgUp = CheckBox (c, "Leave Dialog Up", NULL);

  if (parts_group == NULL)
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) c,
                                (HANDLE) afp->feature_details_dlg,    
                                (HANDLE) feature_details, 
                                NULL);
  }
  else
  {
    AlignObjects (ALIGN_CENTER, (HANDLE) parts_group,
                                (HANDLE) c, 
                                (HANDLE) afp->feature_details_dlg,
                                (HANDLE) feature_details, 
                                NULL);
  }
  
  EnableApplyFeatureAccept (afp);
  RealizeWindow (w);
  Show (w);
  SendMessageToDialog (afp->feature_details_dlg, VIB_MSG_ENTER);
  Update ();
}

extern void CommonApplyToAllProc (BaseFormPtr bfp, Int2 type)
{
  if (bfp == NULL) return;
  CommonApplyToAllProcBfpInfo (bfp->input_entityID,
                               bfp->input_itemID,
                               bfp->input_itemtype, type);
}

static void CommonApplyToAllProcMenuItem (IteM i, Int2 type)
{
  BaseFormPtr bfp;

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

  CommonApplyToAllProc (bfp, type);
}


extern void ApplyCDSBtn (ButtoN b)
{
  BaseFormPtr bfp;

  bfp = (BaseFormPtr) GetObjectExtra (b);
  if (bfp == NULL) {
    return;
  }
  CommonApplyToAllProc (bfp, ADD_CDS);
}


extern void ApplyTitle (IteM i)

{
  CommonApplyToAllProcMenuItem (i, ADD_TITLE);
}

extern void ApplyCDS (IteM i)

{
  CommonApplyToAllProcMenuItem (i, ADD_CDS);
}

extern void ApplyRRNA (IteM i)

{
  CommonApplyToAllProcMenuItem (i, ADD_RRNA);
}

extern void ApplyImpFeat (IteM i)

{
  CommonApplyToAllProcMenuItem (i, ADD_IMP);
}

#define SUBMISSION_PAGE   0
#define CONTACT_PAGE      1
#define AUTHOR_PAGE       2
#define AFFILIATION_PAGE  3

typedef struct submitform {
  FORM_MESSAGE_BLOCK
  GrouP           pages [4];
  Int2            currentPage;
  DialoG          tbs;

  GrouP           hup;
  GrouP           dateGrp;

  TexT            title;
  DialoG          reldate;
  TexT            firstname;
  TexT            middleinit;
  TexT            lastname;
  PopuP           suffix;
  DialoG          phonefaxemail;
  DialoG          authors;
  ButtoN          use_consortium;
  TexT            consortium;
  DialoG          affil;

  Boolean         visitedContact;
  Boolean         visitedAuthor;

  SeqDescrPtr     descriptors;

  ButtoN          nextBtn;
  ButtoN          prevBtn;
  BtnActnProc     goToNext;
  BtnActnProc     goToPrev;
} SubmitForm, PNTR SubmitFormPtr;

static AuthListPtr AddConsortiumToAuthList (AuthListPtr alp, TexT consortium)

{
  AuthorPtr    ap;
  ValNodePtr   names;
  PersonIdPtr  pid;

  if (TextHasNoText (consortium)) return alp;
  if (alp == NULL) {
    alp = AuthListNew ();
    alp->choice = 1;
  }
  pid = PersonIdNew ();
  if (pid == NULL) return NULL;
  pid->choice = 5;
  pid->data = SaveStringFromText (consortium);
  ap = AuthorNew ();
  if (ap == NULL) return NULL;
  ap->name = pid;
  names = ValNodeAdd (&(alp->names));
  names->choice = 1;
  names->data.ptrvalue = ap;
  return alp;
}

static Boolean AuthListToConsortium (AuthListPtr alp, TexT consortium)

{
  AuthorPtr    ap;
  ValNodePtr   names;
  PersonIdPtr  pid;
  CharPtr      str;
  Boolean      rval = FALSE;

  if (alp == NULL || consortium == NULL) return FALSE;
  if (alp->choice != 1) return FALSE;
  for (names = alp->names; names != NULL; names = names->next) {
    ap = names->data.ptrvalue;
    if (ap == NULL) continue;
    pid = ap->name;
    if (pid == NULL || pid->choice != 5 || StringHasNoText (pid->data)) continue;
    str = (CharPtr) pid->data;
    SafeSetTitle (consortium, str);
    rval = TRUE;
  }
  return rval;
}

static void SequinBlockPtrToSubmitForm (ForM f, Pointer data)

{
  AuthorPtr       ap;
  DatePtr         dp;
  NameStdPtr      nsp;
  PersonIdPtr     pid;
  SubmitFormPtr   sbfp;
  SequinBlockPtr  sbp;
  CharPtr         str;
  CharPtr         txt;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  sbp = (SequinBlockPtr) data;
  if (sbfp != NULL) {
    sbfp->descriptors = SeqDescrFree (sbfp->descriptors);
    if (sbp != NULL) {
      SafeSetTitle (sbfp->title, sbp->citsubtitle);
      PointerToDialog (sbfp->reldate, (Pointer) sbp->releasedate);
      ap = sbp->contactperson;
      if (ap != NULL) {
        pid = ap->name;
        if (pid != NULL && pid->choice == 2) {
          nsp = pid->data;
          if (nsp != NULL) {
            str = NameStdPtrToAuthorSpreadsheetString (nsp);
            if (str != NULL) {
              txt = ExtractTagListColumn (str, 0);
              SafeSetTitle (sbfp->firstname, txt);
              MemFree (txt);
              txt = ExtractTagListColumn (str, 1);
              SafeSetTitle (sbfp->middleinit, txt);
              MemFree (txt);
              txt = ExtractTagListColumn (str, 2);
              SafeSetTitle (sbfp->lastname, txt);
              MemFree (txt);
              txt = ExtractTagListColumn (str, 3);
              if (! StringHasNoText (txt)) {
                SetEnumPopupByName (sbfp->suffix, name_suffix_alist, txt);
              }
              /*
              SafeSetTitle (sbfp->suffix, txt);
              */
              MemFree (txt);
              MemFree (str);
            }
          }
        }
        PointerToDialog (sbfp->phonefaxemail, (Pointer) ap->affil);
      }
      PointerToDialog (sbfp->authors, (Pointer) sbp->citsubauthors);
      if (AuthListToConsortium (sbp->citsubauthors, sbfp->consortium)) {
        SetStatus (sbfp->use_consortium, TRUE);
        Enable (sbfp->consortium);
      } else {
        SetStatus (sbfp->use_consortium, FALSE);
        Disable (sbfp->consortium);
      }

      PointerToDialog (sbfp->affil, (Pointer) sbp->citsubaffil);
      if (sbp->holduntilpublished) {
        SafeSetValue (sbfp->hup, 2);
        SafeShow (sbfp->dateGrp);
      } else {
        SafeSetValue (sbfp->hup, 1);
        SafeHide (sbfp->dateGrp);
      }
      sbfp->visitedAuthor = TRUE;
      SetValue (sbfp->tbs, 0);
      sbfp->descriptors = (SeqDescrPtr) AsnIoMemCopy (sbp->descriptors, (AsnReadFunc)SeqDescrAsnRead, (AsnWriteFunc) SeqDescrAsnWrite);
    } else {
      SafeSetTitle (sbfp->title, NULL);
      dp = DateCurr ();
      if (dp != NULL) {
        dp->data [3] = 0; /* force to end of month */
        dp = DateAdvance (dp, 12);
        /*
        (dp->data [1])++;
        if (dp->data [2] == 2 && dp->data [3] > 28) {
          dp->data [3] = 28;
        }
        */
        PointerToDialog (sbfp->reldate, (Pointer) dp);
      } else {
        PointerToDialog (sbfp->reldate, NULL);
      }
      DateFree (dp);
      SafeSetTitle (sbfp->firstname, "");
      SafeSetTitle (sbfp->middleinit, "");
      SafeSetTitle (sbfp->lastname, "");
      PointerToDialog (sbfp->phonefaxemail, NULL);
      PointerToDialog (sbfp->authors, NULL);
      SafeSetTitle (sbfp->consortium, "");
      PointerToDialog (sbfp->affil, NULL);
      SafeSetValue (sbfp->hup, 1);
      SafeHide (sbfp->dateGrp);
      SetValue (sbfp->tbs, 0);
    }
  }
}


static CharPtr ContactNameFromSubmitForm (SubmitFormPtr sbfp)
{
  Char            str [128];
  CharPtr         txt;
  Uint2           suffixVal;
  Char            sfx [32];

  if (sbfp == NULL) {
    return NULL;
  }

  str [0] = '\0';
  txt = SaveStringFromText (sbfp->firstname);
  StringCat (str, txt);
  StringCat (str, "\t");
  MemFree (txt);
  txt = SaveStringFromText (sbfp->middleinit);
  StringCat (str, txt);
  StringCat (str, "\t");
  MemFree (txt);
  txt = SaveStringFromText (sbfp->lastname);
  StringCat (str, txt);
  StringCat (str, "\t");
  MemFree (txt);
  suffixVal = GetValue (sbfp->suffix);
  sprintf (sfx, "%d", (int) (suffixVal - 1));
  StringCat (str, sfx);
  StringCat (str, "\n");
  txt = StringSave (str);
  return txt;
}


static void FixSubmitFormSpecialCharacters (SubmitFormPtr sbfp)
{
  ValNodePtr list = NULL;
  CharPtr         citsubtitle;
  AffilPtr        affil;
  CharPtr         firstname;
  CharPtr         middleinit;
  CharPtr         lastname;

  if (sbfp == NULL) {
    return;
  }

  citsubtitle = SaveStringFromText (sbfp->title);
  SpecialCharFindWithContext (&citsubtitle, &list, NULL, NULL);

  firstname = SaveStringFromText (sbfp->firstname);
  middleinit = SaveStringFromText (sbfp->middleinit);
  lastname = SaveStringFromText (sbfp->lastname);

  SpecialCharFindWithContext (&firstname, &list, NULL, NULL);
  SpecialCharFindWithContext (&middleinit, &list, NULL, NULL);
  SpecialCharFindWithContext (&lastname, &list, NULL, NULL);

  affil = (AffilPtr) DialogToPointer (sbfp->phonefaxemail);
  if (affil != NULL) {
    SpecialCharFindWithContext (&(affil->fax), &list, NULL, NULL);
    SpecialCharFindWithContext (&(affil->phone), &list, NULL, NULL);
    SpecialCharFindWithContext (&(affil->email), &list, NULL, NULL);
  }

  if (list != NULL) {
    FixSpecialCharactersForStringsInList (list, "You must replace all non-ASCII characters.", TRUE);
    SetTitle (sbfp->title, citsubtitle);
    SetTitle (sbfp->firstname, firstname);
    SetTitle (sbfp->middleinit, middleinit);
    SetTitle (sbfp->lastname, lastname);
    PointerToDialog (sbfp->phonefaxemail, affil);
    FreeContextList (list);
  }

  citsubtitle = MemFree (citsubtitle);
  firstname = MemFree (firstname);
  middleinit = MemFree (middleinit);
  lastname = MemFree (lastname);
  affil = AffilFree (affil);
}


static Pointer SubmitFormToSequinBlockPtr (ForM f)

{
  AffilPtr        affil;
  AuthorPtr       ap;
  NameStdPtr      nsp;
  PersonIdPtr     pid;
  SubmitFormPtr   sbfp;
  SequinBlockPtr  sbp;
  CharPtr         txt;

  sbp = NULL;
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    FixSubmitFormSpecialCharacters(sbfp);
    sbp = (SequinBlockPtr) MemNew (sizeof (SequinBlock));
    if (sbp != NULL) {
      sbp->citsubtitle = SaveStringFromTextAndStripNewlines (sbfp->title);
      ap = AuthorNew ();
      if (ap != NULL) {
        pid = PersonIdNew ();
        ap->name = pid;
        if (pid != NULL) {
          pid->choice = 2;
          txt = ContactNameFromSubmitForm (sbfp);
          nsp = AuthorSpreadsheetStringToNameStdPtr (txt);
          MemFree (txt);
          pid->data = nsp;
          if (nsp != NULL) {
            if (StringHasNoText (nsp->names [0])) {
              ap = AuthorFree (ap);
            }
          }
        }
        affil = (AffilPtr) DialogToPointer (sbfp->phonefaxemail);
        if (affil != NULL) {
          if (affil->choice == 2) {
            affil->affil = MemFree (affil->affil);
            affil->div = MemFree (affil->div);
            affil->city = MemFree (affil->city);
            affil->sub = MemFree (affil->sub);
            affil->country = MemFree (affil->country);
            affil->street = MemFree (affil->street);
            affil->postal_code = MemFree (affil->postal_code);
            if (affil->phone == NULL && affil->fax == NULL &&
                affil->email == NULL) {
              affil = AffilFree (affil);
            }
          } else {
            affil = AffilFree (affil);
          }
        }
        if (affil != NULL) {
          if (ap == NULL) {
            ap = AuthorNew();
          }
          ap->affil = affil;
        }
      }
      sbp->contactperson = ap;
      sbp->citsubauthors = (AuthListPtr) DialogToPointer (sbfp->authors);
      if (GetStatus (sbfp->use_consortium)) {
        sbp->citsubauthors = AddConsortiumToAuthList (sbp->citsubauthors, sbfp->consortium);
      }
      sbp->citsubaffil = (AffilPtr) DialogToPointer (sbfp->affil);
      if (GetValue (sbfp->hup) == 2) {
        sbp->holduntilpublished = TRUE;
        sbp->releasedate = (DatePtr) DialogToPointer (sbfp->reldate);
      }
      sbp->descriptors = (SeqDescrPtr) AsnIoMemCopy (sbfp->descriptors, (AsnReadFunc)SeqDescrAsnRead, (AsnWriteFunc) SeqDescrAsnWrite);
      if (sbp->contactperson == NULL &&
          sbp->citsubauthors == NULL &&
          sbp->citsubaffil == NULL &&
          sbp->descriptors == NULL) {
        sbp = SequinBlockFree (sbp);
      }
    }
  }
  return (Pointer) sbp;
}

static void SubmitBlockPtrToSubmitForm (ForM f, Pointer data)

{
  AuthorPtr       ap;
  AuthListPtr     authors;
  ContactInfoPtr  cip;
  CitSubPtr       csp;
  DatePtr         dp;
  NameStdPtr      nsp;
  PersonIdPtr     pid;
  SubmitFormPtr   sbfp;
  SubmitBlockPtr  sbp;
  CharPtr         str;
  CharPtr         txt;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  sbp = (SubmitBlockPtr) data;
  if (sbfp != NULL) {
    if (sbp != NULL) {
      PointerToDialog (sbfp->reldate, (Pointer) sbp->reldate);
      cip = sbp->contact;
      if (cip != NULL) {
        ap = cip->contact;
        if (ap != NULL) {
          pid = ap->name;
          if (pid != NULL && pid->choice == 2) {
            nsp = pid->data;
            if (nsp != NULL) {
              str = NameStdPtrToAuthorSpreadsheetString (nsp);
              if (str != NULL) {
                txt = ExtractTagListColumn (str, 0);
                SafeSetTitle (sbfp->firstname, txt);
                MemFree (txt);
                txt = ExtractTagListColumn (str, 1);
                SafeSetTitle (sbfp->middleinit, txt);
                MemFree (txt);
                txt = ExtractTagListColumn (str, 2);
                SafeSetTitle (sbfp->lastname, txt);
                MemFree (txt);
                txt = ExtractTagListColumn (str, 3);
                if (! StringHasNoText (txt)) {
                  SetEnumPopupByName (sbfp->suffix, name_suffix_alist, txt);
                }
                /*
                SafeSetTitle (sbfp->suffix, txt);
                */
                MemFree (txt);
                MemFree (str);
              }
            }
          }
          PointerToDialog (sbfp->phonefaxemail, (Pointer) ap->affil);
        }
      }
      csp = sbp->cit;
      if (csp != NULL) {
        authors = csp->authors;
        if (authors != NULL) {
          PointerToDialog (sbfp->authors, (Pointer) authors);
          if (AuthListToConsortium (authors, sbfp->consortium)) {
            SetStatus (sbfp->use_consortium, TRUE);
            Enable (sbfp->consortium);
          } else {
            SetStatus (sbfp->use_consortium, FALSE);
            Disable (sbfp->consortium);
          }
          PointerToDialog (sbfp->affil, (Pointer) authors->affil);
        }
      }
      if (sbp->hup) {
        SafeSetValue (sbfp->hup, 2);
        SafeShow (sbfp->dateGrp);
      } else {
        SafeSetValue (sbfp->hup, 1);
        SafeHide (sbfp->dateGrp);
      }
      sbfp->visitedAuthor = TRUE;
      SetValue (sbfp->tbs, 0);
    } else {
      SafeSetTitle (sbfp->title, NULL);
      dp = DateCurr ();
      if (dp != NULL) {
        dp->data [3] = 0; /* force to end of month */
        dp = DateAdvance (dp, 12);
        PointerToDialog (sbfp->reldate, (Pointer) dp);
      } else {
        PointerToDialog (sbfp->reldate, NULL);
      }
      DateFree (dp);
      SafeSetTitle (sbfp->firstname, "");
      SafeSetTitle (sbfp->middleinit, "");
      SafeSetTitle (sbfp->lastname, "");
      SafeSetValue (sbfp->suffix, 0);
      PointerToDialog (sbfp->phonefaxemail, NULL);
      PointerToDialog (sbfp->authors, NULL);
      SafeSetTitle (sbfp->consortium, "");
      PointerToDialog (sbfp->affil, NULL);
      SafeSetValue (sbfp->hup, 1);
      SafeHide (sbfp->dateGrp);
      SetValue (sbfp->tbs, 0);
    }
  }
}

static void TitleToSubmitBlockForm(ForM f, Pointer data)

{
  CharPtr         man_title;
  SubmitFormPtr   sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  man_title = (CharPtr) data;
  if (sbfp != NULL) 
  {
    if (man_title == NULL) {
      SetTitle (sbfp->title, "");
    } else {
      SetTitle (sbfp->title, man_title);
    }
  }
}


static void DescriptorsToSubmitBlockForm(ForM f, Pointer data)

{
  SubmitFormPtr   sbfp;
  SeqDescrPtr     sdp_list;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  sdp_list = (SeqDescrPtr) data;
  if (sbfp != NULL) 
  {
    sbfp->descriptors = SeqDescrFree (sbfp->descriptors);
    if (sdp_list != NULL) 
    {
      sbfp->descriptors = AsnIoMemCopy (sdp_list, (AsnReadFunc) SeqDescrAsnRead, (AsnWriteFunc) SeqDescrAsnWrite);
    }
  }
}


static void AddNonPunctFieldToErrList (TexT t, CharPtr name, ValNodePtr PNTR err_list)
{
  CharPtr val, cp;

  if (t == NULL || err_list == NULL) {
    return;
  }
  if (TextHasNoText (t)) {
    *err_list = AddStringToValNodeChain (*err_list, name, TESTRESULT_MISSING);
  } else {
    val = SaveStringFromText (t);
    for (cp = val; *cp != 0 && ispunct (*cp); cp++) {
    }
    if (*cp == 0) {
      *err_list = AddStringToValNodeChain (*err_list, name, TESTRESULT_INVALID);
    }
    val = MemFree (val);
  }
}


static void AddNonPunctStringToErrList (CharPtr val, CharPtr name, ValNodePtr PNTR err_list)
{
  CharPtr cp;
  Boolean any_punct = FALSE;

  if (err_list == NULL) {
    return;
  }
  if (StringHasNoText (val)) {
    *err_list = AddStringToValNodeChain (*err_list, name, TESTRESULT_MISSING);
  } else {
    for (cp = val; *cp != 0 && (unsigned)(*cp + 1) <= 256 && ispunct (*cp); cp++) {
      any_punct = TRUE;
    }
    if (*cp == 0 && any_punct) {
      *err_list = AddStringToValNodeChain (*err_list, name, TESTRESULT_INVALID);
    }
  }
}


static void AddAuthListErrors (AuthListPtr auth, ValNodePtr PNTR err_list)
{
  ValNodePtr name;
	AuthorPtr  ap;
	NameStdPtr  nsp;
	PersonIdPtr  pid;

  if (auth == NULL || err_list == NULL) {
    return;
  }
  if (auth->choice == 1) {
    for (name = auth->names; name != NULL; name = name->next) {
		  ap = name->data.ptrvalue;
		  if (ap != NULL) {
			  pid = ap->name;
			  if (pid != NULL) {
				  if (pid->choice == 2) {
					  nsp = pid->data;
					  if (nsp != NULL) {
              AddNonPunctStringToErrList (nsp->names [0], "author last name", err_list);
              AddNonPunctStringToErrList (nsp->names [1], "author first name", err_list);
					  }
				  }
			  }
		  }
    }
	}
}


static ValNodePtr TestSubmitForm (ForM f)

{
  AffilPtr       affil;
  AuthListPtr    authors;
  DatePtr        dp;
  ValNodePtr     head;
  SubmitFormPtr  sbfp;

  head = NULL;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp == NULL) {
    return NULL;
  }

  AddNonPunctFieldToErrList (sbfp->firstname, "first_name", &head);
  AddNonPunctFieldToErrList (sbfp->firstname, "last name", &head);

  affil = DialogToPointer (sbfp->phonefaxemail);
  if (affil != NULL) {
    if (StringHasNoText (affil->phone)) {
      head = AddStringToValNodeChain (head, "telephone number", TESTRESULT_WARN);
    }
    if (StringHasNoText (affil->fax)) {
      head = AddStringToValNodeChain (head, "fax number", TESTRESULT_WARN);
    }
    if (StringHasNoText (affil->email)) {
      head = AddStringToValNodeChain (head, "e-mail address", TESTRESULT_WARN);
    }
  } else {
    head = AddStringToValNodeChain (head, "telephone number", TESTRESULT_WARN);
    head = AddStringToValNodeChain (head, "fax number", TESTRESULT_WARN);
    head = AddStringToValNodeChain (head, "e-mail address", TESTRESULT_WARN);
  }
  affil = AffilFree (affil);

  if (GetValue (sbfp->hup) == 2) {
    dp = DialogToPointer (sbfp->reldate);
    if (dp == NULL) {
      head = AddStringToValNodeChain (head, "release date", TESTRESULT_MISSING);
    }
    dp = DateFree (dp);
  }
  if (TextHasNoText (sbfp->title)) {
    head = AddStringToValNodeChain (head, "manuscript title", TESTRESULT_MISSING);
  }

  ValNodeLink (&head, TestDialog (sbfp->authors));
  authors = DialogToPointer (sbfp->authors);
  if (GetStatus (sbfp->use_consortium)) {
    authors = AddConsortiumToAuthList (authors, sbfp->consortium);
  }
  if (authors == NULL) {
    head = AddStringToValNodeChain (head, "author names", TESTRESULT_MISSING);
  } else {
    AddAuthListErrors (authors, &head);
  }
  authors = AuthListFree (authors);
  affil = DialogToPointer (sbfp->affil);
  if (affil == NULL) {
    head = AddStringToValNodeChain (head, "affiliation", TESTRESULT_MISSING);
  } else {
    AddNonPunctStringToErrList (affil->affil, "institution", &head);
  }
  affil = AffilFree (affil);

  return head;
}

/*
static Boolean ReadSubmitBlock (ForM f, CharPtr filename)

{
  AsnIoPtr        aip;
  Char            path [PATH_MAX];
  SubmitFormPtr   sbfp;
  SubmitBlockPtr  sbp;

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      aip = AsnIoOpen (path, "r");
      if (aip != NULL) {
        sbp = SubmitBlockAsnRead (aip, NULL);
        AsnIoClose (aip);
        if (sbp != NULL) {
          SubmitBlockPtrToSubmitForm (f, (Pointer) sbp);
          sbp = SubmitBlockFree (sbp);
          Update ();
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}
*/


static Pointer ReadNextASNObject (FILE *fp, Uint2Ptr datatypeptr, Uint2Ptr entityIDptr)
{
  AsnIoPtr       aip;
  Pointer        dataptr = NULL;
  Int4           pos;
  ObjMgrPtr      omp;
  ObjMgrTypePtr  omtp = NULL;
  FileCache      fc;
  CharPtr        str, tag, pEnd;
  Char           line [4096];
    
  if (fp == NULL) {
      return NULL;
  }
  
  FileCacheSetup (&fc, fp);

  pos = FileCacheTell (&fc);
  
  str = FileCacheReadLine (&fc, line, sizeof (line), NULL);
  while (str != NULL && StringHasNoText (str)) {
    str = FileCacheReadLine (&fc, line, sizeof (line), NULL);
  }

  if (str == NULL) return NULL; /* already at end of file */

  if (StringStr (str, "::=") == NULL) return NULL;

  /* first skip past empty space at start of line */
  tag = str;
  while (*tag != '\0' && IS_WHITESP (*tag)) {
    tag++;
  }
  pEnd = tag;
  while (*pEnd != '\0' && !IS_WHITESP (*pEnd)) {
    pEnd++;
  }
  *pEnd = 0;

  omp = ObjMgrReadLock ();
  omtp = ObjMgrTypeFind (omp, 0, tag, NULL);
  ObjMgrUnlock ();

  if (omtp == NULL) {
      return NULL;
  }
  FileCacheFree (&fc, FALSE);
  fseek (fp, pos, SEEK_SET);

  aip = AsnIoNew (ASNIO_TEXT_IN, fp, NULL, NULL, NULL);
  aip->scan_for_start = TRUE;
  dataptr = (*(omtp->asnread)) (aip, NULL);
  pos = AsnIoTell (aip);
  AsnIoFree (aip, FALSE);
  fseek (fp, pos, SEEK_SET);

  if (dataptr == NULL) {
    ErrPostEx (SEV_ERROR, 0, 0, "Couldn't read type [%s]", omtp->asnname);
  } else {
    if (datatypeptr != NULL) {
      *datatypeptr = omtp->datatype;
    }
    if (entityIDptr != NULL) {
      *entityIDptr = ObjMgrRegister (omtp->datatype, dataptr);
    }
  }
  return dataptr;
}


static Boolean ReadSubmitBlock (ForM f, CharPtr filename)

{
  Pointer         dataptr;
  Uint2           datatype;
  Uint2           entityID;
  SubmitFormPtr   sbfp;
  SubmitBlockPtr  sbp;
  SeqSubmitPtr    ssp;
  Char            path [PATH_MAX];
  CharPtr         man_title;
  CitGenPtr       cgp;
  FILE           * fp;
  PubdescPtr      pdp;
  ValNodePtr      sdp, vnp;
  Boolean         is_title_pub = FALSE;
  SeqDescPtr      sdp_list = NULL;

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp == NULL) {
    return FALSE;
  }
  if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
    fp = FileOpen(path, "r");
    dataptr = ReadNextASNObject (fp, &datatype, &entityID);
    sbp = NULL;
    man_title = NULL;
    /* clear out previous descriptors */
    sbfp->descriptors = SeqDescrFree (sbfp->descriptors);
    while (dataptr != NULL) {
      if (entityID > 0) {
        switch (datatype) {
          case OBJ_SUBMIT_BLOCK :
            if (sbp == NULL) {
              sbp = (SubmitBlockPtr) AsnIoMemCopy (dataptr,
                                                   (AsnReadFunc) SubmitBlockAsnRead,
                                                   (AsnWriteFunc) SubmitBlockAsnWrite);
            }
            break;
          case OBJ_SEQSUB :
            if (sbp == NULL) {
              ssp = (SeqSubmitPtr) dataptr;
              if (ssp != NULL) {
                sbp = (SubmitBlockPtr) AsnIoMemCopy (ssp->sub,
                                                     (AsnReadFunc) SubmitBlockAsnRead,
                                                     (AsnWriteFunc) SubmitBlockAsnWrite);
              }
            }
            break;
          case OBJ_SEQDESC:
            sdp = (ValNodePtr) dataptr;
            is_title_pub = FALSE;
            if (sdp->choice == Seq_descr_pub && sdp->data.ptrvalue != NULL && man_title == NULL) {
              pdp = (PubdescPtr) sdp->data.ptrvalue;
              vnp = pdp->pub;
              while (vnp != NULL && vnp->choice != PUB_Gen) {
                vnp = vnp->next;
              }
              if (vnp != NULL && vnp->data.ptrvalue != NULL) {
                cgp = (CitGenPtr) vnp->data.ptrvalue;
                if (StringICmp (cgp->cit, "unpublished") == 0 
                    && !StringHasNoText (cgp->title)) {
                  man_title = StringSave (cgp->title);
                  is_title_pub = TRUE;
                }
              }
            }
            if (!is_title_pub) {                
              ValNodeLink (&sdp_list, (SeqDescPtr) AsnIoMemCopy (sdp, (AsnReadFunc) SeqDescAsnRead, (AsnWriteFunc) SeqDescAsnWrite));
            }
            break;
          default :
            break;
        }
      }
      ObjMgrDelete (datatype, dataptr);
      dataptr = ReadNextASNObject (fp, &datatype, &entityID);
    }
    FileClose (fp);
    if (sbp != NULL || man_title != NULL || sdp_list != NULL) {
      if (sbp != NULL) {
        SubmitBlockPtrToSubmitForm (f, sbp);
        sbp = SubmitBlockFree (sbp);
      }
      if (man_title != NULL) {
        TitleToSubmitBlockForm (f, man_title);
        man_title = MemFree (man_title);
      }
      if (sdp_list != NULL) {
        DescriptorsToSubmitBlockForm (f, sdp_list);
        sdp_list = SeqDescrFree (sdp_list);
      }
      Update ();
      return TRUE;
    }
  }
  return FALSE;
}

static SeqDescrPtr MakeUnpubPub (CharPtr title, AuthListPtr alp, AffilPtr affil)

{
  CitGenPtr    cgp;
  PubdescPtr   pdp;
  ValNodePtr   pep;
  SeqDescrPtr  vnp;

  if (StringHasNoText (title) || alp == NULL) return NULL;
  pdp = PubdescNew ();
  if (pdp == NULL) return NULL;
  vnp = SeqDescrNew (NULL);
  if (vnp == NULL) return NULL;
  vnp->choice = Seq_descr_pub;
  vnp->data.ptrvalue = (Pointer) pdp;
  pdp->reftype = 0;
  pep = ValNodeNew (NULL);
  pdp->pub = pep;
  if (pep != NULL) {
    cgp = CitGenNew ();
    if (cgp != NULL) {
      pep->choice = PUB_Gen;
      pep->data.ptrvalue = cgp;
      cgp->cit = StringSave ("unpublished");
      cgp->authors = alp;
      if (alp != NULL) {
        alp->affil = affil;
        if (affil != NULL) {
          affil->phone = MemFree (affil->phone);
          affil->fax = MemFree (affil->fax);
          affil->email = MemFree (affil->email);
        }
      }
      cgp->title = StringSave (title);
    }
  }
  return vnp;
}

extern SubmitBlockPtr ConvertSequinBlockToSubmitBlock (SequinBlockPtr sqp);

NLM_EXTERN void AsnPrintNewLine PROTO((AsnIoPtr aip));

static Boolean WriteSubmitBlock (ForM f, CharPtr filename)

{
  AffilPtr        affil;
  AsnIoPtr        aip;
  AuthListPtr     alp;
  Char            path [PATH_MAX];
  SubmitFormPtr   sbfp;
  SubmitBlockPtr  sbp;
  SeqDescrPtr     sdp;
  SequinBlockPtr  sqp;
  CharPtr         title;
#ifdef WIN_MAC
  FILE            *fp;
#endif

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      fp = FileOpen (path, "r");
      if (fp != NULL) {
        FileClose (fp);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      sqp = (SequinBlockPtr) SubmitFormToSequinBlockPtr (f);
      if (sqp != NULL) {
        title = StringSaveNoNull (sqp->citsubtitle);
        alp = AsnIoMemCopy ((Pointer) sqp->citsubauthors,
                            (AsnReadFunc) AuthListAsnRead,
                            (AsnWriteFunc) AuthListAsnWrite);
        affil = AsnIoMemCopy ((Pointer) sqp->citsubaffil,
                              (AsnReadFunc) AffilAsnRead,
                              (AsnWriteFunc) AffilAsnWrite);
        sbp = ConvertSequinBlockToSubmitBlock (sqp);
        if (sbp != NULL) {
          aip = AsnIoOpen (path, "w");
          if (aip != NULL) {
            sbp->tool = MemFree (sbp->tool);
            SubmitBlockAsnWrite (sbp, aip, NULL);
            AsnPrintNewLine (aip);
            sdp = MakeUnpubPub (title, alp, affil);
            if (sdp != NULL) {
              AsnIoReset (aip);
              SeqDescAsnWrite (sdp, aip, NULL);
              AsnPrintNewLine (aip);
              SeqDescFree (sdp);
            }
            AsnIoClose (aip);
            sbp = SubmitBlockFree (sbp);
            return TRUE;
          }
        }
      }
    }
  }
  return FALSE;
}

static Boolean ReadContactPage (ForM f, CharPtr filename)

{
  AsnIoPtr        aip;
  AuthorPtr       ap;
  ContactInfoPtr  cip;
  NameStdPtr      nsp;
  Char            path [PATH_MAX];
  PersonIdPtr     pid;
  SubmitFormPtr   sbfp;
  CharPtr         str;
  CharPtr         txt;

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      aip = AsnIoOpen (path, "r");
      if (aip != NULL) {
        cip = ContactInfoAsnRead (aip, NULL);
        AsnIoClose (aip);
        if (cip != NULL) {
          ap = cip->contact;
          if (ap != NULL) {
            pid = ap->name;
            if (pid != NULL && pid->choice == 2) {
              nsp = pid->data;
              if (nsp != NULL) {
                str = NameStdPtrToAuthorSpreadsheetString (nsp);
                if (str != NULL) {
                  txt = ExtractTagListColumn (str, 0);
                  SafeSetTitle (sbfp->firstname, txt);
                  MemFree (txt);
                  txt = ExtractTagListColumn (str, 1);
                  SafeSetTitle (sbfp->middleinit, txt);
                  MemFree (txt);
                  txt = ExtractTagListColumn (str, 2);
                  SafeSetTitle (sbfp->lastname, txt);
                  MemFree (txt);
                  txt = ExtractTagListColumn (str, 3);
                  if (! StringHasNoText (txt)) {
                    SetEnumPopupByName (sbfp->suffix, name_suffix_alist, txt);
                  }
                  MemFree (txt);
                  MemFree (str);
                }
              }
            }
            PointerToDialog (sbfp->phonefaxemail, (Pointer) ap->affil);
          }
          cip = ContactInfoFree (cip);
          Update ();
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

static Boolean WriteContactPage (ForM f, CharPtr filename)

{
  AffilPtr        affil;
  AsnIoPtr        aip;
  AuthorPtr       ap;
  ContactInfoPtr  cip;
  NameStdPtr      nsp;
  Char            path [PATH_MAX];
  PersonIdPtr     pid;
  SubmitFormPtr   sbfp;
  Char            sfx [32];
  Char            str [128];
  Uint2           suffixVal;
  CharPtr         txt;
#ifdef WIN_MAC
  FILE            *fp;
#endif

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      fp = FileOpen (path, "r");
      if (fp != NULL) {
        FileClose (fp);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      aip = AsnIoOpen (path, "w");
      if (aip != NULL) {
        cip = ContactInfoNew ();
        if (cip != NULL) {
          ap = AuthorNew ();
          if (ap != NULL) {
            pid = PersonIdNew ();
            ap->name = pid;
            if (pid != NULL) {
              pid->choice = 2;
              str [0] = '\0';
              txt = SaveStringFromText (sbfp->firstname);
              StringCat (str, txt);
              StringCat (str, "\t");
              MemFree (txt);
              txt = SaveStringFromText (sbfp->middleinit);
              StringCat (str, txt);
              StringCat (str, "\t");
              MemFree (txt);
              txt = SaveStringFromText (sbfp->lastname);
              StringCat (str, txt);
              StringCat (str, "\t");
              MemFree (txt);
              suffixVal = GetValue (sbfp->suffix);
              sprintf (sfx, "%d", (int) (suffixVal - 1));
              StringCat (str, sfx);
              StringCat (str, "\n");
              txt = StringSave (str);
              nsp = AuthorSpreadsheetStringToNameStdPtr (txt);
              MemFree (txt);
              pid->data = nsp;
              if (nsp != NULL) {
                if (StringHasNoText (nsp->names [0])) {
                  ap = AuthorFree (ap);
                }
              }
            }
            affil = (AffilPtr) DialogToPointer (sbfp->phonefaxemail);
            if (affil != NULL) {
              if (affil->choice == 2) {
                affil->affil = MemFree (affil->affil);
                affil->div = MemFree (affil->div);
                affil->city = MemFree (affil->city);
                affil->sub = MemFree (affil->sub);
                affil->country = MemFree (affil->country);
                affil->street = MemFree (affil->street);
                affil->postal_code = MemFree (affil->postal_code);
                if (affil->phone == NULL && affil->fax == NULL &&
                    affil->email == NULL) {
                  affil = AffilFree (affil);
                }
              } else {
                affil = AffilFree (affil);
              }
            }
            ap->affil = affil;
          }
          cip->contact = ap;
          ContactInfoAsnWrite (cip, aip, NULL);
          AsnIoClose (aip);
          cip = ContactInfoFree (cip);
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

static Boolean ReadAuthListPage (ForM f, CharPtr filename)

{
  AsnIoPtr       aip;
  AuthListPtr    alp;
  Char           path [PATH_MAX];
  SubmitFormPtr  sbfp;

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      aip = AsnIoOpen (path, "r");
      if (aip != NULL) {
        alp = AuthListAsnRead (aip, NULL);
        AsnIoClose (aip);
        if (alp != NULL) {
          PointerToDialog (sbfp->authors, alp);
          if (AuthListToConsortium (alp, sbfp->consortium)) {
            SetStatus (sbfp->use_consortium, TRUE);
            Enable (sbfp->consortium);
          } else {
            SetStatus (sbfp->use_consortium, FALSE);
            Disable (sbfp->consortium);
          }
          alp = AuthListFree (alp);
          Update ();
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

static Boolean WriteAuthListPage (ForM f, CharPtr filename)

{
  AsnIoPtr       aip;
  AuthListPtr    alp;
  Char           path [PATH_MAX];
  SubmitFormPtr  sbfp;
#ifdef WIN_MAC
  FILE           *fp;
#endif

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      fp = FileOpen (path, "r");
      if (fp != NULL) {
        FileClose (fp);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      aip = AsnIoOpen (path, "w");
      if (aip != NULL) {
        alp = DialogToPointer (sbfp->authors);
        if (GetStatus (sbfp->use_consortium)) {
          alp = AddConsortiumToAuthList (alp, sbfp->consortium);
        }
        AuthListAsnWrite (alp, aip, NULL);
        AsnIoClose (aip);
        alp = AuthListFree (alp);
        return TRUE;
      }
    }
  }
  return FALSE;
}

static Boolean ReadAffilPage (ForM f, CharPtr filename)

{
  AffilPtr       affil;
  AsnIoPtr       aip;
  Char           path [PATH_MAX];
  SubmitFormPtr  sbfp;

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      aip = AsnIoOpen (path, "r");
      if (aip != NULL) {
        affil = AffilAsnRead (aip, NULL);
        AsnIoClose (aip);
        if (affil != NULL) {
          affil->phone = MemFree (affil->phone);
          affil->fax = MemFree (affil->fax);
          affil->email = MemFree (affil->email);
          PointerToDialog (sbfp->affil, affil);
          affil = AffilFree (affil);
          Update ();
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

static Boolean WriteAffilPage (ForM f, CharPtr filename)

{
  AffilPtr       affil;
  AsnIoPtr       aip;
  Char           path [PATH_MAX];
  SubmitFormPtr  sbfp;
#ifdef WIN_MAC
  FILE           *fp;
#endif

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      fp = FileOpen (path, "r");
      if (fp != NULL) {
        FileClose (fp);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      aip = AsnIoOpen (path, "w");
      if (aip != NULL) {
        affil = DialogToPointer (sbfp->affil);
        affil->phone = MemFree (affil->phone);
        affil->fax = MemFree (affil->fax);
        affil->email = MemFree (affil->email);
        AffilAsnWrite (affil, aip, NULL);
        AsnIoClose (aip);
        affil = AffilFree (affil);
        return TRUE;
      }
    }
  }
  return FALSE;
}

static Boolean ImportSubmitForm (ForM f, CharPtr filename)

{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    switch (sbfp->currentPage) {
      case SUBMISSION_PAGE :
        return ReadSubmitBlock (f, filename);
      case CONTACT_PAGE :
        return ReadContactPage (f, filename);
      case AUTHOR_PAGE :
        return ReadAuthListPage (f, filename);
      case AFFILIATION_PAGE :
        return ReadAffilPage (f, filename);
      default :
        break;
    }
  }
  return FALSE;
}

static Boolean ExportSubmitForm (ForM f, CharPtr filename)

{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    switch (sbfp->currentPage) {
      case SUBMISSION_PAGE :
        return WriteSubmitBlock (f, filename);
      case CONTACT_PAGE :
        return WriteContactPage (f, filename);
      case AUTHOR_PAGE :
        return WriteAuthListPage (f, filename);
      case AFFILIATION_PAGE :
        return WriteAffilPage (f, filename);
      default :
        break;
    }
  }
  return FALSE;
}

static void CopyContactToAuthors (SubmitFormPtr sbfp)

{
  AuthListPtr  alp;
  AuthorPtr    ap;
  ValNodePtr   names;
  NameStdPtr   nsp;
  PersonIdPtr  pid;
  CharPtr      txt;

  if (sbfp == NULL) return;
  ap = NULL;
  alp = AuthListNew ();
  if (alp != NULL) {
    alp->choice = 1;
    names = ValNodeNew (NULL);
    alp->choice = 1;
    alp->names = names;
    if (names != NULL) {
      ap = AuthorNew ();
      if (ap != NULL) {
        pid = PersonIdNew ();
        ap->name = pid;
        if (pid != NULL) {
          pid->choice = 2;
          FixSubmitFormSpecialCharacters (sbfp);
          txt = ContactNameFromSubmitForm (sbfp);
          nsp = AuthorSpreadsheetStringToNameStdPtr (txt);
          MemFree (txt);
          pid->data = nsp;
          if (nsp != NULL) {
            if (StringHasNoText (nsp->names [0])) {
              ap = AuthorFree (ap);
            }
          }
        }
      }
      names->choice = 1;
      names->data.ptrvalue = ap;
    }
    if (ap == NULL) {
      alp = AuthListFree (alp);
    }
    if (alp != NULL) {
      PointerToDialog (sbfp->authors, (Pointer) alp);
      if (AuthListToConsortium (alp, sbfp->consortium)) {
        SetStatus (sbfp->use_consortium, TRUE);
        Enable (sbfp->consortium);
      } else {
        SetStatus (sbfp->use_consortium, FALSE);
        Disable (sbfp->consortium);
      }
    }
  }
  alp = AuthListFree (alp);
}

static void SetSubmitterImportExportItems (SubmitFormPtr sbfp)

{
  IteM  exportItm;
  IteM  importItm;

  if (sbfp != NULL) {
    importItm = FindFormMenuItem ((BaseFormPtr) sbfp, VIB_MSG_IMPORT);
    exportItm = FindFormMenuItem ((BaseFormPtr) sbfp, VIB_MSG_EXPORT);
    switch (sbfp->currentPage) {
      case SUBMISSION_PAGE :
        SafeSetTitle (importItm, "Import Submitter Info...");
        SafeSetTitle (exportItm, "Export Submitter Info...");
        SafeEnable (importItm);
        SafeEnable (exportItm);
        break;
      case CONTACT_PAGE :
        SafeSetTitle (importItm, "Import Contact...");
        SafeSetTitle (exportItm, "Export Contact...");
        SafeEnable (importItm);
        SafeEnable (exportItm);
        break;
      case AUTHOR_PAGE :
        SafeSetTitle (importItm, "Import Authors...");
        SafeSetTitle (exportItm, "Export Authors...");
        SafeEnable (importItm);
        SafeEnable (exportItm);
        break;
      case AFFILIATION_PAGE :
        SafeSetTitle (importItm, "Import Affiliation...");
        SafeSetTitle (exportItm, "Export Affiliation...");
        SafeEnable (importItm);
        SafeEnable (exportItm);
        break;
      default :
        break;
    }
  }
}

static void EnterSubmitPage (SubmitFormPtr sbfp, Int2 page)

{
  AuthListPtr  alp;

  if (sbfp != NULL) {
    switch (page) {
      case SUBMISSION_PAGE :
        Select (sbfp->title);
        SafeSetTitle (sbfp->prevBtn, "<< Prev Form");
        SafeSetTitle (sbfp->nextBtn, "Next Page >>");
        break;
      case CONTACT_PAGE :
        Select (sbfp->firstname);
        sbfp->visitedContact = TRUE;
        SafeSetTitle (sbfp->prevBtn, "<< Prev Page");
        SafeSetTitle (sbfp->nextBtn, "Next Page >>");
        break;
      case AUTHOR_PAGE :
        alp = (AuthListPtr) DialogToPointer (sbfp->authors);
        if (GetStatus (sbfp->use_consortium)) {
          alp = AddConsortiumToAuthList (alp, sbfp->consortium);
        }
        if (sbfp->visitedContact && alp == NULL) {
          CopyContactToAuthors (sbfp);
        }
        AuthListFree (alp);
        /*
        if (sbfp->visitedContact && (! sbfp->visitedAuthor)) {
          CopyContactToAuthors (sbfp);
        }
        */
        SendMessageToDialog (sbfp->authors, VIB_MSG_ENTER);
        sbfp->visitedAuthor = TRUE;
        SafeSetTitle (sbfp->prevBtn, "<< Prev Page");
        SafeSetTitle (sbfp->nextBtn, "Next Page >>");
        break;
      case AFFILIATION_PAGE :
        SendMessageToDialog (sbfp->affil, VIB_MSG_ENTER);
        SafeSetTitle (sbfp->prevBtn, "<< Prev Page");
        SafeSetTitle (sbfp->nextBtn, "Next Form >>");
        break;
      default :
        break;
    }
  }
}

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

{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) data;
  if (sbfp != NULL) {
    sbfp->currentPage = newval;
    SafeHide (sbfp->pages [oldval]);
    Update ();
#ifdef WIN_MAC
    EnterSubmitPage (sbfp, newval);
#endif
    SetSubmitterImportExportItems (sbfp);
    SafeShow (sbfp->pages [newval]);
#ifndef WIN_MAC
    EnterSubmitPage (sbfp, newval);
#endif
    Update ();
    switch (newval) {
      case SUBMISSION_PAGE :
        SendHelpScrollMessage (helpForm, "Submitting Authors Form", "Submission Page");
        break;
      case CONTACT_PAGE :
        SendHelpScrollMessage (helpForm, "Submitting Authors Form", "Contact Page");
        break;
      case AUTHOR_PAGE :
        SendHelpScrollMessage (helpForm, "Submitting Authors Form", "Authors Page");
        break;
      case AFFILIATION_PAGE :
        SendHelpScrollMessage (helpForm, "Submitting Authors Form", "Affiliation Page");
        break;
      default :
        break;
    }
  }
}

static void NextSubmitFormBtn (ButtoN b)

{
  SubmitFormPtr  sbfp;
  CharPtr        txt;
  Boolean        ok = TRUE;

  sbfp = (SubmitFormPtr) GetObjectExtra (b);
  if (sbfp != NULL) {
    if (sbfp->currentPage == 0)
    {
      txt = SaveStringFromText (sbfp->title);
      if (StringHasNoText (txt))
      {
      	Message (MSG_ERROR, "You must supply a tentative title for your manuscript.");
      	MemFree (txt);
      	return;
      }
      MemFree (txt);
    }
    else if (sbfp->currentPage == 1)
    {
      txt = SaveStringFromText (sbfp->firstname);
      if (StringHasNoText (txt))
      {
      	ok = FALSE;
      }
      MemFree (txt);
      txt = SaveStringFromText (sbfp->lastname);
      if (StringHasNoText (txt))
      {
      	ok = FALSE;
      }
      MemFree (txt);
      if (!ok)
      {
      	Message (MSG_ERROR, "You must supply a first and last name for the contact.");
      	return;
      }
    }
    if (sbfp->currentPage < 3) {
      SetValue (sbfp->tbs, sbfp->currentPage + 1);
    } else if (sbfp->goToNext != NULL) {
      (sbfp->goToNext) (b);
    }
  }
}

static void PrevSubmitFormBtn (ButtoN b)

{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (b);
  if (sbfp != NULL) {
    if (sbfp->currentPage > 0) {
      SetValue (sbfp->tbs, sbfp->currentPage - 1);
    } else if (sbfp->goToPrev != NULL) {
      (sbfp->goToPrev) (b);
    }
  }
}

static void ChangeHup (GrouP g)

{
  Boolean        hup;
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (g);
  if (sbfp != NULL) {
    hup = (Boolean) (GetValue (sbfp->hup) == 2);
    if (hup) {
      SafeShow (sbfp->dateGrp);
    } else {
      SafeHide (sbfp->dateGrp);
    }
  }
}

static CharPtr  submitFormTabs [] = {
  "Submission", "Contact", "Authors", "Affiliation", NULL
};

static void SubmitFormMessage (ForM f, Int2 mssg)

{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (f);
  if (sbfp != NULL) {
    switch (mssg) {
      case VIB_MSG_IMPORT :
        ImportSubmitForm (f, NULL);
        break;
      case VIB_MSG_EXPORT :
        ExportSubmitForm (f, NULL);
        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 (sbfp->appmessage != NULL) {
          sbfp->appmessage (f, mssg);
        }
        break;
    }
  }
}

static void InitSubmitterFormActivate (WindoW w)

{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (w);
  if (sbfp != NULL) {
    if (sbfp->activate != NULL) {
      sbfp->activate (w);
    }
    SetSubmitterImportExportItems (sbfp);
  }
}

static void EnableConsortium (ButtoN b)
{
  SubmitFormPtr  sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (b);
  if (sbfp != NULL) {
    if (GetStatus (sbfp->use_consortium)) {
      Enable (sbfp->consortium);
    } else {
      Disable (sbfp->consortium);
    }
  }
}


static void ImportSubmissionTemplate (ButtoN b)
{
  SubmitFormPtr sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (b);
  if (sbfp == NULL) {
    return;
  }
  ReadSubmitBlock (sbfp->form, NULL);
}


static void ExportSubmissionTemplate (ButtoN b)
{
  SubmitFormPtr sbfp;

  sbfp = (SubmitFormPtr) GetObjectExtra (b);
  if (sbfp == NULL) {
    return;
  }
  WriteSubmitBlock (sbfp->form, NULL);
}


static void CleanupSubmitterForm (Nlm_GraphiC g, Nlm_VoidPtr data)
{
  SubmitFormPtr f;

  f = (SubmitFormPtr) data;
  if (f != NULL) {
    SeqDescrFree (f->descriptors);
  }
  StdCleanupFormProc (g, data);
}


extern ForM CreateInitSubmitterForm (Int2 left, Int2 top, CharPtr title,
                                     BtnActnProc goToNext,
                                     BtnActnProc goBack,
                                     WndActnProc activateForm)

{
  GrouP              c;
  GrouP              g;
  GrouP              h;
  GrouP              j;
  GrouP              m;
  GrouP              n;
  GrouP              q;
  SubmitFormPtr      sbfp;
  StdEditorProcsPtr  sepp;
  GrouP              t;
  WindoW             w;
  GrouP              x;
  GrouP              z;
  GrouP              g1, g2;
  GrouP              p;
  DatePtr            dp;
  ButtoN             b;

  w = NULL;
  sbfp = MemNew (sizeof (SubmitForm));
  if (sbfp != NULL) {
    w = FixedWindow (left, top, -10, -10, title, NULL);
    SetObjectExtra (w, sbfp, CleanupSubmitterForm);
    sbfp->form = (ForM) w;
    sbfp->toform = SequinBlockPtrToSubmitForm;
    sbfp->fromform = SubmitFormToSequinBlockPtr;
    sbfp->testform = TestSubmitForm;
    sbfp->importform = ImportSubmitForm;
    sbfp->exportform = ExportSubmitForm;
    sbfp->formmessage = SubmitFormMessage;

#ifndef WIN_MAC
    CreateSqnInitialFormMenus (w);
#endif

    sepp = (StdEditorProcsPtr) GetAppProperty ("StdEditorForm");
    if (sepp != NULL) {
      sbfp->appmessage = sepp->handleMessages;
    }

    SetGroupSpacing (w, 10, 10);

    j = HiddenGroup (w, -1, 0, NULL);
    SetGroupSpacing (j, 10, 10);

    sbfp->tbs = CreateFolderTabs (j, submitFormTabs, 0, 0, 0,
                                  SYSTEM_FOLDER_TAB,
                                  ChangeSubmitFormPage, (Pointer) sbfp);
    sbfp->currentPage = SUBMISSION_PAGE;

    sbfp->visitedContact = FALSE;
    sbfp->visitedAuthor = FALSE;

    h = HiddenGroup (w, 0, 0, NULL);

    q = HiddenGroup (h, -1, 0, NULL);
    SetGroupSpacing (q, 10, 20);
    m = HiddenGroup (q, -1, 0, NULL);
    g = HiddenGroup (m, 0, -4, NULL);
    SetGroupSpacing (g, 3, 10);
    StaticPrompt (g, "When may we release your sequence record?",
                  0, stdLineHeight, programFont, 'l');
    sbfp->hup = HiddenGroup (g, 0, -2, ChangeHup);
    SetObjectExtra (sbfp->hup, sbfp, NULL);
    RadioButton (sbfp->hup, "Immediately After Processing");
    RadioButton (sbfp->hup, "Release Date:");
    SetValue (sbfp->hup, 1);
    sbfp->dateGrp = HiddenGroup (m, -1, 0, NULL);
    /* StaticPrompt (sbfp->dateGrp, "Release Date: ", 0, popupMenuHeight, programFont, 'l'); */

    dp = DateCurr ();
    if (dp != NULL && dp->data[0] == 1) {
      sbfp->reldate = CreateDateDialogEx (sbfp->dateGrp, NULL, dp->data[1] + 1900, 10);
    } else {
      sbfp->reldate = CreateDateDialog (sbfp->dateGrp, NULL);
    }
    p = MultiLinePrompt (sbfp->dateGrp, 
                         "NOTE: Sequences must be released when the accession number or any portion of the sequence is published.",
                         25 * stdCharWidth, programFont);
    AlignObjects (ALIGN_CENTER, (HANDLE) sbfp->reldate, (HANDLE) p, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) sbfp->dateGrp, NULL);
    Hide (sbfp->dateGrp);
    t = HiddenGroup (q, 1, 0, NULL);
    StaticPrompt (t, "Tentative title for manuscript (required)", 0, 0, programFont, 'c');
    sbfp->title = ScrollText (t, 25, 4, programFont, TRUE, NULL);
    b = PushButton (q, "Click here to import a template", ImportSubmissionTemplate);
    SetObjectExtra (b, sbfp, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) sbfp->reldate,
                  (HANDLE) sbfp->title, (HANDLE) t, (HANDLE) b, NULL);
    sbfp->pages [SUBMISSION_PAGE] = q;
    Hide (sbfp->pages [SUBMISSION_PAGE]);

    q = HiddenGroup (h, -1, 0, NULL);
    SetGroupSpacing (q, 10, 20);
    n = HiddenGroup (q, 4, 0, NULL);
    SetGroupSpacing (n, -1, 2);
    StaticPrompt (n, "First Name", 0, 0, programFont, 'c');
    StaticPrompt (n, "M.I.", 0, 0, programFont, 'c');
    StaticPrompt (n, "Last Name", 0, 0, programFont, 'c');
    StaticPrompt (n, "Sfx", 0, 0, programFont, 'c');
    sbfp->firstname = DialogText (n, "", 8, NULL);
    sbfp->middleinit = DialogText (n, "", 4, NULL);
    sbfp->lastname = DialogText (n, "", 9, NULL);
    /*
    sbfp->suffix = DialogText (n, "", 3, NULL);
    sbfp->suffix = PopupList (n, TRUE, SuffixPopup_Callback);
    */
    sbfp->suffix = PopupList (n, TRUE, NULL);
    SetObjectExtra (sbfp->suffix, sbfp, NULL);
    InitEnumPopup (sbfp->suffix, name_suffix_alist, NULL);
    SetEnumPopup (sbfp->suffix, name_suffix_alist, 0);

    sbfp->phonefaxemail = CreateExtAffilDialog (q, NULL, NULL, &x);
    Show (x);
    Show (sbfp->phonefaxemail);
    AlignObjects (ALIGN_CENTER, (HANDLE) n, (HANDLE) sbfp->phonefaxemail, NULL);
    sbfp->pages [CONTACT_PAGE] = q;
    Hide (sbfp->pages [CONTACT_PAGE]);

    q = HiddenGroup (h, -1, 0, NULL);
    SetGroupSpacing (q, 10, 20);
    sbfp->authors = CreateAuthorDialog (q, 3, -1);
    z = HiddenGroup (q, 0, 2, NULL);
    g1 = HiddenGroup (z, 2, 0, NULL);
    sbfp->use_consortium = CheckBox (g1, "Consortium", EnableConsortium);
    SetObjectExtra (sbfp->use_consortium, sbfp, NULL);
    sbfp->consortium = DialogText (g1, "", 16, NULL);
    Disable (sbfp->consortium);
    g2 = HiddenGroup (z, 1, 0, NULL);
    MultiLinePrompt (g2, "The consortium field should be used when a "
                         "consortium is responsible for the sequencing or "
                         "publication of the data.  Individual authors may "
                         "be listed along with a consortium name.",
                          25 * stdCharWidth, programFont);
    AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) g2, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) sbfp->authors, (HANDLE) z, NULL);
    sbfp->pages [AUTHOR_PAGE] = q;
    Hide (sbfp->pages [AUTHOR_PAGE]);

    q = HiddenGroup (h, -1, 0, NULL);
    SetGroupSpacing (q, 10, 20);
    sbfp->affil = CreateExtAffilDialog (q, NULL, &g, NULL);
    Show (g);
    Show (sbfp->affil);
    b = PushButton (q, "Click here to export a template", ExportSubmissionTemplate);
    SetObjectExtra (b, sbfp, NULL);
    AlignObjects (ALIGN_CENTER, (HANDLE) sbfp->affil, (HANDLE) b, NULL);
    sbfp->pages [AFFILIATION_PAGE] = q;
    Hide (sbfp->pages [AFFILIATION_PAGE]);

    c = HiddenGroup (w, 4, 0, NULL);
    SetGroupSpacing (c, 10, 2);
    sbfp->goToPrev = goBack;
    sbfp->prevBtn = PushButton (c, " << Prev Form ", PrevSubmitFormBtn);
    SetObjectExtra (sbfp->prevBtn, sbfp, NULL);
    sbfp->goToNext = goToNext;
    sbfp->nextBtn = PushButton (c, " Next Page >> ", NextSubmitFormBtn);
    SetObjectExtra (sbfp->nextBtn, sbfp, NULL);

    AlignObjects (ALIGN_CENTER,
                  (HANDLE) sbfp->pages [SUBMISSION_PAGE],
                  (HANDLE) sbfp->pages [CONTACT_PAGE],
                  (HANDLE) sbfp->pages [AUTHOR_PAGE],
                  (HANDLE) sbfp->pages [AFFILIATION_PAGE],
                  (HANDLE) sbfp->tbs, (HANDLE) c, NULL);

    RealizeWindow (w);

    SafeSetTitle (sbfp->prevBtn, "<< Prev Form");
    SafeSetTitle (sbfp->nextBtn, "Next Page >>");

    sbfp->activate = activateForm;
    SetActivate (w, InitSubmitterFormActivate);

    Show (sbfp->pages [sbfp->currentPage]);
    EnterSubmitPage (sbfp, sbfp->currentPage);
  }
  return (ForM) w;
}

extern void AddCodonListTotRNA (tRNAPtr trna, ValNodePtr codons)
{
  ValNodePtr vnp;
  Int2       j, k, q;
  Char       str [8];
  Char       ch;
  Uint1      codon [4]; 
  Uint1      code;

  if (trna == NULL) return;

  for (j = 0; j < 6; j++) {
    trna->codon [j] = 255;
  }
  
  if (codons == NULL)
  {
    return;
  }
  
  for (vnp = codons, j = 0; vnp != NULL && j < 6; vnp = vnp->next) {
    str [0] = '\0';
    StringNCpy_0 (str, (CharPtr) vnp->data.ptrvalue, sizeof (str));
    if (str [0] != '\0') {
      k = 0;
      q = 0;
      ch = str [k];
      while (ch != '\0' && q < 3) {
        ch = TO_UPPER (ch);
        if (StringChr ("ACGTU", ch) != NULL) {
          if (ch == 'U') {
            ch = 'T';
          }
          codon [q] = (Uint1) ch;
          q++;
        }
        k++;
        ch = str [k];
      }
      codon [q] = 0;
      if (q == 3) {
        code = IndexForCodon (codon, Seq_code_iupacna);
        if (code != INVALID_RESIDUE) {
          trna->codon [j] = code;
        }
      }
      j++;
    }
  }
}


static void ParseCodonsFromtRNACommentProc (SeqFeatPtr sfp, Pointer userdata)
{
  RnaRefPtr rrp;
  tRNAPtr   trna;
  CharPtr   cp, sep;
  CharPtr   codon_name;

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

  cp = StringStr (sfp->comment, "recognized codon");
  if (cp == NULL) {
    cp = StringStr (sfp->comment, "codon recognized");
  }
  if (cp == NULL) return;
  sep = StringStr (cp, "=");
  if (sep == NULL) {
    sep = StringStr (cp, ":");
  }
  cp = sep;
  if (cp == NULL) {
    Message (MSG_ERROR, "Unable to read codon from %s", sfp->comment);
    return;
  }

  codon_name = cp + 1;

  if (!SettRNACodons_Recognized(sfp, NULL, codon_name, ExistingTextOption_append_semi)) {
    Message (MSG_ERROR, "Invalid codon in %s", sfp->comment);
  }
}

extern void ParseCodonsFromtRNAComment (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;

  VisitFeaturesInSep (sep, NULL, ParseCodonsFromtRNACommentProc);
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}

static Boolean GetAntiCodonPositionFromComment (CharPtr comment, Int4Ptr start, Int4Ptr stop)
{
  CharPtr cp, cpend;
  Int4    val;
  Char    ch;
  
  if (comment == NULL || start == NULL || stop == NULL) return FALSE;
  cp = StringStr (comment, "anticodon");
  if (cp == NULL) return FALSE;
  cp += 8;
  while (*cp != 0 && !isdigit ((Int4)(*cp)))
  {
  	cp++;
  }
  if (*cp == 0)
  {
  	Message (MSG_ERROR, "Found 'anticodon' but no numbers for position in %s", comment);
  	return FALSE;
  }
  
  cpend = cp + 1;
  while (*cpend != 0 && isdigit ((Int4)(*cpend)))
  {
  	cpend ++;
  }
  if (*cpend == 0)
  {
  	Message (MSG_ERROR, "Found 'anticodon' but no numbers for position in %s", comment);
  	return FALSE;
  }
  ch = *cpend;
  *cpend = 0;
  val = atoi (cp);
  *cpend = ch;
  cp = cpend + 1;
  
  while (*cp != 0 && !isdigit ((Int4)(*cp)))
  {
  	cp++;
  }
  if (*cp == 0)
  {
  	Message (MSG_ERROR, "Found 'anticodon' but no numbers for position in %s", comment);
  	return FALSE;
  }
  cpend = cp + 1;
  while (*cpend != 0 && isdigit ((Int4)(*cpend)))
  {
  	cpend ++;
  }
  ch = *cpend;
  *cpend = 0;
  *start = val;
  *stop = atoi (cp);
  *cpend = ch;
  return TRUE;
}

static void ParseAntiCodonsFromtRNACommentProc (SeqFeatPtr sfp, Pointer userdata)
{
  RnaRefPtr rrp;
  tRNAPtr   trna;
  Int4      start;
  Int4      stop;
  SeqLocPtr slp;
  SeqIntPtr sip;

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

  if (! GetAntiCodonPositionFromComment (sfp->comment, &start, &stop)) return;
  sip = SeqIntNew ();
  if (sip == NULL) return;
  if (start <= stop) {
    sip->from = start - 1;
    sip->to = stop - 1;
  } else {
    sip->from = stop - 1;
    sip->to = start - 1;
  }
  sip->strand = SeqLocStrand (sfp->location);
  sip->id = SeqIdDup (SeqLocId (sfp->location));
  slp = ValNodeNew (NULL);
  if (slp == NULL) return;
  slp->choice = 4;
  slp->data.ptrvalue = sip;

  if (trna->anticodon != NULL) {
    SeqLocFree (trna->anticodon);
  }
  trna->anticodon = slp;
}

extern void ParseAntiCodonsFromtRNAComment (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;

  VisitFeaturesInSep (sep, NULL, ParseAntiCodonsFromtRNACommentProc);
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}

static void ExtractString (
  CharPtr str,
  Char PNTR buf,
  Int2 from,
  Int2 to
)

{
  Int2    i;
  Int2    j;
  size_t  len;

  if (buf == NULL) return;
  *buf = '\0';
  if (str == NULL) return;

  if (from < 0 || to < 0) return;
  len = StringLen (str);
  if (from > len || to > len) return;

  j = 0;
  for (i = from; i < to; i++) {
    buf [j] = str [i];
    j++;
  }
  buf [j] = '\0';
}

static void RevcompString (
  CharPtr str
)

{
  Char    ch;
  int     i;
  int     j;
  size_t  len;

  if (str == NULL) return;
  len = (int) StringLen (str);
  if (len < 1) return;

  for (i = 0, j = len - 1; i < j; i++, j--) {
    ch = str [i];
    str [i] = str [j];
    str [j] = ch;
  }

  for (i = 0; i < len; i++) {
    ch = str [i];
    if (ch == 'A') {
      ch = 'T';
    } else if (ch == 'C') {
      ch = 'G';
    } else if (ch == 'G') {
      ch = 'C';
    } else if (ch == 'T') {
      ch = 'A';
    }
    str [i] = ch;
  }
}

static Int2 LongestExactMatch (
  CharPtr str1,
  CharPtr str2,
  Int2 PNTR gcp
)

{
  Char  ch1, ch2;
  Int2  gc = 0;
  int   i;
  Int2  len = 0;

  i = 0;
  ch1 = str1 [i];
  ch2 = str2 [i];
  while (ch1 != '\0' && ch2 != '\0' && ch1 == ch2) {
    len++;
    i++;
    if (ch1 == 'C' || ch1 == 'G') {
      gc++;
    }
    ch1 = str1 [i];
    ch2 = str2 [i];
  }

  *gcp = gc;

  return len;
}

static Int2 LongestSlidingMatch (
  CharPtr str1,
  CharPtr str2,
  Int2 PNTR gcp
)

{
  Int2    gc;
  Int2    len;
  Int2    max = 0;
  size_t  remains = 0;

  if (StringHasNoText (str1) || StringHasNoText (str2)) return 0;

  remains = StringLen (str1);

  while (remains > 3 && *str1 != '\0') {
    len = LongestExactMatch (str1, str2, &gc);
    if (len > max) {
      max = len;
      *gcp = gc;
    }
    str1++;
    remains--;
  }

  return max;
}

static Int2 LongestMatch (
  CharPtr str1,
  CharPtr str2,
  Int2 PNTR gcp
)

{
  Int2    gc = 0;
  Int2    len;
  Int2    max = 0;
  size_t  remains = 0;

  if (StringHasNoText (str1) || StringHasNoText (str2)) return 0;

  remains = StringLen (str2);

  while (remains > 3 && *str2 != '\0') {
    len = LongestSlidingMatch (str1, str2, &gc);
    if (len > max) {
      max = len;
      *gcp = gc;
    }
    str2++;
    remains--;
  }

  return max;
}


typedef struct orgnamechangedata {
  CharPtr oldname;
  CharPtr newname;
} OrgNameChangeData, PNTR OrgNameChangePtr;

static void ReplaceOldOrgNameInTitle (SeqDescrPtr sdp, OrgNameChangePtr oncp)
{
  CharPtr oldtitle, newtitle, orgnamestart;
  Int4    oldorglen;

  if (sdp == NULL || oncp == NULL
    || oncp->oldname == NULL || oncp->newname == NULL)
  {
    return;
  }

  oldtitle = sdp->data.ptrvalue;

  if (oldtitle == NULL) return;

  orgnamestart = StringRChr (oldtitle, '[');
  if (orgnamestart == NULL) return;
  oldorglen = StringLen (oncp->oldname);
  if (StringNCmp (orgnamestart + 1, oncp->oldname, oldorglen) == 0
    && orgnamestart [ oldorglen + 1] == ']')
  {
    newtitle = MemNew (StringLen (oldtitle) - StringLen (oncp->oldname) + StringLen (oncp->newname) + 2);   
    if (newtitle == NULL) return;
    StringNCpy (newtitle, oldtitle, orgnamestart - oldtitle + 1);
    StringCat (newtitle, oncp->newname);
    StringCat (newtitle, "]");
    MemFree (sdp->data.ptrvalue);
    sdp->data.ptrvalue = newtitle;
  }
}

static void FixProteinTitleAfterOrganismNameChange (BioseqPtr bsp, Pointer userdata)
{
  OrgNameChangePtr oncp;
  SeqDescrPtr sdp;
  BioSourcePtr biop;

  if (bsp == NULL
    || ! ISA_aa (bsp->mol)
    || (oncp = (OrgNameChangePtr)userdata) == NULL
    || oncp->oldname == NULL
    || oncp->newname == NULL)
  {
    return;
  }

  for (sdp = bsp->descr; sdp != NULL; sdp = sdp->next) {
    if (sdp->choice == Seq_descr_title)
    {
      ReplaceOldOrgNameInTitle (sdp, oncp);
    }
    else if (sdp->choice == Seq_descr_source)
    {
      biop = sdp->data.ptrvalue;
      if (biop != NULL && biop->org != NULL 
        && StringCmp (biop->org->taxname, oncp->oldname) == 0)
      {
        SetTaxNameAndRemoveTaxRef (biop->org, StringSave (oncp->newname));
      }   
    }   
  }
}

typedef struct parseformdata {
  FEATURE_FORM_BLOCK

  TexT           atleft;
  TexT           atright;
  DialoG         orgmod;
  DialoG         subsource;
  PopuP          taxname;
  Boolean        parsedef;
  Char           path [PATH_MAX];
  FILE           *fp;
  Boolean        replaceOldAsked;
  Boolean        doReplaceAll;
  Boolean        use_semicolon;
  ButtoN         leaveDlgUp;
} ParseFormData, PNTR ParseFormPtr;

typedef struct localidfinddata {
  CharPtr str;
  Int4    str_size;
} LocalIDFindData, PNTR LocalIDFindPtr;
  
static void LookForLocalID (BioseqPtr bsp, Pointer userdata)
{
  LocalIDFindPtr lidfp;
  SeqIdPtr      sip;

  if (bsp == NULL || (lidfp = (LocalIDFindPtr) userdata) == NULL) {
    return;
  }
  if (lidfp->str [0] != 0) return;

  for (sip = bsp->id; sip != NULL; sip = sip->next) {
    if (sip->choice == SEQID_LOCAL) {
      SeqIdWrite (sip, lidfp->str, PRINTID_REPORT, lidfp->str_size);
      if (StringNICmp (lidfp->str, "tmpseq_", 7) == 0 ||
          StringNICmp (lidfp->str, "segseq_", 7) == 0 ||
          StringNICmp (lidfp->str, "SEG_dna", 7) == 0)
      {
        /* don't want to use this one */
        lidfp->str [0] = 0;
      } else {
        return;
      }
    }
  }
}

static void FindLocalIDForOrganism (
  SeqEntryPtr  sep,
  CharPtr      str,
  Int4         str_size
)
{
  LocalIDFindData lidfd;

  if (sep == NULL || sep->data.ptrvalue == NULL) return;
  lidfd.str = str;
  lidfd.str_size = str_size;
  VisitBioseqsInSep (sep, (Pointer) &lidfd, LookForLocalID);
}

static void GetDataForOrganism (
  SeqEntryPtr  sep,
  CharPtr      str,
  Int4         str_size,
  CharPtr      useme,
  ParseFormPtr pfp
)
{
  SeqEntryPtr nsep;
  ValNodePtr    ttl;
  Char          txt [128];
  CharPtr       ptr;

  str [0] = 0;
  if (sep == NULL || pfp == NULL) return;
  nsep = FindNucSeqEntry (sep);
  if (nsep == NULL || nsep->data.ptrvalue == NULL) return;

  if (useme != NULL) {
    StringNCpy_0 (str, useme, str_size);
  } else if (pfp->parsedef) {
    ttl = SeqEntryGetSeqDescr (nsep, Seq_descr_title, NULL);
    if (ttl == NULL && sep != nsep) {
      ttl = SeqEntryGetSeqDescr (sep, Seq_descr_title, NULL);
    }
    if (ttl == NULL || ttl->data.ptrvalue == NULL) return;
    StringNCpy_0 (str, (CharPtr) ttl->data.ptrvalue, str_size);
    GetTitle (pfp->atleft, txt, sizeof (txt));
    if (! StringHasNoText (txt)) {
      ptr = StringISearch (str, txt);
      if (ptr != NULL) {
        StringNCpy_0 (str, ptr + StringLen (txt), str_size);
      }
    }
    GetTitle (pfp->atright, txt, sizeof (txt));
    if (txt [0] != '\0' /* ! StringHasNoText (txt) */) {
      ptr = StringISearch (str, txt);
      if (ptr != NULL) {
        *ptr = '\0';
      }
    }
  } else {
    FindLocalIDForOrganism (nsep, str, str_size);
    if (str [0] == 0) {
      FindLocalIDForOrganism (sep, str, str_size);
    }
  }
  return;
}

static void ApplyDataToBioSourcePtr (
  ParseFormPtr pfp,
  BioSourcePtr biop,
  BioSourcePtr topbiop,
  SeqEntryPtr  sep,
  CharPtr      data
)
{
  Int2          taxnameval;
  ValNodePtr    vnp;
  OrgRefPtr     orp;
  OrgModPtr     mod;
  Uint1         orgmodtype;
  Uint1         subsourcetype;
  Uint1         subtype;
  OrgNamePtr    onp;
  OrgModPtr     tmpmod;
  SubSourcePtr  ssp;
  SubSourcePtr  tmpssp;

  taxnameval = GetValue (pfp->taxname);
  if (taxnameval == 2) {
    if (biop == NULL) {
      biop = BioSourceNew ();
      if (biop != NULL) {
        orp = OrgRefNew ();
        biop->org = orp;
        vnp = CreateNewDescriptor (sep, Seq_descr_source);
        if (vnp != NULL) {
          vnp->data.ptrvalue = (Pointer) biop;
        }
      }
    }
    if (biop == NULL) return;
    orp = biop->org;
    if (orp == NULL) return;
    SetTaxNameAndRemoveTaxRef (orp, StringSave (data));
  }
  if (biop == NULL && topbiop != NULL) {
    biop = (BioSourcePtr) AsnIoMemCopy ((Pointer) topbiop,
                                        (AsnReadFunc) BioSourceAsnRead,
                                        (AsnWriteFunc) BioSourceAsnWrite);
    if (biop != NULL) {
      vnp = CreateNewDescriptor (sep, Seq_descr_source);
      if (vnp != NULL) {
        vnp->data.ptrvalue = (Pointer) biop;
      }
    }
  }
  if (biop == NULL) return;
  orgmodtype = 0;
  subsourcetype = 0;
  if (taxnameval == 3) {
    orgmodtype = 255;
  } else if (taxnameval == 4) {
    subsourcetype = 255;
  } else {
    vnp = DialogToPointer (pfp->orgmod);
    if (vnp != NULL) {
      orgmodtype = vnp->choice;
      vnp = ValNodeFreeData (vnp);
    }
    if (orgmodtype == 0) {
      vnp = DialogToPointer (pfp->subsource);
      if (vnp != NULL) {
        subsourcetype = vnp->choice;
        vnp = ValNodeFreeData (vnp);
      }
    }
  }
  if (! StringHasNoText (data)) {
    if (orgmodtype > 0) {
      subtype = orgmodtype;
      orp = biop->org;
      if (orp == NULL) {
        orp = OrgRefNew ();
        biop->org = orp;
      }
      if (orp != NULL) {
        onp = orp->orgname;
        if (onp == NULL) {
          onp = OrgNameNew ();
          orp->orgname = onp;
        }
        if (onp != NULL) {
          mod = onp->mod;
          while (mod != NULL && mod->subtype != subtype) {
            mod = mod->next;
          }
          if (mod == NULL) {
            mod = OrgModNew ();
            if (onp->mod == NULL) {
              onp->mod = mod;
            } else {
              tmpmod = onp->mod;
              while (tmpmod->next != NULL) {
                tmpmod = tmpmod->next;
              }
              tmpmod->next = mod;
            }
          }
          if (mod != NULL) {
            mod->subtype = subtype;
            AppendOrReplaceString (&(mod->subname), data,
                                   &(pfp->replaceOldAsked),
                                   &(pfp->doReplaceAll),
                                   &(pfp->use_semicolon));
          }
        }
      }
    } else if (subsourcetype > 0) {
      subtype = subsourcetype;
      ssp = biop->subtype;
      while (ssp != NULL && ssp->subtype != subtype) {
        ssp = ssp->next;
      }
      if (ssp == NULL) {
        ssp = SubSourceNew ();
        if (biop->subtype == NULL) {
          biop->subtype = ssp;
        } else {
          tmpssp = biop->subtype;
          while (tmpssp->next != NULL) {
            tmpssp = tmpssp->next;
          }
          tmpssp->next = ssp;
        }
      }
      if (ssp != NULL) {
        ssp->subtype = subtype;
        AppendOrReplaceString (&(ssp->name), data,
                               &(pfp->replaceOldAsked),
                               &(pfp->doReplaceAll),
                               &(pfp->use_semicolon));
      }
    }
  }
}

static void DoOneParseItem (Uint2 entityID, SeqEntryPtr sep, ParseFormPtr pfp, BioSourcePtr topbiop, CharPtr useme, SeqEntryPtr nps)

{
  BioSourcePtr  biop;
  BioseqSetPtr  bssp;
  Char          str [256];
  SeqEntryPtr   tmp;
  OrgNameChangeData oncd;

  if (sep == NULL || pfp == NULL) return;
  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL) {
      /* if nucprot set, look for biosource here */
      biop = NULL;
      SeqEntryToBioSource (sep, NULL, NULL, 0, &biop);
      if (biop != NULL && topbiop != NULL) {
        topbiop = biop;
        if (bssp->_class == BioseqseqSet_class_nuc_prot)
        {
          GetDataForOrganism ( sep, str, sizeof (str), useme, pfp);
          if (topbiop->org != NULL && topbiop->org->taxname != NULL)
          {
            oncd.oldname = topbiop->org->taxname;
            oncd.newname = str;
            VisitBioseqsInSep (sep, &oncd, FixProteinTitleAfterOrganismNameChange);
          }
          ApplyDataToBioSourcePtr (pfp, topbiop, NULL, sep, str);
          return;
        }
      }
      for (tmp = bssp->seq_set; tmp != NULL; tmp = tmp->next) {
        DoOneParseItem (entityID, tmp, pfp, topbiop, useme, sep);
      }
      return;
    }
  }
  GetDataForOrganism ( sep, str, sizeof (str), useme, pfp);
  if (StringHasNoText (str)) return;
  biop = NULL;
  SeqEntryToBioSource (sep, NULL, NULL, 0, &biop);
  ApplyDataToBioSourcePtr (pfp, biop, topbiop, sep, str);
}

static void DoOneParse (Uint2 entityID, SeqEntryPtr sep, ParseFormPtr pfp, BioSourcePtr topbiop)

{
  BioseqPtr     bsp;
  BioseqSetPtr  bssp;
  Char          gb [256];
  CharPtr       ptr;
  SeqIdPtr      sip;
  Char          str [250];
  SeqEntryPtr   tmp;

  if (sep == NULL || pfp == NULL) return;
  if (pfp->fp != NULL) {
    ReadLine (pfp->fp, str, sizeof (str)); /* FileGets on Mac sometimes sees '\r' instead of '\n' */
    while (Nlm_fileDone || str [0] != 0) {
      if (! StringHasNoText (str)) {
        ptr = StringChr (str, '\t');
        if (ptr != NULL) {
          *ptr = '\0';
          ptr++;
          TrimSpacesAroundString (str);
          TrimSpacesAroundString (ptr);
          sip = MakeSeqID (str);
          bsp = BioseqFind (sip);
          SeqIdFree (sip);
          if (bsp == NULL) {
            sprintf (gb, "gb|%s|", str);
            sip = MakeSeqID (gb);
            bsp = BioseqFind (sip);
            SeqIdFree (sip);
          }
          if (bsp != NULL) {
            tmp = GetBestTopParentForData (entityID, bsp);
             DoOneParseItem (entityID, tmp, pfp, topbiop, ptr, NULL);
          }
        }
      }
      str [0] = 0;
      ReadLine (pfp->fp, str, sizeof (str));
    }
    return;
  }
  if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    if (bssp != NULL && (bssp->_class == 7 ||
                         (IsPopPhyEtcSet (bssp->_class)))) {
      for (tmp = bssp->seq_set; tmp != NULL; tmp = tmp->next) {
        DoOneParse (entityID, tmp, pfp, topbiop);
      }
      return;
    }
  }
  DoOneParseItem (entityID, sep, pfp, topbiop, NULL, NULL);
}

static void DoParseDeflineProc (ButtoN b)

{
  BioSourcePtr  topbiop;
  ParseFormPtr  pfp;
  SeqEntryPtr   sep;

  pfp = (ParseFormPtr) GetObjectExtra (b);
  if (pfp == NULL || pfp->input_entityID == 0) return;
  sep = GetTopSeqEntryForEntityID (pfp->input_entityID);
  if (sep == NULL) return;
  Hide (pfp->form);
  WatchCursor ();
  Update ();
  /* if popset, look for top biosource */
  SeqEntryToBioSource (sep, NULL, NULL, 0, &topbiop);
  if (! StringHasNoText (pfp->path)) {
    pfp->fp = FileOpen (pfp->path, "r");
  }
  DoOneParse (pfp->input_entityID, sep, pfp, topbiop);
  FileClose (pfp->fp);
  ArrowCursor ();
  Update ();
  ObjMgrSetDirtyFlag (pfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, pfp->input_entityID, 0, 0);
  if (GetStatus (pfp->leaveDlgUp)) {
    Show (pfp->form);
  } else {
    Remove (pfp->form);
  }
}

static void ParseDeflineMessageProc (ForM f, Int2 mssg)

{
  StdEditorProcsPtr  sepp;

  sepp = (StdEditorProcsPtr) GetAppProperty ("StdEditorForm");
  if (sepp != NULL) {
    if (sepp->handleMessages != NULL) {
      sepp->handleMessages (f, mssg);
    }
  }
}

static void ParseDefOrLocalIDToSource (BaseFormPtr bfp, Boolean parsedef, CharPtr path)

{
  ButtoN             b;
  GrouP              c;
  GrouP              g;
  GrouP              h;
  GrouP              p;
  ParseFormPtr       pfp;
  SeqEntryPtr        sep;
  StdEditorProcsPtr  sepp;
  WindoW             w;

  if (bfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;
  pfp = (ParseFormPtr) MemNew (sizeof (ParseFormData));
  if (pfp == NULL) return;
  w = FixedWindow (-50, -33, -10, -10, "Parse file to source", StdCloseWindowProc);
  SetObjectExtra (w, pfp, StdCleanupFormProc);
  pfp->form = (ForM) w;
  pfp->formmessage = ParseDeflineMessageProc;

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

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

  pfp->parsedef = parsedef;
  StringNCpy_0 (pfp->path, path, sizeof (pfp->path));
  pfp->fp = NULL;

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

  g = NULL;
  if (parsedef) {
    g = HiddenGroup (h, 4, 0, NULL);
    StaticPrompt (g, "Get text between", 0, dialogTextHeight, programFont, 'l');
    pfp->atleft = DialogText (g, "", 10, NULL);
    StaticPrompt (g, "and", 0, dialogTextHeight, programFont, 'l');
    pfp->atright = DialogText (g, "", 10, NULL);
  }

  p = HiddenGroup (h, 6, 0, NULL);
  if (parsedef || path != NULL) {
    StaticPrompt (p, "Place in", 0, popupMenuHeight, programFont, 'l');
  } else {
    StaticPrompt (p, "Place localID in", 0, popupMenuHeight, programFont, 'l');
  }
  pfp->taxname = PopupList (p, TRUE, NULL);
  SetObjectExtra (pfp->taxname, pfp, NULL);
  PopupItem (pfp->taxname, " ");
  PopupItem (pfp->taxname, "Taxname");
  PopupItem (pfp->taxname, "OrgMod Note");
  PopupItem (pfp->taxname, "SubSource Note");
  SetValue (pfp->taxname, 1);
  StaticPrompt (p, "or", 0, popupMenuHeight, programFont, 'l');
  pfp->orgmod = OrgModTypeDialog (p, SHORT_SELECTION_LIST, NULL, NULL, FALSE, FALSE, TRUE);
  StaticPrompt (p, "or", 0, popupMenuHeight, programFont, 'l');
  pfp->subsource = SubSourceTypeDialog (p, SHORT_SELECTION_LIST, NULL, NULL, FALSE, FALSE, TRUE);

  c = HiddenGroup (h, 4, 0, NULL);
  b = DefaultButton (c, "Accept", DoParseDeflineProc);
  SetObjectExtra (b, pfp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  pfp->leaveDlgUp = CheckBox (c, "Leave Dialog Up", NULL);

  AlignObjects (ALIGN_CENTER, (HANDLE) p, (HANDLE) c, (HANDLE) g, NULL);
  RealizeWindow (w);
  Show (w);
  Select (w);
  Select (pfp->atleft);
  Update ();
}

static void ParseDefOrLocalIDToSourceMenuItem (IteM i, Boolean parsedef, CharPtr path)
{
  BaseFormPtr bfp;

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

  if (bfp == NULL) return;

  ParseDefOrLocalIDToSource (bfp, parsedef, path);
}

extern void ParseFileToSource (IteM i)

{
  Char  path [PATH_MAX];

  if (GetInputFileName (path, sizeof (path), "", "TEXT")) {
    ParseDefOrLocalIDToSourceMenuItem (i, FALSE, path);
  }
}

static void TrimOrgNameCallback (BioSourcePtr biop, Pointer userdata)
{
  OrgRefPtr     orp;
  CharPtr       tmp, cp;
  CharPtr       word_break;
  Int4          space_len;

  if (biop == NULL) return;

  orp = biop->org;
  if (orp == NULL || StringHasNoText (orp->taxname)) return;

  tmp = StringSave (orp->taxname);
  cp = tmp;
  if (StringNICmp (tmp, "uncultured ", 11) == 0) {
    cp = tmp + 11;
  }

  word_break = StringStr (cp, " ");
  if (word_break == NULL) return;
  
  space_len = StringSpn (word_break, " ");

  if (StringNCmp (word_break + space_len, "sp.", 3) == 0
      || StringNCmp (word_break + space_len, "cf.", 3) == 0
      || StringNCmp (word_break + space_len, "aff.", 4) == 0)
  {
    word_break = StringStr (word_break + space_len, " ");
    if (word_break == NULL) return;
  }

  word_break = StringStr (word_break + space_len, " ");
  if (word_break == NULL) return;
  *word_break = 0;
  
  SetTaxNameAndRemoveTaxRef (orp, tmp);
}

extern void TrimOrganismName (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, TrimOrgNameCallback);
  Update ();
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}

typedef struct addmodinfo {
  Boolean isOrgMod;
  Uint1 subtype;
  Boolean only_sp;
  Boolean only_cf;
  Boolean only_aff;
  Boolean only_nr;
  Boolean no_taxid;
  CharPtr abbreviation;
  Boolean use_abbreviation;
} AddModInfo, PNTR AddModInfoPtr;

typedef struct addmodlistitem {
  Boolean isOrgMod;
  Uint1 subtype;
  CharPtr name;
  CharPtr abbreviation;
} AddModListItem, PNTR AddModListItemPtr;

static AddModListItem mods_list[] = {
{ TRUE, ORGMOD_strain, "Strain", NULL },
{ TRUE, ORGMOD_isolate, "Isolate", NULL },
{ TRUE, ORGMOD_authority, "Authority", NULL },
{ TRUE, ORGMOD_bio_material, "Bio-material", NULL },
{ TRUE, ORGMOD_biovar, "Biovar", "bv." },
{ FALSE, SUBSRC_clone, "Clone", NULL },
{ FALSE, SUBSRC_collection_date, "Collection-date", NULL },
{ FALSE, SUBSRC_country, "Country", NULL },
{ TRUE, ORGMOD_culture_collection, "Culture-collection", NULL },
{ TRUE, ORGMOD_forma, "Forma", "f." },
{ TRUE, ORGMOD_forma_specialis, "Forma-specialis", "f. sp." },
{ FALSE, SUBSRC_genotype, "Genotype", "genotype" },
{ FALSE, SUBSRC_haplotype, "Haplotype", NULL },
{ TRUE, ORGMOD_nat_host, "Host", NULL },
{ TRUE, ORGMOD_isolate, "Isolate", NULL },
{ TRUE, ORGMOD_pathovar, "Pathovar", "pv."},
{ TRUE, ORGMOD_serotype, "Serotype", NULL },
{ TRUE, ORGMOD_serovar, "Serovar", "serovar" },
{ TRUE, ORGMOD_specimen_voucher, "Specimen voucher", NULL },
{ TRUE, ORGMOD_strain, "Strain", NULL },
{ TRUE, ORGMOD_sub_species, "Sub-species", "subsp." },
{ TRUE, ORGMOD_subtype, "Subtype", "subtype" },
{ TRUE, ORGMOD_type, "Type", "type" },
{ TRUE, ORGMOD_variety, "Variety", "var." }
};


static void AddModToOrgProc (BioSourcePtr biop, Pointer userdata)
{
  OrgRefPtr     orp;
  OrgModPtr     mod;
  OrgNamePtr    onp;
  Boolean       influenza;
  CharPtr	str_to_add;
  size_t        len;
  CharPtr	str, tmp_name;
  AddModInfoPtr	ami;
  SubSourcePtr  ssp;
  Boolean       ok_to_add = FALSE;

  if (biop == NULL) return;

  ami = (AddModInfoPtr) userdata;

  orp = biop->org;
  if (orp == NULL) return;

  if (! ami->only_sp && ! ami->only_cf && ! ami->only_aff && ! ami->only_nr)
  {
    ok_to_add = TRUE;
  }
  if (ami->only_sp && StringStr (orp->taxname, " sp.") != NULL)
  {
    ok_to_add = TRUE;
  }
  if (ami->only_cf && StringStr (orp->taxname, " cf.") != NULL)
  {
    ok_to_add = TRUE;
  }
  if (ami->only_aff && StringStr (orp->taxname, " aff.") != NULL)
  {
    ok_to_add = TRUE;
  }
  if (ami->only_nr && StringStr (orp->taxname, " nr.") != NULL)
  {
    ok_to_add = TRUE;
  }

  if (ami->no_taxid && HasTaxonomyID (biop)) {
    ok_to_add = FALSE;
  }

  if (!ok_to_add)
  {
    return;
  }
  
  onp = orp->orgname;
  if (onp == NULL && ami->isOrgMod) 
  {
    return;
  }

  str_to_add = NULL;

  if (ami->isOrgMod)
  {
    mod = onp->mod;
    str_to_add = NULL;
    while (mod != NULL) {
      if (mod->subtype == ami->subtype) {
        str_to_add = StringSave (mod->subname);
      }
      mod = mod->next;
    }
  } else {
    if (biop->subtype != NULL) {
      ssp = biop->subtype;
      while (ssp != NULL) {
        if (ssp->subtype == ami->subtype) {
          str_to_add = StringSave (ssp->name);
        }
        ssp = ssp->next;
      }
    }
  }
  if (str_to_add != NULL) {

    /* strip colons for structured modifiers */
    if (ami->isOrgMod
        && (ami->subtype == ORGMOD_bio_material
            || ami->subtype == ORGMOD_culture_collection
            || ami->subtype == ORGMOD_specimen_voucher)) {
      FindReplaceString (&str_to_add, ":", " ", FALSE, FALSE);
    }

    influenza = FALSE;
    if (StringICmp (orp->taxname, "Influenza A virus") == 0 ||
        StringICmp (orp->taxname, "Influenza B virus") == 0) {
      influenza = TRUE;
    }
    len = StringLen (orp->taxname) + StringLen (str_to_add) + 6;
    if (influenza) {
      len += 2;
    }
    if (ami->abbreviation != NULL && ami->use_abbreviation)
    {
      len += StringLen (ami->abbreviation) + 1;
    }
    str = MemNew (len);
    if (str != NULL) {
      StringCpy (str, orp->taxname);
      StringCat (str, " ");
      if (ami->abbreviation != NULL && ami->use_abbreviation)
      {
        StringCat (str, ami->abbreviation);
        StringCat (str, " ");
      }
      if (influenza) {
        StringCat (str, "(");
      }
      StringCat (str, str_to_add);
      if (influenza) {
        StringCat (str, ")");
      }
      
      if (influenza)
      {
        tmp_name = FixInfluenzaVirusName (str);
        if (tmp_name != NULL)
        {
          str = MemFree (str);
          str = tmp_name;
          tmp_name = NULL;
        }
      }
      
      SetTaxNameAndRemoveTaxRef (biop->org, str);
      RemoveOldName (biop->org);
    }
    str_to_add = MemFree (str_to_add);
  }
}

static void AddModToOrgFeat (SeqFeatPtr sfp, Pointer userdata, FilterSetPtr fsp)
{
  if (sfp == NULL || sfp->data.choice != SEQFEAT_ORG 
      || sfp->data.value.ptrvalue == NULL || userdata == NULL)
  {
    return;
  }
  else
  {
    AddModToOrgProc (sfp->data.value.ptrvalue, userdata);
  }
}

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


typedef struct addmodformptr {
  FEATURE_FORM_BLOCK

  PopuP  modifier_to_add;
  ButtoN only_sp;
  ButtoN only_cf;
  ButtoN only_aff;
  ButtoN only_nr;
  ButtoN no_taxid;
  ButtoN use_abbreviation;
  DialoG constraint;

} AddModFormData, PNTR AddModFormPtr;

static void DoAddModToOrg (ButtoN b)
{
  AddModFormPtr	amfp;
  AddModInfoPtr ami;
  SeqEntryPtr   sep;
  Int4		      mod_index;
  FilterSetPtr  fsp;

  amfp = GetObjectExtra (b);
  if (amfp == NULL) return;
  Hide (amfp->form);

  sep = GetTopSeqEntryForEntityID (amfp->input_entityID);
  if (sep == NULL) return;
  ami = (AddModInfoPtr) MemNew (sizeof (AddModInfo));
  mod_index = GetValue (amfp->modifier_to_add);
  if (mod_index < 1
    || mod_index > sizeof (mods_list) / sizeof (AddModListItem))
    return;
  mod_index = mod_index - 1;

  ami->isOrgMod = mods_list[mod_index].isOrgMod;
  ami->subtype = mods_list[mod_index].subtype;
  ami->abbreviation = mods_list[mod_index].abbreviation;
  ami->only_sp = GetStatus (amfp->only_sp);
  ami->only_cf = GetStatus (amfp->only_cf);
  ami->only_aff = GetStatus (amfp->only_aff);
  ami->only_nr = GetStatus (amfp->only_nr);
  ami->no_taxid = GetStatus (amfp->no_taxid);
  ami->use_abbreviation = GetStatus (amfp->use_abbreviation);
  /* always use abbreviation for serovar */
  if (ami->isOrgMod && ami->subtype == ORGMOD_serovar)
  {
  	ami->use_abbreviation = TRUE;
  }
  fsp = (FilterSetPtr) DialogToPointer (amfp->constraint);
  OperateOnSeqEntryConstrainedObjects (sep, fsp, 
                                       AddModToOrgFeat,
                                       AddModToOrgDesc,
                                       SEQFEAT_ORG,
                                       FEATDEF_ORG,
                                       Seq_descr_source,
                                       ami);
  fsp = FilterSetFree (fsp);

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

  if (GetStatus (amfp->leave_dlg_up)) 
  {
    Show (amfp->form);
  }
  else
  {
    Remove (amfp->form);
  }
}

static void ChangeModPopup (PopuP p)
{
  AddModFormPtr amfp;
  Int4          mod_index;
  
  amfp = (AddModFormPtr) GetObjectExtra (p);
  if (amfp == NULL) return;

  mod_index = GetValue (amfp->modifier_to_add);
  if (mod_index < 1
    || mod_index > sizeof (mods_list) / sizeof (AddModListItem))
    return;
  mod_index = mod_index - 1;
  if ( mods_list [mod_index].abbreviation != NULL)
  {
    Enable (amfp->use_abbreviation);
    if ( !mods_list[mod_index].isOrgMod && mods_list [mod_index].subtype == SUBSRC_genotype) {
      SetStatus (amfp->use_abbreviation, FALSE);
    } else {
      SetStatus (amfp->use_abbreviation, TRUE);
    }
  }
  else
  {
    Disable (amfp->use_abbreviation);
  }
}


extern void AddModToOrgBaseForm (BaseFormPtr bfp)
{
  AddModFormPtr amfp;
  WindoW w;
  GrouP	g;
  GrouP	h;
  GrouP c;
  Int2 index;
  ButtoN b;

  if (bfp == NULL) return;

  amfp = (AddModFormPtr) MemNew (sizeof (AddModFormData));
  if (amfp == NULL) return;
  w = FixedWindow (-50, -33, -20, -10, "Append to Organism Name",
	StdCloseWindowProc);
  SetObjectExtra (w, amfp, StdCleanupFormProc);
  amfp->form = (ForM) w;

  amfp->input_entityID = bfp->input_entityID;
  amfp->input_itemID = bfp->input_itemID;

  g = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (g, 10, 10);

  amfp->modifier_to_add = PopupList (g, TRUE, ChangeModPopup);
  for (index=0; index< sizeof (mods_list) / sizeof (AddModListItem); index++)
  {
    PopupItem (amfp->modifier_to_add, mods_list[index].name);
  }
  SetObjectExtra (amfp->modifier_to_add, amfp, NULL);
  SetValue (amfp->modifier_to_add, 1);

  h = HiddenGroup (g, 1, 0, NULL);
  amfp->only_sp = CheckBox (h, "Only append to sp. organisms", NULL);
  amfp->only_cf = CheckBox (h, "Only append to cf. organisms", NULL);
  amfp->only_aff = CheckBox (h, "Only append to aff. organisms", NULL);
  amfp->only_nr = CheckBox (h, "Only append to nr. organisms", NULL);
  amfp->no_taxid = CheckBox (h, "Only to organisms with no taxonomy ID", NULL);
  amfp->use_abbreviation = CheckBox (h, "Use abbreviation (for example, pv., subsp., etc.)", NULL);
  SetStatus (amfp->use_abbreviation, TRUE);
  Disable (amfp->use_abbreviation);
  
  amfp->constraint = FilterGroup (g, FALSE, TRUE, FALSE, FALSE, FALSE, NULL);

  c = HiddenGroup (g, 3, 0, NULL);
  b = DefaultButton(c, "Accept", DoAddModToOrg);
  SetObjectExtra(b, amfp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  amfp->leave_dlg_up = CheckBox (c, "Leave Dialog Up", NULL);

  AlignObjects (ALIGN_CENTER, (HANDLE) amfp->modifier_to_add, 
                              (HANDLE) h, 
                              (HANDLE) amfp->constraint,
                              (HANDLE) c, NULL);
  RealizeWindow(w);
  Show(w);
  Update();
}


extern void AddModToOrg (IteM i)
{
  BaseFormPtr  bfp;

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

  AddModToOrgBaseForm (bfp);
}

typedef struct descformdata {
  FEATURE_FORM_BLOCK

  Uint2          oldEntityID;
  Uint4          oldItemID;
  Uint2          oldItemtype;
  Uint2          lookfor;
  Boolean        found;
  ObjMgrProcPtr  ompp;
  BioseqPtr      bsp;
  BioseqSetPtr   bssp;
  SeqEntryPtr    sep;
  Handle         target;
  Boolean        usePopupForTarget;
  Boolean        hasMutPopPhySet;
  Int2           targetScratchSpace;
  Int2           nucProtCount;
  Int2           segSetCount;
  Uint2          descsubtype;
  ButtoN         createNewBtn;
  CharPtr        user_object_tag;
} DescFormData, PNTR DescFormPtr;


static Boolean IsUserObjectCorrectTag (UserObjectPtr uop, CharPtr user_object_tag)
{
  if (uop == NULL) {
    return FALSE;
  } else if (user_object_tag == NULL) {
    return TRUE;
  } else if (StringCmp (uop->type->str, user_object_tag) == 0) {
    return TRUE;
  } else {
    return FALSE;
  }
}


static Boolean FindDescrFunc (GatherContextPtr gcp)

{
  DescFormPtr  dfp;
  ValNodePtr   vnp;

  if (gcp == NULL) return TRUE;
  dfp = (DescFormPtr) gcp->userdata;
  if (dfp == NULL) return TRUE;
  if (gcp->thistype == OBJ_SEQDESC) {
    vnp = (ValNodePtr) gcp->thisitem;
    if (vnp != NULL && vnp->choice == dfp->lookfor 
        && (vnp->choice != Seq_descr_user || IsUserObjectCorrectTag(vnp->data.ptrvalue, dfp->user_object_tag))) {
      dfp->oldEntityID = gcp->entityID;
      dfp->oldItemID = gcp->itemID;
      dfp->oldItemtype = gcp->thistype;
      dfp->found = TRUE;
      return FALSE;
    }
  }
  return TRUE;
}

static CharPtr segClassList [] = {
  " ", "Nucleotide-Protein Set", "Segmented Nucleotide Set",
  "conset", "parts", "gibb", "gi", "genbank", "pir", "pub-set",
  "equiv", "swissprot", "pdb-entry", "MUTATION SET",
  "POPULATION SET", "PHYLOGENETIC SET", "ENVIRONMENTAL SAMPLES",
  "genomic product set", "other", NULL
};

static Int4 AllButPartsList (SeqEntryPtr sep, Pointer mydata,
                             SeqEntryFunc mycallback,
                             Int4 index, Int2 indent)

{
  BioseqSetPtr  bssp;

  if (sep == NULL) return index;
  if (IS_Bioseq (sep)) {
    if (mycallback != NULL)
      (*mycallback) (sep, mydata, index, indent);
    return index + 1;
  }
  if (Bioseq_set_class (sep) != 4) {
    if (mycallback != NULL)
      (*mycallback) (sep, mydata, index, indent);
    index++;
  }
  bssp = (BioseqSetPtr) sep->data.ptrvalue;
  sep = bssp->seq_set;
  indent++;
  while (sep != NULL) {
    index = AllButPartsList (sep, mydata, mycallback, index, indent);
    sep = sep->next;
  }
  return index;
}

#define AllButPartsCount( a )  AllButPartsList( a ,NULL,NULL,0,0);
#define AllButPartsExplore(a,b,c) AllButPartsList(a, b, c, 0L, 0);

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

{
  DescFormPtr   dfp;
  BioseqPtr     bsp;
  BioseqSetPtr  bssp;
  Uint1         _class;
  CharPtr       ptr;
  SeqIdPtr      sip;
  Char          str [128];

  dfp = (DescFormPtr) mydata;
  if (sep != NULL && sep->data.ptrvalue != NULL) {
    if (sep->choice == 1) {
      bsp = (BioseqPtr) sep->data.ptrvalue;
      sip = SeqIdFindWorst (bsp->id);
      SeqIdWrite (sip, str, PRINTID_REPORT, sizeof (str));
      ptr = StringChr (str, '|');
      if (ptr == NULL) {
        ptr = str;
      } else {
        ptr++;
      }
      if (dfp->usePopupForTarget) {
        PopupItem (dfp->target, ptr);
      } else {
        ListItem (dfp->target, ptr);
      }
      if (bsp == dfp->bsp) {
        dfp->targetScratchSpace = index + 1;
      }
    } else if (sep->choice == 2) {
      bssp = (BioseqSetPtr) sep->data.ptrvalue;
      _class = bssp->_class;
      if (_class > 17) {
        _class = 18;
      }
      if (_class == 7 || (IsPopPhyEtcSet (bssp->_class))) {
        dfp->hasMutPopPhySet = TRUE;
      }
      if (! dfp->hasMutPopPhySet) {
        sprintf (str, "[%s]", segClassList [_class]);
      } else if (_class == 1) {
        (dfp->nucProtCount)++;
        sprintf (str, "[%s %d]", segClassList [_class], (int) dfp->nucProtCount);
        (dfp->segSetCount)++;
      } else if (_class == 2) {
        sprintf (str, "[%s %d]", segClassList [_class], (int) dfp->segSetCount);
      } else {
        sprintf (str, "[%s]", segClassList [_class]);
      }
      if (dfp->usePopupForTarget) {
        PopupItem (dfp->target, str);
      } else {
        ListItem (dfp->target, str);
      }
    }
  }
}

static Int2 PopulateTarget (DescFormPtr dfp, BioseqPtr bsp, Uint2 entityID)

{
  SeqEntryPtr  sep;
  Int2         val;

  val = 0;
  if (dfp != NULL) {
    if (bsp != NULL) {
      sep = SeqMgrGetSeqEntryForData (bsp);
      entityID = ObjMgrGetEntityIDForPointer (sep);
    }
    sep = GetTopSeqEntryForEntityID (entityID);
    dfp->sep = sep;
    if (sep != NULL) {
      dfp->hasMutPopPhySet = FALSE;
      dfp->targetScratchSpace = 1;
      dfp->nucProtCount = 0;
      dfp->segSetCount = 0;
      AllButPartsExplore (sep, (Pointer) dfp, PopTargetProc);
      val = dfp->targetScratchSpace;
    }
  }
  return val;
}

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

{
  DescFormPtr   dfp;

  dfp = (DescFormPtr) mydata;
  if (sep != NULL && sep->data.ptrvalue != NULL) {
    if (index + 1 == dfp->targetScratchSpace) {
      if (sep->choice == 1) {
        dfp->bsp = (BioseqPtr) sep->data.ptrvalue;
      } else if (sep->choice == 2) {
        dfp->bssp = (BioseqSetPtr) sep->data.ptrvalue;
      }
    }
  }
}

static Boolean ChangeTargetItemID (GatherContextPtr gcp)

{
  DescFormPtr  dfp;

  if (gcp == NULL) return TRUE;
  dfp = (DescFormPtr) gcp->userdata;
  if (dfp == NULL) return TRUE;
  if (gcp->thistype == OBJ_BIOSEQ) {
    if (dfp->bsp == (BioseqPtr) gcp->thisitem) {
      dfp->input_entityID = gcp->entityID;
      dfp->input_itemID = gcp->itemID;
      dfp->input_itemtype = gcp->thistype;
      return FALSE;
    }
  } else if (gcp->thistype == OBJ_BIOSEQSET) {
    if (dfp->bssp == (BioseqSetPtr) gcp->thisitem) {
      dfp->input_entityID = gcp->entityID;
      dfp->input_itemID = gcp->itemID;
      dfp->input_itemtype = gcp->thistype;
      return FALSE;
    }
  }
  return TRUE;
}

static void PreventDupTitleCreation (DescFormPtr dfp)

{
  ValNodePtr  sdp;

  if (dfp == NULL) return;
  if (dfp->bsp != NULL) {
    sdp = dfp->bsp->descr;
  } else if (dfp->bssp != NULL) {
    sdp = dfp->bssp->descr;
  } else return;
  if (dfp->descsubtype != Seq_descr_title) return;
  while (sdp != NULL) {
    if (sdp->choice == Seq_descr_title) {
      SafeDisable (dfp->createNewBtn);
      return;
    }
    sdp = sdp->next;
  }
  SafeEnable (dfp->createNewBtn);
}

static void ChangeNewDescTarget (Handle obj)

{
  DescFormPtr  dfp;
  GatherScope  gs;

  dfp = (DescFormPtr) GetObjectExtra (obj);
  if (dfp != NULL) {
    dfp->bsp = NULL;
    dfp->bssp = NULL;
    dfp->targetScratchSpace = GetValue (dfp->target);
    AllButPartsExplore (dfp->sep, (Pointer) dfp, FindNewTargetProc);
    MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
    gs.seglevels = 0;
    MemSet((Pointer) (gs.ignore), (int) (TRUE), (size_t) (OBJ_MAX * sizeof (Boolean)));
    gs.ignore[OBJ_BIOSEQ] = FALSE;
    gs.ignore[OBJ_BIOSEQSET] = FALSE;
    gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
    GatherEntity (dfp->input_entityID, (Pointer) dfp, ChangeTargetItemID, &gs);
    PreventDupTitleCreation (dfp);
  }
}



static void CreateNewDescProc (ButtoN b)

{
  DescFormPtr    dfp;
  OMProcControl  ompc;
  ObjMgrProcPtr  ompp;
  Int2           retval;

  dfp = (DescFormPtr) GetObjectExtra (b);
  if (dfp != NULL) {
    Hide (dfp->form);
    ompp = dfp->ompp;
    MemSet ((Pointer) (&ompc), 0, sizeof (OMProcControl));
    ompc.input_entityID = dfp->input_entityID;
    ompc.input_itemID = dfp->input_itemID;
    ompc.input_itemtype = dfp->input_itemtype;
    GatherDataForProc (&ompc, FALSE);
    ompc.proc = ompp;

    retval = (*(ompp->func)) (&ompc);
    if (retval == OM_MSG_RET_ERROR) {
      ErrShow ();
    }
    Update ();
    Remove (dfp->form);
  }
}

static void EditOldDescProc (ButtoN b)

{
  DescFormPtr  dfp;
  Int2         handled;

  dfp = (DescFormPtr) GetObjectExtra (b);
  if (dfp != NULL) {
    Hide (dfp->form);
    if (dfp->oldEntityID > 0 && dfp->oldItemID > 0 && dfp->oldItemtype > 0) {
      WatchCursor ();
      handled = GatherProcLaunch (OMPROC_EDIT, FALSE, dfp->oldEntityID,
                                  dfp->oldItemID, dfp->oldItemtype,
                                  0, 0, dfp->oldItemtype, 0);
      ArrowCursor ();
      if (handled != OM_MSG_RET_DONE || handled == OM_MSG_RET_NOPROC) {
        Message (MSG_ERROR, "Unable to edit existing descriptor.");
      }
    }
    Update ();
    Remove (dfp->form);
  }
}

static CharPtr  newDescMsg = "\
Descriptors may apply to a single sequence or to a set of \
sequences.  Please set the target control to the desired \
set or sequence.";

static CharPtr  editOldMsg = "\
A descriptor of this type already exists at or above the \
displayed target.  You may want to edit it instead of \
creating a new descriptor.";

static void NewDescriptorMessageProc (ForM f, Int2 mssg)

{
  DescFormPtr  dfp;

  dfp = (DescFormPtr) GetObjectExtra (f);
  if (dfp != NULL) {
    if (dfp->appmessage != NULL) {
      dfp->appmessage (f, mssg);
    }
  }
}

extern void NewDescriptorMenuFuncEx (ObjMgrProcPtr ompp, BaseFormPtr bfp, Uint2 descsubtype, CharPtr user_object_tag)

{
  ButtoN             b;
  BioseqPtr          bsp;
  GrouP              c;
  Int4               count;
  DescFormPtr        dfp;
  Uint2              entityID;
  GrouP              g;
  GatherScope        gs;
  GrouP              h;
  GrouP              p1, p2;
  SeqEntryPtr        sep;
  StdEditorProcsPtr  sepp;
  SeqIdPtr           sip;
  SeqLocPtr          slp;
  Int2               val;
  WindoW             w;
  GrouP              target_grp;

#ifdef WIN_MAC
  bfp = (BaseFormPtr) currentFormDataPtr;
#endif
  if (bfp == NULL) return;
  if (ompp == NULL || ompp->func == NULL) return;
  if (ompp->inputtype != OBJ_SEQDESC) return;
  dfp = (DescFormPtr) MemNew (sizeof (DescFormData));
  if (dfp == NULL) return;
  dfp->ompp = ompp;
  dfp->lookfor = ompp->subinputtype;
  if (dfp->lookfor == Seq_descr_user) {
    dfp->user_object_tag = user_object_tag;
  }
  dfp->found = FALSE;
  dfp->descsubtype = descsubtype;
  bsp =  GetBioseqGivenIDs (bfp->input_entityID, bfp->input_itemID, bfp->input_itemtype);
  MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
  gs.seglevels = 0;
  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_BIOSEQSET] = FALSE;
  gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
  gs.ignore[OBJ_SEQDESC] = FALSE;
  if (bsp != NULL) {
    slp = ValNodeNew (NULL);
    slp->choice = SEQLOC_WHOLE;
    sip = SeqIdStripLocus (SeqIdDup (SeqIdFindBest (bsp->id, 0)));
    slp->data.ptrvalue = sip;
    gs.target = slp;
  }
  dfp->bsp = bsp;
  GatherEntity (bfp->input_entityID, (Pointer) dfp, FindDescrFunc, &gs);
  SeqLocFree (gs.target);
  w = FixedWindow (-50, -33, -10, -10, "Descriptor Target Control", StdCloseWindowProc);
  SetObjectExtra (w, dfp, StdCleanupFormProc);
  dfp->form = (ForM) w;
  dfp->formmessage = NewDescriptorMessageProc;

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

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

  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  
  target_grp = HiddenGroup (h, -1, 0, NULL);
  
  p1 = MultiLinePrompt (target_grp, newDescMsg, 25 * stdCharWidth, programFont);
    
  entityID = bfp->input_entityID;
  if (bsp != NULL) {
    sep = SeqMgrGetSeqEntryForData (bsp);
    entityID = ObjMgrGetEntityIDForChoice (sep);
  }
  sep = GetTopSeqEntryForEntityID (entityID);
  count = AllButPartsCount (sep);
  if (count < 32) {
    g = HiddenGroup (target_grp, 2, 0, NULL);
    StaticPrompt (g, "Target", 0, popupMenuHeight, programFont, 'l');
    dfp->usePopupForTarget = TRUE;
    dfp->target = PopupList (g, TRUE, (PupActnProc) ChangeNewDescTarget);
  } else {
    g = HiddenGroup (target_grp, 0, 2, NULL);
    StaticPrompt (g, "Target", 0, 0, programFont, 'c');
    dfp->usePopupForTarget = FALSE;
    dfp->target = SingleList (g, 14, 3, (LstActnProc) ChangeNewDescTarget);
  }
  SetObjectExtra (dfp->target, dfp, NULL);
  val = PopulateTarget (dfp, bsp, bfp->input_entityID);
  SetValue (dfp->target, val);
  ChangeNewDescTarget ((Handle) dfp->target);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) p1, (HANDLE) g, NULL);
  
  p2 = NULL;
  if (dfp->found) {
    p2 = MultiLinePrompt (h, editOldMsg, 25 * stdCharWidth, programFont);
  }
  
  c = HiddenGroup (h, 4, 0, NULL);
  SetGroupSpacing (c, 10, 2);
  dfp->createNewBtn = PushButton (c, "Create New", CreateNewDescProc);
  SetObjectExtra (dfp->createNewBtn, dfp, NULL);
  if (dfp->found) {
    b = DefaultButton (c, "Edit Old", EditOldDescProc);
    SetObjectExtra (b, dfp, NULL);
  }
  PushButton (c, "Cancel", StdCancelButtonProc);

  AlignObjects (ALIGN_CENTER, (HANDLE) target_grp, (HANDLE) c, (HANDLE) p2, NULL);
  RealizeWindow (w);
  PreventDupTitleCreation (dfp);
  Show (w);
  Update ();
}

extern void NewDescriptorMenuFunc (ObjMgrProcPtr ompp, BaseFormPtr bfp, Uint2 descsubtype)
{
  NewDescriptorMenuFuncEx (ompp, bfp, descsubtype, NULL);
}


static void NewDescriptorMenuProc (IteM i)

{
  NewObjectPtr  nop;

  nop = (NewObjectPtr) GetObjectExtra (i);
  if (nop == NULL) return;
  NewDescriptorMenuFunc (nop->ompp, nop->bfp, nop->descsubtype);
}


static void NewDescriptorMenuProcExtra (IteM i)

{
  NewObjectPtr  nop;

  nop = (NewObjectPtr) GetObjectExtra (i);
  if (nop == NULL) return;
  if (nop->blurb == NULL) {
    NewDescriptorMenuFunc (nop->ompp, nop->bfp, nop->descsubtype);
  } else {
    NewDescriptorMenuFuncEx (nop->ompp, nop->bfp, nop->descsubtype, nop->blurb);
  }
}


extern void SetupNewDescriptorsMenu (MenU m, BaseFormPtr bfp)

{
  Boolean        allowgenbank, allow_structured_comment = FALSE;
  IteM           i;
  NewObjectPtr   nop;
  ObjMgrPtr      omp;
  ObjMgrProcPtr  ompp;
  ObjMgrTypePtr  omtp;

  if (m == NULL) return;
  omp = ObjMgrGet ();
  if (omp == NULL) return;
  ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, 0, 0, NULL);
  if (ompp == NULL) return;
  omtp = NULL;
  allowgenbank = FALSE;
/*#ifdef EXTRA_SERVICES*/
  if (extraServices) {
    allowgenbank = TRUE;
  }
/*#endif*/
  if (indexerVersion) {
    allow_structured_comment = TRUE;
  }

  while ((omtp = ObjMgrTypeFindNext (omp, omtp)) != NULL) {
    ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, omtp->datatype, 0, NULL);
    if (ompp != NULL) {
      switch (omtp->datatype) {
        case OBJ_SEQDESC :
          ompp = NULL;
          while ((ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT,
				  omtp->datatype, 0, ompp)) != NULL) {
            if (ompp->subinputtype != Seq_descr_pub) {
              if (!allowgenbank && ompp->subinputtype == Seq_descr_genbank) {
                /* skip */
              } else if (!allow_structured_comment && ompp->subinputtype == Seq_descr_user && StringCmp (ompp->proclabel, "Structured Comment") == 0) {
                /* skip */
              } else if (ompp->subinputtype == Seq_descr_name) {
                /* skip */
              } else if (ompp->subinputtype == Seq_descr_region) {
                /* skip */
              } else if (ompp->subinputtype == Seq_descr_create_date) {
                /* skip */
              } else if (ompp->subinputtype == Seq_descr_update_date) {
                /* skip */
              } else if (ompp->subinputtype == Seq_descr_user) {
                if (StringCmp (ompp->proclabel, "GenomeProjectsDB") == 0) {
                  /* skip */
                } else {
                  i = CommandItem (m, ompp->proclabel, NewDescriptorMenuProcExtra);
                  nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
                  if (nop != NULL) {
                    nop->ompp = ompp;
                    nop->bfp = bfp;
                    nop->descsubtype = ompp->subinputtype;
                    if (StringCmp (ompp->proclabel, "DBLink") == 0) {
                      nop->blurb = "DBLink"; 
                    /*
                    } else if (StringCmp (ompp->proclabel, "GenomeProjectsDB") == 0) {
                      nop->blurb = "GenomeProjectsDB";
                    */
                    } else if (StringCmp (ompp->proclabel, "Structured Comment") == 0) {
                      nop->blurb = "StructuredComment";
                    } else if (StringCmp (ompp->proclabel, "TPA Assembly") == 0) {
                      nop->blurb = "TpaAssembly";
                    } else if (StringCmp (ompp->proclabel, "RefGene Tracking") == 0) {
                      nop->blurb = "RefGeneTracking";
                    }
                  }
                  SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);               
                }
              } else {
                i = CommandItem (m, ompp->proclabel, NewDescriptorMenuProc);
                nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
                if (nop != NULL) {
                  nop->ompp = ompp;
                  nop->bfp = bfp;
                  nop->descsubtype = ompp->subinputtype;
                }
                SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);
              }
            }
          }
          break;
        default :
          break;
      }
    }
  }
}

static CharPtr  editOldDescMsg = "\
You may really want to edit an existing BioSource descriptor instead.\n\
Proceed anyway?";

static void NewFeatureMenuProc (IteM i)

{
  MsgAnswer      ans;
  BaseFormPtr    bfp;
  NewObjectPtr   nop;
  OMProcControl  ompc;
  ObjMgrProcPtr  ompp;
  Int2           retval;

  nop = (NewObjectPtr) GetObjectExtra (i);
  if (nop == NULL) return;
#ifdef WIN_MAC
  bfp = (BaseFormPtr) currentFormDataPtr;
#else
  bfp = nop->bfp;
#endif
  if (bfp == NULL) return;
  ompp = nop->ompp;
  if (ompp == NULL || ompp->func == NULL) return;
  if (ompp->subinputtype == FEATDEF_BIOSRC) {
    ans = Message (MSG_YN, editOldDescMsg);
    if (ans == ANS_NO) return;
  }
  MemSet ((Pointer) (&ompc), 0, sizeof (OMProcControl));
  ompc.input_entityID = bfp->input_entityID;
  ompc.input_itemID = bfp->input_itemID;
  ompc.input_itemtype = bfp->input_itemtype;
  GatherDataForProc (&ompc, FALSE);
  ompc.proc = ompp;
  retval = (*(ompp->func)) (&ompc);
  if (retval == OM_MSG_RET_ERROR) {
    ErrShow ();
  }
}

#ifdef WIN_MAC
VoidPtr macUserDataPtr = NULL;
#endif

extern void EnableFeaturesPerTarget (BaseFormPtr bfp)

{
  BioseqPtr     bsp;
  Uint1         mol;
  NewObjectPtr  nop;

  if (bfp == NULL) return;
#ifdef WIN_MAC
  nop = (NewObjectPtr) macUserDataPtr;
#else
  nop = (NewObjectPtr) bfp->userDataPtr;
#endif
  bsp =  GetBioseqGivenIDs (bfp->input_entityID, bfp->input_itemID, bfp->input_itemtype);
  mol = 0;
  if (bsp != NULL) {
    mol = bsp->mol;
  }
  while (nop != NULL) {
    if (nop->kind == 2) {
      /* analysis menu item, ignore it */
    } else if (mol == 0) {
      SafeDisable (nop->item);
    } else if (ISA_na (mol) && (nop->molgroup == 2 || nop->molgroup == 3)) {
      SafeEnable (nop->item);
    } else if (ISA_aa (mol) && (nop->molgroup == 1 || nop->molgroup == 3)) {
      SafeEnable (nop->item);
    } else {
      SafeDisable (nop->item);
    }
    nop = nop->next;
  }
}

static VoidPtr LinkNewObjectLists (NewObjectPtr list1, NewObjectPtr list2)

{
  NewObjectPtr  nop;

  if (list1 == NULL) return list2;
  nop = list1;
  while (nop->next != NULL) {
    nop = nop->next;
  }
  nop->next = list2;
  return list1;
}


extern Boolean IsUnwantedFeatureType (Uint1 key)
{
#if 1
  return FALSE;
#else
  if (key == FEATDEF_satellite || key == FEATDEF_repeat_unit) {
    return TRUE;
  } else {
    return FALSE;
  }
#endif
}


extern void SetupNewFeaturesMenu (MenU m, BaseFormPtr bfp)

{
  FeatDispGroupPtr  fdgp;
  FeatDefPtr        fdp;
  NewObjectPtr      first;
  IteM              i;
  Uint1             key;
  CharPtr           label;
  NewObjectPtr      last;
  NewObjectPtr      nop;
  ObjMgrPtr         omp;
  ObjMgrProcPtr     ompp;
  ObjMgrTypePtr     omtp;
  MenU              sub;
  Uint2             subtype;

  if (m == NULL) return;
  omp = ObjMgrGet ();
  if (omp == NULL) return;
  ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, 0, 0, NULL);
  if (ompp == NULL) return;
  omtp = NULL;
  first = NULL;
  last = NULL;
  while ((omtp = ObjMgrTypeFindNext (omp, omtp)) != NULL) {
    ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, omtp->datatype, 0, NULL);
    if (ompp != NULL) {
      switch (omtp->datatype) {
        case OBJ_SEQFEAT :
          fdgp = NULL;
          while ((fdgp = DispGroupFindNext (fdgp, &key, &label)) != NULL) {
            if (fdgp->groupkey != 0) {
              sub = SubMenu (m, fdgp->groupname);
              fdp = NULL;
              label = NULL;
              while ((fdp = FeatDefFindNext (fdp, &key, &label,
                     fdgp->groupkey, FALSE)) != NULL) {
                if (key != FEATDEF_BAD && key != FEATDEF_repeat_unit && key != FEATDEF_LTR) {
                  ompp = NULL;
                  while ((ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT,
				          omtp->datatype, 0, ompp)) != NULL) {
                    if (ompp->subinputtype == fdp->featdef_key &&
                        ompp->subinputtype != FEATDEF_PUB && 
                        !IsRegulatorySubtype(ompp->subinputtype)) {
                      i = CommandItem (sub, ompp->proclabel, NewFeatureMenuProc);
                      nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
                      if (nop != NULL) {
                        nop->kind = 1; /* feature creation item */
                        nop->ompp = ompp;
                        nop->bfp = bfp;
                        nop->item = i;
                        nop->molgroup = fdp->molgroup;
                      }
                      if (first == NULL) {
                        first = nop;
                      }
                      if (last != NULL) {
                        last->next = nop;
                      }
                      last = nop;
                      SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);
                    }
                  }
                }
              }
            }
          }
          /* if (indexerVersion) { */
            sub = SubMenu (m, "Remaining Features");
            fdp = NULL;
            label = NULL;
            while ((fdp = FeatDefFindNext (fdp, &key, &label, 0, FALSE)) != NULL) {
              if (key != FEATDEF_BAD && !IsUnwantedFeatureType(key)) {
                ompp = NULL;
                while ((ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT,
				                                           omtp->datatype, 0, ompp)) != NULL) {
				          subtype = ompp->subinputtype;
                  if (subtype == fdp->featdef_key && OkToListFeatDefInRemainingFeatures (subtype)) {
                    i = CommandItem (sub, ompp->proclabel, NewFeatureMenuProc);
                    nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
                    if (nop != NULL) {
                      nop->kind = 1; /* feature creation item */
                      nop->ompp = ompp;
                      nop->bfp = bfp;
                      nop->item = i;
                      nop->molgroup = fdp->molgroup;
                    }
                    if (first == NULL) {
                      first = nop;
                    }
                    if (last != NULL) {
                      last->next = nop;
                    }
                    last = nop;
                    SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);
                  }
                }
              }
            }
          /* } */
          break;
        default :
          break;
      }
    }
  }
#ifdef WIN_MAC
  macUserDataPtr = LinkNewObjectLists (macUserDataPtr, first);
#else
  bfp->userDataPtr = LinkNewObjectLists (bfp->userDataPtr, first);
#endif
}

extern void SetupNewPublicationsMenu (MenU m, BaseFormPtr bfp)

{
  NewObjectPtr   first;
  IteM           i;
  NewObjectPtr   nop;
  ObjMgrPtr      omp;
  ObjMgrProcPtr  ompp;
  ObjMgrTypePtr  omtp;

  if (m == NULL) return;
  omp = ObjMgrGet ();
  if (omp == NULL) return;
  ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, 0, 0, NULL);
  if (ompp == NULL) return;
  omtp = NULL;
  while ((omtp = ObjMgrTypeFindNext (omp, omtp)) != NULL) {
    ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, omtp->datatype, 0, NULL);
    if (ompp != NULL) {
      switch (omtp->datatype) {
        case OBJ_SEQFEAT :
            ompp = NULL;
            while ((ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT,
				    omtp->datatype, 0, ompp)) != NULL) {
              if (ompp->subinputtype == FEATDEF_PUB) {
                i = CommandItem (m, "Publication Feature", NewFeatureMenuProc);
                nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
                if (nop != NULL) {
                  nop->kind = 1; /* feature creation item */
                  nop->ompp = ompp;
                  nop->bfp = bfp;
                  nop->item = i;
                  nop->molgroup = 3;
#ifdef WIN_MAC
                  first = (NewObjectPtr) macUserDataPtr;
#else
                  first = (NewObjectPtr) bfp->userDataPtr;
#endif
                  if (first != NULL) {
                    while (first->next != NULL) {
                      first = first->next;
                    }
                    first->next = nop;
                  }
                }
                SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);
              }
            }
          break;
        case OBJ_SEQDESC :
          ompp = NULL;
          while ((ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT,
				  omtp->datatype, 0, ompp)) != NULL) {
            if (ompp->subinputtype == Seq_descr_pub) {
              i = CommandItem (m, "Publication Descriptor", NewDescriptorMenuProc);
              nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
              if (nop != NULL) {
                nop->ompp = ompp;
                nop->bfp = bfp;
              }
              SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);
            }
          }
          break;
        default :
          break;
      }
    }
  }
}

#ifdef WIN_MAC
extern IteM  addSecondaryItem;
#endif

extern void SetupEditSecondary (MenU m, BaseFormPtr bfp)

{
  IteM           i;
  NewObjectPtr   nop;
  ObjMgrPtr      omp;
  ObjMgrProcPtr  ompp;
  ObjMgrTypePtr  omtp;

  if (m == NULL) return;
  omp = ObjMgrGet ();
  if (omp == NULL) return;
  ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, 0, 0, NULL);
  if (ompp == NULL) return;
  omtp = NULL;
  while ((omtp = ObjMgrTypeFindNext (omp, omtp)) != NULL) {
    ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT, omtp->datatype, 0, NULL);
    if (ompp != NULL) {
      switch (omtp->datatype) {
        case OBJ_SEQDESC :
          ompp = NULL;
          while ((ompp = ObjMgrProcFindNext (omp, OMPROC_EDIT,
				  omtp->datatype, 0, ompp)) != NULL) {
            if (ompp->subinputtype != Seq_descr_pub) {
              if (ompp->subinputtype == Seq_descr_genbank) {
                i = CommandItem (m, "Add Secondary", NewDescriptorMenuProc);
                nop = (NewObjectPtr) MemNew (sizeof (NewObjectData));
                if (nop != NULL) {
                  nop->ompp = ompp;
                  nop->bfp = bfp;
                }
                SetObjectExtra (i, (Pointer) nop, StdCleanupExtraProc);
#ifdef WIN_MAC
                if (addSecondaryItem == NULL) {
                  addSecondaryItem = i;
                }
#endif
                return;
              }
            }
          }
          break;
        default :
          break;
      }
    }
  }
}

typedef struct gbformdata {
  FEATURE_FORM_BLOCK

  ButtoN         clearExtraAccns;
  ButtoN         clearSource;
  ButtoN         clearKeywords;
  ButtoN         clearOrigin;
  ButtoN         clearOldDate;
  ButtoN         clearEntryDate;
  ButtoN         clearDivision;
  ButtoN         clearTaxonomy;
} GbeditFormData, PNTR GbeditFormPtr;

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

{
  BioseqPtr      bsp;
  BioseqSetPtr   bssp;
  Boolean        empty;
  GBBlockPtr     gbp;
  GbeditFormPtr  gfp;
  ValNodePtr     nextsdp;
  Pointer PNTR   prevsdp;
  ValNodePtr     sdp;

  if (mydata == NULL) return;
  if (sep == NULL || sep->data.ptrvalue == NULL) return;
  gfp = (GbeditFormPtr) mydata;
  if (gfp == NULL) return;
  if (IS_Bioseq (sep)) {
    bsp = (BioseqPtr) sep->data.ptrvalue;
    sdp = bsp->descr;
    prevsdp = (Pointer PNTR) &(bsp->descr);
  } else if (IS_Bioseq_set (sep)) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    sdp = bssp->descr;
    prevsdp = (Pointer PNTR) &(bssp->descr);
  } else return;
  while (sdp != NULL) {
    nextsdp = sdp->next;
    empty = FALSE;
    if (sdp->choice == Seq_descr_genbank && sdp->data.ptrvalue != NULL) {
      gbp = (GBBlockPtr) sdp->data.ptrvalue;
      if (GetStatus (gfp->clearExtraAccns)) {
        gbp->extra_accessions = ValNodeFreeData (gbp->extra_accessions);
      }
      if (GetStatus (gfp->clearSource)) {
        gbp->source = MemFree (gbp->source);
      }
      if (GetStatus (gfp->clearKeywords)) {
        gbp->keywords = ValNodeFreeData (gbp->keywords);
      }
      if (GetStatus (gfp->clearOrigin)) {
        gbp->origin = MemFree (gbp->origin);
      }
      if (GetStatus (gfp->clearOldDate)) {
        gbp->date = MemFree (gbp->date);
      }
      if (GetStatus (gfp->clearEntryDate)) {
        gbp->entry_date = DateFree (gbp->entry_date);
      }
      if (GetStatus (gfp->clearDivision)) {
        gbp->div = MemFree (gbp->div);
      }
      if (GetStatus (gfp->clearTaxonomy)) {
        gbp->taxonomy = MemFree (gbp->taxonomy);
      }
      if (gbp->extra_accessions == NULL && gbp->source == NULL &&
          gbp->keywords == NULL && gbp->origin == NULL &&
          gbp->date == NULL && gbp->entry_date == NULL &&
          gbp->div == NULL && gbp->taxonomy == NULL) {
        empty = TRUE;
        ObjMgrDeSelect (0, 0, 0, 0, NULL);
      }
    }
    if (empty) {
      *(prevsdp) = sdp->next;
      sdp->next = NULL;
      SeqDescFree (sdp);
    } else {
      prevsdp = (Pointer PNTR) &(sdp->next);
    }
    sdp = nextsdp;
  }
}

static void DoEditGenbank (ButtoN b)

{
  GbeditFormPtr  gfp;
  SeqEntryPtr    sep;

  gfp = GetObjectExtra (b);
  if (gfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (gfp->input_entityID);
  if (sep == NULL) return;
  Hide (gfp->form);
  WatchCursor ();
  Update ();
  SeqEntryExplore (sep, (Pointer) gfp, EditGenbankCallback);
  ArrowCursor ();
  Update ();
  ObjMgrSetDirtyFlag (gfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, gfp->input_entityID, 0, 0);
  Remove (gfp->form);
}

static void EditGenbankMessageProc (ForM f, Int2 mssg)

{
  GbeditFormPtr  gfp;

  gfp = (GbeditFormPtr) GetObjectExtra (f);
  if (gfp != NULL) {
    if (gfp->appmessage != NULL) {
      gfp->appmessage (f, mssg);
    }
  }
}

extern void EditGenbankElements (Handle i)

{
  BaseFormPtr        bfp;
  ButtoN             b;
  GrouP              c;
  GrouP              g;
  GbeditFormPtr      gfp;
  GrouP              h;
  SeqEntryPtr        sep;
  StdEditorProcsPtr  sepp;
  WindoW             w;

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL) return;
  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL) return;
  gfp = (GbeditFormPtr) MemNew (sizeof (GbeditFormData));
  if (gfp == NULL) return;
  w = FixedWindow (-50, -33, -10, -10, "GenBank Block Removal", StdCloseWindowProc);
  SetObjectExtra (w, gfp, StdCleanupFormProc);
  gfp->form = (ForM) w;
  gfp->formmessage = EditGenbankMessageProc;

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

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

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

  g = HiddenGroup (h, -1, 0, NULL);

  gfp->clearExtraAccns = CheckBox (g, "Clear Secondary Accessions", NULL);
  gfp->clearSource = CheckBox (g, "Clear Source Line", NULL);
  gfp->clearKeywords = CheckBox (g, "Clear Keywords", NULL);
  gfp->clearOrigin = CheckBox (g, "Clear Origin", NULL);
  gfp->clearOldDate = CheckBox (g, "Clear Old Date", NULL);
  gfp->clearEntryDate = CheckBox (g, "Clear Entry Date", NULL);
  gfp->clearDivision = CheckBox (g, "Clear Division", NULL);
  gfp->clearTaxonomy = CheckBox (g, "Clear Lineage", NULL);

  c = HiddenGroup (h, 4, 0, NULL);
  b = DefaultButton (c, "Accept", DoEditGenbank);
  SetObjectExtra (b, gfp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);

  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) c, NULL);
  RealizeWindow (w);
  Show (w);
  Update ();
}

typedef struct helpindex {
  Int2           item;
  CharPtr        heading;
  CharPtr        section;
  CharPtr        combined;
} HelpIndex, PNTR HelpIndexPtr;

typedef struct helpform {
  FORM_MESSAGE_BLOCK
  DoC            doc;
  DoC            list;
  TexT           findTxt;
  ButtoN         findBtn;
  GrouP          dismissGrp;
  ValNodePtr     mainStrings;
  ValNodePtr     indexStrings;
  ValNodePtr     index;
  Char           file [PATH_MAX];
} HelpForm, PNTR HelpFormPtr;

#define TBL_FMT 1
#define TXT_FMT 2
#define HDG_FMT 3
#define SUB_FMT 4  /* and beyond, encoding indent */

static ParData lstParFmt = {FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0};
static ColData lstColFmt = {0, 0, 80, 0, NULL, 'l', FALSE, FALSE, FALSE, FALSE, TRUE};

static ParData hdgParFmt = {TRUE, FALSE, FALSE, FALSE, FALSE, 0, 0};
static ColData hdgColFmt = {0, 0, 80, 0, NULL, 'c', TRUE, FALSE, FALSE, FALSE, TRUE};

static ParData subParFmt = {TRUE, FALSE, FALSE, FALSE, FALSE, 0, 0};
static ColData subColFmt = {0, 0, 80, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, TRUE};

static ParData tblParFmt = {TRUE, FALSE, FALSE, FALSE, TRUE, 0, 0};
static ColData tblColFmt = {0, 0, 80, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, TRUE};

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

static void ResetHelpLists (HelpFormPtr hfp)

{
  HelpIndexPtr  hip;
  ValNodePtr    vnp;

  if (hfp != NULL) {
    hfp->mainStrings = ValNodeFreeData (hfp->mainStrings);
    hfp->indexStrings = ValNodeFreeData (hfp->indexStrings);
    vnp = hfp->index;
    while (vnp != NULL) {
      hip = (HelpIndexPtr) vnp->data.ptrvalue;
      if (hip != NULL) {
        hip->heading = MemFree (hip->heading);
        hip->section = MemFree (hip->section);
        hip->combined = MemFree (hip->combined);
      }
      vnp = vnp->next;
    }
    hfp->index = ValNodeFreeData (hfp->index);
  }
}

static void TrimTrailingSpaces (CharPtr str)

{
  size_t  len;

  if (str != NULL && str [0] != '\0') {
    len = StringLen (str);
    while (len > 0 && str [len - 1] == ' ') {
      len--;
    }
    str [len] = '\0';
  }
}

static Boolean ParseHelpFile (HelpFormPtr hfp, Boolean printPath)

{
  Char          ch;
  Uint1         choice;
  FileCache     fc;
  FILE          *fp;
  Boolean       goOn;
  Char          heading [512];
  HelpIndexPtr  hip;
  Boolean       inTable;
  Int2          level;
  ValNodePtr    list;
  Int2          numItems;
  Char          path [PATH_MAX];
  CharPtr       ptr;
  Char          section [512];
  Char          str [512];
  ValNodePtr    vnp;

  if (hfp == NULL || hfp->doc == NULL) return FALSE;
  Hide (hfp->list);
  Hide (hfp->doc);
  Update ();
  Reset (hfp->list);
  Reset (hfp->doc);
  ResetHelpLists (hfp);
  if (hfp->file == NULL || hfp->file [0] == '\0') return FALSE;
  numItems = 0;
  ProgramPath (path, sizeof (path));
  ptr = StringRChr (path, DIRDELIMCHR);
  if (ptr != NULL) {
    *ptr = '\0';
  }
  FileBuildPath (path, NULL, hfp->file);
  fp = FileOpen (path, "r");
  if (fp == NULL) {
    if (GetAppParam ("NCBI", "ErrorProcessing", "MsgPath", NULL, path, sizeof (path))) {
      FileBuildPath (path, NULL, hfp->file);
      fp = FileOpen (path, "r");
    }
  }
  if (fp == NULL) {
    if (GetAppParam ("NCBI", "NCBI", "DATA", NULL, path, sizeof (path))) {
      FileBuildPath (path, NULL, hfp->file);
      fp = FileOpen (path, "r");
    }
  }
  if (fp != NULL) {
    if (printPath) {
      SetTitle (hfp->form, path);
    }
    list = NULL;
    heading [0] = '\0';
    section [0] = '\0';
    inTable = FALSE;
    if (! FileCacheSetup (&fc, fp)) return FALSE;
    goOn = (FileCacheGetString (&fc, str, sizeof (str)) != NULL);
    while (goOn) {
      ptr = str;
      ch = *ptr;
      while (ch != '\n' && ch != '\r' && ch != '\0') {
        ptr++;
        ch = *ptr;
      }
      *ptr = '\0';
      if (inTable) {
        TrimTrailingSpaces (str);
      } else {
        TrimSpacesAroundString (str);
      }
      ch = str [0];
      if (ch == '>' || ch == '*' || ch == '#' || ch == '!') {
        if (list != NULL) {
          ptr = MergeValNodeStrings (list, inTable);
          if (inTable) {
            choice = TBL_FMT;
          } else {
            choice = TXT_FMT;
          }
          numItems++;
          vnp = ValNodeAdd (&(hfp->mainStrings));
          if (vnp != NULL) {
            vnp->choice = choice;
            vnp->data.ptrvalue = ptr;
          }
          /* ptr = MemFree (ptr); */
          list = ValNodeFreeData (list);
        }
        ch = str [0];
        if (ch == '>') {
          StringNCpy_0 (heading, str + 1, sizeof (heading));
          numItems++;
          vnp = ValNodeAdd (&(hfp->mainStrings));
          if (vnp != NULL) {
            vnp->choice = HDG_FMT;
            vnp->data.ptrvalue = StringSave (heading);
          }
          vnp = ValNodeAdd (&(hfp->indexStrings));
          if (vnp != NULL) {
            vnp->choice = 0;
            vnp->data.ptrvalue = StringSave (heading);
          }
          vnp = ValNodeAdd (&(hfp->index));
          if (vnp != NULL) {
            hip = (HelpIndexPtr) MemNew (sizeof (HelpIndex));
            vnp->data.ptrvalue = (Pointer) hip;
            if (hip != NULL) {
              hip->item = numItems;
              hip->heading = StringSave (heading);
            }
          }
        } else if (ch == '*') {
          level = 1;
          ch = str [level];
          while (ch == '*') {
            level++;
            ch = str [level];
          }
          StringNCpy_0 (section, str + level, sizeof (section));
          numItems++;
          vnp = ValNodeAdd (&(hfp->mainStrings));
          if (vnp != NULL) {
            if (level < 2) {
              vnp->choice = SUB_FMT;
            } else {
              vnp->choice = 5 * (level - 1);
            }
            vnp->data.ptrvalue = StringSave (section);
          }
          vnp = ValNodeAdd (&(hfp->indexStrings));
          if (vnp != NULL) {
            vnp->choice = 5 * level;
            vnp->data.ptrvalue = StringSave (section);
          }
          vnp = ValNodeAdd (&(hfp->index));
          if (vnp != NULL) {
            hip = (HelpIndexPtr) MemNew (sizeof (HelpIndex));
            vnp->data.ptrvalue = (Pointer) hip;
            if (hip != NULL) {
              hip->item = numItems;
              hip->heading = StringSave (heading);
              hip->section = StringSave (section);
              sprintf (str, "%s|%s", heading, section);
              hip->combined = StringSave (str);
            }
          }
        } else if (ch == '#' || ch == '!') {
          inTable = (Boolean) (ch == '!');
          vnp = ValNodeAdd (&list);
          if (vnp != NULL) {
            if (! StringHasNoText (str + 1)) {
              vnp->data.ptrvalue = StringSave (str + 1);
            }
          }
        }
      } else if (ch == '<') {
      } else if (ch != '\0') {
        vnp = ValNodeAdd (&list);
        if (vnp != NULL) {
          if (! StringHasNoText (str)) {
            vnp->data.ptrvalue = StringSave (str);
          }
        }
      }
      goOn = (Boolean) (goOn && (FileCacheGetString (&fc, str, sizeof (str)) != NULL));
    }
    if (list != NULL) {
      ptr = MergeValNodeStrings (list, inTable);
      if (inTable) {
        choice = TBL_FMT;
      } else {
        choice = TXT_FMT;
      }
      numItems++;
      vnp = ValNodeAdd (&(hfp->mainStrings));
      if (vnp != NULL) {
        vnp->choice = choice;
        vnp->data.ptrvalue = ptr;
      }
      /* ptr = MemFree (ptr); */
      list = ValNodeFreeData (list);
    }
    FileClose (fp);
    return TRUE;
  } else {
    return FALSE;
  }
}

static FonT GetHelpFontFromConfig (CharPtr param, FonT dfault)

{
  FonT  f;
  Char  str [128];

  f = dfault;
  if (GetSequinAppParam ("SCREEN", param, NULL, str, sizeof (str))) {
    f = Nlm_ParseFontEx (str, NULL);
  }
  if (f == NULL) {
    f = dfault;
  }
  return f;
}

static void SetupHelpFonts (void)

{
  if (IsJapanese()) {
#ifdef WIN_MAC
    /* Osaka is common font for Japanese on Macintosh. */
    hdgColFmt.font = Nlm_ParseFontEx ("Osaka,14,b", NULL);
    subColFmt.font = Nlm_ParseFontEx ("Osaka,10,b", NULL);
    txtColFmt.font = Nlm_ParseFontEx ("Osaka,10", NULL);
    lstColFmt.font = Nlm_ParseFontEx ("Osaka\x81\x7c\x93\x99\x95\x9d,12", NULL);
    tblColFmt.font = Nlm_ParseFontEx ("Osaka\x81\x7c\x93\x99\x95\x9d,12", NULL);
/*    hdgColFmt.font = GetResidentFont (Nlm_Minchou(14, STYLE_BOLD));	*/
/*    subColFmt.font = GetResidentFont (Nlm_Minchou(10, STYLE_BOLD));	*/
/*    txtColFmt.font = GetResidentFont (Nlm_Gothic(10, STYLE_REGULAR));	*/
/*    lstColFmt.font = GetResidentFont (Nlm_MinchouFixed(12, STYLE_REGULAR));	*/
/*    tblColFmt.font = GetResidentFont (Nlm_MinchouFixed(12, STYLE_REGULAR));	*/
#endif
#ifdef WIN_MSWIN
    hdgColFmt.font = Nlm_ParseFontEx ("\x82\x6c\x82\x72\x20\x82\x6f\x96\xbe\x92\xa9,14,b,Kanji", NULL);
    subColFmt.font = Nlm_ParseFontEx ("\x82\x6c\x82\x72\x20\x82\x6f\x96\xbe\x92\xa9,11,b,Kanji", NULL);
    txtColFmt.font = Nlm_ParseFontEx ("\x82\x6c\x82\x72\x20\x82\x6f\x83\x53\x83\x56\x83\x62\x83\x4e,11,,Kanji", NULL);
    lstColFmt.font = Nlm_ParseFontEx ("\x82\x6c\x82\x72\x20\x96\xbe\x92\xa9,12,f,Kanji", NULL);
    tblColFmt.font = Nlm_ParseFontEx ("\x82\x6c\x82\x72\x20\x96\xbe\x92\xa9,12,f,Kanji", NULL);
/*    hdgColFmt.font = GetResidentFont (Nlm_Minchou(14, STYLE_BOLD));	*/
/*    subColFmt.font = GetResidentFont (Nlm_Minchou(11, STYLE_BOLD));	*/
/*    txtColFmt.font = GetResidentFont (Nlm_Gothic(11, STYLE_REGULAR));	*/
/*    lstColFmt.font = GetResidentFont (Nlm_MinchouFixed(12, STYLE_REGULAR));	*/
/*    tblColFmt.font = GetResidentFont (Nlm_MinchouFixed(12, STYLE_REGULAR));	*/
#endif
#ifdef WIN_MOTIF
    hdgColFmt.font = ParseFont ("Times,18,b");
    subColFmt.font = ParseFont ("Helvetica,12,b");
    txtColFmt.font = ParseFont ("fixed,13");
    lstColFmt.font = programFont;
    tblColFmt.font = programFont;
#endif
  } else if (IsEnglish() || IsFrench() || IsGerman() || IsItalian() || IsSystemLang()) {
#ifdef WIN_MAC
    hdgColFmt.font = ParseFont ("Times,14,b");
    subColFmt.font = ParseFont ("Geneva,10,b");
    txtColFmt.font = ParseFont ("Geneva,10");
#endif /*WIN_MAC*/
#ifdef WIN_MSWIN
    hdgColFmt.font = ParseFont ("Times New Roman,14,b");
    subColFmt.font = ParseFont ("Arial,11,b");
    txtColFmt.font = ParseFont ("Times New Roman,11");
#endif /*WIN_MSWIN*/
#ifdef WIN_MOTIF
    hdgColFmt.font = ParseFont ("Times,18,b");
    subColFmt.font = ParseFont ("Helvetica,12,b");
    txtColFmt.font = ParseFont ("Times,14");
#endif /*WIN_MOTIF*/
    lstColFmt.font = programFont;
    tblColFmt.font = programFont;
  } else {
  /* above call to IsSystemLang should override this section */
    /* default system font is often native character set */
    /* because native character sets have a set of ascii letters,
       english help file can also be drawn as well as native letters. */
    hdgColFmt.font = systemFont;
    subColFmt.font = systemFont;
    txtColFmt.font = systemFont;
    lstColFmt.font = systemFont;
    tblColFmt.font = systemFont;
  }

  /* now allow override by sequin config file */
  hdgColFmt.font = GetHelpFontFromConfig ("HEADING", hdgColFmt.font);
  subColFmt.font = GetHelpFontFromConfig ("SUBHEAD", subColFmt.font);
  txtColFmt.font = GetHelpFontFromConfig ("TEXT", txtColFmt.font);
  lstColFmt.font = GetHelpFontFromConfig ("LIST", lstColFmt.font);
  tblColFmt.font = GetHelpFontFromConfig ("TABLE", tblColFmt.font);
}

static Boolean PopulateHelpForm (HelpFormPtr hfp)

{
  Int2        firstLine;
  Int2        firstShown;
  RecT        r;
  BaR         sb;
  Int4        startsAt;
  CharPtr     text;
  ValNodePtr  vnp;

  if (hfp == NULL || hfp->doc == NULL) return FALSE;
  Hide (hfp->list);
  Hide (hfp->doc);
  Update ();
  if (! GetScrlParams4 (hfp->doc, NULL, &firstShown, &firstLine)) {
    firstShown = 0;
    firstLine = 0;
  }
  sb = GetSlateVScrollBar ((SlatE) hfp->doc);
  Reset (hfp->list);
  Reset (hfp->doc);
  if (hfp->file == NULL || hfp->file [0] == '\0') return FALSE;
  if (hfp->mainStrings == NULL || hfp->indexStrings == NULL) return FALSE;
  ObjectRect (hfp->doc, &r);
  InsetRect (&r, 4, 4);
  lstColFmt.pixWidth = r.right - r.left;
  hdgColFmt.pixWidth = r.right - r.left;
  subColFmt.pixWidth = r.right - r.left;
  /*
  tblColFmt.pixWidth = screenRect.right - screenRect.left;
  */
  tblColFmt.pixWidth = r.right - r.left;
  tblColFmt.pixInset = 10;
  txtColFmt.pixWidth = r.right - r.left;
  txtColFmt.pixInset = 10;

  for (vnp = hfp->indexStrings; vnp != NULL; vnp = vnp->next) {
    lstColFmt.pixInset = vnp->choice;
    AppendText (hfp->list, vnp->data.ptrvalue, &lstParFmt, &lstColFmt, programFont);
  }

  for (vnp = hfp->mainStrings; vnp != NULL; vnp = vnp->next) {
    switch (vnp->choice) {
      case TBL_FMT :
        AppendText (hfp->doc, vnp->data.ptrvalue, &tblParFmt, &tblColFmt, programFont);
        break;
      case TXT_FMT :
        AppendText (hfp->doc, vnp->data.ptrvalue, &txtParFmt, &txtColFmt, programFont);
        break;
      case HDG_FMT :
        AppendText (hfp->doc, vnp->data.ptrvalue, &hdgParFmt, &hdgColFmt, systemFont);
        break;
      case SUB_FMT :
        subColFmt.pixInset = 0;
        AppendText (hfp->doc, vnp->data.ptrvalue, &subParFmt, &subColFmt, programFont);
        break;
      default :
        subColFmt.pixInset = vnp->choice;
        AppendText (hfp->doc, vnp->data.ptrvalue, &subParFmt, &subColFmt, programFont);
        break;
    }
  }

  UpdateDocument (hfp->list, 0, 0);
  /*
  UpdateDocument (hfp->doc, 0, 0);
  */
  text = GetDocText (hfp->doc, firstShown, 0, 0);
  MemFree (text);
  AdjustDocScroll (hfp->doc);
  GetItemParams4 (hfp->doc, firstShown, &startsAt, NULL, NULL, NULL, NULL);
  CorrectBarValue (sb, startsAt + firstLine);
  Show (hfp->list);
  Show (hfp->doc);
  Update ();
  return TRUE;
}

static void RefreshHelpForm (ButtoN b)

{
  HelpFormPtr  hfp;

  hfp = (HelpFormPtr) GetObjectExtra (b);
  if (hfp == NULL) return;
  ParseHelpFile (hfp, TRUE);
  PopulateHelpForm (hfp);
  Update ();
}

static void CleanupHelpForm (GraphiC g, VoidPtr data)

{
  ResetHelpLists ((HelpFormPtr) data);
  StdCleanupFormProc (g, data);
}

static void HelpListNotify (DoC d, Int2 item, Int2 row, Int2 col, Boolean dblclick)

{
  HelpFormPtr   hfp;
  HelpIndexPtr  hip;
  BaR           sb;
  Int2          startsAt;
  ValNodePtr    vnp;

  hfp = (HelpFormPtr) GetObjectExtra (d);
  if (hfp == NULL || hfp->doc == NULL) return;
  if (item == 0 || row == 0 || col == 0) return;
  vnp = hfp->index;
  while (vnp != NULL && item > 1) {
    item--;
    vnp = vnp->next;
  }
  if (vnp != NULL) {
    hip = (HelpIndexPtr) vnp->data.ptrvalue;
    if (hip != NULL) {
      GetItemParams (hfp->doc, hip->item, &startsAt, NULL, NULL, NULL, NULL);
      ResetClip ();
      sb = GetSlateVScrollBar ((SlatE) hfp->doc);
      SetValue (sb, startsAt);
      Update ();
    }
  }
}

static void ResizeHelpForm (WindoW w)

{
  Int2         delta;
  Int2         diff;
  Int2         gap;
  Int2         height;
  HelpFormPtr  hfp;
  RecT         r;
  RecT         s;
  RecT         t;
  Int2         width;

  hfp = (HelpFormPtr) GetObjectExtra (w);
  if (hfp != NULL) {
    ObjectRect (w, &r);
    width = r.right - r.left;
    height = r.bottom - r.top;
    GetPosition (hfp->doc, &s);
    GetPosition (hfp->dismissGrp, &t);
    diff = t.bottom - t.top;
    gap = t.top - s.bottom;
    t.bottom = height - s.left;
    t.top = t.bottom - diff;
    delta = (width - t.right - t.left) / 2;
    t.left += delta;
    t.right += delta;
    s.right = width - s.left;
    /*
    s.bottom = height - s.left;
    */
    s.bottom = t.top - gap;
    SetPosition (hfp->dismissGrp, &t);
    SetPosition (hfp->doc, &s);
    AdjustPrnt (hfp->doc, &s, FALSE);
    PopulateHelpForm (hfp);
    Update ();
  }
}

static Boolean ScrollToTextInDoc (DoC d, Int2 item, CharPtr text)

{
  Pointer  data;
  Boolean  found;
  RecT     rct;
  BaR      sb;
  Int4     startsAt;
  CharPtr  str;
  WindoW   tempPort;

  found = FALSE;
  GetItemParams4 (d, item, &startsAt, NULL, NULL, NULL, &data);
  str = (CharPtr) data;
  if (StringISearch (str, text) != NULL) {
    found = TRUE;
  }
  if (found) {
    tempPort = SavePort (d);
    Select (d);
    sb = GetSlateVScrollBar ((SlatE) d);
    CorrectBarValue (sb, startsAt);
    ObjectRect (d, &rct);
    InsetRect (&rct, 4, 4);
    InsetRect (&rct, -1, -1);
    InvalRect (&rct);
    RestorePort (tempPort);
    Update ();
  }
  return found;
}

static void FindHelpBtnProc (ButtoN b)

{
  Int2         firstShown;
  HelpFormPtr  hfp;
  Int2         i;
  Int2         numItems;
  Char         str [256];

  hfp = (HelpFormPtr) GetObjectExtra (b);
  if (hfp == NULL) return;
  GetTitle (hfp->findTxt, str, sizeof (str) - 1);
  GetDocParams (hfp->doc, &numItems, NULL);
  if (GetScrlParams (hfp->doc, NULL, &firstShown, NULL)) {
    for (i = firstShown + 1; i <= numItems; i++) {
      if (ScrollToTextInDoc (hfp->doc, i, str)) {
        return;
      }
    }
    for (i = 1; i < firstShown; i++) {
      if (ScrollToTextInDoc (hfp->doc, i, str)) {
        return;
      }
    }
  }
}

static void FindHelpTextProc (TexT t)

{
  HelpFormPtr  hfp;

  hfp = (HelpFormPtr) GetObjectExtra (t);
  if (hfp == NULL) return;
  if (TextLength (t) > 0) {
    SafeEnable (hfp->findBtn);
  } else {
    SafeDisable (hfp->findBtn);
  }
}

static void HelpFormMessage (ForM f, Int2 mssg)

{
  FILE         *fp;
  HelpFormPtr  hfp;
  Char         path [PATH_MAX];

  hfp = (HelpFormPtr) GetObjectExtra (f);
  if (hfp != NULL) {
    switch (mssg) {
      case VIB_MSG_CLOSE :
        Hide (f);
        break;
      case VIB_MSG_PRINT :
        PrintDocument (hfp->doc);
        break;
      case VIB_MSG_EXPORT :
        if (GetOutputFileName (path, sizeof (path), "help.txt")) {
          WatchCursor ();
#ifdef WIN_MAC
          fp = FileOpen (path, "r");
          if (fp != NULL) {
            FileClose (fp);
          } else {
            FileCreate (path, "TEXT", "ttxt");
          }
#endif
          fp = FileOpen (path, "w");
          if (fp != NULL) {
            SaveDocument (hfp->doc, fp);
            FileClose (fp);
          }
          ArrowCursor ();
        }
        break;
      default :
        if (hfp->appmessage != NULL) {
          hfp->appmessage (f, mssg);
        }
        break;
    }
  }
}

static void HelpFormActivate (WindoW w)

{
  IteM         exportItm;
  HelpFormPtr  hfp;

  hfp = (HelpFormPtr) GetObjectExtra (w);
  if (hfp != NULL) {
    if (hfp->activate != NULL) {
      hfp->activate (w);
    }
    exportItm = FindFormMenuItem ((BaseFormPtr) hfp, VIB_MSG_EXPORT);
    SafeSetTitle (exportItm, "Export Help...");
  }
}

extern ForM CreateHelpForm (Int2 left, Int2 top, CharPtr title,
                            CharPtr file, BtnActnProc closeForm,
                            WndActnProc activateForm)

{
  ButtoN             b;
  GrouP              c;
  GrouP              h;
  Int2               height;
  HelpFormPtr        hfp;
  StdEditorProcsPtr  sepp;
  WindoW             w;
#ifndef WIN_MAC
  MenU               m;
#endif

  w = NULL;
  hfp = MemNew (sizeof (HelpForm));
  if (hfp != NULL) {
    w = DocumentWindow (left, top, -10, -10, title,
                        StdSendCloseWindowMessageProc, ResizeHelpForm);
    SetObjectExtra (w, hfp, CleanupHelpForm);
    hfp->form = (ForM) w;
    hfp->formmessage = HelpFormMessage;

    sepp = (StdEditorProcsPtr) GetAppProperty ("StdEditorForm");
    if (sepp != NULL) {
      hfp->appmessage = sepp->handleMessages;
    }

    StringNCpy_0 (hfp->file, file, sizeof (hfp->file));

#ifndef WIN_MAC
    m = PulldownMenu (w, "File");
    FormCommandItem (m, "Export", (BaseFormPtr) hfp, VIB_MSG_EXPORT);
    SeparatorItem (m);
#ifdef WIN_MSWIN
    FormCommandItem (m, "Print", (BaseFormPtr) hfp, VIB_MSG_PRINT);
    SeparatorItem (m);
#endif
    FormCommandItem (m, "Close", (BaseFormPtr) hfp, VIB_MSG_CLOSE);
#endif

    c = HiddenGroup (w, 4, 0, NULL);
    StaticPrompt (c, "Find", 0, dialogTextHeight, programFont, 'l');
    hfp->findTxt = DialogText (c, "", 20, FindHelpTextProc);
    SetObjectExtra (hfp->findTxt, hfp, NULL);
    hfp->findBtn = PushButton (c, "Find", FindHelpBtnProc);
    SetObjectExtra (hfp->findBtn, hfp, NULL);
    Disable (hfp->findBtn);

    h = HiddenGroup (w, -1, 0, NULL);
    SelectFont (programFont);
    height = LineHeight ();
    SelectFont (systemFont);
    hfp->list = DocumentPanel (h, stdCharWidth * 28, height * 5);
    SetDocAutoAdjust (hfp->list, FALSE);
    SetObjectExtra (hfp->list, hfp, NULL);
    SetDocNotify (hfp->list, HelpListNotify);

    hfp->doc = DocumentPanel (h, stdCharWidth * 33, stdLineHeight * 20);
    SetDocAutoAdjust (hfp->doc, FALSE);
    SetObjectExtra (hfp->doc, hfp, NULL);

    hfp->dismissGrp = HiddenGroup (w, 2, 0, NULL);
    PushButton (hfp->dismissGrp, "Dismiss", closeForm);
    if (indexerVersion) {
      b = PushButton (hfp->dismissGrp, "Refresh", RefreshHelpForm);
      SetObjectExtra (b, hfp, NULL);
    }

    AlignObjects (ALIGN_CENTER, (HANDLE) hfp->doc, (HANDLE) hfp->dismissGrp, NULL);

    RealizeWindow (w);

    SetupHelpFonts ();

    if (activateForm != NULL) {
      hfp->activate = activateForm;
    } else {
      if (sepp != NULL) {
        hfp->activate = sepp->activateForm;
      }
    }
    SetActivate (w, HelpFormActivate);
    HelpFormActivate ((WindoW) hfp->form);

    if (ParseHelpFile (hfp, FALSE)) {
      if (! PopulateHelpForm (hfp)) {
        w = Remove (w);
      }
    } else {
      w = Remove (w);
    }
  }
  return (ForM) w;
}

extern void SendHelpScrollMessage (ForM f, CharPtr heading, CharPtr section)

{
  HelpFormPtr   hfp;
  HelpIndexPtr  hip;
  BaR           sb;
  Int2          startsAt;
  CharPtr       str;
  Char          txt [256];
  Boolean       useBoth;
  Boolean       useHeading;
  Boolean       useSection;
  ValNodePtr    vnp;

  hfp = (HelpFormPtr) GetObjectExtra (f);
  if (hfp != NULL) {
    vnp = hfp->index;
    txt [0] = '\0';
    useBoth = FALSE;
    useHeading = FALSE;
    useSection = FALSE;
    if (heading != NULL && *heading != '\0' && section != NULL && *section != '\0') {
      useBoth = TRUE;
      if (StringLen (heading) + StringLen (section) < sizeof (txt) - 2) {
        StringCpy (txt, heading);
        StringCat (txt, "|");
        StringCat (txt, section);
      }
    } else if (heading != NULL && *heading != '\0') {
      useHeading = TRUE;
      StringNCpy_0 (txt, heading, sizeof (txt));
    } else if (section != NULL && *section != '\0') {
      useSection = TRUE;
      StringNCpy_0 (txt, section, sizeof (txt));
    }
    while (vnp != NULL) {
      hip = (HelpIndexPtr) vnp->data.ptrvalue;
      if (hip != NULL) {
        if (useBoth) {
          str = hip->combined;
        } else if (useHeading) {
          str = hip->heading;
        } else if (useSection) {
          str = hip->section;
        } else {
          str = NULL;
        }
        if (str != NULL && StringICmp (txt, str) == 0) {
          GetItemParams (hfp->doc, hip->item, &startsAt, NULL, NULL, NULL, NULL);
          sb = GetSlateVScrollBar ((SlatE) hfp->doc);
          ResetClip ();
          SetValue (sb, startsAt);
          Update ();
          return;
        }
      }
      vnp = vnp->next;
    }
  }
}

typedef struct applycdsframe 
{
  FEATURE_FORM_BLOCK
  DialoG constraint_dlg;
  PopuP  current_frame_popup;
  PopuP  new_frame_popup;
  ButtoN retranslate_btn;
  
  Int4 current_frame_flag;
  Int4 new_frame;
  Boolean retranslate_flag;
  ConstraintChoiceSetPtr constraint;
  LogInfoPtr lip;
} ApplyCDSFrameData, PNTR ApplyCDSFramePtr;

static void ApplyCDSFrameCallback (SeqFeatPtr sfp, Pointer userdata)
{
  CdRegionPtr       crp;
  ApplyCDSFramePtr acfp;
  
  if (sfp == NULL || sfp->data.choice != SEQFEAT_CDREGION 
      || (acfp = (ApplyCDSFramePtr) userdata) == NULL
      || !DoesObjectMatchConstraintChoiceSet (OBJ_SEQFEAT, sfp, acfp->constraint))
  {
    return;
  }
  
  crp = (CdRegionPtr) sfp->data.value.ptrvalue;
  if (crp == NULL)
  {
    crp = CdRegionNew ();
    sfp->data.value.ptrvalue = crp;
  }
  
  if (acfp->current_frame_flag == 4 || crp->frame == acfp->current_frame_flag)
  {
    if (acfp->new_frame == 4) {
      if (!SetBestFrameByLocation (sfp)) {
        LogCDSAmbiguousFrame (acfp->lip, sfp);
      }
    } else {
      crp->frame = acfp->new_frame;
    }
    if (acfp->retranslate_flag)
    {
      RetranslateOneCDS (sfp, acfp->input_entityID, TRUE, FALSE);
    }
  }
}

static void DoApplyCDSFrame (ButtoN b)
{
  ApplyCDSFramePtr acfp;
  SeqEntryPtr      sep;
  
  acfp = (ApplyCDSFramePtr) GetObjectExtra (b);
  if (acfp == NULL)
  {
    return;
  }
  
  sep = GetTopSeqEntryForEntityID (acfp->input_entityID);
  if (sep == NULL)
  {
    return;
  }
  
  WatchCursor ();
  Update ();
  
  acfp->lip = OpenLog ("Ambiguous frames");
  acfp->constraint = DialogToPointer (acfp->constraint_dlg);
  
  acfp->current_frame_flag = GetValue (acfp->current_frame_popup);
  acfp->new_frame = GetValue (acfp->new_frame_popup);
  acfp->retranslate_flag = GetStatus (acfp->retranslate_btn);

  VisitFeaturesInSep (sep, acfp, ApplyCDSFrameCallback);

  acfp->constraint = ConstraintChoiceSetFree (acfp->constraint);

  ArrowCursor ();
  Update ();
  ObjMgrSetDirtyFlag (acfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, acfp->input_entityID, 0, 0);  
  Remove (acfp->form);
  CloseLog (acfp->lip);
  acfp->lip = FreeLog (acfp->lip);

}

extern void ApplyCDSFrame (IteM i)
{
  BaseFormPtr        bfp;
  GrouP              g, h, c;
  ApplyCDSFramePtr   acfp;
  WindoW             w;
  ButtoN             b;

  /* Get current sequence */

#ifdef WIN_MAC
  bfp = currentFormDataPtr;
#else
  bfp = GetObjectExtra (i);
#endif
  if (bfp == NULL)
    return;
  
  acfp = (ApplyCDSFramePtr) MemNew (sizeof (ApplyCDSFrameData));
  if (acfp == NULL)
  {
    return;
  }
  acfp->lip = NULL;

  w = FixedWindow (-50, -33, -20, -10, "Apply CDS Frame",	StdCloseWindowProc);
  SetObjectExtra (w, acfp, StdCleanupFormProc);
  acfp->form = (ForM) w;

  acfp->input_entityID = bfp->input_entityID;
  acfp->input_itemID = bfp->input_itemID;

  g = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (g, 10, 10);

  h = HiddenGroup (g, 2, 0, NULL);
  SetGroupSpacing (h, 10, 10);
  StaticPrompt (h, "Set Coding Region frame to:", 0, dialogTextHeight,
                programFont, 'l');
  acfp->new_frame_popup = PopupList (h, TRUE, NULL);
  PopupItem (acfp->new_frame_popup, "1");
  PopupItem (acfp->new_frame_popup, "2");
  PopupItem (acfp->new_frame_popup, "3");
  PopupItem (acfp->new_frame_popup, "Best");
  SetValue (acfp->new_frame_popup, 1);

  StaticPrompt (h, "Where Coding Region frame is:", 0, dialogTextHeight,
                programFont, 'l');
  acfp->current_frame_popup = PopupList (h, TRUE, NULL);
  PopupItem (acfp->current_frame_popup, "1");
  PopupItem (acfp->current_frame_popup, "2");
  PopupItem (acfp->current_frame_popup, "3");
  PopupItem (acfp->current_frame_popup, "Any frame");
  
  SetValue (acfp->current_frame_popup, 4);
  
  acfp->constraint_dlg = ComplexConstraintDialog (g, NULL, NULL);
  ChangeComplexConstraintFieldType (acfp->constraint_dlg, FieldType_cds_gene_prot, NULL, Macro_feature_type_cds);

  acfp->retranslate_btn = CheckBox (g, "Retranslate adjusted coding regions", NULL);
  SetStatus (acfp->retranslate_btn, TRUE);
  
  c = HiddenGroup (g, 2, 0, NULL);
  b = DefaultButton(c, "Accept", DoApplyCDSFrame);
  SetObjectExtra(b, acfp, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);

  AlignObjects (ALIGN_CENTER, (HANDLE) h, 
                              (HANDLE) acfp->constraint,
                              (HANDLE) acfp->retranslate_btn,
                              (HANDLE) c, NULL);
  RealizeWindow(w);
  Show(w);
  Update();
  
}

extern ValNodePtr FreeSeqIdList (ValNodePtr id_list)
{
  SeqIdPtr sip;
  ValNodePtr vnp;
  
  for (vnp = id_list; vnp != NULL; vnp = vnp->next) {
       sip = vnp->data.ptrvalue;
       sip = SeqIdFree (sip);
     vnp->data.ptrvalue = NULL;
  }
  id_list = ValNodeFree (id_list);
  return NULL;
}


static ValNodePtr SplitByCommasAndSpaces (CharPtr list)
{
  CharPtr cp;
  Char    ch;
  ValNodePtr str_list = NULL;
  Int4       len;

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

  /* skip any leading blanks */
  list += StringSpn (list, " ,\t");
  len = StringCSpn (list, " ,\t");
  while (len != 0) {
    cp = list + len;
    ch = *cp;
    *cp = 0;
    ValNodeAddPointer (&str_list, 0, StringSave (list));
    *cp = ch;
    list = cp + StringSpn (cp, " ,\t");
    len = StringCSpn (list, " ,\t");
  }
  return str_list;
}


static Boolean ParseIDStr (CharPtr id_str, CharPtr PNTR prefix, Int4Ptr num, Int4Ptr num_len)
{
  CharPtr start_number;
  Char    ch;

  if (StringHasNoText (id_str) || prefix == NULL || num == NULL || num_len == NULL) {
    return FALSE;
  }

  start_number = id_str + StringLen (id_str);
  while (start_number > id_str && isdigit (*(start_number - 1))) {
    start_number--;
  }
  *num_len = StringLen (start_number);
  if (num_len == 0) {
    return FALSE;
  }
  *num = atoi (start_number);
  if (start_number == id_str) {
    *prefix = NULL;
  } else {
    ch = *start_number;
    *start_number = 0;
    *prefix = StringSave (id_str);
    *start_number = ch;
  }
  return TRUE;
}

static ValNodePtr ExpandAccessionRanges (CharPtr list_str, SeqEntryPtr sep)
{
  ValNodePtr id_list = NULL;
  CharPtr    cp;
  Char       ch;
  SeqIdPtr   sip;
  CharPtr    last_prefix = NULL, this_prefix = NULL, tmpstr;
  Int4       num_len = 0, num_len2;
  Int4       range_start, range_end, sw;
  
  if (StringHasNoText (list_str)) {
    return NULL;
  } else if ((cp = StringChr (list_str, '-')) == NULL) {
    sip = CreateSeqIdFromText(list_str, sep);      
    if (sip == NULL) {/* Bad SeqId string */
      Message (MSG_ERROR, "Unable to parse %s as ID\n", list_str);
      return NULL;
    } else {
      ValNodeAddPointer (&id_list, 0, sip);
      return id_list;
    }
  } else {
    ch = *cp;
    *cp = 0;
    if (!ParseIDStr (list_str, &this_prefix, &range_start, &num_len)) {
      *cp = ch;
      Message (MSG_ERROR, "Unable to parse %s", list_str);
      return NULL;
    }
    *cp = ch;
    if (!ParseIDStr (cp + 1, &last_prefix, &range_end, &num_len2)) {
      Message (MSG_ERROR, "Unable to parse %s", list_str);
      this_prefix = MemFree (this_prefix);
      return NULL;
    }
    if (last_prefix != NULL && StringCmp (this_prefix, last_prefix) != 0) {
      Message (MSG_ERROR, "Unable to parse range for %s", list_str);
      this_prefix = MemFree (this_prefix);
      last_prefix = MemFree (last_prefix);
      return NULL;
    }

    if (range_start > range_end) {
      sw = range_start;
      range_start = range_end;
      range_end = sw;
    }
    tmpstr = (CharPtr) MemNew (sizeof (Char) * StringLen (this_prefix) + 15);
    for (sw = range_start; sw <= range_end; sw++) {
      sprintf (tmpstr, "%s%0*d", this_prefix == NULL ? "" : this_prefix, num_len, sw);
      sip = CreateSeqIdFromText(tmpstr, sep);
      if (sip == NULL) {
        if (sw == range_start || sw == range_end) {
          Message (MSG_ERROR, "Unable to parse range for %s", list_str);
          id_list = FreeSeqIdList (id_list);
          break;
        }
      } else {
        ValNodeAddPointer (&id_list, 0, sip);
      }
    }
    tmpstr = MemFree (tmpstr);
  }

  this_prefix = MemFree (this_prefix);  
  last_prefix = MemFree (last_prefix);
  
  return id_list;  
}


extern ValNodePtr ParseAccessionNumberListFromString (CharPtr list_str, SeqEntryPtr sep)
{
  ValNodePtr token_list, vnp, id_list = NULL, tmp;

  if (StringHasNoText (list_str)) {
      Message (MSG_ERROR, "No accession numbers listed!");
      return NULL;
  }

  token_list = SplitByCommasAndSpaces (list_str);
  for (vnp = token_list; vnp != NULL; vnp = vnp->next) {
    tmp = ExpandAccessionRanges (vnp->data.ptrvalue, sep);
    if (tmp == NULL) {
      id_list = FreeSeqIdList (id_list);
      break;
    } else {
      ValNodeLink (&id_list, tmp);
    }
  }
  token_list = ValNodeFreeData (token_list);
  return id_list;
}


typedef struct resolvefeatureoverlaps {
  Uint1 trim_type;
  ValNodePtr trim_constraint;
  Uint1 intersect_type;
  ValNodePtr intersect_constraint;
  LogInfoPtr lip;
} ResolveFeatureOverlapsData, PNTR ResolveFeatureOverlapsPtr;

static Boolean TrimLocationForIntersectingFeatures (SeqLocPtr PNTR p_slp, ValNodePtr feat_list)
{
  SeqLocPtr  slp;
  SeqFeatPtr sfp;
  ValNodePtr vnp;
  Int4       orig_left, new_left, feat_left;
  Int4       orig_right, new_right, feat_right;
  Int4       tmp;
  SeqIdPtr   sip;
  Boolean    changed = FALSE, end_changed;

  if (p_slp == NULL || *p_slp == NULL || feat_list == NULL) return FALSE;

  slp = *p_slp;
  orig_left = SeqLocStart (slp);
  orig_right = SeqLocStop (slp);
  if (orig_left > orig_right)
  {
    tmp = orig_left;
    orig_left = orig_right;
    orig_right = tmp;
  }

  new_left = orig_left;
  new_right = orig_right;

  for (vnp = feat_list; vnp != NULL; vnp = vnp->next)
  {
    sfp = (SeqFeatPtr) vnp->data.ptrvalue;
    feat_left = SeqLocStart (sfp->location);
    feat_right = SeqLocStop (sfp->location);
    if (feat_left > feat_right)
    {
      tmp = feat_left;
      feat_left = feat_right;
      feat_right = tmp;
    }
    if (feat_left <= orig_left && feat_right >= orig_left && feat_right <= orig_right)
    {
      /* trim on left */
      if (new_left < feat_right + 1)
      {
        new_left = feat_right + 1;
      }
    }
    else if (feat_left <= orig_right && feat_right >= orig_right && feat_left >= orig_left)
    {
      /* trim on right */
      if (new_right > feat_left - 1)
      {
        new_right = feat_left - 1;
      }
    }
  }

  if (new_left >= new_right)
  {
    *p_slp = SeqLocFree (*p_slp);
  }
  else
  {
    if (new_left > orig_left)
    {
      sip = SeqLocId (slp);
      end_changed = FALSE;
      *p_slp = SeqLocDelete (*p_slp, sip, orig_left, new_left - 1, FALSE, &end_changed);
      changed |= end_changed;
    }
    if (new_right < orig_right)
    {
      sip = SeqLocId (slp);
      end_changed = FALSE;
      *p_slp = SeqLocDelete (*p_slp, sip, new_right + 1, orig_right, FALSE, &end_changed);
      changed |= end_changed;
    }
  }
  return changed;
}


static void ResolveFeatureOverlapsBioseqCallback (BioseqPtr bsp, Pointer data)
{
  ResolveFeatureOverlapsPtr r;
  SeqFeatPtr sfp;
  SeqMgrFeatContext fcontext;
  ValNodePtr        overlap_list;
  CharPtr           feat_desc;
  ValNode           vn;

  r = (ResolveFeatureOverlapsPtr) data;
  if (bsp == NULL || r == NULL) return;

  MemSet (&vn, 0, sizeof (ValNode));
  vn.next = NULL;
  vn.choice = OBJ_SEQFEAT;

  for (sfp = SeqMgrGetNextFeature (bsp, NULL, 0, r->trim_type, &fcontext);
       sfp != NULL;
       sfp = SeqMgrGetNextFeature (bsp, sfp, 0, r->trim_type, &fcontext))
  {
    vn.data.ptrvalue = sfp;
    if (!DoesObjectMatchConstraintChoiceSet(OBJ_SEQFEAT, sfp, r->trim_constraint)) {
      continue;
    }
    feat_desc = GetDiscrepancyItemText (&vn);
    overlap_list = ListFeaturesOverlappingLocationEx (bsp, sfp->location, 0, r->intersect_type, r->intersect_constraint);
    if (TrimLocationForIntersectingFeatures (&(sfp->location), overlap_list))
    {
      if (sfp->location == NULL)
      {
        fprintf (r->lip->fp, "Feature completely overlapped and removed:\n%s\n", feat_desc);
        sfp->idx.deleteme = TRUE;
        r->lip->data_in_log = TRUE;
      }
      else
      {
        fprintf (r->lip->fp, "Trimmed Feature: %sNew location:", feat_desc);
        LogTrimmedLocation (r->lip, sfp->location);
        fprintf (r->lip->fp, "\n");
      }
    }
    feat_desc = MemFree (feat_desc);
    overlap_list = ValNodeFree (overlap_list);
  }  
}


typedef struct resolvefeatureoverlapsform {
  FORM_MESSAGE_BLOCK
  DialoG trim_type;
  DialoG trim_constraint;
  DialoG intersect_type;
  DialoG intersect_constraint;
  DialoG accept_cancel;

} ResolveFeatureOverlapsFormData, PNTR ResolveFeatureOverlapsFormPtr;


static void SetResolveFeatureOverlapsFormAccept (Pointer data)
{
  ResolveFeatureOverlapsFormPtr dlg;
  ValNodePtr                    vnp1, vnp2;

  dlg = (ResolveFeatureOverlapsFormPtr) data;
  if (dlg == NULL) return;

  vnp1 = DialogToPointer (dlg->trim_type);
  vnp2 = DialogToPointer (dlg->intersect_type);
  if (vnp1 == NULL || vnp2 == NULL)
  {
    DisableAcceptCancelDialogAccept (dlg->accept_cancel);
  }
  else
  {
    EnableAcceptCancelDialogAccept (dlg->accept_cancel);
  }

  vnp1 = ValNodeFree (vnp1);
  vnp2 = ValNodeFree (vnp2);
}


static void ChangeTrimFeatureType (Pointer data)
{
  ResolveFeatureOverlapsFormPtr dlg;
  ValNodePtr                    vnp;

  dlg = (ResolveFeatureOverlapsFormPtr) data;
  if (dlg == NULL) return;

  vnp = DialogToPointer (dlg->trim_type);
  if (vnp != NULL) {
    ChangeComplexConstraintFieldType (dlg->trim_constraint, FieldType_feature_field, NULL, vnp->choice);
  }
  vnp = ValNodeFree (vnp);
  SetResolveFeatureOverlapsFormAccept (data);
}


static void ChangeIntersectFeatureType (Pointer data)
{
  ResolveFeatureOverlapsFormPtr dlg;
  ValNodePtr                    vnp;

  dlg = (ResolveFeatureOverlapsFormPtr) data;
  if (dlg == NULL) return;

  vnp = DialogToPointer (dlg->intersect_type);
  if (vnp != NULL) {
    ChangeComplexConstraintFieldType (dlg->intersect_constraint, FieldType_feature_field, NULL, vnp->choice);
  }
  vnp = ValNodeFree (vnp);
  SetResolveFeatureOverlapsFormAccept (data);
}


static Boolean ResolveFeatureOverlapAction (Pointer data)
{
  ResolveFeatureOverlapsFormPtr dlg;
  ValNodePtr                    vnp1, vnp2;
  ResolveFeatureOverlapsData    r;
  SeqEntryPtr                   sep;

  dlg = (ResolveFeatureOverlapsFormPtr) data;
  if (dlg == NULL) return FALSE;

  vnp1 = DialogToPointer (dlg->trim_type);
  vnp2 = DialogToPointer (dlg->intersect_type);
 
  WatchCursor();
  Update();
  r.trim_type = vnp1->choice;
  r.trim_constraint = DialogToPointer (dlg->trim_constraint);
  r.intersect_type = vnp2->choice;
  r.intersect_constraint = DialogToPointer (dlg->intersect_constraint);
  r.lip = OpenLog ("Trimmed Features");

  sep = GetTopSeqEntryForEntityID (dlg->input_entityID);
  VisitBioseqsInSep (sep, &r, ResolveFeatureOverlapsBioseqCallback);
  
  CloseLog (r.lip);
  FreeLog (r.lip);

  r.trim_constraint = ConstraintChoiceSetFree (r.trim_constraint);
  r.intersect_constraint = ConstraintChoiceSetFree (r.intersect_constraint);

  DeleteMarkedObjects (dlg->input_entityID, 0, NULL);
  ObjMgrSetDirtyFlag (dlg->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, dlg->input_entityID, 0, 0);

  ArrowCursor ();
  Update ();
  return TRUE;
}


extern void ResolveFeatureOverlaps (IteM i)
{
  BaseFormPtr        bfp;
  GrouP              h, g;
  SeqEntryPtr        sep;
  WindoW             w;
  ResolveFeatureOverlapsFormPtr dlg;

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

  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL)
    return;

  dlg = (ResolveFeatureOverlapsFormPtr) MemNew (sizeof (ResolveFeatureOverlapsFormData));
  w = FixedWindow (-50, -33, -10, -10, "Resolve Feature Overlaps",
		   StdCloseWindowProc);
  SetObjectExtra (w, dlg, StdCleanupFormProc);
  dlg->form = (ForM) w;

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

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

  g = HiddenGroup (h, 3, 0, NULL);
  StaticPrompt (g, "Trim features of type", 0, popupMenuHeight, programFont, 'c');
  dlg->trim_type = FeatureSelectionDialogEx (g, FALSE, sep, ChangeTrimFeatureType, dlg);
  dlg->trim_constraint = ComplexConstraintDialog (g, NULL, NULL);
  ChangeComplexConstraintFieldType (dlg->trim_constraint, FieldType_feature_field, NULL, Macro_feature_type_any);

  StaticPrompt (g, "Where they overlap features of type", 0, popupMenuHeight, programFont, 'c');
  dlg->intersect_type = FeatureSelectionDialogEx (g, FALSE, sep, ChangeIntersectFeatureType, dlg);
  dlg->intersect_constraint = ComplexConstraintDialog (g, NULL, NULL);
  ChangeComplexConstraintFieldType (dlg->intersect_constraint, FieldType_feature_field, NULL, Macro_feature_type_any);

  /* Accept and Cancel buttons */

  dlg->accept_cancel = AcceptCancelDialog (h, ResolveFeatureOverlapAction, NULL, 
                                          NULL, NULL, (Pointer) dlg, w);

  /* Line things up and display the window */

  AlignObjects (ALIGN_CENTER,
                (HANDLE) g,
		            (HANDLE) dlg->accept_cancel,
		            NULL);

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


static Boolean IsExternalGeneralID (SeqIdPtr sip)
{
  DbtagPtr dbtag;

  if (sip == NULL || sip->data.ptrvalue == NULL || sip->choice != SEQID_GENERAL) return FALSE;
  dbtag = (DbtagPtr) sip->data.ptrvalue;
  if (StringCmp (dbtag->db, "NCBIFILE") != 0
      && StringCmp (dbtag->db, "TMSMART") != 0)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}


static void ListBioseqsWithExternalGeneralID (BioseqPtr bsp, Pointer userdata)
{
  SeqIdPtr sip;

  if (bsp != NULL && userdata != NULL)
  {
    for (sip = bsp->id; sip != NULL && !IsExternalGeneralID (sip); sip = sip->next)
    {
    }
    if (sip != NULL) 
    {
      ValNodeAddPointer ((ValNodePtr PNTR) userdata, OBJ_BIOSEQ, bsp);
    }
  }      
}


static SeqIdPtr LocalIdFromGeneralId (SeqIdPtr sip)
{
  DbtagPtr dbtag;
  ObjectIdPtr oip;
  SeqIdPtr sip_new;
  Char     label[15];

  if (!IsExternalGeneralID (sip)) return NULL;

  dbtag = (DbtagPtr) sip->data.ptrvalue;
  if (dbtag->tag == NULL) return NULL;
  
  sip_new = ValNodeNew (NULL);
  sip_new->choice = SEQID_LOCAL;
  oip = ObjectIdNew ();
  if (dbtag->tag->id > 0)
  {
    sprintf (label, "%d", dbtag->tag->id);
    oip->str = StringSave (label);
  }
  else
  {
    oip->str = StringSave (dbtag->tag->str);
  }
  sip_new->data.ptrvalue = oip;
  return sip_new;
}

extern void ConvertGeneralIdToLocalID (IteM i)
{
  BaseFormPtr        bfp;
  SeqEntryPtr        sep;
  ValNodePtr         bsp_list = NULL, vnp;
  BioseqPtr          bsp;
  SeqIdPtr           sip_new, sip_next, sip_prev, sip_list, sip;

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

  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL)
    return;

  VisitBioseqsInSep (sep, &bsp_list, ListBioseqsWithExternalGeneralID);

  for (vnp = bsp_list; vnp != NULL; vnp = vnp->next)
  {
    bsp = (BioseqPtr) vnp->data.ptrvalue;
    if (bsp == NULL) continue;
    sip = bsp->id;
    sip_prev = NULL;
    while (sip != NULL)
    {
      sip_next = sip->next;
      if (IsExternalGeneralID (sip) && (sip_new = LocalIdFromGeneralId (bsp->id)) != NULL)
      {
        sip_new->next = sip->next;
        sip->next = NULL;
        if (sip_prev == NULL)
        {
          bsp->id = sip_new;
        }
        else
        {
          sip_prev->next = sip_new;
        }
        sip_list = bsp->id;
        /* now use just old ID for replace */
        bsp->id = sip;
        BioseqReplaceID (bsp, sip_new);
        /* put list back */
        bsp->id = SeqIdFree (bsp->id);
        bsp->id = sip_list;
        SeqMgrReplaceInBioseqIndex (bsp);
      }
      else
      {
        sip_prev = sip;
      }
      sip = sip_next;
    }
  }
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);
}


extern void AbbreviateCitSubAffilStates (IteM i)
{
  BaseFormPtr        bfp;

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

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


typedef struct flucommentlist {
  BioseqPtr bsp;
  BioSourcePtr biop;
} FluCommentListData, PNTR FluCommentListPtr;


static FluCommentListPtr FluCommentListNew (BioseqPtr bsp, BioSourcePtr biop)
{
  FluCommentListPtr f;

  f = (FluCommentListPtr) MemNew (sizeof (FluCommentListData));
  f->bsp = bsp;
  f->biop = biop;
  return f;
}


static int LIBCALLBACK SortFluCommentList (VoidPtr ptr1, VoidPtr ptr2)

{
  ValNodePtr    vnp1, vnp2;
  FluCommentListPtr f1, f2;
  Int4              rval = 0;
  
  if (ptr1 != NULL && ptr2 != NULL) {
    vnp1 = *((ValNodePtr PNTR) ptr1);
    vnp2 = *((ValNodePtr PNTR) ptr2);
    if (vnp1 != NULL && vnp2 != NULL) {
      f1 = (FluCommentListPtr) vnp1->data.ptrvalue;
      f2 = (FluCommentListPtr) vnp2->data.ptrvalue;
      if (f1 != NULL && f2 != NULL) {
        if (f1->biop == NULL && f2->biop == NULL) {
          rval = 0;
        } else if (f1->biop == NULL) {
          rval = -1;
        } else if (f2->biop == NULL) {
          rval = 1;
        } else if (f1->biop->org == NULL && f2->biop->org == NULL) {
          rval = 0;
        } else if (f1->biop->org == NULL) {
          rval = -1;
        } else if (f2->biop->org == NULL) {
          rval = 1;
        } else {
          rval = StringCmp (f1->biop->org->taxname, f2->biop->org->taxname);
        }
      }
    }
  }
  return rval;
}

static void GetBioSourceAndSeqIdPairs (BioseqPtr bsp, Pointer data)
{
  SeqDescrPtr sdp;
  SeqMgrDescContext context;

  if (bsp == NULL || ISA_aa (bsp->mol) || data == NULL) {
    return;
  }

  sdp = SeqMgrGetNextDescriptor (bsp, NULL, Seq_descr_source, &context);
  if (sdp != NULL) {
    ValNodeAddPointer ((ValNodePtr PNTR) data, 0, FluCommentListNew (bsp, sdp->data.ptrvalue));
  }
}


static CharPtr MakeFluCommentIdList (ValNodePtr vnp_start, ValNodePtr vnp_end)
{
  FluCommentListPtr  f;
  ValNodePtr         id_list = NULL;
  ValNodePtr         vnp, vnp_a;
  Int4               prefix_len, count, last_number, new_number, id_len;
  Char               id_buf[255];
  CharPtr            id_start, id_next, last_id;
  SeqIdPtr           sip;
  Boolean            mismatch = FALSE;
  Int4               list_len = 0;
  CharPtr            list = NULL;

  if (vnp_start == NULL || vnp_start->next == vnp_end) {
    return NULL;
  }

  /* make temporary list of id strings */
  for (vnp_a = vnp_start; vnp_a != vnp_end; vnp_a = vnp_a->next) {
    f = (FluCommentListPtr) vnp_a->data.ptrvalue;
    sip = SeqIdFindBest (f->bsp->id, SEQID_GENBANK);
    SeqIdWrite (sip, id_buf, PRINTID_TEXTID_ACC_ONLY, sizeof (id_buf) - 1);
    ValNodeAddPointer (&id_list, 0, StringSave (id_buf));
  }

  /* sort id_list */
  id_list = ValNodeSort (id_list, SortVnpByString);

  /* calculate length of id list */
  vnp_a = id_list;
  while (vnp_a != NULL) {
    id_start = vnp_a->data.ptrvalue;
    list_len += StringLen (vnp_a->data.ptrvalue) + 2;
    prefix_len = StringCSpn (id_start, "1234567890");
    if (prefix_len == 0) {
      vnp_a = vnp_a->next;
    } else if (StringSpn (id_start + prefix_len, "1234567890") != StringLen (id_start + prefix_len)) {
      vnp_a = vnp_a->next;
    } else {
      last_number = atoi (id_start + prefix_len);
      count = 1;
      vnp = vnp_a->next;
      mismatch = FALSE;
      while (vnp != NULL && !mismatch) {
        id_next = vnp->data.ptrvalue;
        if (StringNICmp (id_next, id_start, prefix_len) != 0) {
          mismatch = TRUE;
        } else if (StringSpn (id_next + prefix_len, "1234567890") != StringLen (id_next + prefix_len)) {
          mismatch = TRUE;
        } else if ((new_number = atoi (id_next + prefix_len)) != last_number + 1) {
          mismatch = TRUE;
        } else {
          count++;
          last_number = new_number;
          id_len = StringLen (id_next);
          vnp = vnp->next;
        }
      }
      if (count > 1) {
        list_len += id_len + 2;
      }
      vnp_a = vnp;
    }
  }

  /* allocate memory */
  list = (CharPtr) MemNew (sizeof (Char) * list_len);
  list[0] = 0;

  /* make list */
  vnp_a = id_list;
  while (vnp_a != NULL) {
    if (list[0] != 0) {
      StringCat (list, ", ");
    }
    id_start = vnp_a->data.ptrvalue;
    StringCat (list, id_start);
    prefix_len = StringCSpn (id_start, "1234567890");
    if (prefix_len == 0) {
      vnp_a = vnp_a->next;
    } else if (StringSpn (id_start + prefix_len, "1234567890") != StringLen (id_start + prefix_len)) {
      vnp_a = vnp_a->next;
    } else {
      last_number = atoi (id_start + prefix_len);
      count = 1;
      vnp = vnp_a->next;
      mismatch = FALSE;
      while (vnp != NULL && !mismatch) {
        id_next = vnp->data.ptrvalue;
        if (StringNICmp (id_next, id_start, prefix_len) != 0) {
          mismatch = TRUE;
        } else if (StringSpn (id_next + prefix_len, "1234567890") != StringLen (id_next + prefix_len)) {
          mismatch = TRUE;
        } else if ((new_number = atoi (id_next + prefix_len)) != last_number + 1) {
          mismatch = TRUE;
        } else {
          count++;
          last_number = new_number;
          last_id = id_next;
          id_len = StringLen (id_next);
          vnp = vnp->next;
        }
      }
      if (count > 1) {
        if (count == 2) {
          StringCat (list, ", ");
          StringCat (list, last_id);
        } else {
          StringCat (list, "-");
          StringCat (list, last_id);
        }
      }
      vnp_a = vnp;
    }
  }

  id_list = ValNodeFreeData (id_list);

  return list;
}


static void AddCommentsToList (ValNodePtr vnp_start, ValNodePtr vnp_end, CharPtr last_taxname)
{
  CharPtr            id_list_txt;
  CharPtr            comment_fmt = "GenBank Accession Numbers %s represent sequences from the %d segments of %s";
  CharPtr            comment;
  Int4               num_segments = 0;
  SeqDescrPtr        sdp;
  FluCommentListPtr  f;
  ValNodePtr         vnp_a;

  if (vnp_start == NULL || StringHasNoText (last_taxname)) {
    return;
  } else if (vnp_start->next == vnp_end) {
    /* only one in the group, don't bother with comment */
  } else {
    /* get number of segments */
    for (vnp_a = vnp_start; vnp_a != vnp_end; vnp_a = vnp_a->next) {
      num_segments++;
    }

    id_list_txt = MakeFluCommentIdList (vnp_start, vnp_end);

    /* make comment */
    comment = (CharPtr) MemNew (sizeof (Char) * (StringLen (comment_fmt) + 15 + StringLen (id_list_txt) + StringLen (last_taxname) ));
    sprintf (comment, comment_fmt, id_list_txt, num_segments, last_taxname);
    id_list_txt = MemFree (id_list_txt);
    /* add comments */
    for (vnp_a = vnp_start; vnp_a != vnp_end; vnp_a = vnp_a->next) {
      f = (FluCommentListPtr) vnp_a->data.ptrvalue;
      if (f != NULL && f->bsp != NULL) {
        sdp = CreateNewDescriptorOnBioseq (f->bsp, Seq_descr_comment);
        sdp->data.ptrvalue = StringSave (comment);
      }
    }
    comment = MemFree (comment);
  }
}


NLM_EXTERN void AddFluComments (IteM i)
{
  BaseFormPtr        bfp;
  SeqEntryPtr        sep;
  ValNodePtr         list = NULL, vnp, vnp_start;
  CharPtr            last_taxname = NULL;
  FluCommentListPtr  f;

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

  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL)
    return;

  /* get list of biosources and sequence IDs */
  VisitBioseqsInSep (sep, &list, GetBioSourceAndSeqIdPairs);
  /* sort so that identical organisms are together */
  list = ValNodeSort (list, SortFluCommentList);
  /* create comments for all sequences with the same organism name */
  vnp_start = NULL;
  vnp = list;
  while (vnp != NULL) {
    f = (FluCommentListPtr) vnp->data.ptrvalue;
    
    if (f == NULL || f->biop == NULL || f->biop->org == NULL) {
      /* skip this one */
    } else if (last_taxname == NULL) {
      last_taxname = f->biop->org->taxname;
      vnp_start = vnp;
    } else if (StringCmp (last_taxname, f->biop->org->taxname) != 0) {      
      if (vnp_start->next == vnp) {
        /* only one in the group, don't bother with comment */
      } else {
        AddCommentsToList (vnp_start, vnp, last_taxname);
      }
      vnp_start = vnp;
      f = (FluCommentListPtr) vnp->data.ptrvalue;       
      last_taxname = f->biop->org->taxname;
    }
    vnp = vnp->next;
  }
  AddCommentsToList (vnp_start, NULL, last_taxname);

  list = ValNodeFreeData (list);

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


static CharPtr RNA_words[] = {
  "ITS1",
  "ITS2",
  "internal transcribed spacer 1",
  "internal transcribed spacer 2",
  "5.8S",
  "16S",
  "18S",
  "28S",
  "5.8S ribosomal RNA", 
  "16S ribosomal RNA",
  "18S ribosomal RNA",
  "28S ribosomal RNA"
};

static const Int4 k_num_RNA_words = sizeof (RNA_words) / sizeof (CharPtr);


static ValNodePtr RNAWordsFromString (CharPtr str, TextFsaPtr tags)
{
  Int4        state;
  Char        ch;
  CharPtr     ptr;
  ValNodePtr  matches;
  CharPtr     last_hit = NULL, last_pos = NULL;
  Int4        match_len;
  ValNodePtr  tokens = NULL, last_match = NULL;

  if (StringHasNoText (str) || tags == NULL) {
    return NULL;
  }

  state = 0;
  ptr = str;
  ch = *ptr;
  while (ch != '\0') {
    matches = NULL;
    state = TextFsaNext (tags, state, ch, &matches);
    if (matches != NULL && (isspace (*(ptr + 1)) || ispunct (*(ptr + 1)) || *(ptr + 1) == 0) && (match_len = StringLen (matches->data.ptrvalue)) > 0
        && (ptr - match_len + 1 == str || isspace (*(ptr - match_len)) || ispunct (*(ptr - match_len)))) {
      if (last_match == NULL || last_match->data.ptrvalue != ptr - match_len + 1) {
        last_match = ValNodeAddPointer (&tokens, 0, ptr - match_len + 1);
      }
      last_pos = ptr;
      last_hit = (CharPtr) matches->data.ptrvalue;
    }
    ptr++;
    ch = *ptr;
  }
  return tokens;
}


static void ExpandFeatureForIntervals (SeqFeatPtr sfp)
{
  SeqLocPtr   slp, slp_next, orig;
  SeqFeatPtr  sfp_new;
  Boolean     part5, part3;

  orig = sfp->location;
  sfp->location = orig->data.ptrvalue;
  orig->data.ptrvalue = NULL;
  orig = SeqLocFree (orig);
  slp = sfp->location->next;
  sfp->location->next = NULL;
  sfp->partial = CheckSeqLocForPartial (sfp->location, &part5, &part3);
  while (slp != NULL) {
    sfp_new = SeqFeatCopy (sfp);
    sfp_new->next = sfp->next;
    sfp->next = sfp_new;
    sfp_new->location = SeqLocFree (sfp_new->location);
    slp_next = slp->next;
    slp->next = NULL;
    sfp_new->location = slp;
    sfp_new->partial = CheckSeqLocForPartial (sfp_new->location, &part5, &part3);
    slp = slp_next;
    sfp = sfp_new;
  }
} 
    

/* TODO - for each string, remove trailing delimiters and spaces, replace abbreviations */

static void FixExplodedRNAProduct (CharPtr PNTR pProduct)
{
  CharPtr cp;
  Int4    len, i;
  Char    repl_buf[200];
  CharPtr repl_fmt = "%s ribosomal RNA";

  if (pProduct == NULL || *pProduct == NULL || StringHasNoText (*pProduct)) {
    return;
  }

  FindReplaceString (pProduct, " and ", "", FALSE, FALSE);

  len = StringLen (*pProduct);
  cp = *pProduct + len - 1;

  while (ispunct (*cp) || isspace (*cp)) {
    *cp = 0;
    cp--;
  }
   
  if (StringStr (*pProduct, " ribosomal RNA") != NULL || StringStr (*pProduct, "internal transcribed spacer") != NULL) {
    return;
  }
  for (i = 2; i < k_num_RNA_words; i++) {
    sprintf (repl_buf, repl_fmt, RNA_words[i]);
    FindReplaceString (pProduct, RNA_words[i], repl_buf, FALSE, TRUE);
  }
}


static void FixOneExplodedRNA (SeqFeatPtr sfp, CharPtr new_product, Int4 len)
{
  RnaRefPtr rrp;
  RNAGenPtr rgp;
  CharPtr   adjusted_product;

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

  if (len < 0) {
    adjusted_product = StringSave (new_product);
  } else {
    adjusted_product = (CharPtr) MemNew (sizeof (Char) * (len + 1));
    StringNCpy (adjusted_product, new_product, len);
    adjusted_product[len] = 0;
  }
  FixExplodedRNAProduct (&adjusted_product);

  if (rrp->ext.choice == 1) {
    rrp->ext.value.ptrvalue = MemFree (rrp->ext.value.ptrvalue);
    rrp->ext.value.ptrvalue = adjusted_product;
  } else if (rrp->ext.choice == 3) {
    rgp = rrp->ext.value.ptrvalue;
    rgp->product = MemFree (rgp->product);
    rgp->product = StringSave (adjusted_product);
  } else if (rrp->ext.choice == 0) {
    rrp->ext.choice = 1;
    rrp->ext.value.ptrvalue = adjusted_product;
  }
  if (StringStr (adjusted_product, "internal transcribed spacer") != NULL) {
    rrp->type = RNA_TYPE_other;
  } else {
    rrp->type = RNA_TYPE_rRNA;
  }
}


static void ExplodeRNACallback (SeqFeatPtr sfp, Pointer data)
{
  TextFsaPtr  tags;
  Int4        num_interval = 0;
  SeqLocPtr   tmp;
  ValNodePtr  tokens = NULL, vnp;
  RnaRefPtr   rrp;
  RNAGenPtr   rgp;
  SeqFeatPtr  sfp_new;
  CharPtr     orig_product;

  if (sfp == NULL || sfp->data.choice != SEQFEAT_RNA || (tags = (TextFsaPtr) data) == NULL
      || sfp->location == NULL
      || (sfp->location->choice != SEQLOC_MIX && sfp->location->choice != SEQLOC_PACKED_INT)) {
    return;
  }

  for (tmp = sfp->location->data.ptrvalue; tmp != NULL; tmp = tmp->next) {
    num_interval++;
  }

  if (!StringHasNoText (sfp->comment)
      && (tokens = RNAWordsFromString (sfp->comment, tags)) != NULL
      && (num_interval == ValNodeLen (tokens))) {
    orig_product = sfp->comment;
    sfp->comment = NULL;
    ExpandFeatureForIntervals (sfp);
    for (vnp = tokens, sfp_new = sfp; vnp != NULL && sfp_new != NULL; vnp = vnp->next, sfp_new = sfp_new->next) {
      FixOneExplodedRNA (sfp_new, vnp->data.ptrvalue, 
                         vnp->next == NULL ? -1 : (CharPtr)vnp->next->data.ptrvalue - (CharPtr) vnp->data.ptrvalue);
    }  
    tokens = ValNodeFree (tokens);
    orig_product = MemFree (orig_product);
  } else if ((rrp = (RnaRefPtr) sfp->data.value.ptrvalue) != NULL) {
    if (rrp->ext.choice == 1) {
      orig_product = rrp->ext.value.ptrvalue;
      rrp->ext.value.ptrvalue = NULL;
      tokens = RNAWordsFromString (orig_product, tags);
      if (num_interval == ValNodeLen (tokens)) {
        ExpandFeatureForIntervals (sfp);
        for (vnp = tokens, sfp_new = sfp; vnp != NULL && sfp_new != NULL; vnp = vnp->next, sfp_new = sfp_new->next) {
          FixOneExplodedRNA (sfp_new, vnp->data.ptrvalue, 
                             vnp->next == NULL ? -1 : (CharPtr)vnp->next->data.ptrvalue - (CharPtr) vnp->data.ptrvalue);
        }  
      }
      tokens = ValNodeFree (tokens);
      orig_product = MemFree (orig_product);
    } else if (rrp->ext.choice == 3 && (rgp = (RNAGenPtr) rrp->ext.value.ptrvalue) != NULL) {
      orig_product = rgp->product;
      rgp->product = NULL;
      tokens = RNAWordsFromString (orig_product, tags);
      if (num_interval == ValNodeLen (tokens)) {
        ExpandFeatureForIntervals (sfp);
        for (vnp = tokens, sfp_new = sfp; vnp != NULL && sfp_new != NULL; vnp = vnp->next, sfp_new = sfp_new->next) {
          FixOneExplodedRNA (sfp_new, vnp->data.ptrvalue,
                             vnp->next == NULL ? -1 : (CharPtr)vnp->next->data.ptrvalue - (CharPtr) vnp->data.ptrvalue);
        }      
      }
      tokens = ValNodeFree (tokens);
      orig_product = MemFree (orig_product);
    }
  }
}


NLM_EXTERN void ExplodeRNA (IteM i)
{
  BaseFormPtr        bfp;
  SeqEntryPtr        sep;
  TextFsaPtr         tags;
  Int4               j;

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

  sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
  if (sep == NULL)
    return;

  
  tags = TextFsaNew();

  for (j = 0; j < k_num_RNA_words; j++) {
    TextFsaAdd (tags, RNA_words[j]);
  }

  VisitFeaturesInSep (sep, tags, ExplodeRNACallback);

  tags = TextFsaFree (tags);

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


static void RemoveFeaturesLikeGapsCallback (BioseqPtr bsp, Pointer userdata)
{
  SeqFeatPtr           sfp, gap_feat;
  SeqMgrFeatContext    context;
  DeltaSeqPtr          dsp;
  SeqLitPtr            slip;
  SeqLocPtr            slp;
  Int4                 pos = 0, len;
  CharPtr              new_note;
  ImpFeatPtr           imp;
  GBQualPtr            gbq;
  Char                 buf[15];

  if (bsp == NULL || bsp->repr != Seq_repr_delta) {
    return;
  }

  dsp = (DeltaSeqPtr)(bsp->seq_ext);
  sfp = SeqMgrGetNextFeature(bsp, NULL, 0, 0, &context);
  while (dsp != NULL && sfp != NULL) {
    while (dsp != NULL && !IsDeltaSeqGap(dsp)) {
      if (dsp->choice == 1) {
        pos += SeqLocLen (dsp->data.ptrvalue);
      } else if (dsp->choice == 2) {
        pos += ((SeqLitPtr)dsp->data.ptrvalue)->length;
      }
      dsp = dsp->next;
    }
    if (dsp != NULL) {
      slip = (SeqLitPtr)dsp->data.ptrvalue;
      len = slip->length;
      while (context.left < pos && sfp != NULL) {
        sfp = SeqMgrGetNextFeature (bsp, sfp, 0, 0, &context);
      }
      if (sfp != NULL) {
        gap_feat = NULL;
        new_note = NULL;
        while (context.left == pos && sfp != NULL) {
          if (context.right == pos + len - 1) {
            if (sfp->idx.subtype == FEATDEF_gap) {
              if (!context.external) {
                gap_feat = sfp;
              }
            } else {
              if (sfp->comment != NULL) {
                SetStringValue(&new_note, sfp->comment, ExistingTextOption_append_semi);
              }
              sfp->idx.deleteme = TRUE;
            }
          }
          sfp = SeqMgrGetNextFeature (bsp, sfp, 0, 0, &context);
        }
        /* only need to instantiate gap if there is a note to add */
        if (new_note != NULL) {
          if (gap_feat == NULL) {
            slp = SeqLocIntNew (pos, pos + len - 1, Seq_strand_plus, SeqIdFindWorst (bsp->id));
            gap_feat = CreateNewFeatureOnBioseq (bsp, SEQFEAT_IMP, slp);
            imp = ImpFeatNew ();
            imp->key = StringSave ("gap");
            gap_feat->data.value.ptrvalue = imp;
            gbq = GBQualNew();
            gbq->qual = StringSave("estimated_length");
            if (slip->fuzz != NULL) {
              gbq->val = StringSave("unknown");
            } else {
              sprintf (buf, "%d", slip->length);
              gbq->val = StringSave(buf);
            }
            gbq->next = gap_feat->qual;
            gap_feat->qual = gbq;
          }
          if (gap_feat->comment == NULL) {
            gap_feat->comment = new_note;
          } else {
            SetStringValue (&(gap_feat->comment), new_note, ExistingTextOption_append_semi);
            new_note = MemFree (new_note);
          }
        }
      }
      pos += len;
      dsp = dsp->next;
    }
  }
}


NLM_EXTERN void RemoveFeaturesLikeGaps (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);
  VisitBioseqsInSep (sep, NULL, RemoveFeaturesLikeGapsCallback);

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


NLM_EXTERN void SplitPCRPrimersByPositionMenuItem (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);
  SplitPCRPrimersByPosition (sep);

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


typedef struct splitpcrprimersbyconstraintform {
  FORM_MESSAGE_BLOCK

  DialoG fwd_constraint;
  DialoG rev_constraint;
} SplitPCRPrimersByConstraintFormData, PNTR SplitPCRPrimersByConstraintFormPtr;


static void SplitPCRPrimersByConstraintAccept (ButtoN b)
{
  SplitPCRPrimersByConstraintFormPtr frm;
  StringConstraintPtr fwd_scp, rev_scp;
  SeqEntryPtr sep;

  frm = (SplitPCRPrimersByConstraintFormPtr) GetObjectExtra (b);
  if (frm == NULL) {
    return;
  }

  fwd_scp = DialogToPointer (frm->fwd_constraint);
  rev_scp = DialogToPointer (frm->rev_constraint);
  sep = GetTopSeqEntryForEntityID (frm->input_entityID);

  SplitPCRPrimersByConstraints (sep, fwd_scp, rev_scp);
  fwd_scp = StringConstraintFree (fwd_scp);
  rev_scp = StringConstraintFree (rev_scp);
  ObjMgrSetDirtyFlag (frm->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, frm->input_entityID, 0, 0);  
  Remove (frm->form);
}

  
NLM_EXTERN void SplitPCRPrimersByConstraintsMenuItem (IteM i)
{
  BaseFormPtr          bfp;
  SplitPCRPrimersByConstraintFormPtr frm;
  WindoW              w;
  GrouP               h, g, c;
  ButtoN              b;
  PrompT              ppt;

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

  frm = (SplitPCRPrimersByConstraintFormPtr) MemNew (sizeof (SplitPCRPrimersByConstraintFormData));

  w = FixedWindow (-50, -33, -10, -10, "Convert BioSource Dbxrefs to Feature Dbxrefs", StdCloseWindowProc);
  SetObjectExtra (w, frm, StdCleanupExtraProc);
  frm->form = (ForM) w;
  frm->input_entityID = bfp->input_entityID;

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

  ppt = StaticPrompt (h, "Choose string constraints for forward and reverse primer names for new sets", 0, dialogTextHeight, programFont, 'l');
  g = HiddenGroup (h, 0, 2, NULL);
  SetGroupSpacing (g, 10, 10);
  frm->fwd_constraint = StringConstraintDialog (g, "Forward", FALSE, NULL, NULL);
  frm->rev_constraint = StringConstraintDialog (g, "Reverse", FALSE, NULL, NULL);

  c = HiddenGroup (h, 2, 0, NULL);
  b = PushButton (c, "Accept", SplitPCRPrimersByConstraintAccept);
  SetObjectExtra (b, frm, NULL);
  PushButton (c, "Cancel", StdCancelButtonProc);
  AlignObjects (ALIGN_CENTER, (HANDLE) ppt, (HANDLE) g, (HANDLE) c, NULL);
  Show (w);
  Select (w);
}


NLM_EXTERN void MergePCRPrimerSetsMenuItem (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);
  MergePCRPrimers (sep);

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


static void ConvertToDelayedGenProdSetQualifiersCallback (SeqFeatPtr sfp, Pointer data)
{
  GBQualPtr gbq;

  if (sfp == NULL) {
    return;
  } else if (sfp->idx.subtype != FEATDEF_CDS && sfp->idx.subtype != FEATDEF_mRNA) {
    return;
  }

  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    if (StringICmp (gbq->qual, "protein_id") == 0) {
      gbq->qual = MemFree (gbq->qual);
      gbq->qual = StringSave ("orig_protein_id");
    } else if (StringICmp (gbq->qual, "transcript_id") == 0) {
      gbq->qual = MemFree (gbq->qual);
      gbq->qual = StringSave ("orig_transcript_id");
    }
  }
}


NLM_EXTERN void ConvertToDelayedGenProdSetQualifiers (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);
  VisitFeaturesInSep (sep, NULL, ConvertToDelayedGenProdSetQualifiersCallback);
  ObjMgrSetDirtyFlag (bfp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, bfp->input_entityID, 0, 0);  
}


#define NUM_FEAT_ED_PAGES 2

#define NUM_FEAT_ED_ACTIONS 8

typedef struct feated
{
  FORM_MESSAGE_BLOCK
  DialoG  feature_type;
  DialoG  constraint;
  DialoG  accept_cancel;

  /* for evidence */  
  Int4    evidence_val;
      
  /* for exceptions */
  PopuP   explanation_choice;
  ButtoN  move_to_comment;
  GrouP   product_choice;
  TexT    other_explanation;
  CharPtr explanation_text;
  Boolean do_move_to_comment;
  Int4    product_val;
  
  /* for partials */
  PopuP  convert_choice;
  PopuP  partial5_choice;
  ButtoN extend5_btn;
  PopuP  partial3_choice;
  ButtoN extend3_btn;
  ButtoN retranslate_btn;
  ButtoN adjust_gene_btn;
  Int4   orderjoinpolicy;
  Int4   leftpolicy;
  Int4   rightpolicy;
  Boolean extend5;
  Boolean extend3;
  Boolean retranslate;
  Boolean adjust_gene;
  
  /* for strand */
  PopuP  from_strand;
  PopuP  to_strand;
  Uint1  from_strand_val;
  Uint1  to_strand_val;

  GrouP  action_grps [NUM_FEAT_ED_ACTIONS];
  ButtoN action_btns [NUM_FEAT_ED_ACTIONS];
  GrouP  action_choice_grp;
  
  /* for citations */
  DialoG     citation_list;
  ButtoN     explanation_constraint;
  PopuP      explanation_constraint_choice;
  ButtoN     citation_translation;
  Boolean    use_explanation_constraint;
  Int4       explanation_constraint_choice_val;
  Boolean    use_product_match;

  /* for experiment */
  GrouP      experiment_action;
  TexT       experiment_text;
  
  /* for pseudo */
  PopuP      pseudo_action;
  Boolean    pseudo_val;
  CharPtr    pseudo_str;
  
  /* for inferences */
  DialoG     inference_dlg;
} FeatEdData, PNTR FeatEdPtr;


static void FeatEdChangeNotify (Pointer userdata)
{
  FeatEdPtr mp;
  ValNodePtr      err_list = NULL;
  CharPtr         str;

  mp = (FeatEdPtr) userdata;
  if (mp == NULL) return;
  
  if (mp->explanation_choice != NULL)
  {
    if (GetValue (mp->explanation_choice) == 9 
        && ((mp->action_btns[1] == NULL && mp->action_choice_grp == NULL)
            || (mp->action_btns[1] != NULL && GetStatus (mp->action_btns[1]))
            || (mp->action_choice_grp != NULL && GetValue (mp->action_choice_grp) == 2)))
    {
      Enable (mp->other_explanation);
      str = SaveStringFromText (mp->other_explanation);
      if (StringHasNoText (str))
      {
        DisableAcceptCancelDialogAccept (mp->accept_cancel);
        str = MemFree (str);
        return;
      }
      str = MemFree (str);
    }
    else
    {
      Disable (mp->other_explanation);
    }
  }
  
  if (GetValue (mp->action_choice_grp) == FEAT_ED_INFERENCE)
  {
    ValNodeLink (&err_list, TestDialog (mp->inference_dlg));
  }
  
  if (err_list == NULL)
  {
    EnableAcceptCancelDialogAccept (mp->accept_cancel);
  }
  else
  {
    DisableAcceptCancelDialogAccept (mp->accept_cancel);
  }
  ValNodeFree (err_list);  
}


static void FeatEdClear (Pointer data)
{
  FeatEdPtr mp;

  mp = (FeatEdPtr) data;
  if (mp == NULL) return;
 
  SetFeatureTypeInFeatureTypeDialog(mp->feature_type, Macro_feature_type_any);
  PointerToDialog (mp->constraint, NULL);

  if (mp->explanation_choice != NULL)
  {
    SetValue (mp->explanation_choice, 1);
  }
  if (mp->move_to_comment != NULL)
  {
    SetStatus (mp->move_to_comment, FALSE);
  }
  if (mp->product_choice != NULL)
  {
    SetValue (mp->product_choice, 3);
  }
  if (mp->other_explanation != NULL)
  {
    SetTitle (mp->other_explanation, "");
    Disable (mp->other_explanation);
  }
  
  if (mp->convert_choice != NULL)
  {
    SetValue (mp->convert_choice, 1);
  }
  if (mp->partial5_choice != NULL)
  {
    SetValue (mp->partial5_choice, 8);
  }
  if (mp->extend5_btn != NULL)
  {
    SetStatus (mp->extend5_btn, FALSE);
  }
  if (mp->partial3_choice != NULL)
  {
    SetValue (mp->partial3_choice, 7);
  }
  if (mp->extend3_btn != NULL)
  {
    SetStatus (mp->extend3_btn, FALSE);
  }
  if (mp->retranslate_btn != NULL)
  {
    SetStatus (mp->retranslate_btn, FALSE);
  }
  if (mp->adjust_gene_btn != NULL) 
  {
    SetStatus (mp->adjust_gene_btn, FALSE);
  }

  /* for strand */
  if (mp->from_strand != NULL)
  {
    SetValue (mp->from_strand, 1);
  }
  if (mp->to_strand != NULL)
  {
    SetValue (mp->to_strand, 1);
  }

  /* for experiment */
  if (mp->experiment_text != NULL)
  {
    SetTitle (mp->experiment_text, "");
  }
  
  /* for pseudo */
  if (mp->pseudo_action != NULL)
  {
    SetValue (mp->pseudo_action, 1);
  }

  FeatEdChangeNotify (mp);
}


static void FeatEdClearText (Pointer data)
{
  FeatEdPtr     mp;

  mp = (FeatEdPtr) data;
  if (mp == NULL) return;
   
  /* for explanation */
  if (mp->other_explanation != NULL)
  {
    SetTitle (mp->other_explanation, "");
  }

  /* for experiment */
  if (mp->experiment_text != NULL)
  {
    SetTitle (mp->experiment_text, "");
  }

  FeatEdChangeNotify (mp);
}

static GrouP FeaturePseudoGroup (GrouP h, FeatEdPtr mp)
{
  GrouP g;
  
  if (mp == NULL)
  {
    return NULL;
  }
  
  g = HiddenGroup (h, 0, 0, NULL);
  mp->pseudo_action = PopupList (g, TRUE, NULL);
  InitEnumPopup (mp->pseudo_action, legacy_pseudogene_alist, NULL);
  
  return g;  
}

static GrouP FeatureEvidenceGroup (GrouP h, FeatEdPtr mp)
{
  GrouP g;
  
  if (mp == NULL)
  {
    return NULL;
  }

  g = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  
  StaticPrompt (g, "Click Accept to clear feature evidence.", 0, dialogTextHeight, systemFont, 'l');
  
  return g;
}


static void FeatExplanationChange (PopuP p)
{
  FeatEdPtr mp;

  mp = (FeatEdPtr) GetObjectExtra (p);
  if (mp == NULL) return;
  
  if (GetValue (mp->explanation_choice) == 9)
  {
    Enable (mp->other_explanation);
  }
  else
  {
    Disable (mp->other_explanation);
  }
  
  FeatEdChangeNotify (mp);
}

static void FeatOtherExplanationChange (TexT t)
{
  FeatEdPtr mp;
 
  mp = (FeatEdPtr) GetObjectExtra (t);
  FeatEdChangeNotify (mp);
}

static CharPtr exception_explanations[] = 
{
  "RNA Editing", "reasons given in citation", "ribosomal slippage",
  "trans splicing", "artificial frameshift", "nonconsensus splice site",
  "rearrangement required", "other->"
};

static Int4 num_exception_explanations = sizeof (exception_explanations) / sizeof (CharPtr);

static GrouP FeatureExceptionGroup (GrouP h, FeatEdPtr mp)
{
  GrouP  g, k1, k2;
  Int4   i;
  
  if (mp == NULL)
  {
    return NULL;
  }
  g = HiddenGroup (h, -1, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  
  k1 = HiddenGroup (g, 3, 0, NULL);
  SetGroupSpacing (k1, 10, 10);
  StaticPrompt (k1, "Set Explanation to ", 0, dialogTextHeight, systemFont, 'l');
  mp->explanation_choice = PopupList (k1, TRUE, FeatExplanationChange);
  SetObjectExtra (mp->explanation_choice, mp, NULL);
  PopupItem (mp->explanation_choice, " ");
  for (i = 0; i < num_exception_explanations; i++)
  {
    PopupItem (mp->explanation_choice, exception_explanations [i]);
  }
  SetValue (mp->explanation_choice, 1);
  mp->other_explanation = DialogText (k1, "", 10, FeatOtherExplanationChange);
  Disable (mp->other_explanation);
  SetObjectExtra (mp->other_explanation, mp, NULL);

  k2 = HiddenGroup (g, 2, 0, NULL);
  SetGroupSpacing (k2, 10, 10);
  StaticPrompt (k2, "Where feature product is", 0, dialogTextHeight, systemFont, 'l');
  mp->product_choice = HiddenGroup (k2, 3, 0, NULL);
  RadioButton (mp->product_choice, "Present");
  RadioButton (mp->product_choice, "Absent");
  RadioButton (mp->product_choice, "Either");
  SetValue (mp->product_choice, 3);
  
  mp->move_to_comment = CheckBox (g, "Move explanation to comment", NULL);

  AlignObjects (ALIGN_CENTER, (HANDLE) k1,
                              (HANDLE) k2,
                              (HANDLE) mp->move_to_comment,
                              NULL);
  
  return g;
}

static void ChangeExplanationConstraint (ButtoN b)
{
  FeatEdPtr mp;
  
  mp = (FeatEdPtr) GetObjectExtra (b);
  if (mp == NULL)
  {
    return;
  }
  if (GetStatus (mp->explanation_constraint))
  {
    Enable (mp->explanation_constraint_choice);
  }
  else
  {
    Disable (mp->explanation_constraint_choice);
  }  
}

static GrouP FeatureEditorCitationGroup (GrouP h, FeatEdPtr mp)
{
  GrouP  g, k1, k2;
  Int4   i;
  
  if (mp == NULL)
  {
    return NULL;
  }
  g = HiddenGroup (h, -1, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  
  /* need citation selector */
  mp->citation_list = FeatCitEditDialog (g, mp->input_entityID);
  PointerToDialog (mp->citation_list, NULL);

  k1 = HiddenGroup (g, 3, 0, NULL);
  SetGroupSpacing (k1, 10, 10);
  mp->explanation_constraint = CheckBox (k1, "Where explanation is", ChangeExplanationConstraint);
  SetObjectExtra (mp->explanation_constraint, mp, NULL);
  SetStatus (mp->explanation_constraint, TRUE);
  mp->explanation_constraint_choice = PopupList (k1, TRUE, NULL);
  PopupItem (mp->explanation_constraint_choice, "Any");
  PopupItem (mp->explanation_constraint_choice, " ");
  for (i = 0; i < num_exception_explanations; i++)
  {
    PopupItem (mp->explanation_constraint_choice, exception_explanations [i]);
  }
  SetValue (mp->explanation_constraint_choice, 4);

  k2 = HiddenGroup (g, 2, 0, NULL);
  SetGroupSpacing (k2, 10, 10);
  mp->citation_translation = CheckBox (k2, "Where translation does not match product", NULL);

  AlignObjects (ALIGN_CENTER, (HANDLE) mp->citation_list,
                              (HANDLE) k1,
                              (HANDLE) k2,
                              (HANDLE) mp->citation_translation,
                              NULL);
  
  return g;
}


static GrouP FeatureEditorPartialGroup (GrouP h, FeatEdPtr mp)
{
  GrouP g, g1, g2,k1, k2, top;
  
  if (mp == NULL)
  {
    return NULL;
  }
  
  top = HiddenGroup (h, -1, 0, NULL);
  SetGroupSpacing (top, 10, 10);

  g = HiddenGroup (top, 2, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  
  StaticPrompt (g, "Partials:", 0, dialogTextHeight, systemFont, 'l');
  g1 = NormalGroup (g, 1, 0, "", programFont, NULL);
  k1 = HiddenGroup (g1, 2, 0, NULL);

  SetGroupSpacing (k1, 10, 10);
  StaticPrompt (k1, "5' partial:", 0, dialogTextHeight, systemFont, 'l');
  mp->partial5_choice = PopupList (k1, TRUE, NULL);
  PopupItem (mp->partial5_choice, "Set");
  PopupItem (mp->partial5_choice, "Set only if at 5' end");
  PopupItem (mp->partial5_choice, "Set if bad start codon");
  PopupItem (mp->partial5_choice, "Set if CDS frame > 1");
  PopupItem (mp->partial5_choice, "Clear");
  PopupItem (mp->partial5_choice, "Clear if not at 5' end");
  PopupItem (mp->partial5_choice, "Clear if good start codon");
  PopupItem (mp->partial5_choice, "Do not change");
  SetValue (mp->partial5_choice, 8);
  
  mp->extend5_btn = CheckBox (g1, "Extend to 5' end of sequence if setting 5' partial", NULL);

  StaticPrompt (g1, "", 0, dialogTextHeight, systemFont, 'l');

  k2 = HiddenGroup (g1, 2, 0, NULL);
  SetGroupSpacing (k2, 10, 10);
  StaticPrompt (k2, "3' partial:", 0, dialogTextHeight, systemFont, 'l');
  mp->partial3_choice = PopupList (k2, TRUE, NULL);
  PopupItem (mp->partial3_choice, "Set");
  PopupItem (mp->partial3_choice, "Set only if at 3' end");
  PopupItem (mp->partial3_choice, "Set if bad stop codon");
  PopupItem (mp->partial3_choice, "Clear");
  PopupItem (mp->partial3_choice, "Clear if not at 3' end");
  PopupItem (mp->partial3_choice, "Clear if good stop codon");
  PopupItem (mp->partial3_choice, "Do not change");
  SetValue (mp->partial3_choice, 7);
  
  mp->extend3_btn = CheckBox (g1, "Extend to 3' end of sequence if setting 3' partial", NULL);

  StaticPrompt (g, "Join/Order/Merge:", 0, dialogTextHeight, systemFont, 'l');
  g2 = NormalGroup (g, 1, 0, "", programFont, NULL);
  mp->convert_choice = PopupList (g2, TRUE, NULL);
  PopupItem (mp->convert_choice, "Do not convert location");
  PopupItem (mp->convert_choice, "Convert location to join");
  PopupItem (mp->convert_choice, "Convert location to order");
  PopupItem (mp->convert_choice, "Convert location to single interval");
  SetValue (mp->convert_choice, 1);

  mp->retranslate_btn = CheckBox (top, "Retranslate coding regions", NULL);
  mp->adjust_gene_btn = CheckBox (top, "Adjust gene location", NULL);

  AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) mp->retranslate_btn, (HANDLE) mp->adjust_gene_btn, NULL);
  
  return top;
}

#define FEATED_STRAND_ANY_REVERSE 1
#define FEATED_STRAND_UNKNOWN     2
#define FEATED_STRAND_PLUS        3
#define FEATED_STRAND_MINUS       4
#define FEATED_STRAND_BOTH        5

static GrouP FeatureEditorStrandGroup (GrouP h, FeatEdPtr mp)
{
  GrouP  g;
  
  if (mp == NULL)
  {
    return NULL;
  }
  g = HiddenGroup (h, 5, 0, NULL);
  SetGroupSpacing (g, 10, 10);
  StaticPrompt (g, "Convert location strand", 0, dialogTextHeight, systemFont, 'l');
  StaticPrompt (g, "From", 0, dialogTextHeight, systemFont, 'l');
  mp->from_strand = PopupList (g, TRUE, NULL);
  PopupItem (mp->from_strand, "Any");
  PopupItem (mp->from_strand, "Unknown");
  PopupItem (mp->from_strand, "Plus");
  PopupItem (mp->from_strand, "Minus");
  PopupItem (mp->from_strand, "Both");
  SetValue (mp->from_strand, FEATED_STRAND_PLUS); 
  StaticPrompt (g, "To", 0, dialogTextHeight, systemFont, 'l');
  mp->to_strand = PopupList (g, TRUE, NULL);
  PopupItem (mp->to_strand, "Reverse");
  PopupItem (mp->to_strand, "Unknown");
  PopupItem (mp->to_strand, "Plus");
  PopupItem (mp->to_strand, "Minus");
  PopupItem (mp->to_strand, "Both");
  SetValue (mp->to_strand, FEATED_STRAND_MINUS); 
  return g;
}


static void ChangeExperimentAction (GrouP g)
{
  FeatEdPtr mp;
  mp = (FeatEdPtr) GetObjectExtra (g);
  if (mp != NULL)
  {
    if (GetValue (mp->experiment_action) == 1)
    {
      Enable (mp->experiment_text);
    }
    else
    {
      Disable (mp->experiment_text);
    }
  }
}

static GrouP FeatureEditorExperimentGroup (GrouP h, FeatEdPtr mp)
{
  if (mp == NULL)
  {
    return NULL;
  }
  mp->experiment_action = HiddenGroup (h, 2, 0, ChangeExperimentAction);
  SetObjectExtra (mp->experiment_action, mp, NULL);
  SetGroupSpacing (mp->experiment_action, 10, 10);
  RadioButton (mp->experiment_action, "Set Experiment");
  mp->experiment_text = DialogText (mp->experiment_action, "", 20, NULL);
  RadioButton (mp->experiment_action, "Remove Experiment");
  SetValue (mp->experiment_action, 1);
  return mp->experiment_action;
}

static GrouP FeatureEditorInferenceGroup (GrouP h, FeatEdPtr mp)
{
  GrouP g;

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

  mp->inference_dlg = CreateInferenceEditDialog (g, FeatEdChangeNotify, mp);
  return g;
}

static void ChangeFeatureEditorActionGroup (GrouP g)
{
  FeatEdPtr mp;
  Int4            i;
  
  mp = (FeatEdPtr) GetObjectExtra (g);
  if (mp == NULL)
  {
    return;
  }
  
  for (i = 0; i < NUM_FEAT_ED_ACTIONS; i++)
  {
    Hide (mp->action_grps[i]);
  }
  
  i = GetValue (mp->action_choice_grp);
  if (i > 0 && i < NUM_FEAT_ED_ACTIONS + 1)
  {
    Show (mp->action_grps [i-1]);
  }

  FeatEdChangeNotify (mp);
  
}

static GrouP FeatureEditorActionGroup (GrouP h, FeatEdPtr mp, Int4 first_action)
{
  GrouP g, k, n = NULL;
  PrompT p1;
  SeqEntryPtr sep;
  
  if (mp == NULL)
  {
    return NULL;
  }
  g = HiddenGroup (h, -1, 0, NULL);

  sep = GetTopSeqEntryForEntityID(mp->input_entityID);
  p1 = StaticPrompt (g, "For", 0, dialogTextHeight, systemFont, 'l');
  mp->feature_type = FeatureTypeDialogMulti (g, FeatEdChangeNotify, mp);
  
  k = HiddenGroup (g, -1, 0, NULL);
  mp->action_choice_grp = HiddenGroup (g, NUM_FEAT_ED_ACTIONS, 0, ChangeFeatureEditorActionGroup);
  SetObjectExtra (mp->action_choice_grp, mp, NULL);
  RadioButton (mp->action_choice_grp, "Evidence");
  RadioButton (mp->action_choice_grp, "Exceptions");
  RadioButton (mp->action_choice_grp, "Location");
  RadioButton (mp->action_choice_grp, "Strands");
  RadioButton (mp->action_choice_grp, "Citations");
  RadioButton (mp->action_choice_grp, "Experiment");
  RadioButton (mp->action_choice_grp, "Inference");
  RadioButton (mp->action_choice_grp, "Pseudo");
  if (first_action >= 1 && first_action <= NUM_FEAT_ED_ACTIONS)
  {
    SetValue (mp->action_choice_grp, first_action);
  }
  else
  {
    SetValue (mp->action_choice_grp, FEAT_ED_PSEUDO);
  }
    
  n = HiddenGroup (g, 0, 0, NULL);
  mp->action_grps [0] = FeatureEvidenceGroup (n, mp);
  mp->action_grps[1] = FeatureExceptionGroup (n, mp);
  mp->action_grps[2] = FeatureEditorPartialGroup (n, mp);
  mp->action_grps[3] = FeatureEditorStrandGroup (n, mp);
  mp->action_grps[4] = FeatureEditorCitationGroup (n, mp);
  mp->action_grps[5] = FeatureEditorExperimentGroup (n, mp);
  mp->action_grps[6] = FeatureEditorInferenceGroup (n, mp);
  mp->action_grps[7] = FeaturePseudoGroup (n, mp);
  AlignObjects (ALIGN_CENTER, (HANDLE) mp->action_grps [0],
                              (HANDLE) mp->action_grps [1],
                              (HANDLE) mp->action_grps [2],
                              (HANDLE) mp->action_grps [3],
                              (HANDLE) mp->action_grps [4], 
                                            (HANDLE) mp->action_grps [5],
                                            (HANDLE) mp->action_grps [6],
                                            (HANDLE) mp->action_grps [7],
                              NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) mp->action_choice_grp, (HANDLE) n, NULL);
  
  AlignObjects (ALIGN_CENTER, (HANDLE) p1,
                              (HANDLE) mp->feature_type,
                              (HANDLE) k, (HANDLE) n, NULL);
      
  return g;
}


static Boolean DoesInferenceMatchCategory (CharPtr inf_cat, CharPtr match_cat)
{
  if (StringHasNoText (inf_cat)) return FALSE;

  if (StringHasNoText (match_cat) 
      || StringICmp (match_cat, "ANY") == 0
      || StringICmp (inf_cat, match_cat) == 0)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }

}


static void DoInferenceFeatureProc (SeqFeatPtr sfp, Pointer userdata)
{
  InferenceEditPtr iep;
  GBQualPtr        gbqual, gbqual_last = NULL, gbqual_next;
  CharPtr          new_val;
  InferenceParsePtr ipp;
  Boolean           changed;
  ApplyValueData    avd;

  iep = (InferenceEditPtr) userdata;

  if (iep == NULL || sfp == NULL) return;

  /* apply requested changes */
  gbqual = sfp->qual;
  while (gbqual != NULL)
  {
    gbqual_next = gbqual->next;
    if (StringCmp (gbqual->qual, "inference") == 0)
    {
      if (iep->action == eInferenceRemove)
      {
        if (gbqual_last == NULL)
        {
          sfp->qual = gbqual->next;
        }
        else
        {
          gbqual_last->next = gbqual->next;
        }
        gbqual->next = NULL;
        GBQualFree (gbqual);
      }
      else
      {
        /* perform editing here */
        ipp = ParseInferenceText (gbqual->val);
        changed = FALSE;
        if (ipp != NULL)
        {
          switch (iep->action)
          {
            case eInferenceEditCategory:
              if (DoesInferenceMatchCategory(ipp->category, iep->category_from))
              {
                ipp->category = MemFree (ipp->category);
                ipp->category = StringSave (iep->category_to);
                changed = TRUE;
              }
              break;
            case eInferenceEditType:
              if (DoesInferenceMatchCategory(ipp->type, iep->type_from))
              {
                ipp->type = MemFree (ipp->type);
                ipp->type = StringSave (iep->type_to);
                changed = TRUE;
              }
              break;
            case eInferenceApplyTypeFields:
            case eInferenceEditTypeFields:
              if (iep->field_edit != NULL 
                  && iep->field_edit->edit_apply != NULL
                  && DoesInferenceMatchCategory (ipp->type, iep->field_edit->field_type))
              {
                avd.where_to_replace = EditApplyFindLocation_anywhere;
                avd.field_list = NULL;
                avd.etp = NULL;
                AddEditApplyDataToApplyValue (iep->action == eInferenceApplyTypeFields ? 1 : 2,
                                              iep->field_edit->edit_apply, &avd);
                if (iep->field_edit->field_choice == 0)
                {
                  ipp->first_field = HandleApplyValue (ipp->first_field, &avd);
                }
                else
                {
                  ipp->second_field = HandleApplyValue (ipp->second_field, &avd);
                }
                avd.text_to_replace = MemFree (avd.text_to_replace);
                avd.new_text = MemFree (avd.new_text);
                changed = TRUE;
              }
              break;
            default:
              break;
          }
          if (changed)
          {
            new_val = InferenceTextFromStruct (ipp);
            gbqual->val = MemFree (gbqual->val);
            gbqual->val = new_val;
          }
        }
        gbqual_last = gbqual;
      }
    }
    else
    {
      gbqual_last = gbqual;
    }
    gbqual = gbqual_next;
  }
}


static void DoEvidenceFeatureProc (SeqFeatPtr sfp, Pointer userdata)
{
  FeatEdPtr mp;

  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  mp = (FeatEdPtr) userdata;
  
  sfp->exp_ev = (Uint1) mp->evidence_val;
}


static void DoExceptionFeatureProc (SeqFeatPtr sfp, Pointer userdata)
{
  FeatEdPtr  mp;
  ExistingTextData etd;
  CharPtr          old_explanation = NULL;

  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  mp = (FeatEdPtr) userdata;
  
  if ((mp->product_val == 1 && sfp->product != NULL)
      || (mp->product_val == 2 && sfp->product == NULL)
      || mp->product_val == 3)
  {
    /* first, take care of any pre-existing exception explanation */
    old_explanation = sfp->except_text;
    sfp->except_text = NULL;
    if (mp->do_move_to_comment)
    {
      etd.existing_text_choice = eExistingTextChoiceAppendSemi;
      sfp->comment = HandleExistingText (sfp->comment, old_explanation, &etd);
      old_explanation = NULL;
    }
    else
    {
      old_explanation = MemFree (old_explanation);
    }
    
    if (StringHasNoText (mp->explanation_text))
    {
      sfp->excpt = FALSE;
    }
    else
    {
      sfp->excpt = TRUE;
      sfp->except_text = StringSave (mp->explanation_text);
    }
  }
}

#define DO_NOT_CONVERT             1
#define CONVERT_TO_JOIN            2
#define CONVERT_TO_ORDER           3
#define CONVERT_TO_SINGLE_INTERVAL 4

static void DoPartialFeatureProc (SeqFeatPtr sfp, Pointer userdata)
{
  FeatEdPtr mp;
  Boolean      partial5, partial3;
  Boolean      dash_at_end = FALSE;
  Boolean      star_at_end = FALSE;
  ByteStorePtr bs;
  CharPtr      prot;
  CharPtr      ptr;
  Char         first_char = '\0', ch;
  Boolean      hasNulls;
  SeqLocPtr    slp, firstSlp = NULL, lastSlp = NULL;
  Uint1        strand;
  Boolean      atEnd, moved5 = FALSE;
  BioseqPtr    bsp;
  CdRegionPtr  crp = NULL;
  SeqFeatPtr   gene = NULL;
  
  if (sfp == NULL || sfp->location == NULL || userdata == NULL)
  {
    return;
  }
  
  mp = (FeatEdPtr) userdata;
  
  CheckSeqLocForPartial (sfp->location, &partial5, &partial3);
  bsp = BioseqFindFromSeqLoc (sfp->location);

  if (mp->adjust_gene && sfp->data.choice != SEQFEAT_GENE) {
    gene = GetGeneForFeature (sfp);
  }

  if (sfp->idx.subtype == FEATDEF_CDS) 
  {
    crp = (CdRegionPtr) sfp->data.value.ptrvalue;
    bs = ProteinFromCdRegionEx (sfp, TRUE, FALSE);
    if (bs != NULL) 
    {
      prot = BSMerge (bs, NULL);
      bs = BSFree (bs);
      if (prot != NULL) 
      {
        first_char = *prot;
        ptr = prot;
        ch = *ptr;
        if (ch == '-') 
        {
          dash_at_end = TRUE;
        }
        while (ch != '\0') 
        {
          /* *ptr = TO_UPPER (ch); */
          if (ch == '*') 
          {
            star_at_end = TRUE;
          } else {
            star_at_end = FALSE;
          }
          ptr++;
          ch = *ptr;
        }
      }
      prot = MemFree (prot);
    }
  }

    /* If requested, convert joins to orders */
    /* or orders to joins.                   */

    hasNulls = LocationHasNullsBetween (sfp->location);
    switch (mp->orderjoinpolicy) 
    {
      case CONVERT_TO_JOIN :
        if (hasNulls) 
        {
          slp = SeqLocMerge (bsp, sfp->location, NULL, FALSE, FALSE, FALSE);
            sfp->location = SeqLocFree (sfp->location);
            sfp->location = slp;
            if (bsp->repr == Seq_repr_seg) 
            {
              slp = SegLocToPartsEx (bsp, sfp->location, FALSE);
              sfp->location = SeqLocFree (sfp->location);
              sfp->location = slp;
              hasNulls = LocationHasNullsBetween (sfp->location);
              sfp->partial = (sfp->partial || hasNulls);
            }
            FreeAllFuzz (sfp->location);
            SetSeqLocPartial (sfp->location, partial5, partial3);
        }
        break;
      case CONVERT_TO_ORDER :
        if (!hasNulls) 
        {
            slp = SeqLocMerge (bsp, sfp->location, NULL, FALSE, FALSE, TRUE);
        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 (sfp->location);
            SetSeqLocPartial (sfp->location, partial5, partial3);
        }
        break;
      case CONVERT_TO_SINGLE_INTERVAL :
        slp = SeqLocMerge (bsp, sfp->location, NULL, TRUE, FALSE, FALSE);
        sfp->location = SeqLocFree (sfp->location);
        sfp->location = slp;
          SetSeqLocPartial (sfp->location, partial5, partial3);
      default:
        break;
    }

  /* find first and last location parts in a multi-part location */
  slp = SeqLocFindNext (sfp->location, NULL);
  while (slp != NULL) 
  {
    if (firstSlp == NULL) 
    {
      firstSlp = slp;
    }
    lastSlp = slp;
    slp = SeqLocFindNext (sfp->location, slp);
  }

  /* Set the 5' partial */

  if (firstSlp != NULL) 
  {
    strand = SeqLocStrand (firstSlp);
    atEnd = FALSE;
    if (strand == Seq_strand_minus) 
    {
      if (GetOffsetInBioseq (firstSlp, bsp, SEQLOC_START) == bsp->length - 1)
          {
        atEnd = TRUE;
      }
    } else {
      if (GetOffsetInBioseq (firstSlp, bsp, SEQLOC_START) == 0) 
      {
        atEnd = TRUE;
      }
    }
    switch (mp->leftpolicy) 
    {
      case 1 :
        partial5 = TRUE;
        if (! atEnd && mp->extend5)
        {
          moved5 = TRUE;
          ExtendSeqLocToEnd (sfp->location, bsp, TRUE);
        }
        break;
      case 2 :
        if (atEnd) 
        {
          partial5 = TRUE;
        }
        break;
      case 3 :
        if (crp != NULL && (crp->frame > 1 || first_char != 'M')) 
        {
          partial5 = TRUE;
          if (! atEnd && mp->extend5)
          {
            ExtendSeqLocToEnd (sfp->location, bsp, TRUE);
            moved5 = TRUE;
          }
        }
        break;
      case 4:
        if (crp != NULL && crp->frame > 1)
        {
          partial5 = TRUE;
          if (! atEnd && mp->extend5)
          {
            ExtendSeqLocToEnd (sfp->location, bsp, TRUE);
            moved5 = TRUE;
          }
        }
        break;
      case 5 :
        partial5 = FALSE;
        break;
      case 6 :
        if (! atEnd) 
        {
          partial5 = FALSE;
        }
        break;
      case 7 :
        if (crp != NULL && (crp->frame == 1 || crp->frame == 0) && first_char == 'M')
        {
          partial5 = FALSE;
        }
        break;
      default :
        break;
    }
  }

  /* Set the 3' partial */

  if (lastSlp != NULL) 
  {
    strand = SeqLocStrand (firstSlp);
    atEnd = FALSE;
    if (strand == Seq_strand_minus) 
    {
      if (GetOffsetInBioseq (lastSlp, bsp, SEQLOC_STOP) == 0) 
      {
        atEnd = TRUE;
      }
    } 
    else 
    {
      if (GetOffsetInBioseq (lastSlp, bsp, SEQLOC_STOP) == bsp->length - 1) 
      {
        atEnd = TRUE;
      }
    }
    switch (mp->rightpolicy) 
    {
      case 1 :
        partial3 = TRUE;
        if (! atEnd && mp->extend3)
        {
          ExtendSeqLocToEnd (sfp->location, bsp, FALSE);
        }
        break;
      case 2 :
        if (atEnd) 
        {
          partial3 = TRUE;
        }
        break;
      case 3 :
        if ((! star_at_end) && crp != NULL) 
        {
          partial3 = TRUE;
          if (! atEnd && mp->extend3)
          {
            ExtendSeqLocToEnd (sfp->location, bsp, FALSE);
          }
        }
        break;
      case 4 :
        partial3 = FALSE;
        break;
      case 5 :
        if (! atEnd) 
        {
          partial3 = FALSE;
        }
        break;
      case 6 :
        if (star_at_end && crp != NULL) 
        {
          partial3 = FALSE;
        }
        break;
      default :
        break;
    }
  }

  SetSeqLocPartial (sfp->location, partial5, partial3);
  sfp->partial = (partial5 || partial3 ||
                  LocationHasNullsBetween (sfp->location));
  if (partial5 && moved5)
  {
       SetBestFrame (sfp);
  }

  /* set protein partials for coding regions */
  if (sfp->idx.subtype == FEATDEF_CDS) 
  {
    ResynchCDSPartials (sfp, NULL);
  }

  if (mp->retranslate) {
    RetranslateOneCDS (sfp, sfp->idx.entityID, TRUE, TRUE);
  }
  if (gene != NULL) {
    gene->location = SeqLocFree (gene->location);
    gene->location = SeqLocMerge (bsp, sfp->location, NULL, TRUE, FALSE, FALSE);
    SetSeqLocPartial (gene->location, partial5, partial3);
    gene->partial = partial5 || partial3;
  }
}


static Boolean DoesStrandMatch (Int4 strand_choice, Uint1 strand_val)
{
  Boolean rval = FALSE;
  
  switch (strand_choice)
  {
    case FEATED_STRAND_ANY_REVERSE:
      rval = TRUE;
      break;
    case FEATED_STRAND_UNKNOWN:
      if (strand_val == Seq_strand_unknown)
      {
        rval = TRUE;
      }
      break;
    case FEATED_STRAND_PLUS:
      if (strand_val == Seq_strand_plus)
      {
        rval = TRUE;
      }
      break;
    case FEATED_STRAND_MINUS:
      if (strand_val == Seq_strand_minus)
      {
        rval = TRUE;
      }
      break;
    case FEATED_STRAND_BOTH:
      if (strand_val == Seq_strand_both)
      {
        rval = TRUE;
      }
      break;
  }
  return rval;
}

static Uint1 GetNewStrandValue (Int4 strand_choice, Uint1 strand_val)
{
  Uint1 rval = Seq_strand_unknown;
  
  switch (strand_choice)
  {
    case FEATED_STRAND_ANY_REVERSE:
      switch (strand_val)
      {
        case Seq_strand_plus:
          rval = Seq_strand_minus;
          break;
        case Seq_strand_minus:
          rval = Seq_strand_plus;
          break;
        default:
          rval = strand_val;
          break;
      }
      break;
    case FEATED_STRAND_UNKNOWN:
      rval = Seq_strand_unknown;
      break;
    case FEATED_STRAND_PLUS:
      rval = Seq_strand_plus;
      break;
    case FEATED_STRAND_MINUS:
      rval = Seq_strand_minus;
      break;
    case FEATED_STRAND_BOTH:
      rval = Seq_strand_both;
      break;
  }  
  return rval;
}

static void ConvertLocationStrand (SeqLocPtr slp, Int4 fromStrand, Int4 toStrand)
{
  SeqLocPtr      loc;
  PackSeqPntPtr  psp;
  SeqBondPtr     sbp;
  SeqIntPtr      sinp;
  SeqPntPtr      spp;

  while (slp != NULL) {
    switch (slp->choice) {
      case SEQLOC_NULL :
        break;
      case SEQLOC_EMPTY :
      case SEQLOC_WHOLE :
        break;
      case SEQLOC_INT :
        sinp = (SeqIntPtr) slp->data.ptrvalue;
        if (sinp != NULL && DoesStrandMatch (fromStrand, sinp->strand)) 
        {
          sinp->strand = GetNewStrandValue (toStrand, sinp->strand);
        }
        break;
      case SEQLOC_PNT :
        spp = (SeqPntPtr) slp->data.ptrvalue;
        if (spp != NULL && DoesStrandMatch (fromStrand, spp->strand))
        {
          spp->strand = GetNewStrandValue (toStrand, spp->strand);
        }
        break;
      case SEQLOC_PACKED_PNT :
        psp = (PackSeqPntPtr) slp->data.ptrvalue;
        if (psp != NULL && DoesStrandMatch (fromStrand, psp->strand)) 
        {
          psp->strand = GetNewStrandValue (toStrand, psp->strand);
        }
        break;
      case SEQLOC_PACKED_INT :
      case SEQLOC_MIX :
      case SEQLOC_EQUIV :
        loc = (SeqLocPtr) slp->data.ptrvalue;
        while (loc != NULL) {
          ConvertLocationStrand (loc, fromStrand, toStrand);
          loc = loc->next;
        }
        break;
      case SEQLOC_BOND :
        sbp = (SeqBondPtr) slp->data.ptrvalue;
        if (sbp != NULL) {
          spp = (SeqPntPtr) sbp->a;
          if (spp != NULL && DoesStrandMatch (fromStrand, spp->strand)) 
          {
            spp->strand = GetNewStrandValue (toStrand, spp->strand);
          }
          spp = (SeqPntPtr) sbp->b;
          if (spp != NULL && DoesStrandMatch (fromStrand, spp->strand)) 
          {
            spp->strand = GetNewStrandValue (toStrand, spp->strand);
          }
        }
        break;
      case SEQLOC_FEAT :
        break;
      default :
        break;
    }
    slp = slp->next;
  }
}

static void DoStrandFeatureProc (SeqFeatPtr sfp, Pointer userdata)
{
  FeatEdPtr mp;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  mp = (FeatEdPtr) userdata;
  
  ConvertLocationStrand (sfp->location, mp->from_strand_val, mp->to_strand_val);
  
}

static void DoCitationFeatureProc (SeqFeatPtr sfp, Pointer userdata)
{
  FeatEdPtr mp;
  ByteStorePtr    bs;
  BioseqPtr       protbsp;
  CharPtr         seq1 = NULL, seq2 = NULL;
  Boolean         add_citations = TRUE;
  
  if (sfp == NULL || userdata == NULL)
  {
    return;
  }
  
  mp = (FeatEdPtr) userdata;
  
  if (mp->use_explanation_constraint && mp->explanation_constraint_choice_val != 1)
  {
    /* do not continue unless feature matches explanation constraint */
    if (StringHasNoText (sfp->except_text))
    {
      if (mp->explanation_constraint_choice_val != 2)
      {
        add_citations = FALSE;
      }
    }
    else if (mp->explanation_constraint_choice_val > num_exception_explanations + 2)
    {
      add_citations = FALSE;
    }
    else if (StringICmp (sfp->except_text, 
             exception_explanations [mp->explanation_constraint_choice_val - 3]) != 0)
    {
      add_citations = FALSE;
    }
  }

  if (mp->use_product_match)
  {
    /* do not continue unless translation does not match product */
    if (sfp->data.choice != SEQFEAT_CDREGION)
    {
      add_citations = FALSE;
    }
    else if (sfp->product != NULL)
    {
      protbsp = BioseqFindFromSeqLoc (sfp->product);
      seq1 = GetSequenceByBsp (protbsp);
      bs = ProteinFromCdRegionEx (sfp, TRUE, FALSE);
      if (bs != NULL)
      {
        seq2 = BSMerge (bs, NULL);
      }
      bs = BSFree (bs);
      if (StringICmp (seq1, seq2) == 0)
      {
        add_citations = FALSE;
      }
      seq1 = MemFree (seq1);
      seq2 = MemFree (seq2);
    }
  }

  /* add citations to feature */
  if (add_citations)
  {
    sfp->cit = PubSetFree (sfp->cit);
    sfp->cit = DialogToPointer (mp->citation_list);
  }
  
}


static ValNodePtr MakeRemoveFeatureFieldAction (Uint2 ftype, Int4 legalqual, DialoG constraint_dlg)
{
  ValNodePtr      single_macro = NULL;
  RemoveActionPtr remove;
  AECRActionPtr   act;

  remove = RemoveActionNew();
  remove->field = MakeFeatureFieldField (ftype, legalqual);
  act = AECRActionNew();
  act->constraint = DialogToPointer (constraint_dlg);
  act->action = ValNodeNew (NULL);
  act->action->choice = ActionChoice_remove;
  act->action->data.ptrvalue = remove;
  single_macro = ValNodeNew (NULL);
  single_macro->choice = MacroActionChoice_aecr;
  single_macro->data.ptrvalue = act;
  return single_macro;
}


static ValNodePtr 
MakeApplyFeatureFieldAction 
(Uint2 ftype, Int4 legalqual, CharPtr val, DialoG constraint_dlg, SeqEntryPtr sep)
{
  ValNodePtr      single_macro = NULL;
  ApplyActionPtr  apply;
  AECRActionPtr   act;
  ValNodePtr      sample_list;
  AECRSamplePtr   sample;

  apply = ApplyActionNew();
  apply->field = MakeFeatureFieldField (ftype, legalqual);
  apply->value = StringSave (val);
  act = AECRActionNew();
  act->constraint = DialogToPointer (constraint_dlg);
  act->action = ValNodeNew (NULL);
  act->action->choice = ActionChoice_apply;
  act->action->data.ptrvalue = apply;
  apply->existing_text = ExistingTextOption_replace_old;
  if (sep != NULL) {
    sample_list = GetAECRSampleList (act, sep);
    sample = GetFieldSampleFromList (sample_list, apply->field);
    sample_list = AECRSampleListFree (sample_list);
    if (sample != NULL && sample->num_found > 0) {
      apply->existing_text = TwoStepExistingText (sample->num_found, IsFieldTypeNonText(apply->field), AllowFieldMulti (apply->field));
    }
    sample = AECRSampleFree (sample);
  }
  if (apply->existing_text == 0) {
    act = AECRActionFree (act);
  } else {
    single_macro = ValNodeNew (NULL);
    single_macro->choice = MacroActionChoice_aecr;
    single_macro->data.ptrvalue = act;
  }
  return single_macro;
}


static void 
FeatureEditorDoOneAction 
(SeqEntryPtr     sep, 
 Uint2           ftype, 
 FeatEdPtr mp,
 Int4            action_choice)
{
  Int4            exception_choice;
  ValNodePtr      vnp;
  MsgAnswer       ans;
  Boolean         also_remove_evidence = FALSE;
  InferenceEditPtr iep;
  ValNodePtr      sample_list;
  ValNodePtr      single_field;
  AECRSamplePtr   sample;
  ValNodePtr      macro_list = NULL;
  ValNodePtr      object_list = NULL;
  CharPtr         val;
  VisitFeaturesFunc callback_func = NULL;

  switch (action_choice) 
  {
    case FEAT_ED_EXPERIMENT:
      if (TextHasNoText (mp->experiment_text)) {
        macro_list = MakeRemoveFeatureFieldAction (ftype, Feat_qual_legal_experiment, mp->constraint);
      } else {
        val = SaveStringFromText (mp->experiment_text);
        macro_list = MakeApplyFeatureFieldAction (ftype, Feat_qual_legal_experiment, val, mp->constraint, sep);
        val = MemFree (val);
      }
      break;
    case FEAT_ED_INFERENCE:
      iep = DialogToPointer (mp->inference_dlg);
      if (iep->action == eInferenceRemove)
      {
        macro_list = MakeRemoveFeatureFieldAction (ftype, Feat_qual_legal_inference, mp->constraint);
        vnp = MakeApplyFeatureFieldAction (ftype, Feat_qual_legal_evidence, "X", mp->constraint, NULL);
        sample_list = GetAECRSampleList (vnp->data.ptrvalue, sep);
        single_field = MakeFeatureFieldField (ftype, Feat_qual_legal_evidence);
        sample = GetFieldSampleFromList (sample_list, single_field);
        single_field = FieldTypeFree (single_field);
        sample_list = AECRSampleListFree (sample_list);
        if (sample != NULL && sample->num_found > 0) {
          ans = Message (MSG_YNC, "There are evidence qualifiers on your features, which are displayed as inference qualifiers in the flat file.  Would you like to remove them as well?");
          if (ans == ANS_CANCEL)
          {
            macro_list = MacroActionListFree (macro_list);
          } 
          else if (ans == ANS_YES) 
          {
            also_remove_evidence = TRUE;
            mp->evidence_val = 0;
          }
        }
        sample = AECRSampleFree (sample);
      }
      else 
      {
        macro_list = MakeApplyFeatureFieldAction (ftype, Feat_qual_legal_inference, "X", mp->constraint, NULL);
      }
      if (macro_list != NULL)
      {
        object_list = GetObjectListForAECRAction (sep, macro_list->data.ptrvalue);
        macro_list = MacroActionListFree (macro_list);
        for (vnp = object_list; vnp != NULL; vnp = vnp->next) 
        {
          DoInferenceFeatureProc (vnp->data.ptrvalue, iep);
          if (also_remove_evidence) 
          {
            DoEvidenceFeatureProc (vnp->data.ptrvalue, mp);
          }
        }
        object_list = FreeObjectList (object_list);

      }
      iep = InferenceEditFree (iep);

      break;
    case FEAT_ED_PSEUDO:
      val = GetEnumPopupByName (mp->pseudo_action, legacy_pseudogene_alist);
      if (StringHasNoText (val))
      {
        macro_list = MakeRemoveFeatureFieldAction (ftype, Feat_qual_legal_pseudo, mp->constraint);
      } 
      else 
      {
        macro_list = MakeApplyFeatureFieldAction (ftype, Feat_qual_legal_pseudo, val, mp->constraint, NULL);
      } 
      val = MemFree (val);
      break;
    case FEAT_ED_EVIDENCE:
      macro_list = MakeRemoveFeatureFieldAction (ftype, Feat_qual_legal_evidence, mp->constraint);
      break;
    case FEAT_ED_EXCEPTION:
      mp->do_move_to_comment = GetStatus (mp->move_to_comment);
      mp->product_val = GetValue (mp->product_choice);
      exception_choice = GetValue (mp->explanation_choice);
      if (exception_choice == 1)
      {
        mp->explanation_text = NULL;
      }
      else if (exception_choice > 1 && exception_choice < num_exception_explanations + 1)
      {
        mp->explanation_text = StringSave (exception_explanations [exception_choice - 2]);
      }
      else if (exception_choice == num_exception_explanations + 1)
      {
        mp->explanation_text = SaveStringFromText (mp->other_explanation);
      }
      callback_func = DoExceptionFeatureProc;
      break;
    case FEAT_ED_PARTIAL:
      mp->orderjoinpolicy = GetValue (mp->convert_choice);
      mp->leftpolicy = GetValue (mp->partial5_choice);
      mp->rightpolicy = GetValue (mp->partial3_choice);
      mp->extend5 = GetStatus (mp->extend5_btn);
      mp->extend3 = GetStatus (mp->extend3_btn);
      mp->retranslate = GetStatus (mp->retranslate_btn);
      mp->adjust_gene = GetStatus (mp->adjust_gene_btn);
      callback_func = DoPartialFeatureProc;
      break;
    case FEAT_ED_STRAND:
      mp->from_strand_val = (Uint1) GetValue (mp->from_strand);
      mp->to_strand_val = (Uint1) GetValue (mp->to_strand);
      callback_func = DoStrandFeatureProc;
      break;
    case FEAT_ED_CITATION:
      mp->use_explanation_constraint = GetStatus (mp->explanation_constraint);
      mp->explanation_constraint_choice_val = GetValue (mp->explanation_constraint_choice);
      mp->use_product_match = GetStatus (mp->citation_translation);
      callback_func = DoCitationFeatureProc;
      break;
  }

  if (callback_func != NULL) 
  {
    macro_list = MakeApplyFeatureFieldAction (ftype, Feat_qual_legal_note, "X", mp->constraint, NULL);
    object_list = GetObjectListForAECRAction (sep, macro_list->data.ptrvalue);
    macro_list = MacroActionListFree (macro_list);
    for (vnp = object_list; vnp != NULL; vnp = vnp->next) 
    {
      callback_func (vnp->data.ptrvalue, mp);
    }
    object_list = FreeObjectList (object_list);
  }
  
  if (macro_list != NULL) 
  {
    ApplyMacroToSeqEntryEx (sep, macro_list, NULL, Sequin_GlobalAlign2Seq);
    macro_list = MacroActionListFree (macro_list);
  }

  mp->explanation_text = MemFree (mp->explanation_text);
  if (action_choice == FEAT_ED_PARTIAL) 
  {
    ResynchCodingRegionPartials (sep);
  }
}

static Boolean FeatureEditorAction (Pointer userdata)
{
  FeatEdPtr mp;
  Uint2           ftype;
  Int4            action_choice;
  SeqEntryPtr     sep;

  mp = (FeatEdPtr) userdata;
  if (mp == NULL)
  {
    return FALSE;
  }

  sep = GetTopSeqEntryForEntityID (mp->input_entityID);
  if (sep == NULL)
  {
    return FALSE;
  }

  WatchCursor ();
  Update ();
  
  ftype = GetFeatureTypeFromFeatureTypeDialog (mp->feature_type);
    
  action_choice = GetValue (mp->action_choice_grp);
  FeatureEditorDoOneAction (sep, ftype, mp, action_choice);
  ArrowCursor ();
  Update ();
  ObjMgrSetDirtyFlag (mp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, mp->input_entityID, 0, 0);  
  return TRUE;
}

NLM_EXTERN void FeatureEditorBaseForm (BaseFormPtr bfp, Int4 first_action)
{
  FeatEdPtr mp;
  WindoW          w;
  GrouP           h, g;

  if (bfp == NULL) return;
  mp = (FeatEdPtr) MemNew (sizeof (FeatEdData));
  if (mp == NULL) return;
  
  w = FixedWindow (-50, -33, -10, -10, "Feature Editor", StdCloseWindowProc);
  SetObjectExtra (w, mp, StdCleanupFormProc);
  mp->form = (ForM) w;
  mp->input_entityID = bfp->input_entityID;
  
  h = HiddenGroup (w, -1, 0, NULL);
  
  g = FeatureEditorActionGroup (h, mp, first_action);
  
  mp->constraint = ComplexConstraintDialog (h, NULL, NULL);
  SetComplexConstraintType (mp->constraint, eComplexConstraintType_string);
  mp->accept_cancel = AcceptCancelDialog (h, FeatureEditorAction, NULL, 
                                          FeatEdClear, 
                                          FeatEdClearText, 
                                          (Pointer)mp, w);
  AlignObjects (ALIGN_CENTER, (HANDLE) g,
                              (HANDLE) mp->constraint,
                              (HANDLE) mp->accept_cancel, NULL);
  
  ChangeFeatureEditorActionGroup (mp->action_choice_grp);
  Show (w);
}
