/*   dlgutil1.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:  dlgutil1.c
*
* Author:  Jonathan Kans
*
* Version Creation Date:   1/22/95
*
* $Revision: 6.197 $
*
* File Description: 
*
* Modifications:  
* --------------------------------------------------------------------------
* Date     Name        Description of modification
* -------  ----------  -----------------------------------------------------
*
*
* ==========================================================================
*/

#include <dlogutil.h>
#include <document.h>
#include <gather.h>
#include <subutil.h>
#include <objfdef.h>
#include <gbfeat.h>
#include <gbftdef.h>
#include <edutil.h>
#include <explore.h>
#include <sqnutils.h>
#include <alignmgr2.h>
#include <toasn3.h>
#include <vibforms.h>
#include <cdrgn.h>
#include <findrepl.h>
#include <pubdesc.h>

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

/* for formatting */
#include <asn2gnbp.h>

#define NUMBER_OF_SUFFIXES    13

static CharPtr name_suffix_labels [] = {
  " ", "Jr.", "Sr.", "II", "III", "IV", "V", "VI", "2nd", "3rd", "4th", "5th", "6th", NULL
};

static ENUM_ALIST(name_suffix_alist)
  {" ",    0},
  {"Jr.",  1},
  {"Sr.",  2},
  {"II",   3},
  {"III",  4},
  {"IV",   5},
  {"V",    6},
  {"VI",   7},
  {"2nd",  8},
  {"3rd",  9},
  {"4th", 10},
  {"5th", 11},
  {"6th", 12},
END_ENUM_ALIST

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

Uint2 std_author_widths [] = {
  8, 4, 9, 0
};

static EnumFieldAssocPtr author_popups [] = {
  NULL, NULL, NULL, name_suffix_alist
};

Uint2 str_author_widths [] = {
  24
};

typedef struct authordialog {
  DIALOG_MESSAGE_BLOCK
  DialoG             stdAuthor;
  DialoG             strAuthor;
  GrouP              stdGrp;
  GrouP              strGrp;
  Uint1              type;
  Nlm_ChangeNotifyProc change_notify;
  Pointer              change_data;
} AuthorDialog, PNTR AuthorDialogPtr;

StdPrintOptionsPtr  spop = NULL;

extern Boolean SetupPrintOptions (void)

{
  if (spop == NULL) {
    spop = StdPrintOptionsNew (NULL);
    if (spop != NULL) {
      spop->newline = "\r";
      spop->indent = "";
    } else {
      Message (MSG_FATAL, "StdPrintOptionsNew failed");
    }
  }
  return (Boolean) (spop != NULL);
}

extern void FreePrintOptions (void)

{
  spop = StdPrintOptionsFree (spop);
}

ENUM_ALIST(months_alist)
  {" ",     0},
  {"Jan",   1},
  {"Feb",   2},
  {"Mar",   3},
  {"Apr",   4},
  {"May",   5},
  {"Jun",   6},
  {"Jul",   7},
  {"Aug",   8},
  {"Sep",   9},
  {"Oct",  10},
  {"Nov",  11},
  {"Dec",  12},
END_ENUM_ALIST

static void CopyOneDescriptorToSeqEntry (SeqDescrPtr sdp, SeqEntryPtr sep)
{
  BioseqPtr bsp;
  BioseqSetPtr bssp;

  if (sdp == NULL || sep == NULL) {
    return;
  }
  
  /* NOTE - we are using SeqDescAsnRead and SeqDescAsnWrite
   * instead of SeqDescrAsnWrite and SeqDescrAsnWrite
   * so that only THIS descriptor is propagated, rather than than chain
   */
  if (sep->choice == 1) {
    bsp = (BioseqPtr) sep->data.ptrvalue;
    ValNodeLink (&(bsp->descr),
                  AsnIoMemCopy ((Pointer) sdp,
                                (AsnReadFunc) SeqDescAsnRead,
                                (AsnWriteFunc) SeqDescAsnWrite));
  } else if (sep->choice == 2) {
    bssp = (BioseqSetPtr) sep->data.ptrvalue;
    ValNodeLink (&(bssp->descr),
                  AsnIoMemCopy ((Pointer) sdp,
                                (AsnReadFunc) SeqDescAsnRead,
                                (AsnWriteFunc) SeqDescAsnWrite));
  }
}


extern Int2 LIBCALLBACK DescriptorPropagate (Pointer data)

{
  BioseqSetPtr      bssp = NULL;
  OMProcControlPtr  ompcp;
  SeqDescrPtr sdp;
  ObjValNodePtr ovp;
  SeqEntryPtr   sep;

  ompcp = (OMProcControlPtr) data;
  if (ompcp == NULL || ompcp->input_entityID == 0) {
    Message (MSG_ERROR, "Please select a BioseqSet");
    return OM_MSG_RET_ERROR;
  }
  switch (ompcp->input_itemtype) {
    case OBJ_BIOSEQSET :
      bssp = (BioseqSetPtr) ompcp->input_data;
      break;
    case OBJ_SEQDESC:
      /* special case - propagate just this descriptor */
      sdp = (SeqDescrPtr) ompcp->input_data;
      if (sdp != NULL && sdp->extended > 0) {
        ovp = (ObjValNodePtr) sdp;
        if (ovp->idx.parenttype == OBJ_BIOSEQSET && ovp->idx.parentptr != NULL) {
          bssp = ovp->idx.parentptr;
          for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
            CopyOneDescriptorToSeqEntry (sdp, sep);
          }
          ovp->idx.deleteme = TRUE;
          DeleteMarkedObjects (ompcp->input_entityID, 0, NULL);
          ObjMgrSetDirtyFlag (ompcp->input_entityID, TRUE);
          ObjMgrSendMsg (OM_MSG_UPDATE, ompcp->input_entityID, 0, 0);
          return OM_MSG_RET_DONE;
        }
      }
      return OM_MSG_RET_ERROR;
      break;
    case 0 :
      Message (MSG_ERROR, "Please select a BioseqSet");
      return OM_MSG_RET_ERROR;
    default :
      Message (MSG_ERROR, "Please select a BioseqSet");
      return OM_MSG_RET_ERROR;
  }

  SetDescriptorPropagate (bssp);

  ObjMgrSetDirtyFlag (ompcp->input_entityID, TRUE);
  ObjMgrSendMsg (OM_MSG_UPDATE, ompcp->input_entityID, 0, 0);
  return OM_MSG_RET_DONE;
}

static Int2 OneDescriptorPropagate (Pointer data)

{
  BioseqSetPtr      bssp = NULL;
  OMProcControlPtr  ompcp;
  ObjValNodePtr     ovp;
  SeqDescrPtr       sdp;
  SeqEntryPtr       sep;

  ompcp = (OMProcControlPtr) data;
  if (ompcp == NULL || ompcp->input_entityID == 0) {
    Message (MSG_ERROR, "Please select a BioseqSet");
    return OM_MSG_RET_ERROR;
  }
  /* propagate just this descriptor */
  sdp = (SeqDescrPtr) ompcp->output_data;
  if (sdp != NULL && sdp->extended > 0 && ompcp->input_itemtype == OBJ_BIOSEQSET) {
    ovp = (ObjValNodePtr) sdp;
    bssp = (BioseqSetPtr) ompcp->input_data;
    if (bssp != NULL) {
      for (sep = bssp->seq_set; sep != NULL; sep = sep->next) {
        CopyOneDescriptorToSeqEntry (sdp, sep);
      }
      ovp->idx.deleteme = TRUE;
      DeleteMarkedObjects (ompcp->input_entityID, 0, NULL);
      ObjMgrSetDirtyFlag (ompcp->input_entityID, TRUE);
      ObjMgrSendMsg (OM_MSG_UPDATE, ompcp->input_entityID, 0, 0);
      return OM_MSG_RET_DONE;
    }
  }
  return OM_MSG_RET_ERROR;
}

extern Boolean DescFormReplaceWithoutUpdateProcEx (ForM f, Boolean feature_or_molinfo_change)

{
  MsgAnswer          ans;
  DescriptorFormPtr  dfp;
  Int4Ptr            intptr;
  OMProcControl      ompc;
  Boolean            rsult;
  ValNodePtr         sdp;
  SeqEntryPtr        sep;
  BioseqSetPtr       bssp;

  rsult = FALSE;
  dfp = (DescriptorFormPtr) GetObjectExtra (f);
  if (dfp != NULL) {
    sep = GetTopSeqEntryForEntityID (dfp->input_entityID);
    MemSet ((Pointer) &ompc, 0, sizeof (OMProcControl));
    ompc.input_entityID = dfp->input_entityID;
    ompc.input_itemID = dfp->input_itemID;
    ompc.input_itemtype = dfp->input_itemtype;
    ompc.output_itemtype = dfp->input_itemtype;
    sdp = SeqDescrNew (NULL);
    if (sdp != NULL) {
      sdp->choice = (Uint1)dfp->this_subtype;
      switch (sdp->choice) {
        case Seq_descr_mol_type :
        case Seq_descr_method :
          intptr = (Int4Ptr) DialogToPointer (dfp->data);
          if (intptr != NULL) {
            sdp->data.intvalue = *intptr;
          }
          break;
        default :
          sdp->data.ptrvalue = DialogToPointer (dfp->data);
          break;
      }
      FixSpecialCharactersForObject (OBJ_SEQDESC, sdp, "You may not include special characters in the text.\nIf you do not choose replacement characters, these special characters will be replaced with '#'.", TRUE, NULL);
      if (!feature_or_molinfo_change) {
        CleanupStringsForOneDescriptor (sdp, sep);
      }

      ompc.output_data = (Pointer) sdp;
      if (ompc.input_entityID == 0) {
        if (! ObjMgrRegister (OBJ_SEQDESC, (Pointer) sdp)) {
          Message (MSG_ERROR, "ObjMgrRegister failed");
        }
      } else if (ompc.input_itemtype != OBJ_SEQDESC) {
        ompc.output_itemtype = OBJ_SEQDESC;
        if (! AttachDataForProc (&ompc, FALSE)) {
          Message (MSG_ERROR, "AttachDataForProc failed");
        }
        rsult = TRUE;
      } else {
        if (! ReplaceDataForProc (&ompc, FALSE)) {
          Message (MSG_ERROR, "ReplaceDataForProc failed");
        }
        rsult = TRUE;
      }
    }

    /* If the descriptor was added to a GenBank set then*/
    /* optionally propagate it to the set's Bioseqs.    */

    if (ompc.input_itemtype == OBJ_BIOSEQSET) {
      sep = (SeqEntryPtr) ompc.input_choice;
      bssp = (BioseqSetPtr) sep->data.ptrvalue;
      if (bssp->_class == BioseqseqSet_class_genbank) {
        ans = Message (MSG_YN, "Do you wish to propagate the descriptor to "
                       "the set's Bioseqs?");
        if (ANS_YES == ans) {
          OneDescriptorPropagate (&ompc);
        }
      }
    }
    
    if (!feature_or_molinfo_change) {
      CleanUpProteinTitles (sep);
    }
  }
  return rsult;
}


extern Boolean DescFormReplaceWithoutUpdateProc (ForM f)
{
  return DescFormReplaceWithoutUpdateProcEx (f, TRUE);
}


static void StdDescFormActnProcEx (ForM f, Boolean feature_or_molinfo_change)
{
  DescriptorFormPtr  dfp;

  if (DescFormReplaceWithoutUpdateProcEx (f, feature_or_molinfo_change)) {
    dfp = (DescriptorFormPtr) GetObjectExtra (f);
    if (dfp != NULL) {
      GetRidOfEmptyFeatsDescStrings (dfp->input_entityID, NULL);
      if (feature_or_molinfo_change && GetAppProperty ("InternalNcbiSequin") != NULL) {
        ExtendGeneFeatIfOnMRNA (dfp->input_entityID, NULL);
      }
      
      ObjMgrSetDirtyFlag (dfp->input_entityID, TRUE);
      if (feature_or_molinfo_change) {
        ObjMgrSendMsg (OM_MSG_UPDATE, dfp->input_entityID,
                       dfp->input_itemID, dfp->input_itemtype);
      } else {
        ObjMgrSendMsgNoFeatureChange(OM_MSG_UPDATE, dfp->input_entityID,
                       dfp->input_itemID, dfp->input_itemtype);
      }
    }
  }

}


extern void StdDescFormActnProc (ForM f)

{
  StdDescFormActnProcEx (f, TRUE);
}


extern void StdDescFormActnProcNoFeatureChangeNoMolInfoChange (ForM f)

{
  StdDescFormActnProcEx (f, FALSE);
}


extern void StdDescFormCleanupProc (GraphiC g, VoidPtr data)

{
  DescriptorFormPtr  dfp;
  Uint2              userkey;

  dfp = (DescriptorFormPtr) data;
  if (dfp != NULL) {
    if (dfp->input_entityID > 0 && dfp->userkey > 0) {
      userkey = dfp->userkey;
      dfp->userkey = 0;
      ObjMgrFreeUserData (dfp->input_entityID, dfp->procid, dfp->proctype, userkey);
    }
  }
  StdCleanupExtraProc (g, data);
}

extern OMUserDataPtr ItemAlreadyHasEditor (Uint2 entityID, Uint4 itemID, Uint2 itemtype, Uint2 procid)

{
  BaseFormPtr    bfp;
  Uint4          j;
  Uint4          num;
  ObjMgrPtr      omp;
  ObjMgrDataPtr  PNTR omdpp;
  OMUserDataPtr  omudp;
  ObjMgrDataPtr  tmp;

  if (entityID == 0 || itemID == 0 || itemtype == 0 || procid == 0) return NULL;
  omp = ObjMgrGet ();
  if (omp == NULL) return NULL;
  num = omp->currobj;
  for (j = 0, omdpp = omp->datalist; j < num && omdpp != NULL; j++, omdpp++) {
    tmp = *omdpp;
    if (tmp->parentptr == NULL && tmp->EntityID == entityID) {

      for (omudp = tmp->userdata; omudp != NULL; omudp = omudp->next) {
        if (omudp->proctype == OMPROC_EDIT && omudp->procid == procid) {
          bfp = (BaseFormPtr) omudp->userdata.ptrvalue;
          if (bfp != NULL) {
            if (bfp->input_itemID == itemID && bfp->input_itemtype == itemtype) {
              return omudp;
            }
          }
        }
      }
    }
  }
  return NULL;
}


extern Uint2 GetProcIdForItemEditor (Uint2 entityID, Uint2 itemID, Uint1 itemtype, Uint2 subinputtype)
{
  ObjMgrPtr     omp;
    ObjMgrProcPtr ompp=NULL;
  Uint2         best_procid = 0;

  omp = ObjMgrGet ();

    while ((ompp = ObjMgrProcFindNext(omp, OMPROC_EDIT, itemtype, itemtype, ompp)) != NULL)
    {
        if (ompp->subinputtype == subinputtype)
        {
      return ompp->procid;
        }
        else if (! ompp->subinputtype)  /* general proc found */
    {
            best_procid = ompp->procid;
    }
    }
  return best_procid;
}


/* Note - if a viewer displays features from a far sequence mapped to
 * the current sequence, it will register an interest in the entityID
 * for the far sequence.
 * To find the "main" viewer for just the original sequence, we want
 * to find a view for which only one entityID is registered.
 */
extern OMUserDataPtr EntityAlreadyHasViewer (Uint2 entityID)

{
  BaseFormPtr    bfp;
  Uint4          j;
  Uint4          num;
  ObjMgrPtr      omp;
  ObjMgrDataPtr  PNTR omdpp;
  OMUserDataPtr  omudp;
  ObjMgrDataPtr  tmp;
  ValNodePtr     found_views = NULL, vnp_view;
  ValNodePtr     view_forms = NULL, vnp_form;
  Boolean        not_this_one;

  if (entityID == 0) return NULL;
  omp = ObjMgrGet ();
  if (omp == NULL) return NULL;
  num = omp->currobj;
  for (j = 0, omdpp = omp->datalist; j < num && omdpp != NULL; j++, omdpp++) {
    tmp = *omdpp;
    if (tmp->parentptr == NULL && tmp->EntityID == entityID) {

      for (omudp = tmp->userdata; omudp != NULL; omudp = omudp->next) {
        if (omudp->proctype == OMPROC_VIEW) {
          bfp = (BaseFormPtr) omudp->userdata.ptrvalue;
          if (bfp != NULL) {
            ValNodeAddPointer (&found_views, 0, omudp);
            ValNodeAddPointer (&view_forms, 0, bfp);
          }
        }
      }
    }
  }
  
  /* now look to see if the form is also a view for another entityID */
  for (vnp_view = found_views, vnp_form = view_forms;
       vnp_view != NULL && vnp_form != NULL;
       vnp_view = vnp_view->next, vnp_form = vnp_form->next) {
    not_this_one = FALSE;
    for (j = 0, omdpp = omp->datalist; j < num && omdpp != NULL && !not_this_one; j++, omdpp++) {
      tmp = *omdpp;
      if (tmp->parentptr == NULL && tmp->EntityID != entityID) {

        for (omudp = tmp->userdata; omudp != NULL && !not_this_one; omudp = omudp->next) {
          if (omudp->proctype == OMPROC_VIEW) {
            bfp = (BaseFormPtr) omudp->userdata.ptrvalue;
            if (bfp != NULL && bfp == (BaseFormPtr) vnp_form->data.ptrvalue) {
              not_this_one = TRUE;
            }
          }
        }
      }
    }
    if (!not_this_one) {
      omudp = vnp_view->data.ptrvalue;
      found_views = ValNodeFree (found_views);
      view_forms = ValNodeFree (view_forms);
      return omudp;
    }
  }
  found_views = ValNodeFree (found_views);
  view_forms = ValNodeFree (view_forms);
  
  return NULL;
}


extern Boolean MakeViewerIndependent (Uint2 entityID, OMUserDataPtr omudp)
{
  Uint4          j;
  Uint4          num;
  ObjMgrPtr      omp;
  ObjMgrDataPtr  PNTR omdpp;
  OMUserDataPtr  omudp_tmp;
  ObjMgrDataPtr  tmp;

  if (entityID == 0 || omudp == NULL) return FALSE;
  omp = ObjMgrGet ();
  if (omp == NULL) return FALSE;
  num = omp->currobj;
  for (j = 0, omdpp = omp->datalist; j < num && omdpp != NULL; j++, omdpp++) {
    tmp = *omdpp;
    if (tmp->parentptr == NULL && tmp->EntityID == entityID) {
      omudp_tmp = tmp->userdata;
      while (omudp_tmp != NULL && omudp_tmp != omudp) {
        omudp_tmp = omudp_tmp->next;
      }
      if (omudp_tmp != NULL) {
        tmp->tempload = TL_NOT_TEMP;
        return TRUE;
      }
    }
  }
  return FALSE;
}


typedef struct genegatherlist {
  FeatureFormPtr  ffp;
  ObjMgrPtr       omp;
  SeqLocPtr       slp;
  GeneRefPtr      genexref;
  Boolean         xrefmatch;
  Int2            idx;
  Int2            val;
  Int4            min;
  Uint2           geneEntityID;
  Uint4           geneItemID;
  Uint2           geneItemtype;
  Boolean         geneFound;
  SeqLocPtr       old_feature_location;
  ValNodePtr      lastgene;
} GeneGatherList, PNTR GeneGatherPtr;

static Boolean GeneFindFunc (GatherContextPtr gcp)

{
  GeneGatherPtr  ggp;
  ObjMgrTypePtr  omtp;
  SeqFeatPtr     sfp;
  Char           thislabel [41];

  if (gcp == NULL) return TRUE;

  ggp = (GeneGatherPtr) gcp->userdata;
  if (ggp == NULL ) return TRUE;

  thislabel [0] = '\0';

  if (gcp->thistype == OBJ_SEQFEAT) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp != NULL && sfp->data.choice == SEQFEAT_GENE) {
      omtp = ObjMgrTypeFind (ggp->omp, gcp->thistype, NULL, NULL);
      if (omtp == NULL) {
        return TRUE;
      }
      if (omtp->labelfunc != NULL) {
        (*(omtp->labelfunc)) (gcp->thisitem, thislabel, 40, OM_LABEL_CONTENT);
      }
      if (thislabel [0] != '\0') {
        ggp->idx++;
        if (ggp->idx == ggp->val) {
          ggp->geneEntityID = gcp->entityID;
          ggp->geneItemID = gcp->itemID;
          ggp->geneItemtype = gcp->thistype;
          ggp->geneFound = TRUE;
          return FALSE;
        }
      }
    }
  }
  return TRUE;
}

extern void Nlm_LaunchGeneFeatEd (ButtoN b);
extern void Nlm_LaunchGeneFeatEd (ButtoN b)

{
  FeatureFormPtr  ffp;
  GeneGatherList  ggl;
  GatherScope     gs;
  Int2            handled;
  Int2            val;

  ffp = (FeatureFormPtr) GetObjectExtra (b);
  if (ffp != NULL && ffp->gene != NULL && GetValue (ffp->useGeneXref) == 1) {
    val = GetValue (ffp->gene);
    if (val > 2) {
      ggl.ffp = ffp;
      ggl.omp = ObjMgrGet ();
      ggl.idx = 2;
      ggl.val = val;
      ggl.min = INT4_MAX;
      ggl.geneFound = FALSE;
      ggl.geneEntityID = 0;
      ggl.geneItemID = 0;
      ggl.geneItemtype = 0;
      MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
      gs.seglevels = 1;
      gs.get_feats_location = TRUE;
      MemSet((Pointer)(gs.ignore), (int)(TRUE), (size_t)(OBJ_MAX * sizeof(Boolean)));
      gs.ignore[OBJ_BIOSEQ] = FALSE;
      gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
      gs.ignore[OBJ_SEQFEAT] = FALSE;
      gs.ignore[OBJ_SEQANNOT] = FALSE;
      gs.scope = GetBestTopParentForItemID (ffp->input_entityID,
                                            ffp->input_itemID,
                                            ffp->input_itemtype);
      GatherEntity (ffp->input_entityID, (Pointer) &ggl, GeneFindFunc, &gs);
      if (ggl.geneFound) {
        WatchCursor ();
        Update ();
        handled = GatherProcLaunch (OMPROC_EDIT, FALSE, ggl.geneEntityID, ggl.geneItemID,
                                    ggl.geneItemtype, 0, 0, ggl.geneItemtype, 0);
        ArrowCursor ();
        Update ();
        if (handled != OM_MSG_RET_DONE || handled == OM_MSG_RET_NOPROC) {
          Message (MSG_ERROR, "Unable to launch editor on gene feature.");
        }
      }
    }
  }
}

extern void UpdateGeneLocation 
(SeqFeatPtr gene,
 SeqLocPtr  old_feat_loc,
 SeqLocPtr  new_feat_loc,
 Uint2      entityID)
{
  Uint1          strandfeat, strandgene, strandold;
  BioseqPtr      bsp;
  SeqLocPtr      tmpslp, slp;
  Boolean        hasNulls;
  Boolean        noLeft;
  Boolean        noRight;
  Boolean        noLeftFeat;
  Boolean        noLeftGene;
  Boolean        noRightFeat;
  Boolean        noRightGene;

  if (gene == NULL || new_feat_loc == NULL)
  {
    return;
  }
  
  strandfeat = SeqLocStrand (new_feat_loc);
  strandgene = SeqLocStrand (gene->location); 
  if (old_feat_loc == NULL)
  {
    strandold = strandfeat;
  }
  else
  {
    strandold = SeqLocStrand (old_feat_loc);
  }
          
  /* only correct gene location if gene is on same strand as old feature
   * location and contained in new feature location (on either strand).
   */
  if (SeqLocAinB (new_feat_loc, gene->location) <= 0
      && ((strandold == Seq_strand_minus && strandgene == Seq_strand_minus)
          || (strandold != Seq_strand_minus && strandgene != Seq_strand_minus))) 
  {
    bsp = GetBioseqGivenSeqLoc (gene->location, entityID);
    if (bsp != NULL) {
      hasNulls = LocationHasNullsBetween (gene->location);
      if ((strandfeat == Seq_strand_minus && strandgene != Seq_strand_minus)
          || (strandfeat != Seq_strand_minus && strandgene == Seq_strand_minus))
      {
        tmpslp = SeqLocCopy (gene->location);
        SeqLocRevCmp (tmpslp);
        slp = SeqLocMerge (bsp, tmpslp, new_feat_loc, TRUE, FALSE, hasNulls);
        tmpslp = SeqLocFree (tmpslp);
      }
      else
      {
        slp = SeqLocMergeExEx (bsp, gene->location, new_feat_loc, TRUE, FALSE, TRUE, hasNulls, TRUE, TRUE, FALSE);
      }

      if (slp != NULL) {
        CheckSeqLocForPartial (gene->location, &noLeftGene, &noRightGene);
        gene->location = SeqLocFree (gene->location);
        gene->location = slp;
        CheckSeqLocForPartial (new_feat_loc, &noLeftFeat, &noRightFeat);
        if (bsp->repr == Seq_repr_seg) {
          slp = SegLocToPartsEx (bsp, gene->location, TRUE);
          gene->location = SeqLocFree (gene->location);
          gene->location = slp;
          hasNulls = LocationHasNullsBetween (gene->location);
          gene->partial = (gene->partial || hasNulls);
        }
        FreeAllFuzz (gene->location);
        noLeft = (noLeftFeat || noLeftGene);
        noRight = (noRightFeat || noRightGene);
        SetSeqLocPartial (gene->location, noLeft, noRight);
        gene->partial = (gene->partial || noLeft || noRight);
      }
    }
  }
}

static Boolean DlgStrandsMatch (Uint1 featstrand, Uint1 locstrand)

{
  if (featstrand == locstrand) return TRUE;
  if (locstrand == Seq_strand_unknown && featstrand != Seq_strand_minus) return TRUE;
  if (featstrand == Seq_strand_unknown && locstrand != Seq_strand_minus) return TRUE;
  if (featstrand == Seq_strand_both && locstrand != Seq_strand_minus) return TRUE;
  if (locstrand == Seq_strand_both) return TRUE;
  return FALSE;
}

static Boolean GeneUpdateFunc (GatherContextPtr gcp)

{
  GeneGatherPtr  ggp;
  ObjMgrTypePtr  omtp;
  SeqFeatPtr     sfp;
  Uint1          strand1, strand2;
  Char           thislabel [41];

  if (gcp == NULL) return TRUE;

  ggp = (GeneGatherPtr) gcp->userdata;
  if (ggp == NULL ) return TRUE;

  thislabel [0] = '\0';

  if (gcp->thistype == OBJ_SEQFEAT) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp != NULL && sfp->data.choice == SEQFEAT_GENE) {
      omtp = ObjMgrTypeFind (ggp->omp, gcp->thistype, NULL, NULL);
      if (omtp == NULL) {
        return TRUE;
      }
      if (omtp->labelfunc != NULL) {
        (*(omtp->labelfunc)) (gcp->thisitem, thislabel, 40, OM_LABEL_CONTENT);
      }
      if (thislabel [0] != '\0') {
        ggp->idx++;
        if (ggp->idx == ggp->val) {
          strand1 = SeqLocStrand (sfp->location);
          strand2 = SeqLocStrand (ggp->slp);
          if (DlgStrandsMatch (strand1, strand2)) {
            UpdateGeneLocation (sfp, ggp->old_feature_location, ggp->slp, gcp->entityID);
            return FALSE;
          }
        }
      }
    }
  }
  return TRUE;
}

static Boolean GeneGatherFunc (GatherContextPtr gcp)

{
  FeatureFormPtr  ffp;
  GeneGatherPtr   ggp;
  GeneRefPtr      grp;
  ObjMgrTypePtr   omtp;
  SeqFeatPtr      sfp;
  Char            thislabel [41];
  ValNodePtr      vnp;

  if (gcp == NULL) return TRUE;

  ggp = (GeneGatherPtr) gcp->userdata;
  if (ggp == NULL || ggp->ffp == NULL) return TRUE;

  thislabel [0] = '\0';

  if (gcp->thistype == OBJ_SEQFEAT) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp != NULL && sfp->data.choice == SEQFEAT_GENE) {
      omtp = ObjMgrTypeFind (ggp->omp, gcp->thistype, NULL, NULL);
      if (omtp == NULL) {
        return TRUE;
      }
      if (omtp->labelfunc != NULL) {
        (*(omtp->labelfunc)) (gcp->thisitem, thislabel, 40, OM_LABEL_CONTENT);
      }
      if (thislabel [0] != '\0') {
        ffp = (FeatureFormPtr) ggp->ffp;
        grp = (GeneRefPtr) sfp->data.value.ptrvalue;
        if (grp != NULL 
            && (grp->locus != NULL || grp->locus_tag != NULL || grp->desc != NULL))
        {
          vnp = ValNodeNew (ggp->lastgene);
          if (ffp->geneNames == NULL) {
            ffp->geneNames = vnp;
          }
          ggp->lastgene = vnp;
          if (vnp != NULL) {
            vnp->data.ptrvalue = StringSave (thislabel);
            if (grp->locus != NULL) {
              vnp->choice = 1;
            } else if (grp->desc != NULL) {
              vnp->choice = 2;
            } else if (grp->locus_tag != NULL) {
              vnp->choice = 3;
            }
          }
        }
      }
    }
  }

  return TRUE;
}

extern void PopulateGenePopup (FeatureFormPtr ffp)

{
  Int2            count;
  GeneGatherList  ggl;
  GatherScope     gs;
  CharPtr         str;
  Boolean         usePopupForGene;
  ValNodePtr      vnp;

  if (ffp != NULL && ffp->genePopup != NULL && ffp->geneList != NULL) {
    ggl.ffp = ffp;
    ggl.omp = ObjMgrGet ();
    ggl.slp = NULL;
    ggl.genexref = NULL;
    ggl.xrefmatch = FALSE;
    ggl.idx = 0;
    ggl.val = 0;
    ggl.min = 0;
    ggl.lastgene = NULL;
    MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
    gs.seglevels = 1;
    gs.get_feats_location = TRUE;
    MemSet((Pointer)(gs.ignore), (int)(TRUE), (size_t)(OBJ_MAX * sizeof(Boolean)));
    gs.ignore[OBJ_BIOSEQ] = FALSE;
    gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
    gs.ignore[OBJ_SEQFEAT] = FALSE;
    gs.ignore[OBJ_SEQANNOT] = FALSE;
    gs.scope = GetBestTopParentForItemID (ffp->input_entityID,
                                          ffp->input_itemID,
                                          ffp->input_itemtype);
    GatherEntity (ffp->input_entityID, (Pointer) &ggl, GeneGatherFunc, &gs);
    count = 0;
    for (vnp = ffp->geneNames; vnp != NULL; vnp = vnp->next) {
      count++;
    }
    if (count < 32) {
      usePopupForGene = TRUE;
      ffp->gene = ffp->genePopup;
    } else {
      usePopupForGene = FALSE;
      ffp->gene = ffp->geneList;
    }
    for (vnp = ffp->geneNames; vnp != NULL; vnp = vnp->next) {
      str = (CharPtr) vnp->data.ptrvalue;
      if (StringHasNoText (str)) {
        str = "??";
      }
      if (usePopupForGene) {
        PopupItem (ffp->gene, str);
      } else {
        ListItem (ffp->gene, str);
      }
    }
    Show (ffp->gene);
  }
}

static Boolean GeneMatchFunc (GatherContextPtr gcp)

{
  Int4            diff;
  FeatureFormPtr  ffp;
  GeneRefPtr      genexref;
  GeneGatherPtr   ggp;
  GeneRefPtr      grp;
  ObjMgrTypePtr   omtp;
  SeqFeatPtr      sfp;
  Uint1           strand1, strand2;
  Char            thislabel [41];

  if (gcp == NULL) return TRUE;

  ggp = (GeneGatherPtr) gcp->userdata;
  if (ggp == NULL || ggp->ffp == NULL) return TRUE;

  thislabel [0] = '\0';

  if (gcp->thistype == OBJ_SEQFEAT) {
    sfp = (SeqFeatPtr) gcp->thisitem;
    if (sfp != NULL && sfp->data.choice == SEQFEAT_GENE && sfp->data.value.ptrvalue != NULL) {
      omtp = ObjMgrTypeFind (ggp->omp, gcp->thistype, NULL, NULL);
      if (omtp == NULL) {
        return TRUE;
      }
      if (omtp->labelfunc != NULL) {
        (*(omtp->labelfunc)) (gcp->thisitem, thislabel, 40, OM_LABEL_CONTENT);
      }
      if (thislabel [0] != '\0') {
        ffp = (FeatureFormPtr) ggp->ffp;
        ggp->idx++;
        genexref = ggp->genexref;
        if (genexref != NULL) {
          grp = (GeneRefPtr) sfp->data.value.ptrvalue;
          if (! StringHasNoText (genexref->locus)) {
            if (StringICmp (genexref->locus, grp->locus) == 0) {
              ggp->val = ggp->idx;
              ggp->xrefmatch = TRUE;
              if (ffp != NULL) {
                SetValue (ffp->useGeneXref, 2);
              }
            }
          } else if (! StringHasNoText (genexref->locus_tag)) {
            if (StringICmp (genexref->locus_tag, grp->locus_tag) == 0) {
              ggp->val = ggp->idx;
              ggp->xrefmatch = TRUE;
              if (ffp != NULL) {
                SetValue (ffp->useGeneXref, 2);
              }
            }
          } else if (! StringHasNoText (genexref->desc)) {
            if (StringICmp (genexref->desc, grp->desc) == 0) {
              ggp->val = ggp->idx;
              ggp->xrefmatch = TRUE;
              if (ffp != NULL) {
                SetValue (ffp->useGeneXref, 2);
              }
            }
          }
        }
        diff = SeqLocAinB (ggp->slp, sfp->location);
        if (diff >= 0) {
          if (diff < ggp->min) {
            strand1 = SeqLocStrand (ggp->slp);
            strand2 = SeqLocStrand (sfp->location);
            if (DlgStrandsMatch (strand1, strand2)) {
              ggp->min = diff;
              if (! ggp->xrefmatch) {
                ggp->val = ggp->idx;
              }
            }
          }
        }
      }
    }
  }

  return TRUE;
}

static void SaveGoTermsInSfp (UserObjectPtr uop, Pointer userdata)

{
  FeatureFormPtr  ffp;
  ObjectIdPtr     oip;

  if (uop == NULL || userdata == NULL) return;
  oip = uop->type;
  if (oip == NULL) return;
  if (StringCmp (oip->str, "GeneOntology") == 0) {
    ffp = (FeatureFormPtr) userdata;
    ffp->goTermUserObj = AsnIoMemCopy (uop, (AsnReadFunc) UserObjectAsnRead,
                                       (AsnWriteFunc) UserObjectAsnWrite);
  }
}

static void FeatIDtoText (TexT t, ChoicePtr cp)

{
  Char         buf [32];
  ObjectIdPtr  oip;

  if (t == NULL) return;
  if (cp == NULL) {
    SetTitle (t, "");
    return;
  }

  if (cp->choice == 3) {
    oip = (ObjectIdPtr) cp->value.ptrvalue;
    if (oip != NULL) {
      if (StringDoesHaveText (oip->str)) {
        SetTitle (t, oip->str);
        return;
      } else {
        sprintf (buf, "%ld", (long) oip->id);
        SetTitle (t, buf);
        return;
      }
    }
  }

  SetTitle (t, "");
}

NLM_EXTERN void TextToFeatID (TexT t, ChoicePtr cp)

{
  Boolean      all_digits = TRUE;
  Char         buf [128];
  Char         ch;
  ObjectIdPtr  oip;
  CharPtr      str;
  long int     val;

  if (t == NULL || cp == NULL) return;

  GetTitle (t, buf, sizeof (buf) - 1);
  if (StringHasNoText (buf)) {
    SeqFeatIdFree (cp);
    cp->choice = 0;
    return;
  }

  oip = ObjectIdNew ();
  if (oip == NULL) return;

  str = buf;
  ch = *str;
  while (ch != '\0') {
    if (! IS_DIGIT (ch)) {
      all_digits = FALSE;
    }
    str++;
    ch = *str;
  }

  if (all_digits && sscanf (buf, "%ld", &val) == 1) {
    oip->id = (Int4) val;
  } else {
    oip->str = StringSave (buf);
  }
  SeqFeatIdFree (cp);
  cp->choice = 3;
  cp->value.ptrvalue = (Pointer) oip;
}

static void FeatXreftoText (TexT t, SeqFeatPtr sfp)

{
  Char            buf [32];
  ObjectIdPtr     oip;
  SeqFeatXrefPtr  xref;

  if (t == NULL) return;
  if (sfp == NULL) {
    SetTitle (t, "");
    return;
  }

  for (xref = sfp->xref; xref != NULL; xref = xref->next) {
    if (xref->id.choice != 3) continue;
    oip = (ObjectIdPtr) xref->id.value.ptrvalue;
    if (oip != NULL) {
      if (StringDoesHaveText (oip->str)) {
        SetTitle (t, oip->str);
        return;
      } else {
        sprintf (buf, "%ld", (long) oip->id);
        SetTitle (t, buf);
        return;
      }
    }
  }

  SetTitle (t, "");
}

NLM_EXTERN void TextToFeatXref (TexT t, SeqFeatPtr sfp)

{
  Boolean         all_digits = TRUE;
  Char            buf [128];
  Char            ch;
  ObjectIdPtr     oip;
  CharPtr         str;
  long int        val;
  SeqFeatXrefPtr  xref;

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

  GetTitle (t, buf, sizeof (buf) - 1);
  if (StringHasNoText (buf)) {
    ClearFeatIDXrefs (sfp);
    return;
  }

  ClearFeatIDXrefs (sfp);

  oip = ObjectIdNew ();
  if (oip == NULL) return;

  str = buf;
  ch = *str;
  while (ch != '\0') {
    if (! IS_DIGIT (ch)) {
      all_digits = FALSE;
    }
    str++;
    ch = *str;
  }

  if (all_digits && sscanf (buf, "%ld", &val) == 1) {
    oip->id = (Int4) val;
  } else {
    oip->str = StringSave (buf);
  }

  xref = SeqFeatXrefNew ();
  if (xref != NULL) {
    xref->id.choice = 3;
    xref->id.value.ptrvalue = (Pointer) oip;
    xref->next = sfp->xref;
    sfp->xref = xref;
  }
}

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

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

ENUM_ALIST(new_pseudogene_alist)
  { " ",              0 },
  { "Processed",      1 },
  { "Unprocessed",    2 },
  { "Unitary",        3 },
  { "Allelic",        4 },
  { "Unknown",        5 },
END_ENUM_ALIST

ENUM_ALIST(legacy_pseudogene_alist)
  { " ",              0 },
  { "Processed",      1 },
  { "Unprocessed",    2 },
  { "Unitary",        3 },
  { "Allelic",        4 },
  { "Unknown",        5 },
  { "Unqualified",  255 },
END_ENUM_ALIST

extern void InitPseudogenePopup (FeatureFormPtr ffp, PopuP p, Boolean ispseudo, CharPtr pseudogene, Boolean indexerVersion)

{
  if (ffp == NULL) return;
  ffp->pseudoalist = new_pseudogene_alist;
  if (indexerVersion) {
    ffp->pseudoalist = legacy_pseudogene_alist;
  } else if (ispseudo && pseudogene == NULL) {
    ffp->pseudoalist = legacy_pseudogene_alist;
  } else if (ispseudo) {
    if ((StringICmp (pseudogene, "processed") != 0) &&
        (StringICmp (pseudogene, "unprocessed") != 0) &&
        (StringICmp (pseudogene, "unitary") != 0) &&
        (StringICmp (pseudogene, "allelic") != 0) &&
        (StringICmp (pseudogene, "unknown") != 0)) {
      ffp->pseudoalist = legacy_pseudogene_alist;
    }
  }
  InitEnumPopup (p, ffp->pseudoalist, NULL);
}

static void GbqualsToPseudogenePopup (SeqFeatPtr sfp, FeatureFormPtr ffp, PopuP p)

{
  GBQualPtr  gbq;
  CharPtr    str = NULL;

  if (sfp == NULL) {
    SetValue (p, 0);
    return;
  }

  if (ffp == NULL) return;

  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    if (StringICmp (gbq->qual, "pseudogene") != 0) continue;
    str = gbq->val;
  }

  if (StringHasNoText (str) && sfp->pseudo) {
    str = "Unqualified";
  }

  SafeSetEnumPopupByName (p, ffp->pseudoalist, str);
}

static void PseudogenePopupToGbquals (SeqFeatPtr sfp, FeatureFormPtr ffp, PopuP p)

{
  Char       ch;
  GBQualPtr  gbq, gbqlast = NULL;
  CharPtr    str;

  if (sfp == NULL) return;
  if (ffp == NULL) return;

  str = GetEnumPopupByName (p, ffp->pseudoalist);
  if (StringHasNoText (str)) return;

  if (StringICmp (str, "Unqualified") == 0) {
    sfp->pseudo = TRUE;
    MemFree (str);
    return;
  }

  ch = str [0];
  str [0] = TO_LOWER (ch);

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

  gbq = GBQualNew ();
  if (gbq == NULL) return;
  gbq->qual = StringSave ("pseudogene");
  gbq->val = str;
  if (gbqlast == NULL) {
    sfp->qual = gbq;
  } else {
    gbqlast->next = gbq;
  }
}

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

{
  GBQualPtr   gbq;
  ValNodePtr  head = NULL;

  if (sfp == NULL || StringHasNoText (qual)) {
    PointerToDialog (d, NULL);
    return;
  }
  for (gbq = sfp->qual; gbq != NULL; gbq = gbq->next) {
    if (StringICmp (gbq->qual, qual) != 0) continue;
    ValNodeCopyStr (&head, 0, gbq->val);
  }
  PointerToDialog (d, head);
  ValNodeFreeData (head);
}

extern void VisStringDialogToGbquals (SeqFeatPtr sfp, DialoG d, CharPtr qual);
extern void VisStringDialogToGbquals (SeqFeatPtr sfp, DialoG d, CharPtr qual)

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

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

extern void SeqFeatPtrToFieldPage (DialoG d, SeqFeatPtr sfp);

extern void SeqFeatPtrToCommon (FeatureFormPtr ffp, SeqFeatPtr sfp)

{
  GeneGatherList  ggl;
  GeneRefPtr      grp;
  GatherScope     gs;
  ProtRefPtr      prp;
  CharPtr         str;
  ValNodePtr      vnp;
  SeqFeatXrefPtr  xref;
  /*
  Char            ch;
  CharPtr         ptr;
  */

  if (ffp != NULL) {
    if (sfp != NULL) {
      str = StringSave (sfp->comment);
      /*
      ptr = str;
      if (ptr != NULL) {
        ch = *ptr;
        while (ch != '\0') {
          if (ch == '~') {
#ifdef WIN_MAC
            *ptr = '\015';
#else
            *ptr = '\n';
#endif
          }
          ptr++;
          ch = *ptr;
        }
      }
      */
      SetTitle (ffp->comment, str);
      SetTitle (ffp->title, sfp->title);
      SetValue (ffp->evidence, sfp->exp_ev + 1);
      if (GetAppProperty ("InternalNcbiSequin") == NULL) {
        if (sfp->exp_ev == 0) {
          SafeDisable (ffp->evidence);
        }
      }
      SetStatus (ffp->partial, sfp->partial);
      SetStatus (ffp->exception, sfp->excpt);
      SetStatus (ffp->pseudo, sfp->pseudo);
      SetTitle (ffp->exceptText, sfp->except_text);
      SetValue (ffp->useGeneXref, 1);
      SetTitle (ffp->geneSymbol, "");
      SetTitle (ffp->geneAllele, "");
      SetTitle (ffp->geneDesc, "");
      SetTitle (ffp->locusTag, "");
      SetTitle (ffp->geneSynonym, "");
      ggl.ffp = ffp;
      ggl.omp = ObjMgrGet ();
      ggl.slp = sfp->location;
      ggl.genexref = NULL;
      grp = NULL;
      xref = sfp->xref;
      while (xref != NULL && xref->data.choice != SEQFEAT_PROT) {
        xref = xref->next;
      }
      if (xref != NULL) {
        prp = (ProtRefPtr) xref->data.value.ptrvalue;
        if (prp != NULL && ffp->protXrefName != NULL) {
          vnp = prp->name;
          if (vnp != NULL) {
            SetTitle (ffp->protXrefName, (CharPtr) vnp->data.ptrvalue);
          }
        }
        if (prp != NULL && StringDoesHaveText (prp->desc)) {
          SetTitle (ffp->protXrefDesc, prp->desc);
        }
      }
      xref = sfp->xref;
      while (xref != NULL && xref->data.choice != SEQFEAT_GENE) {
        xref = xref->next;
      }
      if (xref != NULL) {
        grp = (GeneRefPtr) xref->data.value.ptrvalue;
        ggl.genexref = grp;
      }
      ggl.xrefmatch = FALSE;
      ggl.idx = 2;
      ggl.val = 1;
      ggl.min = INT4_MAX;
      MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
      gs.seglevels = 1;
      gs.get_feats_location = TRUE;
      MemSet((Pointer)(gs.ignore), (int)(TRUE), (size_t)(OBJ_MAX * sizeof(Boolean)));
      gs.ignore[OBJ_BIOSEQ] = FALSE;
      gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
      gs.ignore[OBJ_SEQFEAT] = FALSE;
      gs.ignore[OBJ_SEQANNOT] = FALSE;
      gs.scope = GetBestTopParentForItemID (ffp->input_entityID,
                                            ffp->input_itemID,
                                            ffp->input_itemtype);
      GatherEntity (ffp->input_entityID, (Pointer) &ggl, GeneMatchFunc, &gs);
      if (grp != NULL && StringHasNoText (grp->locus) && StringHasNoText (grp->allele) &&
            StringHasNoText (grp->desc) && StringHasNoText (grp->maploc) &&
            grp->db == NULL && grp->syn == NULL && grp->locus_tag == NULL) {
        SetValue (ffp->gene, 1);
        SetValue (ffp->useGeneXref, 3);
        SetTitle (ffp->geneSymbol, grp->locus);
        SetTitle (ffp->geneAllele, grp->allele);
        SetTitle (ffp->geneDesc, grp->desc);
        SetTitle (ffp->locusTag, grp->locus_tag);
        if (grp->syn == NULL) {
          SetTitle (ffp->geneSynonym, "");
        } else {
          SetTitle (ffp->geneSynonym, grp->syn->data.ptrvalue);
        }
        SafeHide (ffp->editGeneBtn);
        SafeHide (ffp->newGeneGrp);
      } else if (ggl.val == 1 && grp != NULL && (! ggl.xrefmatch)) {
        SetValue (ffp->gene, 2);
        SetValue (ffp->useGeneXref, 2);
        SetTitle (ffp->geneSymbol, grp->locus);
        SetTitle (ffp->geneAllele, grp->allele);
        SetTitle (ffp->geneDesc, grp->desc);
        SetTitle (ffp->locusTag, grp->locus_tag);
        if (grp->syn == NULL) {
          SetTitle (ffp->geneSynonym, "");
        } else {
          SetTitle (ffp->geneSynonym, grp->syn->data.ptrvalue);
        }
        SafeHide (ffp->editGeneBtn);
        SafeShow (ffp->newGeneGrp);
      } else if (grp != NULL && (! ggl.xrefmatch)) {
        SetValue (ffp->gene, 2);
        SetValue (ffp->useGeneXref, 2);
        SetTitle (ffp->geneSymbol, grp->locus);
        SetTitle (ffp->geneAllele, grp->allele);
        SetTitle (ffp->geneDesc, grp->desc);
        SetTitle (ffp->locusTag, grp->locus_tag);
        if (grp->syn == NULL) {
          SetTitle (ffp->geneSynonym, "");
        } else {
          SetTitle (ffp->geneSynonym, grp->syn->data.ptrvalue);
        }
        SafeHide (ffp->editGeneBtn);
        SafeShow (ffp->newGeneGrp);
      } else if (ggl.val > 2) {
        SetValue (ffp->gene, ggl.val);
        SafeShow (ffp->editGeneBtn);
        if (ffp->gene == ffp->geneList) {
          SetOffset (ffp->gene, 0, (Int2) (ggl.val - 1));
        }
        if (grp != NULL && (! ggl.xrefmatch)) {
          SetValue (ffp->useGeneXref, 2);
        }
      } else {
        SetValue (ffp->gene, ggl.val);
      }
      PointerToDialog (ffp->featcits, sfp->cit);
      PointerToDialog (ffp->dbxrefs, sfp->dbxref);
      PointerToDialog (ffp->gbquals, sfp->qual);
      SeqFeatPtrToFieldPage (ffp->gbquals, sfp);
      GbqualsToPseudogenePopup (sfp, ffp, ffp->pseudogene);
      GBQualsToExperimentDialog (sfp, ffp->experiment);
      GBQualsToInferenceDialog (ffp->inference, sfp);
      PointerToDialog (ffp->usrobjext, sfp->ext);
      VisitUserObjectsInUop (sfp->ext, (Pointer) ffp, SaveGoTermsInSfp);
      FeatIDtoText (ffp->featid, &(sfp->id));
      FeatXreftoText (ffp->fidxref, sfp);
    } else {
      SetTitle (ffp->comment, "");
      SetValue (ffp->evidence, 1);
      if (GetAppProperty ("InternalNcbiSequin") == NULL) {
        SafeDisable (ffp->evidence);
      }
      SetStatus (ffp->partial, FALSE);
      SetStatus (ffp->exception, FALSE);
      SetStatus (ffp->pseudo, FALSE);
      SetTitle (ffp->exceptText, "");
      SetValue (ffp->gene, 1);
      SafeHide (ffp->newGeneGrp);
      SafeHide (ffp->editGeneBtn);      
      SetValue (ffp->useGeneXref, 1);
      SetTitle (ffp->geneSymbol, "");
      SetTitle (ffp->geneAllele, "");
      SetTitle (ffp->geneDesc, "");
      SetTitle (ffp->locusTag, "");
      SetTitle (ffp->geneSynonym, "");
      PointerToDialog (ffp->featcits, NULL);
      PointerToDialog (ffp->dbxrefs, NULL);
      PointerToDialog (ffp->gbquals, NULL);
      PointerToDialog (ffp->experiment, NULL);
      GBQualsToInferenceDialog (ffp->inference, NULL);
      PointerToDialog (ffp->usrobjext, NULL);
      ffp->goTermUserObj = NULL;
      FeatIDtoText (ffp->featid, NULL);
      FeatXreftoText (ffp->fidxref, NULL);
    }
  }
}

extern void CleanupEvidenceGBQuals (GBQualPtr PNTR prevgbq)

{
  GBQualPtr  gbq;
  GBQualPtr  next;
  Boolean    unlink;

  if (prevgbq == NULL) return;
  gbq = *prevgbq;
  while (gbq != NULL) {
    next = gbq->next;
    unlink = FALSE;
    if (StringICmp (gbq->qual, "experiment") == 0 ||
        StringICmp (gbq->qual, "inference") == 0 ||
        StringICmp (gbq->qual, "pseudogene") == 0) {
      unlink = TRUE;
    }
    if (unlink) {
      *prevgbq = gbq->next;
      gbq->next = NULL;
      GBQualFree (gbq);
    } else {
      prevgbq = (GBQualPtr PNTR) &(gbq->next);
    }
    gbq = next;
  }
}

static UserObjectPtr CombineGOTermUserObjects (UserObjectPtr origuop, UserObjectPtr newuop)

{
  ObjectIdPtr  oip;

  if (newuop == NULL) return origuop;
  if (origuop == NULL) return newuop;

  oip = origuop->type;
  if (oip != NULL && StringCmp (oip->str, "GeneOntology") == 0) return origuop;

  return CombineUserObjects (origuop, newuop);
}

typedef struct replacesdata {
  FeatureFormPtr  ffp;
  SeqFeatPtr      sfp;
} ReplaceData, PNTR ReplaceDataPtr;

static Boolean ReplaceFeatureExtras (GatherContextPtr gcp)

{
  SeqFeatXrefPtr  curr;
  FeatureFormPtr  ffp;
  SeqFeatXrefPtr  PNTR last;
  SeqFeatXrefPtr  next;
  SeqFeatPtr      old;
  ReplaceDataPtr  rdp;
  SeqFeatPtr      sfp;

  rdp = (ReplaceDataPtr) gcp->userdata;
  if (rdp != NULL && rdp->sfp != NULL && rdp->ffp != NULL) {
    sfp = rdp->sfp;
    ffp = rdp->ffp;
    old = gcp->thisitem;
    if (old != NULL) {
      if (ffp->gbquals != NULL) {
        sfp->qual = DialogToPointer (ffp->gbquals);
      } else if (sfp->qual == NULL) {
        sfp->qual = old->qual;
        old->qual = NULL;
      }
      CleanupEvidenceGBQuals (&(sfp->qual));
      PseudogenePopupToGbquals (sfp, ffp, ffp->pseudogene);
      VisStringDialogToGbquals (sfp, ffp->experiment, "experiment");
      InferenceDialogToGBQuals (ffp->inference, sfp, TRUE);
      if (ffp->usrobjext != NULL) {
        sfp->ext = DialogToPointer (ffp->usrobjext);
      } else if (sfp->ext == NULL) {
        sfp->ext = old->ext;
        old->ext = NULL;
      }
      if (ffp->goTermUserObj != NULL) {
        sfp->ext = CombineGOTermUserObjects (sfp->ext, ffp->goTermUserObj);
      }
      if (ffp->featid != NULL) {
        TextToFeatID (ffp->featid, &(sfp->id));
      }
      /*
      if (sfp->cit == NULL) {
        sfp->cit = old->cit;
        old->cit = NULL;
      }
      */
      if (old->xref != NULL) {
        last = (SeqFeatXrefPtr PNTR) &(old->xref);
        curr = old->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;
        }
      }
      if (sfp->xref == NULL) {
        sfp->xref = old->xref;
      } else {
        curr = sfp->xref;
        while (curr->next != NULL) {
          curr = curr->next;
        }
        if (curr != NULL) {
          curr->next = old->xref;
        }
      }
      old->xref = NULL;
    }
  }
  return TRUE;
}

static Boolean GetOldFeatureLocation (GatherContextPtr gcp)
{
  SeqLocPtr PNTR  old_loc;
  SeqFeatPtr      old_feat;

  if (gcp == NULL || gcp->userdata == NULL)
  {
    return FALSE;
  }
  old_loc = (SeqLocPtr PNTR) gcp->userdata;
  if (*old_loc != NULL)
  {
    return TRUE;
  }
  old_feat = gcp->thisitem;
  if (old_feat != NULL)
  {
    *old_loc = (SeqLocPtr) AsnIoMemCopy (old_feat->location,
                                         (AsnReadFunc) SeqLocAsnRead,
                                         (AsnWriteFunc) SeqLocAsnWrite);
  }
  return TRUE;
}

static Boolean HasExceptionGBQual (SeqFeatPtr sfp)

{
  GBQualPtr  qual;
  Boolean    rsult;

  rsult = FALSE;
  if (sfp != NULL) {
    qual = sfp->qual;
    while (qual != NULL) {
      if (StringICmp (qual->qual, "exception") == 0) {
        if (! StringHasNoText (qual->val)) {
          rsult = TRUE;
        }
      }
      qual = qual->next;
    }
  }
  return rsult;
}

static void AddProtRefXref (SeqFeatPtr sfp, TexT protXrefName, TexT protXrefDesc)

{
  Char            desc [256];
  Char            name [256];
  ProtRefPtr      prp;
  SeqFeatXrefPtr  xref;

  if (sfp == NULL) return;
  if (protXrefName == NULL && protXrefDesc == NULL) return;
  GetTitle (protXrefName, name, sizeof (name) - 1);
  GetTitle (protXrefDesc, desc, sizeof (desc) - 1);
  if (StringHasNoText (name) && StringHasNoText (desc)) return;
  for (xref = sfp->xref; xref != NULL; xref = xref->next) {
    if (xref->data.choice == SEQFEAT_PROT) break;
  }
  if (xref == NULL) {
    xref = SeqFeatXrefNew ();
    if (xref != NULL) {
      prp = ProtRefNew ();
      xref->data.choice = SEQFEAT_PROT;
      xref->data.value.ptrvalue = (Pointer) prp;
      xref->next = sfp->xref;
      sfp->xref = xref;
    }
  }
  if (xref != NULL && xref->data.choice == SEQFEAT_PROT) {
    prp = (ProtRefPtr) xref->data.value.ptrvalue;
    xref->data.value.ptrvalue = ProtRefFree (prp);
    prp = CreateNewProtRef (name, desc, NULL, NULL);
    xref->data.value.ptrvalue = (Pointer) prp;
  }
}


static CharPtr infDetails [] = {
  "unknown error",
  "empty inference string",
  "bad inference prefix",
  "bad inference body",
  "single inference field",
  "spaces in inference",
  "same species misused",
  "bad inference accession",
  "accession is missing version",
  "accession.version not public",
  NULL
};

extern Boolean TestInference (FeatureFormPtr ffp, CharPtr badInfQual, size_t len, CharPtr badInfMssg)

{
  GBQualPtr   gbq;
  Int2        inferenceCode;
  Boolean     rsult = TRUE;
  CharPtr     str;
  SeqFeatPtr  tmp;

  if (ffp == NULL) return FALSE;
  tmp = SeqFeatNew ();
  if (tmp == NULL) return FALSE;
  if (badInfQual != NULL) {
    *badInfQual = '\0';
  }
  if (badInfMssg != NULL) {
    *badInfMssg = '\0';
  }
  InferenceDialogToGBQuals (ffp->inference, tmp, FALSE);
  for (gbq = tmp->qual; gbq != NULL; gbq = gbq->next) {
    str = gbq->val;
    if (StringICmp (gbq->qual, "inference") != 0) continue;
    inferenceCode = ValidateInferenceQualifier (str, FALSE);
    if (inferenceCode != VALID_INFERENCE) {
        if (inferenceCode < VALID_INFERENCE || inferenceCode > ACC_VERSION_NOT_PUBLIC) {
          inferenceCode = VALID_INFERENCE;
        }
        if (badInfQual != NULL) {
        StringNCpy_0 (badInfQual, str, len);
        }
        if (badInfMssg != NULL) {
        StringCpy (badInfMssg, infDetails [(int) inferenceCode]);
        }
      rsult = FALSE;
      break;
    }
  }
  SeqFeatFree (tmp);
  return rsult;
}

static CharPtr infWarning1 =
"Bad inference qualifier!  You must conform to international nucleotide sequence database\nconventions for inference qualifiers!";

static CharPtr infWarning2 =
"Still has bad inference!  You must conform to international nucleotide sequence database\nconventions for inference qualifiers!";

static CharPtr infAccept =
"Do you want to accept changes with bad inference data?  The bad qualifier will be converted to a note!";

const char * kTransSplicing = "trans-splicing";

extern Boolean FeatFormReplaceWithoutUpdateProc (ForM f)

{
  Char            allele [128];
  MsgAnswer       ans;
  Int2            attempts = 3;
  Char            badInfMssg [32];
  Char            badInfQual [256];
  BioseqPtr       bsp;
  Char            desc [128];
  Int2            expev;
  SeqMgrFeatContext  fcontext;
  FeatureFormPtr  ffp;
  SeqFeatPtr      gene;
  GeneGatherList  ggl;
  GeneRefPtr      grp;
  GeneRefPtr      grpfeat;
  GatherScope     gs;
  SeqLocPtr       gslp;
  Boolean         hasNulls;
  Int2            i;
  Int4Ptr         intptr;
  Uint4           itemID;
  Char            locustag [128];
  Boolean         noLeft;
  Boolean         noRight;
  SeqEntryPtr     oldscope;
  OMProcControl   ompc;
  ReplaceData     rd;
  Boolean         rsult;
  SeqAnnotPtr     sap;
  SeqEntryPtr     sep;
  SeqFeatPtr      sfp = NULL;
  SeqFeatPtr      new_gene = NULL;
  SeqLocPtr       slp;
  CharPtr         str;
  Char            symbol [128];
  Int2            usexref;
  Int2            val;
  ValNodePtr      vnp, err_list;
  SeqFeatXrefPtr  xref;
  SeqLocPtr       old_location = NULL; /* we need the old location of the feature 
                                        * if we're going to do a gene update    
                                        */
  Boolean         fix_interval_order;
  Boolean         trans_spliced = FALSE;

  rsult = FALSE;
  if ((ffp = (FeatureFormPtr) GetObjectExtra (f)) == NULL) {
    return FALSE;
  }
  MemSet ((Pointer) &ompc, 0, sizeof (OMProcControl));
  ompc.input_entityID = ffp->input_entityID;
  ompc.input_itemID = ffp->input_itemID;
  ompc.input_itemtype = ffp->input_itemtype;
  ompc.output_itemtype = ffp->input_itemtype;
  sfp = SeqFeatNew ();
  if (sfp == NULL) {
    return FALSE;
  }

  sep = GetTopSeqEntryForEntityID (ffp->input_entityID);
  oldscope = SeqEntrySetScope (sep);
  sfp->data.choice = FindFeatFromFeatDefType (ffp->this_subtype);
  switch (sfp->data.choice) {
    case SEQFEAT_BOND :
    case SEQFEAT_SITE :
    case SEQFEAT_PSEC_STR :
      intptr = (Int4Ptr) DialogToPointer (ffp->data);
      if (intptr != NULL) {
        sfp->data.value.intvalue = *intptr;
      }
      break;
    case SEQFEAT_COMMENT:
      sfp->data.value.ptrvalue = NULL;
      break;
    default :
      sfp->data.value.ptrvalue = DialogToPointer (ffp->data);
      break;
  }
  sfp->comment = SaveStringFromText (ffp->comment);
  NewlinesToTildes (sfp->comment);
  expev = GetValue (ffp->evidence);
  if (expev > 0 && expev <= 3) {
    sfp->exp_ev = expev - 1;
  } else {
    sfp->exp_ev = 0;
  }
  sfp->partial = GetStatus (ffp->partial);
  sfp->excpt = GetStatus (ffp->exception);
  sfp->pseudo = GetStatus (ffp->pseudo);
  sfp->except_text = SaveStringFromText (ffp->exceptText);
  sfp->title = NULL;
  sfp->product = DialogToPointer (ffp->product);
  sfp->location = DialogToPointer (ffp->location);
  if (sfp->location == NULL) {
    SeqEntrySetScope (oldscope);
    ErrPostEx (SEV_ERROR, 0, 0, "Feature must have a location!");
    err_list = TestDialog (ffp->location);
    DisplayErrorMessages ("Location Errors", err_list);
    err_list = ValNodeFree (err_list);    
    return FALSE;
  }
  if ((! ffp->acceptBadInf) && (! TestInference (ffp, badInfQual, sizeof (badInfQual), badInfMssg))) {
    (ffp->badInfAttempts)++;
    if (GetAppProperty ("InternalNcbiSequin") != NULL) {
      attempts = 2;
    }
    if (ffp->badInfAttempts < attempts) {
      if (ffp->badInfAttempts == 2) {
        Message (MSG_OK, "%s - Please fix the %s error in\n%s", infWarning2, badInfMssg, badInfQual);
      } else {
        Message (MSG_OK, "%s - Please fix the %s error in\n%s", infWarning1, badInfMssg, badInfQual);
      }
      return FALSE;
    } else {
      if (Message (MSG_YN, "%s", infAccept) == ANS_NO) return FALSE;
      ffp->acceptBadInf = TRUE;
    }
  }

  if (StringISearch (sfp->except_text, kTransSplicing) != NULL) 
  {
    trans_spliced = TRUE;
  }

  bsp = GetBioseqGivenSeqLoc (sfp->location, ffp->input_entityID);
  if (bsp != NULL) {
    if (SeqLocBadSortOrder (bsp, sfp->location)) {
      fix_interval_order = FALSE;
      if (!trans_spliced)
      {
        ans = Message (MSG_YN, "Your feature intervals are out of order, but this coding region has a trans-splicing exception. Continue without correcting interval order?");
        if (ans == ANS_NO)
        {
          fix_interval_order = TRUE;
        }
      }
      else
      {
        ans = Message (MSG_YN,
        "Feature location intervals are out of order.  Do you want them repaired?");
        if (ans == ANS_YES)
        {
          fix_interval_order = TRUE;
        }
      }
      if (fix_interval_order) {
        hasNulls = LocationHasNullsBetween (sfp->location);
        gslp = SeqLocMerge (bsp, sfp->location, NULL, FALSE, FALSE, hasNulls);
        if (gslp != NULL) {
          CheckSeqLocForPartial (sfp->location, &noLeft, &noRight);
          sfp->location = SeqLocFree (sfp->location);
          sfp->location = gslp;
          if (bsp->repr == Seq_repr_seg) {
            gslp = SegLocToParts (bsp, sfp->location);
            sfp->location = SeqLocFree (sfp->location);
            sfp->location = gslp;
          }
          FreeAllFuzz (sfp->location);
          SetSeqLocPartial (sfp->location, noLeft, noRight);
        }
      }
    }
  }
  if (/* CheckSeqLocForPartial (sfp->location, NULL, NULL) */ SeqLocPartialCheck (sfp->location) != SLP_COMPLETE) {
    sfp->partial = TRUE;
  }
  sfp->cit = DialogToPointer (ffp->featcits);
  sfp->dbxref = DialogToPointer (ffp->dbxrefs);
  slp = AsnIoMemCopy (sfp->location, (AsnReadFunc) SeqLocAsnRead,
                      (AsnWriteFunc) SeqLocAsnWrite);
  usexref = GetValue (ffp->useGeneXref);
  if (ffp->gene != NULL) {
    val = GetValue (ffp->gene);
/*
    if (StringISearch (sfp->except_text, "trans-splicing") != NULL) {
      val = 1;
      usexref = 0;
    }
*/
    if (usexref == 3 || (val > 1 && usexref == 2)) {
      grp = NULL;
      if (usexref == 3) {
        grp = GeneRefNew ();
      } else if (val == 2) {
        GetTitle (ffp->geneSymbol, symbol, sizeof (symbol));
        GetTitle (ffp->geneAllele, allele, sizeof (allele));
        GetTitle (ffp->geneDesc, desc, sizeof (desc));
        GetTitle (ffp->locusTag, locustag, sizeof (locustag));
        grp = CreateNewGeneRef (symbol, allele, desc, FALSE);
        if (! StringHasNoText (locustag)) {
          if (grp == NULL) {
            grp = GeneRefNew ();
          }
          grp->locus_tag = StringSave (locustag);
        }
        if (!TextHasNoText (ffp->geneSynonym)) {
          if (grp == NULL) {
            grp = GeneRefNew ();
          }
          ValNodeAddPointer (&(grp->syn), 0, SaveStringFromText (ffp->geneSynonym));
        }
      } else {
        vnp = ffp->geneNames;
        i = val - 3;
        while (i > 0 && vnp != NULL) {
          vnp = vnp->next;
          i--;
        }
        if (vnp != NULL) {
          if (vnp->choice == 1) {
            str = (CharPtr) vnp->data.ptrvalue;
            if (StringDoesHaveText (str)) {
              grp = CreateNewGeneRef (str, NULL, NULL, FALSE);
              gene = SeqMgrGetFeatureByLabel (bsp, str, SEQFEAT_GENE, 0, &fcontext);
              if (gene != NULL && gene->data.choice == SEQFEAT_GENE) {
                grpfeat = (GeneRefPtr) gene->data.value.ptrvalue;
                if (grpfeat != NULL) {
                  grp->locus_tag = StringSaveNoNull (grpfeat->locus_tag);
                }
              }
            }
          } else if (vnp->choice == 2) {
             grp = GeneRefNew ();
            if (grp != NULL) {
              grp->desc = StringSave ((CharPtr) vnp->data.ptrvalue);
            }
         } else if (vnp->choice == 3) {
            grp = GeneRefNew ();
            if (grp != NULL) {
              grp->locus_tag = StringSave ((CharPtr) vnp->data.ptrvalue);
            }
          }
        }
      }
      if (grp != NULL) {
        xref = SeqFeatXrefNew ();
        sfp->xref = xref;
        if (xref != NULL) {
          xref->data.choice = SEQFEAT_GENE;
          xref->data.value.ptrvalue = (Pointer) grp;
        }
      }
    }
  } else if (usexref == 3) {
    /* protein feature can now suppress gene on GenBank view */
    grp = GeneRefNew ();
    if (grp != NULL) {
      xref = SeqFeatXrefNew ();
      sfp->xref = xref;
      if (xref != NULL) {
        xref->data.choice = SEQFEAT_GENE;
        xref->data.value.ptrvalue = (Pointer) grp;
      }
    }
  }

  ompc.output_data = (Pointer) sfp;
  if (ompc.input_entityID == 0) {
    sfp->qual = DialogToPointer (ffp->gbquals);
    PseudogenePopupToGbquals (sfp, ffp, ffp->pseudogene);
    VisStringDialogToGbquals (sfp, ffp->experiment, "experiment");
    InferenceDialogToGBQuals (ffp->inference, sfp, TRUE);

    sfp->ext = DialogToPointer (ffp->usrobjext);
    if (ffp->goTermUserObj != NULL) {
      sfp->ext = CombineGOTermUserObjects (sfp->ext, ffp->goTermUserObj);
    }
    if (ffp->featid != NULL) {
      TextToFeatID (ffp->featid, &(sfp->id));
    }
    if (HasExceptionGBQual (sfp)) {
      sfp->excpt = TRUE;
    }
    if (ffp->fidxref != NULL) {
      TextToFeatXref (ffp->fidxref, sfp);
    }
    AddProtRefXref (sfp, ffp->protXrefName, ffp->protXrefDesc);
    FixSpecialCharactersForObject (OBJ_SEQFEAT, sfp, "You may not include special characters in the text.\nIf you do not choose replacement characters, these special characters will be replaced with '#'.", TRUE, NULL);
    if (! ObjMgrRegister (OBJ_SEQFEAT, (Pointer) sfp)) {
      Message (MSG_ERROR, "ObjMgrRegister failed");
    }
    SeqLocFree (slp);
    SeqEntrySetScope (oldscope);
    return TRUE;
  } else if (ompc.input_itemtype != OBJ_SEQFEAT) {
    sfp->qual = DialogToPointer (ffp->gbquals);
    PseudogenePopupToGbquals (sfp, ffp, ffp->pseudogene);
    VisStringDialogToGbquals (sfp, ffp->experiment, "experiment");
    InferenceDialogToGBQuals (ffp->inference, sfp, TRUE);

    sfp->ext = DialogToPointer (ffp->usrobjext);
    if (ffp->goTermUserObj != NULL) {
      sfp->ext = CombineGOTermUserObjects (sfp->ext, ffp->goTermUserObj);
    }
    if (ffp->featid != NULL) {
      TextToFeatID (ffp->featid, &(sfp->id));
    }
    if (HasExceptionGBQual (sfp)) {
      sfp->excpt = TRUE;
    }
    if (ffp->fidxref != NULL) {
      TextToFeatXref (ffp->fidxref, sfp);
    }
    AddProtRefXref (sfp, ffp->protXrefName, ffp->protXrefDesc);
    FixSpecialCharactersForObject (OBJ_SEQFEAT, sfp, "You may not include special characters in the text.\nIf you do not choose replacement characters, these special characters will be replaced with '#'.", TRUE, NULL);

    /* adjust mRNA location and product name here */
    if (sfp != NULL && sfp->data.choice == SEQFEAT_CDREGION) {
      UpdatemRNAAfterEditing (ffp->data, sfp->location, sfp->location);
    }

    ompc.output_itemtype = OBJ_SEQFEAT;
    if (ompc.input_itemtype == OBJ_BIOSEQ) {
      bsp = GetBioseqGivenIDs (ompc.input_entityID, ompc.input_itemID, ompc.input_itemtype);
      if (bsp != NULL) {
        sap = bsp->annot;
        while (sap != NULL && (sap->name != NULL || sap->desc != NULL || sap->type != 1)) {
          sap = sap->next;
        }
        if (sap == NULL) {
          sap = SeqAnnotNew ();
          if (sap != NULL) {
            sap->type = 1;
            sap->next = bsp->annot;
            bsp->annot = sap;
          }
        }
        if (sap != NULL) {
          itemID = GetItemIDGivenPointer (ompc.input_entityID, OBJ_SEQANNOT, (Pointer) sap);
          if (itemID > 0) {
            ompc.input_itemID = itemID;
            ompc.input_itemtype = OBJ_SEQANNOT;
          }
        }
      }
    }
    if (! AttachDataForProc (&ompc, FALSE)) {
      Message (MSG_ERROR, "AttachDataForProc failed");
    }
    rsult = TRUE;
  } else {
    GatherItem (ompc.input_entityID, ompc.input_itemID, ompc.input_itemtype,
                (Pointer) &old_location, GetOldFeatureLocation);

    /* adjust mRNA location and product name here, using old location */
    if (sfp != NULL && sfp->data.choice == SEQFEAT_CDREGION) {
      UpdatemRNAAfterEditing (ffp->data, old_location == NULL ? sfp->location : old_location, sfp->location);
    }
  
    rd.ffp = ffp;
    rd.sfp = sfp;
    GatherItem (ompc.input_entityID, ompc.input_itemID, ompc.input_itemtype,
                (Pointer) &rd, ReplaceFeatureExtras);
    if (HasExceptionGBQual (sfp)) {
      sfp->excpt = TRUE;
    }
    if (ffp->fidxref != NULL) {
      TextToFeatXref (ffp->fidxref, sfp);
    }
    AddProtRefXref (sfp, ffp->protXrefName, ffp->protXrefDesc);

    FixSpecialCharactersForObject (OBJ_SEQFEAT, sfp, "You may not include special characters in the text.\nIf you do not choose replacement characters, these special characters will be replaced with '#'.", TRUE, NULL);
    if (! ReplaceDataForProc (&ompc, FALSE)) {
      Message (MSG_ERROR, "ReplaceDataForProc failed");
    }
    rsult = TRUE;
  }
  if (ffp->gene != NULL && usexref == 1) {
    val = GetValue (ffp->gene);
    if (val == 2) {
      sep = GetBestTopParentForItemID (ffp->input_entityID,
                                       ffp->input_itemID,
                                       ffp->input_itemtype);
      /*
      sep = GetTopSeqEntryForEntityID (ffp->input_entityID);
      */
      if (sep != NULL) {
        sep = FindNucSeqEntry (sep);
      }
      if (sep != NULL && sep->data.ptrvalue != NULL) {
        GetTitle (ffp->geneSymbol, symbol, sizeof (symbol));
        GetTitle (ffp->geneAllele, allele, sizeof (allele));
        GetTitle (ffp->geneDesc, desc, sizeof (desc));
        GetTitle (ffp->locusTag, locustag, sizeof (locustag));
        grp = CreateNewGeneRef (symbol, allele, desc, FALSE);
        if (! StringHasNoText (locustag)) {
          if (grp == NULL) {
            grp = GeneRefNew ();
          }
          grp->locus_tag = StringSave (locustag);
        }
        if (!TextHasNoText (ffp->geneSynonym)) {
          if (grp == NULL) {
            grp = GeneRefNew ();
          }
          ValNodeAddPointer (&(grp->syn), 0, SaveStringFromText (ffp->geneSynonym));
        }
        if (grp != NULL) {
          new_gene = CreateNewFeature (sep, NULL, SEQFEAT_GENE, NULL);
          if (new_gene != NULL) {
            new_gene->data.value.ptrvalue = (Pointer) grp;
            FixSpecialCharactersForObject (OBJ_SEQFEAT, new_gene, "You may not include special characters in the text.\nIf you do not choose replacement characters, these special characters will be replaced with '#'.", TRUE, NULL);
            new_gene->location = SeqLocFree (new_gene->location);
            new_gene->location = DialogToPointer (ffp->location);
            gslp = MakeGeneLocForFeatureLoc (new_gene->location, ffp->input_entityID, trans_spliced);
            new_gene->location = SeqLocFree (new_gene->location);
            new_gene->location = gslp;
            hasNulls = LocationHasNullsBetween (new_gene->location);
            CheckSeqLocForPartial (new_gene->location, &noLeft, &noRight);
            new_gene->partial = (hasNulls || noLeft || noRight);

            /* copy trans-splicing exception if present */
            if (trans_spliced) {
                new_gene->excpt = TRUE;
                new_gene->except_text = StringSave(kTransSplicing);
            }

          }
        }
      }
    } else if (val > 2 && !trans_spliced) {
      /* do not update gene location if trans-spliced */
      ggl.ffp = ffp;
      ggl.omp = ObjMgrGet ();
      ggl.slp = slp;
      ggl.genexref = NULL;
      ggl.xrefmatch = FALSE;
      ggl.idx = 2;
      ggl.val = val;
      ggl.min = INT4_MAX;
      ggl.old_feature_location = old_location;
      MemSet ((Pointer) (&gs), 0, sizeof (GatherScope));
      gs.seglevels = 1;
      gs.get_feats_location = TRUE;
        MemSet((Pointer)(gs.ignore), (int)(TRUE), (size_t)(OBJ_MAX * sizeof(Boolean)));
        gs.ignore[OBJ_BIOSEQ] = FALSE;
        gs.ignore[OBJ_BIOSEQ_SEG] = FALSE;
        gs.ignore[OBJ_SEQFEAT] = FALSE;
        gs.ignore[OBJ_SEQANNOT] = FALSE;
      gs.scope = GetBestTopParentForItemID (ffp->input_entityID,
                                            ffp->input_itemID,
                                            ffp->input_itemtype);
      GatherEntity (ffp->input_entityID, (Pointer) &ggl, GeneUpdateFunc, &gs);
    }
  }
  SeqLocFree (slp);
  SeqEntrySetScope (oldscope);

  old_location = SeqLocFree (old_location);
  return rsult;
}

extern void StdFeatFormActnProc (ForM f)

{
  FeatureFormPtr  ffp;

  if (FeatFormReplaceWithoutUpdateProc (f)) {
    ffp = (FeatureFormPtr) GetObjectExtra (f);
    if (ffp != NULL) {
      GetRidOfEmptyFeatsDescStrings (ffp->input_entityID, NULL);
      if (GetAppProperty ("InternalNcbiSequin") != NULL) {
        ExtendGeneFeatIfOnMRNA (ffp->input_entityID, NULL);
      }
      ObjMgrSetDirtyFlag (ffp->input_entityID, TRUE);
      ObjMgrSendMsg (OM_MSG_UPDATE, ffp->input_entityID,
                     ffp->input_itemID, ffp->input_itemtype);
    }
  }
}

extern void StdSeqFeatPtrToFeatFormProc (ForM f, Pointer data)

{
  FeatureFormPtr  ffp;
  SeqEntryPtr     oldsep;
  SeqEntryPtr     sep;
  SeqFeatPtr      sfp;
  Int4            val;

  ffp = (FeatureFormPtr) GetObjectExtra (f);
  if (ffp != NULL) {
    sep = GetTopSeqEntryForEntityID (ffp->input_entityID);
    oldsep = SeqEntrySetScope (sep);
    sfp = (SeqFeatPtr) data;
    if (sfp != NULL) {
      switch (sfp->data.choice) {
        case SEQFEAT_BOND :
        case SEQFEAT_SITE :
        case SEQFEAT_PSEC_STR :
          val = (Int4) sfp->data.value.intvalue;
          PointerToDialog (ffp->data, (Pointer) &(val));
          break;
        case SEQFEAT_COMMENT:
          break;
        default :
          PointerToDialog (ffp->data, sfp->data.value.ptrvalue);
          break;
      }
      SeqFeatPtrToCommon (ffp, sfp);
      PointerToDialog (ffp->location, sfp->location);
    }
    SeqEntrySetScope (oldsep);
  }
}

extern void StdInitFeatFormProc (ForM f)

{
  FeatureFormPtr  ffp;

  ffp = (FeatureFormPtr) GetObjectExtra (f);
  if (ffp != NULL) {
    PopulateGenePopup (ffp);
  }
}

extern void StdFeatFormAcceptButtonProc (ButtoN b)

{
  Int2            attempts = 3;
  Char            badInfMssg [32];
  Char            badInfQual [256];
  FeatureFormPtr  ffp;
  SeqLocPtr       slp;
  WindoW          w;
  ValNodePtr      err_list;
  SeqEntryPtr     oldscope, sep;

  if (b != NULL) {
    w = ParentWindow (b);
    ffp = (FeatureFormPtr) GetObjectExtra (b);
    if (ffp != NULL && ffp->form != NULL && ffp->actproc != NULL) {
      sep = GetTopSeqEntryForEntityID (ffp->input_entityID);
      oldscope = SeqEntrySetScope (sep);
      slp = DialogToPointer (ffp->location);
      if (slp == NULL) {
        ErrPostEx (SEV_ERROR, 0, 0, "Feature must have a location!");
        err_list = TestDialog (ffp->location);
        DisplayErrorMessages ("Location Errors", err_list);
        err_list = ValNodeFree (err_list);    
        SeqEntrySetScope (oldscope);
        return;
      }
      SeqLocFree (slp);
      if ((! ffp->acceptBadInf) && (! TestInference (ffp, badInfQual, sizeof (badInfQual), badInfMssg))) {
        (ffp->badInfAttempts)++;
        if (GetAppProperty ("InternalNcbiSequin") != NULL) {
          attempts = 2;
        }
        if (ffp->badInfAttempts < attempts) {
          if (ffp->badInfAttempts == 2) {
            Message (MSG_OK, "%s - Please fix the %s error in\n%s", infWarning2, badInfMssg, badInfQual);
          } else {
            Message (MSG_OK, "%s - Please fix the %s error in\n%s", infWarning1, badInfMssg, badInfQual);
          }
          return;
        } else {
          if (Message (MSG_YN, "%s", infAccept) == ANS_NO) return;
          ffp->acceptBadInf = TRUE;
        }
      }
      Hide (w);
      (ffp->actproc) (ffp->form);
      SeqEntrySetScope (oldscope);
    }
    Update ();
    if (ffp != NULL && (ffp->leave_dlg_up == NULL || ! GetStatus (ffp->leave_dlg_up)))
    {
      Remove (w);
    }
    else if (ffp != NULL)
    {
      /* set strand and sequence ID, but clear other location information */
      SendMessageToDialog (ffp->location, NUM_VIB_MSG + 1);
      SendMessageToDialog (ffp->location, NUM_VIB_MSG + 2);
      Show (w);
    }
  }
}

extern void StdFeatFormCleanupProc (GraphiC g, VoidPtr data)

{
  FeatureFormPtr  ffp;
  Uint2           userkey;

  ffp = (FeatureFormPtr) data;
  if (ffp != NULL) {
    ValNodeFreeData (ffp->geneNames);
    if (ffp->input_entityID > 0 && ffp->userkey > 0) {
      userkey = ffp->userkey;
      ffp->userkey = 0;
      ObjMgrFreeUserData (ffp->input_entityID, ffp->procid, ffp->proctype, userkey);
    }
  }
  StdCleanupFormProc (g, data);
}

extern ValNodePtr AddStringToValNodeChain (ValNodePtr head, CharPtr str, Uint1 choice)

{
  ValNodePtr  vnp;

  vnp = ValNodeNew (head);
  if (head == NULL) {
    head = vnp;
  }
  if (vnp != NULL) {
    vnp->choice = choice;
    vnp->data.ptrvalue = StringSave (str);
  }
  return head;
}

static void StripPeriods (CharPtr str)

{
  Char     ch;
  CharPtr  dst;

  if (str != NULL) {
    dst = str;
    ch = *str;
    while (ch != '\0') {
      if (ch != '.') {
        *dst = ch;
        dst++;
      }
      str++;
      ch = *str;
    }
    *dst = '\0';
  }
}

static void TrimLeadingSpaces (CharPtr str)

{
  Char     ch;
  CharPtr  dst;

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

extern CharPtr NameStdPtrToAuthorSpreadsheetString (NameStdPtr nsp);
extern CharPtr NameStdPtrToAuthorSpreadsheetString (NameStdPtr nsp)

{
  Char   first [256];
  Char   frstinits [64];
  Char   initials [64];
  Int2   j;
  Char   last [256];
  Char   middle [128];
  Char   str [512];
  Char   suffix [64];
  Char   suffixPosition[64];
  Int2   i;

  if (nsp == NULL) return NULL;
  str [0] = '\0';
  StringNCpy_0 (first, nsp->names [1], sizeof (first));
  TrimSpacesAroundString (first);
  StringNCpy_0 (initials, nsp->names [4], sizeof (initials));
  StripPeriods (initials);
  TrimLeadingSpaces (initials);
  StringNCpy_0 (last, nsp->names [0], sizeof (last));
  TrimLeadingSpaces (last);
  StringNCpy_0 (middle, nsp->names [2], sizeof (middle));
  TrimLeadingSpaces (middle);
  if (StringCmp (initials, "al") == 0 &&
      StringCmp (last, "et") == 0 &&
      first [0] == '\0') {
    initials [0] = '\0';
    StringCpy (last, "et al.");
  }
  if (first [0] == '\0') {
    StringNCpy_0 (first, initials, sizeof (first));
    if (IS_ALPHA (first [0])) {
      if (first [1] == '-') {
        first [3] = '\0';
      } else {
        first [1] = '\0';
      }
    } else {
      first [0] = '\0';
    }
  }
  FirstNameToInitials (first, frstinits, sizeof (frstinits) - 1);
  StripPeriods (first);
  TrimLeadingSpaces (first);
  if (first [0] != '\0') {
    StringCat (str, first);
  } else {
  }
  StringCat (str, "\t");
  j = 0;
  while (initials [j] != '\0' && TO_UPPER (initials [j]) == TO_UPPER (frstinits [j])) {
    j++;
  }
  if (initials [j] != '\0') {
    StringCat (str, initials + j);
  } else {
  }
  StringCat (str, "\t");
  StringCat (str, last);
  StringNCpy_0 (suffix, nsp->names [5], sizeof (suffix));
  for (i = 0; i <= NUMBER_OF_SUFFIXES; i++)
    if (StringICmp (suffix, name_suffix_labels [i]) == 0) {
      sprintf (suffixPosition, "%d", i);
      break;
    }
  if (i == NUMBER_OF_SUFFIXES)
    sprintf (suffixPosition, "%d", 0);
  StringCat (str, "\t");
  if (suffix [0] != '\0') {
    StringCat (str, suffixPosition);
  } else {
  }
  StringCat (str, "\t");
  StringCat (str, middle);
  StringCat (str, "\n");
  return StringSave (str);
}

static void StdAuthListPtrToAuthorDialog (DialoG d, Pointer data)

{
  AuthListPtr  alp;
  AuthorPtr    ap;
  ValNodePtr   head;
  Int2         j;
  ValNodePtr   names;
  NameStdPtr   nsp;
  PersonIdPtr  pid;
  TagListPtr   tlp;
  ValNodePtr   vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  alp = (AuthListPtr) data;
  if (tlp != NULL) {
    head = NULL;
    if (alp != NULL) {
      if (alp->choice == 1) {
        names = alp->names;
        while (names != NULL) {
          ap = names->data.ptrvalue;
          if (ap != NULL) {
            pid = ap->name;
            if (pid != NULL) {
              if (pid->choice == 2) {
                nsp = pid->data;
                if (nsp != NULL) {
                  vnp = ValNodeNew (head);
                  if (head == NULL) {
                    head = vnp;
                  }
                  if (vnp != NULL) {
                    vnp->data.ptrvalue = NameStdPtrToAuthorSpreadsheetString (nsp);
                  }
                }
              }
            }
          }
          names = names->next;
        }
      } else {
        Message (MSG_ERROR, "Unable to handle author type %d", (int) alp->choice);
      }
    }
    SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
    tlp->vnp = head;
    SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
    for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
    }
    tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
    CorrectBarMax (tlp->bar, tlp->max);
    CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
  }
}

extern NameStdPtr AuthorSpreadsheetStringToNameStdPtr (CharPtr txt);
extern NameStdPtr AuthorSpreadsheetStringToNameStdPtr (CharPtr txt)

{
  Char        ch;
  CharPtr     first;
  Char        initials [64];
  Int2        j;
  Int2        k;
  Char        last;
  NameStdPtr  nsp;
  Char        periods [128];
  CharPtr     str;
  Char        str1 [64];
  CharPtr     suffix;
  Char        suffixVal [80];

  if (txt == NULL) return NULL;
  nsp = NameStdNew ();
  if (nsp == NULL) return NULL;
  nsp->names [0] = ExtractTagListColumn (txt, 2);
  TrimLeadingSpaces (nsp->names [0]);
  first = ExtractTagListColumn (txt, 0);
  StripPeriods (first);
  nsp->names [1] = StringSave (first);
  TrimLeadingSpaces (nsp->names [1]);
  FirstNameToInitials (first, str1, sizeof (str1) - 1);
  str = ExtractTagListColumn (txt, 1);
  StringNCat (str1, str, sizeof (str1) - 1);
  MemFree (str);
  j = 0;
  k = 0;
  ch = str1 [j];
  while (ch != '\0') {
    if (ch != ' ') {
      initials [k] = ch;
      k++;
    }
    j++;
    ch = str1 [j];
  }
  initials [k] = '\0';
  periods [0] = '\0';
          j = 0;
          ch = initials [j];
          while (ch != '\0') {
            if (ch == ',') {
              initials [j] = '.';
            }
            j++;
            ch = initials [j];
          }
          str = StringStr (initials, ".ST.");
          if (str != NULL) {
            *(str + 2) = 't';
          }
  j = 0;
  k = 0;
  ch = initials [j];
  while (ch != '\0') {
    if (ch == '-') {
      periods [k] = ch;
      k++;
      j++;
      ch = initials [j];
    } else if (ch == '.') {
      j++;
      ch = initials [j];
            } else if (ch == ' ') {
              j++;
              ch = initials [j];
    } else {
      periods [k] = ch;
              last = ch;
      k++;
      j++;
      ch = initials [j];
              if (ch == '\0') {
                if (! (IS_LOWER (last))) {
                  periods [k] = '.';
                  k++;
                }
              /* } else if (ch == '.' && initials [j + 1] == '\0') { */
              } else if (! (IS_LOWER (ch))) {
                periods [k] = '.';
                k++;
              }
    }
  }
  periods [k] = '\0';
  nsp->names [4] = StringSave (periods);
  TrimLeadingSpaces (nsp->names [4]);
  str = ExtractTagListColumn (txt, 3);
  StringNCpy_0 (str1, str, sizeof (str1));
  MemFree (str);
  j = 0;
  k = 0;
  ch = str1 [j];
  while (ch != '\0') {
    if (ch != ' ') {
      suffixVal [k] = ch;
      k++;
    }
    j++;
    ch = str1 [j];
  }
  suffixVal [k] = '\0';
  if (suffixVal [0] != '\0') {
    suffix = GetEnumName (atoi(suffixVal), name_suffix_alist);
    nsp->names [5] = StringSave (suffix);
    TrimLeadingSpaces (nsp->names [5]);
    if (StringHasNoText (nsp->names [5])) {
      nsp->names [5] = MemFree (nsp->names [5]);
    }
  }
  if (StringCmp (nsp->names [0], "et al") == 0) {
    nsp->names [0] = MemFree (nsp->names [0]);
    nsp->names [0] = StringSave ("et al.");
  }
  nsp->names [2] = ExtractTagListColumn (txt, 4);
  TrimLeadingSpaces (nsp->names [2]);
  if (StringHasNoText (nsp->names [2])) {
    nsp->names [2] = MemFree (nsp->names [2]);
  }
  return nsp;
}

static Pointer AuthorDialogToStdAuthListPtr (DialoG d)

{
  AuthListPtr  alp;
  AuthorPtr    ap;
  Char         ch;
  Int2         j;
  Int2         len;
  ValNodePtr   names;
  NameStdPtr   nsp;
  Boolean      okay;
  PersonIdPtr  pid;
  CharPtr      str;
  TagListPtr   tlp;
  ValNodePtr   vnp;

  alp = NULL;
  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp != NULL && tlp->vnp != NULL) {
    alp = AuthListNew ();
    if (alp != NULL) {
      alp->choice = 1;
      names = NULL;
      for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
        str = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 2);
        okay = FALSE;
        len = StringLen (str);
        for (j = 0; j < len; j++) {
          ch = str [j];
          if (ch != ' ' && ch != '\t' && ch != '\n') {
            okay = TRUE;
          }
        }
        MemFree (str);
        if (okay) {
          names = ValNodeNew (names);
          if (alp->names == NULL) {
            alp->names = names;
          }
          if (names != NULL) {
            ap = AuthorNew ();
            names->choice = 1;
            names->data.ptrvalue = ap;
            if (ap != NULL) {
              pid = PersonIdNew ();
              ap->name = pid;
              if (pid != NULL) {
                pid->choice = 2;
                nsp = AuthorSpreadsheetStringToNameStdPtr ((CharPtr) vnp->data.ptrvalue);
                pid->data = nsp;
              }
            }
          }
        }
      }
      if (alp->names == NULL) {
        alp = AuthListFree (alp);
      }
    }
  }
  return (Pointer) alp;
}

static void StrAuthListPtrToAuthorDialog (DialoG d, Pointer data)

{
  AuthListPtr  alp;
  ValNodePtr   head;
  Int2         j;
  ValNodePtr   names;
  Char         str [128];
  TagListPtr   tlp;
  ValNodePtr   vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  alp = (AuthListPtr) data;
  if (tlp != NULL) {
    head = NULL;
    if (alp != NULL) {
      if (alp->choice == 2 || alp->choice == 3) {
        names = alp->names;
        while (names != NULL) {
          StringNCpy_0 (str, names->data.ptrvalue, sizeof (str) - 2);
          StringCat (str, "\n");
          vnp = ValNodeNew (head);
          if (head == NULL) {
            head = vnp;
          }
          if (vnp != NULL) {
            vnp->data.ptrvalue = StringSave (str);
          }
          names = names->next;
        }
      } else {
        Message (MSG_ERROR, "Unable to handle author type %d", (int) alp->choice);
      }
    }
    SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
    tlp->vnp = head;
    SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
    for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
    }
    tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
    CorrectBarMax (tlp->bar, tlp->max);
    CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
  }
}

static Pointer AuthorDialogToStrAuthListPtr (DialoG d)

{
  AuthListPtr  alp;
  Char         ch;
  Int2         j;
  Int2         len;
  ValNodePtr   names;
  Boolean      okay;
  CharPtr      str;
  TagListPtr   tlp;
  ValNodePtr   vnp;

  alp = NULL;
  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp != NULL && tlp->vnp != NULL) {
    alp = AuthListNew ();
    if (alp != NULL) {
      alp->choice = 2;
      names = NULL;
      for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
        str = (CharPtr) vnp->data.ptrvalue;
        okay = FALSE;
        len = StringLen (str);
        for (j = 0; j < len; j++) {
          ch = str [j];
          if (ch != ' ' && ch != '\t' && ch != '\n') {
            okay = TRUE;
          }
        }
        if (okay) {
          names = ValNodeNew (names);
          if (alp->names == NULL) {
            alp->names = names;
          }
          if (names != NULL) {
            names->choice = 2;
            names->data.ptrvalue = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);

          }
        }
      }
      if (alp->names == NULL) {
        alp = AuthListFree (alp);
      }
    }
  }
  return (Pointer) alp;
}

static void AuthListPtrToAuthorDialog (DialoG d, Pointer data)

{
  AuthorDialogPtr  adp;
  AuthListPtr      alp;

  adp = (AuthorDialogPtr) GetObjectExtra (d);
  if (adp != NULL) {
    alp = (AuthListPtr) data;
    if (alp != NULL) {
      adp->type = alp->choice;
    }
    if (adp->type == 1) {
      Hide (adp->strGrp);
      Show (adp->stdGrp);
      PointerToDialog (adp->stdAuthor, data);
    } else if (adp->type == 2 || adp->type == 3) {
      Hide (adp->stdGrp);
      Show (adp->strGrp);
      PointerToDialog (adp->strAuthor, data);
    }
  }
}

static Pointer AuthorDialogToAuthListPtr (DialoG d)

{
  AuthorDialogPtr  adp;
  AuthListPtr      alp;

  adp = (AuthorDialogPtr) GetObjectExtra (d);
  alp = NULL;
  if (adp != NULL) {
    if (adp->type == 1) {
      alp = (AuthListPtr) DialogToPointer (adp->stdAuthor);
    } else if (adp->type == 2 || adp->type == 3) {
      if ((alp = (AuthListPtr) DialogToPointer (adp->strAuthor)) != NULL)
        alp->choice = adp->type;
    }
  }
  return (Pointer) alp;
}

static void AuthorDialogMessage (DialoG d, Int2 mssg)

{
  AuthorDialogPtr  adp;

  adp = (AuthorDialogPtr) GetObjectExtra (d);
  if (adp != NULL) {
    switch (mssg) {
      case VIB_MSG_ENTER :
        if (adp->type == 1) {
          SendMessageToDialog (adp->stdAuthor, VIB_MSG_ENTER);
        } else if (adp->type == 2 || adp->type == 3) {
          SendMessageToDialog (adp->strAuthor, VIB_MSG_ENTER);
        }
        break;
      default :
        break;
    }
  }
}


static Boolean IsAuthList (CharPtr path)
{
  FILE *fp;
  Char buffer[10];
  Int4 len;
  Boolean rval = FALSE;

  fp = FileOpen (path, "r");
  if (fp == NULL) return FALSE;
  
  len = fread (buffer, 1, 9, fp);
  buffer[9] = 0;
  if (StringCmp (buffer, "Auth-list") == 0)
  {
    rval = TRUE;
  }
  FileClose (fp);
  return rval;
}


static NameStdPtr ReadNameFromString (CharPtr str, CharPtr PNTR next_name, BoolPtr found_special)
{
  CharPtr cp_end, cp_space;
  NameStdPtr n;

  if (StringHasNoText (str)) 
  {
    if (next_name != NULL)
    {
      *next_name = NULL;
    }
    return NULL;
  }

  /* skip over any leading spaces */
  str += StringSpn (str, " \t");

  /* skip over "and" if found */
  if (StringNCmp (str, "and ", 4) == 0)
  {
    str += 4;
  }
  if (StringHasNoText (str)) return NULL;

  cp_end = StringChr (str, ',');
  if (cp_end != NULL)
  {
    *cp_end = 0;
    if (next_name != NULL)
    {
      if (StringHasNoText (cp_end + 1))
      {
        *next_name = NULL;
      }
      else
      {
        *next_name = cp_end + 1;
      }
    }
  }
  else if (next_name != NULL)
  {
    *next_name = NULL;
  }

  n = NameStdNew ();  
  /* look for elements in name */
  cp_space = StringRChr (str, ' ');
  if (cp_space == NULL)
  {
    n->names[0] = StringSave (str);
  }
  else
  {
    n->names[0] = StringSave (cp_space + 1);
    while (isspace (*cp_space))
    {
      cp_space--;
    }
    *(cp_space + 1) = 0;
    cp_space = StringChr (str, ' ');
    if (cp_space == NULL)
    {
       n->names[1] = StringSave (str);
       n->names[4] = (CharPtr) MemNew (sizeof (Char) * 3);
       sprintf (n->names[4], "%c.", *(n->names[1]));
    }
    else
    {
      *(cp_space) = 0;
      n->names[1] = StringSave (str);

      cp_space++;
      while (isspace (*cp_space))
      {
        cp_space++;
      }
      
      n->names[4] = (CharPtr) MemNew (sizeof (Char) * (4 + StringLen (cp_space)));
      sprintf (n->names[4], "%c.%s.", *(n->names[1]), cp_space);
    }
    SpecialCharReplace (&(n->names[1]), NULL, found_special, NULL);
    SpecialCharReplace (&(n->names[4]), NULL, found_special, NULL);
  }
  SpecialCharReplace (&(n->names[0]), NULL, found_special, NULL);

  return n;
}


static Boolean EndsWithComma (CharPtr str)
{
  CharPtr cp;

  if (StringHasNoText (str)) return FALSE;
  cp = str + StringLen (str) - 1;
  while (isspace (*cp) && cp > str)
  {
    cp--;
  }
  if (*cp == ',')
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}


static CharPtr ExtendToComma (CharPtr line, ReadBufferPtr rbp)
{
  CharPtr next_line = NULL, tmp;
  Boolean end_of_file = FALSE;

  while (!EndsWithComma (line) && !end_of_file)
  {
    next_line = AbstractReadFunction (rbp);
    if (next_line == NULL)
    {
      end_of_file = TRUE;
    }
    else if (StringHasNoText (next_line))
    {
      next_line = MemFree (next_line);
    }
    else if (StringHasNoText (line))
    {
      line = MemFree (line);
      line = next_line;
    }
    else
    {
      tmp = (CharPtr) MemNew (sizeof (Char) * (StringLen (line) + StringLen (next_line) + 2));
      sprintf (tmp, "%s %s", line, next_line);
      next_line = MemFree (next_line);
      line = MemFree (line);
      line = tmp;
    }
  }
  return line;
}


static AuthListPtr ReadAuthorListFromTextFile (CharPtr path)
{
  ReadBufferData rbd;
  CharPtr        line;
  AuthListPtr    alp = NULL;
  AuthorPtr      ap;
  CharPtr        cp, next_cp;
  NameStdPtr     n;
  ValNodePtr     names = NULL;
  Boolean        found_special = FALSE;

  rbd.fp = FileOpen (path, "r");
  if (rbd.fp == NULL) return FALSE;
  rbd.current_data = NULL;

  line = ExtendToComma (NULL, &rbd);
  while (line != NULL) 
  {
    cp = line;
    next_cp = NULL;
    while (cp != NULL)
    {
      n = ReadNameFromString (cp, &next_cp, &found_special);
      if (n != NULL)
      {
        ap = AuthorNew ();
        ap->name = PersonIdNew ();
        ap->name->choice = 2;
        ap->name->data = n;
        ValNodeAddPointer (&names, 0, ap);
      }
      cp = next_cp;
    }
    line = MemFree (line);
    line = ExtendToComma (NULL, &rbd);
  }
  if (names != NULL)
  {
    alp = AuthListNew ();
    alp->choice = 1;
    alp->names = names;
  }
  if (found_special)
  {
    Message (MSG_OK, "Special characters in names were converted");
  }
  
  FileClose (rbd.fp);
  return alp;
}


static Boolean ReadAuthorDialog (DialoG d, CharPtr filename)

{
  AuthorDialogPtr  adp;
  AsnIoPtr         aip;
  AuthListPtr      alp = NULL;
  Char             path [PATH_MAX];

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  adp = (AuthorDialogPtr) GetObjectExtra (d);
  if (adp != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      if (IsAuthList (path)) {
        aip = AsnIoOpen (path, "r");
        if (aip != NULL) {
          alp = AuthListAsnRead (aip, NULL);
          AsnIoClose (aip);
        }
      } else {
        alp = ReadAuthorListFromTextFile (path);
      }
      if (alp != NULL) {
        PointerToDialog (adp->dialog, (Pointer) alp);
        alp = AuthListFree (alp);
        Update ();
        return TRUE;
      }
    }
  }
  return FALSE;
}

static Boolean WriteAuthorDialog (DialoG d, CharPtr filename)

{
  AuthorDialogPtr  adp;
  AsnIoPtr         aip;
  AuthListPtr      alp;
  Char             path [PATH_MAX];
#ifdef WIN_MAC
  FILE            *f;
#endif

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


static void InsertFirstAuthor (ButtoN b)
{
  AuthorDialogPtr  adp;
  AuthListPtr      alp;
  AuthorPtr        ap;
  NameStdPtr       nsp;
  ValNodePtr       vnp;

  adp = (AuthorDialogPtr) GetObjectExtra (b);
  if (adp == NULL) {
    return;
  }

  alp = DialogToPointer (adp->dialog);
  if (alp != NULL && alp->names != NULL) {
    vnp = ValNodeNew (NULL);
    vnp->next = alp->names;
    alp->names = vnp;
    if (alp->choice == 1) {
      ap = AuthorNew ();
      ap->name = PersonIdNew ();
      ap->name->choice = 2;
      nsp = NameStdNew ();
      ap->name->data = nsp;
      vnp->data.ptrvalue = ap;
    } else {
      vnp->data.ptrvalue = StringSave ("");
    }
    PointerToDialog (adp->dialog, alp);
    SendMessageToDialog (adp->dialog, VIB_MSG_ENTER);
  }
  alp = AuthListFree (alp);
}


static void RemoveTabAndCrFromContextList (ValNodePtr PNTR find_list)
{
  ValNodePtr vnp, prev = NULL, vnp_next;

  if (find_list == NULL) {
    return;
  }
  for (vnp = *find_list; vnp != NULL; vnp = vnp_next) {
    vnp_next = vnp->next;
    if (vnp->choice == '\t' || vnp->choice == '\n') {
      if (prev == NULL) {
        *find_list = vnp_next;
      } else{
        prev->next = vnp_next;
      }
      vnp->next = NULL;
      vnp = FreeContextList (vnp);
    } else {
      prev = vnp;
    }
  }
}

  
static ValNodePtr TestAuthorDialog (DialoG d)
{
  AuthorDialogPtr  adp;
  ValNodePtr       find_list = NULL, vnp;
  TagListPtr       tlp;
  Boolean          did_find = FALSE;
  Boolean          did_change = FALSE;
  CharPtr PNTR     vals;
  Int4             i;
  AuthListPtr      alp;

  adp = (AuthorDialogPtr)GetObjectExtra (d);
  if (adp == NULL) {
    return NULL;
  }
  tlp = (TagListPtr) GetObjectExtra (adp->stdAuthor);
  if (tlp == NULL || tlp->vnp == NULL) {
    return NULL;
  }
  vals = (CharPtr PNTR) MemNew (sizeof (CharPtr) * ValNodeLen (tlp->vnp));
  for (vnp = tlp->vnp, i = 0; vnp != NULL; vnp = vnp->next, i++) {
    vals[i] = (CharPtr) vnp->data.ptrvalue;
    SpecialCharFindWithContext (vals + i, &find_list, &did_find, &did_change);
  }

  RemoveTabAndCrFromContextList (&find_list);
  if (find_list != NULL && FixSpecialCharactersForStringsInList (find_list,
              "The author names contain special characters. You must replace them.",
              TRUE)) {
    for (vnp = tlp->vnp, i = 0; vnp != NULL; vnp = vnp->next, i++) {
      vnp->data.ptrvalue = vals[i];
    }
    alp = DialogToPointer (adp->stdAuthor);
    PointerToDialog (adp->stdAuthor, alp);
    alp = AuthListFree (alp);
  }
  vals = MemFree (vals);

  find_list = FreeContextList (find_list);
  return NULL;
}


static void ImportAuthorsBtn (ButtoN b)
{
  AuthorDialogPtr dlg;
  Char              path [PATH_MAX];

  dlg = (AuthorDialogPtr) GetObjectExtra (b);
  if (dlg == NULL) {
    return;
  }
  path[0] = '\0';
  if (GetInputFileName (path, sizeof (path), "", "TEXT")) {
    ImportDialog (dlg->dialog, path);
    if (dlg->change_notify != NULL) {
      (dlg->change_notify)(dlg->change_data);
    }
  }
}


static void AuthorNotify (Pointer data)
{
  AuthorDialogPtr adp;

  adp = (AuthorDialogPtr) data;
  if (adp != NULL && adp->change_notify != NULL) {
    (adp->change_notify)(adp->change_data);
  }
}


static TaglistCallback author_callback_list[4] = 
 { AuthorNotify, AuthorNotify, AuthorNotify, AuthorNotify };


extern DialoG CreateAuthorDialogEx (GrouP prnt, Uint2 rows, Int2 spacing, Boolean use_import_btn, Nlm_ChangeNotifyProc change_notify, Pointer change_data)

{
  AuthorDialogPtr  adp;
  GrouP            k;
  GrouP            p, list_grp, btn_grp;
  PrompT           p1, p2, p3, p4, p5;
  TagListPtr       tlp;
  ButtoN           b;

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

  adp = (AuthorDialogPtr) MemNew (sizeof (AuthorDialog));
  if (adp != NULL) {

    SetObjectExtra (p, adp, StdCleanupExtraProc);
    adp->dialog = (DialoG) p;
    adp->todialog = AuthListPtrToAuthorDialog;
    adp->fromdialog = AuthorDialogToAuthListPtr;
    adp->testdialog = TestAuthorDialog;
    adp->importdialog = ReadAuthorDialog;
    adp->exportdialog = WriteAuthorDialog;
    adp->dialogmessage = AuthorDialogMessage;
    adp->change_notify = change_notify;
    adp->change_data = change_data;

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

    adp->strGrp = HiddenGroup (list_grp, -1, 0, NULL);
    SetGroupSpacing (adp->strGrp, 3, 2);

    k = HiddenGroup (adp->strGrp, -4, 0, NULL);
    SetGroupSpacing (k, spacing, spacing);
    p1 = StaticPrompt (k, "Name", 0, 0, programFont, 'c');

    adp->strAuthor = CreateTagListDialogExEx (adp->strGrp, rows, 1, spacing,
                                          author_types, str_author_widths, NULL,
                                          TRUE, FALSE, 
                                          StrAuthListPtrToAuthorDialog,
                                          AuthorDialogToStrAuthListPtr,
                                          author_callback_list, adp, FALSE);
    Hide (adp->strGrp);

    adp->stdGrp = HiddenGroup (list_grp, -1, 0, NULL);
    SetGroupSpacing (adp->stdGrp, 3, 2);

    k = HiddenGroup (adp->stdGrp, -4, 0, NULL);
    SetGroupSpacing (k, spacing, spacing);
    p2 = StaticPrompt (k, "First Name", 0, 0, programFont, 'c');
    p3 = StaticPrompt (k, "M.I.", 0, 0, programFont, 'c');
    p4 = StaticPrompt (k, "Last Name", 0, 0, programFont, 'c');
    p5 = StaticPrompt (k, "Sfx", 0, 0, programFont, 'c');

    adp->stdAuthor = CreateTagListDialogEx3 (adp->stdGrp, rows, 4, spacing,
                                             author_types, std_author_widths,
                                             author_popups,
                                             TRUE, FALSE,
                                             StdAuthListPtrToAuthorDialog,
                                             AuthorDialogToStdAuthListPtr,
                                             author_callback_list, adp, FALSE, TRUE);
    adp->type = 1;

    tlp = (TagListPtr) GetObjectExtra (adp->strAuthor);
    if (tlp != NULL) {
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [0], (HANDLE) p1, NULL);
    }
    tlp = (TagListPtr) GetObjectExtra (adp->stdAuthor);
    if (tlp != NULL) {
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [0], (HANDLE) p2, NULL);
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [1], (HANDLE) p3, NULL);
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [2], (HANDLE) p4, NULL);
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [3], (HANDLE) p5, NULL);
    }

    btn_grp = HiddenGroup (p, 2, 0, NULL);
    SetGroupSpacing (btn_grp, 10, 10);
    b = PushButton (btn_grp, "Insert First Author", InsertFirstAuthor);
    SetObjectExtra (b, adp, NULL);
    if (use_import_btn) {
      b = PushButton (btn_grp, "Import Authors", ImportAuthorsBtn);
      SetObjectExtra (b, adp, NULL);
    }
    AlignObjects (ALIGN_CENTER, (HANDLE) list_grp, (HANDLE) btn_grp, NULL);
  }

  return (DialoG) p;
}


extern DialoG CreateAuthorDialog (GrouP prnt, Uint2 rows, Int2 spacing)

{
  return CreateAuthorDialogEx (prnt, rows, spacing, FALSE, NULL, NULL);

}


typedef struct affildialog {
  DIALOG_MESSAGE_BLOCK
  TexT            affil;
  TexT            div;
  TexT            address;
  TexT            city;
  TexT            state;
  TexT            zip;
  TexT            country;
  TexT            phone;
  TexT            fax;
  TexT            email;
} AffilDialog, PNTR AffilDialogPtr;

static void AffilPtrToAffilDialog (DialoG d, Pointer data)

{
  AffilDialogPtr  adp;
  AffilPtr        ap;

  adp = (AffilDialogPtr) GetObjectExtra (d);
  ap = (AffilPtr) data;
  if (adp != NULL) {
    if (ap != NULL) {
      SafeSetTitle (adp->affil, ap->affil);
      SafeSetTitle (adp->div, ap->div);
      SafeSetTitle (adp->address, ap->street);
      SafeSetTitle (adp->city, ap->city);
      SafeSetTitle (adp->state, ap->sub);
      SafeSetTitle (adp->zip, ap->postal_code);
      SafeSetTitle (adp->country, ap->country);
      SafeSetTitle (adp->phone, ap->phone);
      SafeSetTitle (adp->fax, ap->fax);
      SafeSetTitle (adp->email, ap->email);
    } else {
      SafeSetTitle (adp->affil, "");
      SafeSetTitle (adp->div, "");
      SafeSetTitle (adp->address, "");
      SafeSetTitle (adp->city, "");
      SafeSetTitle (adp->state, "");
      SafeSetTitle (adp->zip, "");
      SafeSetTitle (adp->country, "");
      SafeSetTitle (adp->phone, "");
      SafeSetTitle (adp->fax, "");
      SafeSetTitle (adp->email, "");
    }
  }
}

static Pointer AffilDialogToAffilPtr (DialoG d)

{
  AffilDialogPtr  adp;
  AffilPtr        ap;

  ap = NULL;
  adp = (AffilDialogPtr) GetObjectExtra (d);
  if (adp != NULL) {
    ap = AffilNew ();
    if (ap != NULL) {
      ap->affil = SaveStringFromText (adp->affil);
      ap->div = SaveStringFromText (adp->div);
      ap->street = SaveStringFromText (adp->address);
      ap->city = SaveStringFromText (adp->city);
      ap->sub = SaveStringFromText (adp->state);
      ap->postal_code = SaveStringFromText (adp->zip);
      ap->country = SaveStringFromText (adp->country);
      ap->phone = SaveStringFromText (adp->phone);
      ap->fax = SaveStringFromText (adp->fax);
      ap->email = SaveStringFromText (adp->email);
      if (ap->div == NULL && ap->street == NULL && ap->city == NULL &&
           ap->sub == NULL && ap->postal_code == NULL && ap->country == NULL &&
           ap->phone == NULL && ap->fax == NULL && ap->email == NULL) {
        ap->choice = 1;
        if (ap->affil == NULL) {
          ap = AffilFree (ap);
        }
      } else {
        ap->choice = 2;
      }
    }
  }
  return (Pointer) ap;
}

static void AffilDialogMessage (DialoG d, Int2 mssg)

{
  AffilDialogPtr  adp;

  adp = (AffilDialogPtr) GetObjectExtra (d);
  if (adp != NULL) {
    switch (mssg) {
      case VIB_MSG_ENTER :
        Select (adp->affil);
        break;
      default :
        break;
    }
  }
}

static DialoG CreateAnAffilDialog (GrouP prnt, CharPtr title,
                                   Boolean publisher,
                                   Boolean split,
                                   Boolean proceedings,
                                   GrouP PNTR grp1, GrouP PNTR grp2)

{
  AffilDialogPtr  adp;
  GrouP           g;
  GrouP           g1, g2;
  GrouP           j;
  GrouP           m;
  GrouP           p;
  GrouP           q;
  GrouP           s;
#ifdef WIN_MAC
  Int2            wid = 20;
  Int2            zipw = 6;
  Int2            ewid = 8;
#else
  Int2            wid = 30;
  Int2            zipw = 10;
  Int2            ewid = 20;
#endif

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

  adp = (AffilDialogPtr) MemNew (sizeof (AffilDialog));
  if (adp != NULL) {

    SetObjectExtra (p, adp, StdCleanupExtraProc);
    adp->dialog = (DialoG) p;
    adp->todialog = AffilPtrToAffilDialog;
    adp->fromdialog = AffilDialogToAffilPtr;
    adp->testdialog = NULL;
    adp->dialogmessage = AffilDialogMessage;

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

    q = HiddenGroup (m, 2, 0, NULL);
    g1 = q;
    g2 = NULL;
    if (grp1 != NULL) {
      *grp1 = q;
    }
    if (grp2 != NULL) {
      *grp2 = NULL;
    }
    g = HiddenGroup (q, 0, 20, NULL);
    if (publisher) {
      StaticPrompt (g, "Publisher", 0, dialogTextHeight, programFont, 'l');
    } else if (proceedings) {
      StaticPrompt (g, "Location", 0, dialogTextHeight, programFont, 'l');
    } else {
      StaticPrompt (g, "Institution", 0, dialogTextHeight, programFont, 'l');
      StaticPrompt (g, "Department", 0, dialogTextHeight, programFont, 'l');
    }
    StaticPrompt (g, "Street Address", 0, dialogTextHeight, programFont, 'l');
    StaticPrompt (g, "City", 0, dialogTextHeight, programFont, 'l');
    StaticPrompt (g, "State/Province", 0, dialogTextHeight, programFont, 'l');
    StaticPrompt (g, "Country", 0, dialogTextHeight, programFont, 'l');
    if (! split) {
      if (! proceedings) {
        StaticPrompt (g, "", 0, stdLineHeight, programFont, 'l');
        StaticPrompt (g, "Phone", 0, dialogTextHeight, programFont, 'l');
        if (publisher) {
          StaticPrompt (g, "Internet Access", 0, dialogTextHeight, programFont, 'l');
        } else {
          StaticPrompt (g, "Email", 0, dialogTextHeight, programFont, 'l');
        }
      }
    }
    j = HiddenGroup (q, 0, 20, NULL);
    g = HiddenGroup (j, 0, 20, NULL);
    adp->affil = DialogText (g, "", wid, NULL);
    adp->div = NULL;
    if (! publisher && ! proceedings) {
      adp->div = DialogText (g, "", wid, NULL);
    }
    adp->address = DialogText (g, "", wid, NULL);
    adp->city = DialogText (g, "", wid, NULL);
    g = HiddenGroup (j, 3, 0, NULL);
    SetGroupSpacing (g, 20, 2);
    adp->state = DialogText (g, "", 10, NULL);
    if (! proceedings) {
      /* StaticPrompt (g, "Zip/Postal Code", 7 * stdCharWidth,
                    dialogTextHeight, programFont, 'l'); */
      StaticPrompt (g, "Zip/Postal Code", 0, dialogTextHeight, programFont, 'l');
      adp->zip = DialogText (g, "", zipw, NULL);
    }
    g = HiddenGroup (j, 0, 20, NULL);
    adp->country = DialogText (g, "", wid, NULL);
    if (split) {
      if (! proceedings) {
        q = HiddenGroup (m, 2, 0, NULL);
        g2 = q;
        if (grp2 != NULL) {
          *grp2 = q;
        }
        g = HiddenGroup (q, 0, 20, NULL);
        StaticPrompt (g, "", 0, stdLineHeight, programFont, 'l');
        StaticPrompt (g, "Phone", 0, dialogTextHeight, programFont, 'l');
        if (publisher) {
          StaticPrompt (g, "Internet Access", 0, dialogTextHeight, programFont, 'l');
        } else {
          StaticPrompt (g, "Email", 0, dialogTextHeight, programFont, 'l');
        }
        j = HiddenGroup (q, 0, 20, NULL);
      }
    }
    if (! proceedings) {
      StaticPrompt (j, "Please include country code for non-U.S. phone numbers.",
                    0, stdLineHeight, programFont, 'l');
      g = HiddenGroup (j, 3, 0, NULL);
      SetGroupSpacing (g, 20, 2);
      adp->phone = DialogText (g, "", 10, NULL);
      if (split) {
        StaticPrompt (g, "Fax", 0,
                      dialogTextHeight, programFont, 'l');
      } else {
        StaticPrompt (g, "Fax", /* 7 * stdCharWidth */ 0,
                      dialogTextHeight, programFont, 'l');
      }
      adp->fax = DialogText (g, "", 10, NULL);
      g = HiddenGroup (j, 0, 20, NULL);
      if (split) {
        adp->email = DialogText (g, "", ewid, NULL);
      } else {
        adp->email = DialogText (g, "", ewid, NULL);
      }
    }

    if (split) {
      AlignObjects (ALIGN_RIGHT, (HANDLE) adp->affil,
                    (HANDLE) adp->address, (HANDLE) adp->city,
                    (HANDLE) adp->zip, (HANDLE) adp->country,
                    (HANDLE) adp->div, NULL);
      AlignObjects (ALIGN_RIGHT, (HANDLE) adp->fax, (HANDLE) adp->email, NULL);
      AlignObjects (ALIGN_CENTER, (HANDLE) g1, (HANDLE) g2, NULL);
      Hide (g1);
      Hide (g2);
    } else {
      AlignObjects (ALIGN_RIGHT, (HANDLE) adp->affil,
                    (HANDLE) adp->address, (HANDLE) adp->city,
                    (HANDLE) adp->zip, (HANDLE) adp->country,
                    (HANDLE) adp->fax, (HANDLE) adp->email,
                    (HANDLE) adp->div, NULL);
    }
  }

  return (DialoG) p;
}

extern DialoG CreateAffilDialog (GrouP prnt, CharPtr title)

{
  return CreateAnAffilDialog (prnt, title, FALSE, FALSE, FALSE, NULL, NULL);
}

extern DialoG CreatePublisherAffilDialog (GrouP prnt, CharPtr title)

{
  return CreateAnAffilDialog (prnt, title, TRUE, FALSE, FALSE, NULL, NULL);
}

extern DialoG CreateProceedingsDialog (GrouP prnt, CharPtr title)

{
  return CreateAnAffilDialog (prnt, title, FALSE, FALSE, TRUE, NULL, NULL);
}

extern DialoG CreateExtAffilDialog (GrouP prnt, CharPtr title, GrouP PNTR grp1, GrouP PNTR grp2)

{
  return CreateAnAffilDialog (prnt, title, FALSE, TRUE, FALSE, grp1, grp2);
}

extern DialoG CreateExtPublisherAffilDialog (GrouP prnt, CharPtr title, GrouP PNTR grp1, GrouP PNTR grp2)

{
  return CreateAnAffilDialog (prnt, title, TRUE, TRUE, FALSE, grp1, grp2);
}

extern DialoG CreateExtProceedingsDialog (GrouP prnt, CharPtr title, GrouP PNTR grp1, GrouP PNTR grp2)

{
  return CreateAnAffilDialog (prnt, title, FALSE, TRUE, TRUE, grp1, grp2);
}

extern void DatePtrToVibrant (DatePtr dp, PopuP dateMonth, TexT dateDay, TexT dateYear)

{
  Int2  day;
  Char  str [32];
  Int2  year;

  if (dp != NULL) {
    if (dp->data [0] == 0) {
      DatePrint (dp, str);
    } else if (dp->data [0] == 1) {
      SetEnumPopup (dateMonth, months_alist, (UIEnum) dp->data [2]);
      day = (Int2) dp->data [3];
      if (day > 0 && day <= 31) {
        sprintf (str, "%d", (int) day);
        SafeSetTitle (dateDay, str);
      } else {
        SafeSetTitle (dateDay, "");
      }
      year = (Int2) dp->data [1];
      if (year > 0) {
        sprintf (str, "%d", (int) (year + 1900));
        SafeSetTitle (dateYear, str);
      } else {
        SafeSetTitle (dateYear, "");
      }
    } else {
      Message (MSG_ERROR, "Unknown date type");
    }
  } else {
    SafeSetValue (dateMonth, 1);
    SafeSetTitle (dateDay, "");
    SafeSetTitle (dateYear, "");
  }
}

extern DatePtr VibrantToDatePtr (PopuP dateMonth, TexT dateDay, TexT dateYear)

{
  Int2     day;
  Int2     dateType;
  DatePtr  dp;
  UIEnum   month;
  Char     str [32];
  Int2     year;

  dp = DateNew ();
  if (dp != NULL) {
    dateType = 1;
    dp->data [0] = (Uint1) dateType;
    if (dateType == 0) {
    } else if (dateType == 1) {
      GetTitle (dateYear, str, sizeof (str));
      if (! StringHasNoText (str)) {
        StrToInt (str, &year);
        if (year >= 1900) {
          dp->data [1] = (Uint1) (year - 1900);
        } else {
          /* dp->data [1] = 0; */
          dp = DateFree (dp);
          return dp;
        }
        if (GetEnumPopup (dateMonth, months_alist, &month)) {
          dp->data [2] = (Uint1) month;
        }
        GetTitle (dateDay, str, sizeof (str));
        StrToInt (str, &day);
        dp->data [3] = (Uint1) day;
      } else {
        dp = DateFree (dp);
      }
    } else {
      Message (MSG_ERROR, "Unknown date type");
    }
  }
  return dp;
}

static void GBQualPtrToQualsDialog (DialoG d, Pointer data)

{
  ValNodePtr  head;
  Int2        j;
  size_t      len;
  GBQualPtr   list;
  CharPtr     str;
  TagListPtr  tlp;
  ValNodePtr  vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  list = (GBQualPtr) data;
  if (tlp != NULL) {
    head = NULL;
    while (list != NULL) {
      vnp = ValNodeNew (head);
      if (head == NULL) {
        head = vnp;
      }
      if (vnp != NULL) {
        if (list->qual != NULL && list->val != NULL) {
          len = StringLen (list->qual) + StringLen (list->val);
          str = MemNew (len + 4);
          if (str != NULL) {
            StringCpy (str, list->qual);
            StringCat (str, "\t");
            StringCat (str, list->val);
            StringCat (str, "\n");
          }
          vnp->data.ptrvalue = str;
        }
      }
      list = list->next;
    }
    SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
    tlp->vnp = head;
    SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
    for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
    }
    tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
    CorrectBarMax (tlp->bar, tlp->max);
    CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
  }
}

static Pointer QualsDialogToGBQualPtr (DialoG d)

{
  Char         ch;
  GBQualPtr    gbq;
  GBQualPtr    gbqlast;
  GBQualPtr    head;
  Int2         j;
  Int2         len;
  Boolean      okay;
  CharPtr      str;
  TagListPtr   tlp;
  ValNodePtr   vnp;

  head = NULL;
  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp != NULL && tlp->vnp != NULL) {
    gbq = NULL;
    gbqlast = NULL;
    for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
      str = (CharPtr) vnp->data.ptrvalue;
      okay = FALSE;
      len = StringLen (str);
      for (j = 0; j < len; j++) {
        ch = str [j];
        if (ch != ' ' && ch != '\t' && ch != '\n') {
          okay = TRUE;
        }
      }
      if (okay) {
        gbq = GBQualNew ();
        if (gbqlast == NULL) {
          head = gbq;
        } else {
          gbqlast->next = gbq;
        }
        gbqlast = gbq;
        if (gbq != NULL) {
          gbq->qual = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
          gbq->val = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 1);
        }
      }
    }
  }
  return (Pointer) head;
}

Uint2 gbqual_types [] = {
  TAGLIST_TEXT, TAGLIST_TEXT
};

Uint2 gbqual_widths [] = {
  0, 0, 0, 0
};

extern DialoG CreateQualsDialog (GrouP h, Uint2 rows, Int2 spacing,
                                 Int2 width1, Int2 width2);
extern DialoG CreateQualsDialog (GrouP h, Uint2 rows, Int2 spacing,
                                 Int2 width1, Int2 width2)

{
  gbqual_widths [0] = width1;
  gbqual_widths [1] = width2;
  return CreateTagListDialog (h, rows, 2, spacing,
                              gbqual_types, gbqual_widths, NULL,
                              GBQualPtrToQualsDialog,
                              QualsDialogToGBQualPtr);
}

extern void CreateSeqAlignLabel (SeqAlignPtr salp, CharPtr buf, Int4 buf_size)
{
  Int4     remaining_len, aln_pos, id_len;
  SeqIdPtr sip;
  CharPtr  buf_ptr;
  
  if (buf == NULL || buf_size < 1)
  {
    return;
  }
  MemSet (buf, 0, buf_size);
  if (salp == NULL)
  {
    return;
  }
  remaining_len = buf_size - 1;
  buf_ptr = buf;
  StringNCat (buf_ptr, "aln|", remaining_len);
  remaining_len -= 4;
  
  if (remaining_len <= 3)
  {
    return;
  }
  buf_ptr += 4;
  
  for (aln_pos = 1; aln_pos <= salp->dim && remaining_len > 2; aln_pos++)
  {
    sip = AlnMgr2GetNthSeqIdPtr(salp, aln_pos);
    SeqIdWrite (sip, buf_ptr, PRINTID_REPORT, remaining_len);
    id_len = StringLen (buf_ptr);
    remaining_len -= id_len;
    buf_ptr += id_len;
    /* put comma between IDs in list */
    if (aln_pos < salp->dim && remaining_len > 2)
    {
      StringCat (buf_ptr, ",");
      remaining_len -= 1;
      buf_ptr += 1;
    }
  }
  
  /* add ellipsis to indicate unshown IDs */
  if (remaining_len == 0 && aln_pos < salp->dim)
  {
    buf_ptr -= 3;
    StringCat (buf_ptr, "...");
  }
}


static Boolean HasDisqualifyingUserObjects(SeqAnnotPtr sap)
{
  AnnotDescrPtr desc;
  UserObjectPtr uop;
  ObjectIdPtr   oip;
  
  if (sap == NULL) {
    return FALSE;
  }
  
  desc = sap->desc;
  while (desc != NULL) {
    if (desc->choice == Annot_descr_user) {
      uop = (UserObjectPtr) desc->data.ptrvalue;
      while(uop) {
        if(uop->type) {
          oip = uop->type;
          if(StringCmp(oip->str, "Hist Seqalign") == 0
             || StringCmp (oip->str, "Blast Type") == 0) {
            return TRUE;
          }
        }
        uop = uop->next;
      }
    }
    desc = desc->next;
  }
  return FALSE;
}


extern void GetAlignmentsInSeqEntryCallback (SeqAnnotPtr sap, Pointer userdata)
{
  SeqAlignPtr PNTR salp_list;
  SeqAlignPtr salp, last_salp;
  
  if (sap == NULL || sap->type != 2 || userdata == NULL || HasDisqualifyingUserObjects(sap)) 
  {
    return;
  }
  salp_list = (SeqAlignPtr PNTR) userdata;
  salp = (SeqAlignPtr) sap->data;
  if (salp == NULL) return;
  if (salp->segtype == SAS_SPLICED || salp->segtype == SAS_SPARSE) return;
  salp = AlnMgr2DupAlnAndIndexes(salp);
  AlnMgr2IndexSeqAlign(salp);
  if (*salp_list == NULL)
  {
    *salp_list = salp; 
  }
  else
  {
    last_salp = *salp_list;
    while (last_salp->next != NULL)
    {
      last_salp = last_salp->next;
    }
    last_salp->next = salp;
  }
}

typedef struct intervalpage {
  DIALOG_MESSAGE_BLOCK
  DialoG             ivals;
  ButtoN             partial5;
  ButtoN             partial3;
  ButtoN             nullsBetween;
  Int2               count;
  SeqIdPtr           PNTR sip_list;
  EnumFieldAssoc     PNTR alist;
  EnumFieldAssocPtr  alists [5];
  Int4               PNTR lengths;
  Boolean            nucsOK;
  Boolean            protsOK;
  Boolean            showIdTags;
  FeatureFormPtr     ffp;
  IntEdPartialProc   proc;
  
  Int4               strand_col;  /* column for selecting strand.
                                   * 2 if nucsOK, -1 otherwise
                                   */
  Int4               seqid_col;   /* column for entering SeqIds,
                                   * which could be in a different place 
                                   * depending on nucsOK or -1 if not show_seqid
                                   */
  Int4               aln_col;     /* column for selecting alignments,
                                   * could be in a different place if not show_seqid
                                   * or -1 if not use_aln
                                   */
  Boolean            show_seqids; /* false when entering coordinates for an entire
                                   * alignments, true otherwise
                                   */
  
  /* for editing alignment intervals */
  SeqAlignPtr        PNTR salp_list; 
  TaglistCallback    PNTR callbacks;
  EnumFieldAssoc     PNTR aln_alist;
  Int2               aln_count;
  Int4               PNTR aln_lengths;
  Boolean            allow_nulls_in_list; /* some sequences may be all gap
                                           * in the specified interval -
                                           * if this value is FALSE, return
                                           * a NULL for the entire list,
                                           * otherwise allow NULLs to appear
                                           * in the list.
                                           */
  
} IntervalPage, PNTR IntervalPagePtr;

#define NUM_IVAL_ROWS  7
#define EXTRA_HEIGHT   2

static void ClearBspScratch (BioseqPtr bsp, Pointer userdata)

{
  if (bsp == NULL) return;
  bsp->idx.scratch = NULL;
}

static void AddToSipList (IntervalPagePtr ipp, BioseqPtr bsp)

{
  /*
  Int2      j;
  */
  SeqIdPtr  sip;

  if (ipp == NULL || bsp == NULL) return;
  if (bsp->idx.scratch != NULL) return;
  sip = SeqIdFindBest (bsp->id, 0);
  if (sip == NULL) return;
  /*
  for (j = 1; j <= ipp->count; j++) {
    if (SeqIdComp (sip, ipp->sip_list [j]) == SIC_YES) return;
  }
  */
  ipp->count++;
  ipp->sip_list [ipp->count] = SeqIdDup (sip);
  bsp->idx.scratch = (Pointer) bsp;
}

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

{
  BioseqPtr        bsp;
  IntervalPagePtr  ipp;
  SeqIdPtr         sip;
  SeqLocPtr        slp;
  ValNode          vn;

  if (sep != NULL && mydata != NULL && sep->choice == 1) {
    ipp = (IntervalPagePtr) mydata;
    bsp = (BioseqPtr) sep->data.ptrvalue;
    if (bsp != NULL) {
      if ((ipp->nucsOK && ISA_na (bsp->mol)) ||
          (ipp->protsOK && ISA_aa (bsp->mol))) {
        AddToSipList (ipp, bsp);
      }
      if (bsp->repr == Seq_repr_seg && bsp->seq_ext != NULL) {
        vn.choice = SEQLOC_MIX;
        vn.next = NULL;
        vn.data.ptrvalue = bsp->seq_ext;
        slp = SeqLocFindNext (&vn, NULL);
        while (slp != NULL) {
          sip = SeqLocId (slp);
          if (sip != NULL) {
            bsp = BioseqFindCore (sip);
            if (bsp != NULL) {
              AddToSipList (ipp, bsp);
            } else {
              bsp = BioseqLockById (sip);
              if (bsp != NULL) {
                AddToSipList (ipp, bsp);
                BioseqUnlock (bsp);
              }
            }
          }
          slp = SeqLocFindNext (&vn, slp);
        }
      }
    }
  }
}

static Int4 SegmentedEntryList (SeqEntryPtr sep, Pointer mydata,
                                SeqEntryFunc mycallback,
                                Int4 index, Int2 indent)

{
  BioseqPtr     bsp;
  BioseqSetPtr  bssp;
  SeqLocPtr     slp;
  ValNode       vn;

  if (sep == NULL) return index;
  if (IS_Bioseq (sep)) {
    if (mycallback != NULL)
      (*mycallback) (sep, mydata, index, indent);
    bsp = (BioseqPtr) sep->data.ptrvalue;
    if (bsp != NULL && bsp->repr == Seq_repr_seg && bsp->seq_ext != NULL) {
      vn.choice = SEQLOC_MIX;
      vn.next = NULL;
      vn.data.ptrvalue = bsp->seq_ext;
      slp = SeqLocFindNext (&vn, NULL);
      while (slp != NULL) {
        index++;
        slp = SeqLocFindNext (&vn, slp);
      }
    }
    return index + 1;
  }
  /*
  if (Bioseq_set_class (sep) == 4) return index;
  index++;
  */
  bssp = (BioseqSetPtr) sep->data.ptrvalue;
  sep = bssp->seq_set;
  indent++;
  while (sep != NULL) {
    index = SegmentedEntryList (sep, mydata, mycallback, index, indent);
    sep = sep->next;
  }
  return index;
}

#define SegmentedEntryCount( a )  SegmentedEntryList( a ,NULL,NULL,0,0);

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

static Boolean IsSeqIdInValNodeList (SeqIdPtr sip, ValNodePtr list)
{
  if (sip == NULL)
  {
    return FALSE;
  }
  while (list != NULL)
  {
    if (SeqIdComp (sip, list->data.ptrvalue) == SIC_YES)
    {
      return TRUE;
    }
    list = list->next;
  }
  return FALSE;
}

static Int4 FindLastStopForSeqId (SeqLocPtr slp, SeqIdPtr sip)
{
  Int4      last_stop = 0, new_stop;
  SeqLocPtr tmp_slp;
  SeqIdPtr  tmp_sip;
  
  tmp_slp = SeqLocFindNext (slp, NULL);
  while (tmp_slp != NULL)
  {
    tmp_sip = SeqLocId (tmp_slp);
    if (SeqIdComp (sip, tmp_sip) == SIC_YES)
    {
      new_stop = SeqLocStop (slp);
      if (new_stop > last_stop)
      {
        last_stop = new_stop;
      }
    }
    tmp_slp = SeqLocFindNext (slp, tmp_slp);
  }
  return last_stop;
}

extern void UpdateTagListPopupChoices (DialoG d, Int4 column);

/* We need to make sure that all IDs in the location are found in the enum list
 * for the interval editor ID Enum */
static void CorrectIntervalEditorSeqIdEnum (IntervalPagePtr ipp, SeqLocPtr slp)
{
  SeqLocPtr           tmp_slp;
  SeqIdPtr            sip;
  Int4                j = 0;
  Boolean             found;
  ValNodePtr          missing_list = NULL, missing_vnp;
  Int4                new_count;
  SeqIdPtr PNTR       new_sip_list;
  EnumFieldAssoc PNTR new_alist;
  Int4 PNTR           new_lengths;
  BioseqPtr           bsp;
  Char                str [128];
  CharPtr             ptr;
  
  if (ipp == NULL || slp == NULL)
  {
    return;
  }
  
  tmp_slp = SeqLocFindNext (slp, NULL);
  while (tmp_slp != NULL)
  {
    sip = SeqLocId (tmp_slp);
    if (!IsSeqIdInValNodeList (sip, missing_list))
    {
      found = FALSE;
      for (j = 1; j <= ipp->count && !found; j++)
      {
        if (SeqIdComp (sip, ipp->sip_list [j]) == SIC_YES)
        {
          found = TRUE;
        }
      }
      /* this process takes longer, so don't combine it with the above
       * loop
       */
      if (!found)
      {
        for (j = 1; j <= ipp->count && !found; j++)
        {
          bsp = BioseqFindCore (ipp->sip_list [j]);
          if (bsp == NULL) {
            bsp = BioseqLockById (ipp->sip_list [j]);
            BioseqUnlock (bsp);
          }
          if (bsp != NULL && SeqIdIn (sip, bsp->id))
          {
            found = TRUE;
          }
        }
      }
      if (!found)
      {
        ValNodeAddPointer (&missing_list, 0, sip);
      }
    }
    
    tmp_slp = SeqLocFindNext (slp, tmp_slp);
  }
  
  if (missing_list != NULL)
  {
    new_count = ipp->count + 4 + ValNodeLen (missing_list);
    new_sip_list = MemNew (sizeof (SeqIdPtr) * (size_t) new_count);
    new_alist = MemNew (sizeof (EnumFieldAssoc) * (size_t) new_count);
    new_lengths = MemNew (sizeof (Int4) * (size_t) new_count);
    if (ipp->sip_list != NULL) {
      /* first one is blank, remainder are actual data */
      for (j = 0; j < ipp->count + 1; j++)
      {
        new_sip_list [j] = ipp->sip_list [j];
        ipp->sip_list [j] = NULL;
        new_alist [j].name = ipp->alist [j].name;
        ipp->alist [j].name = NULL;
        new_alist [j].value = ipp->alist [j].value;
        new_lengths [j] = ipp->lengths [j];
      }
    }
    
    missing_vnp = missing_list;
    while (j < new_count - 1 && missing_vnp != NULL)
    {
      new_sip_list [j] = SeqIdDup (missing_vnp->data.ptrvalue);
      new_alist [j].value = j;
      
      sip = new_sip_list [j];
      bsp = BioseqFindCore (sip);
      if (bsp == NULL) {
        bsp = BioseqLockById (sip);
        BioseqUnlock (bsp);
      }
      if (bsp != NULL)
      {
        new_lengths [j] = bsp->length;
        sip = SeqIdFindWorst (bsp->id);
      }
      else
      {
        new_lengths [j] = FindLastStopForSeqId (slp, sip);
      }
      SeqIdWrite (sip, str, PRINTID_REPORT, sizeof (str));
      ptr = StringChr (str, '|');
      if (ptr == NULL) {
        ptr = str;
      } else {
        ptr++;
      }
      new_alist [j].name = StringSave (ptr);
      missing_vnp = missing_vnp->next;
      j++;
    }
    /* set terminator for enum list */
    new_alist [j].name = NULL;
    new_alist [j].value = (UIEnum) 0;

    ipp->sip_list = MemFree (ipp->sip_list);
    ipp->sip_list = new_sip_list;
    ipp->alist = MemFree (ipp->alist);
    ipp->alist = new_alist;
    ipp->lengths = MemFree (ipp->lengths);
    ipp->lengths = new_lengths;
    ipp->count = j;
    
    ipp->alists [ipp->seqid_col] = ipp->alist;
    UpdateTagListPopupChoices (ipp->ivals, ipp->seqid_col);
  }
  missing_list = ValNodeFree (missing_list);
}

static void 
BuildIntervalString 
(IntervalPagePtr ipp,
 CharPtr         fuzz_from_ch,
 Int4            start,
 CharPtr         fuzz_to_ch,
 Int4            stop,
 Uint2           strand,
 Int4            seq,
 Int4            salp_num,
 Boolean         isInterval,
 Boolean         isPoint,
 CharPtr         buf,
 Int4            buf_len)
{
  CharPtr cp;
  Boolean need_tab = FALSE;
  
  if (ipp == NULL || fuzz_from_ch == NULL || fuzz_to_ch == NULL || buf == NULL || buf_len == 0)
  {
    return;
  }
  
  MemSet (buf, 0, buf_len);  
  
  if (isInterval)
  {
    sprintf (buf, "%s%ld\t%s%ld",
             fuzz_from_ch, (long) (start + 1),
             fuzz_to_ch, (long) (stop + 1));
    need_tab = TRUE;
  }
  else if (isPoint)
  {
    sprintf (buf, "%ld%s\t%ld%s",
             (long) (start + 1), fuzz_from_ch,
             (long) (stop + 1), fuzz_to_ch);
    need_tab = TRUE;
  }
  
  cp = buf + StringLen (buf);
  if (ipp->strand_col > -1)
  {
    if (need_tab)
    {
      StringCat (cp, "\t");
      cp++;
    }
    sprintf (cp, "%d", (int) strand);
    need_tab = TRUE;
    cp += StringLen (cp);
  }
  
  if (ipp->seqid_col > -1)
  {
    if (need_tab)
    {
      StringCat (cp, "\t");
      cp++;
    }
    sprintf (cp, "%d", (int) seq);
    need_tab = TRUE;
    cp += StringLen (cp);
  }
  
  if (ipp->aln_col > -1)
  {
    if (need_tab)
    {
      StringCat (cp, "\t");
      cp++;
    }
    sprintf (cp, "%d", (int) salp_num);
    need_tab = TRUE;
    cp += StringLen (cp);
  }
  StringCat (cp, "\n");
}

static void SeqLocPtrToIntervalPage (DialoG d, Pointer data)

{
  BioseqPtr        bsp;
  SeqLocPtr        firstSlp;
  Char             fuzz_from_ch [4];
  Char             fuzz_to_ch [4];
  ValNodePtr       head;
  SeqIdPtr         id;
  IntFuzzPtr       ifp;
  IntervalPagePtr  ipp;
  Boolean          isInterval;
  Boolean          isPoint;
  Int2             j;
  SeqLocPtr        lastSlp;
  SeqLocPtr        location;
  SeqLocPtr        next;
  SeqEntryPtr      oldscope;
  Boolean          partial5;
  Boolean          partial3;
  Int2             seq;
  SeqIntPtr        sip;
  SeqLocPtr        slp;
  SeqPntPtr        spp;
  Int4             start;
  Int4             stop;
  Char             str [255];
  Uint1            strand, aln_strand;
  TagListPtr       tlp;
  ValNodePtr       vnp;
  Int4             salp_num, salp_row;
  SeqIdPtr         tmp_sip, bsp_id;

  ipp = (IntervalPagePtr) GetObjectExtra (d);
  if (ipp == NULL) return;
  SafeSetStatus (ipp->partial5, FALSE);
  SafeSetStatus (ipp->partial3, FALSE);
  tlp = GetObjectExtra (ipp->ivals);
  if (tlp == NULL) return;

  location = (SeqLocPtr) data;
  partial5 = FALSE;
  partial3 = FALSE;
  head = NULL;

  if (location == NULL)
  {
      if (ipp->count == 1)
      {
       sprintf (str, "\t\t\t1\n");
       vnp = ValNodeNew (head);
       if (head == NULL) {
         head = vnp;
       }
       if (vnp != NULL) {
         vnp->data.ptrvalue = StringSave (str);
       }    
      }
  }
  if (location != NULL) {
    CorrectIntervalEditorSeqIdEnum (ipp, location);
    firstSlp = NULL;
    lastSlp = NULL;
    slp = SeqLocFindNext (location, NULL);
    while (slp != NULL) {
      if (firstSlp == NULL) {
        firstSlp = slp;
      }
      lastSlp = slp;
      next = SeqLocFindNext (location, slp);
      if (slp->choice == SEQLOC_NULL) {
        SafeSetStatus (ipp->nullsBetween, TRUE);
        SafeShow (ipp->nullsBetween);
      } else {
        id = SeqLocId (slp);
        if (id != NULL) {
          bsp = BioseqFind (id);
          if (bsp == NULL) {
            oldscope = SeqEntrySetScope (NULL);
            if (oldscope != NULL) {
              bsp = BioseqFind (id);
              SeqEntrySetScope (oldscope);
            }
          }
          isInterval = TRUE;
          isPoint = FALSE;
          StringCpy (fuzz_from_ch, "");
          StringCpy (fuzz_to_ch, "");
          if (bsp == NULL) {
            start = SeqLocStart (slp);
            stop = SeqLocStop (slp);
          }
          else
          {
            start = GetOffsetInBioseq (slp, bsp, SEQLOC_START);
            stop = GetOffsetInBioseq (slp, bsp, SEQLOC_STOP);
          }
          if (start == stop && slp->choice == SEQLOC_PNT) {
            spp = (SeqPntPtr) slp->data.ptrvalue;
            if (spp != NULL) {
              ifp = spp->fuzz;
              if (ifp != NULL && ifp->choice == 4 && ifp->a ==  3) {
                isInterval = FALSE;
                isPoint = TRUE;
                StringCpy (fuzz_from_ch, "^");
                /* start--; */  /* compensate for other fix */
                stop++; /* compensate for other fix */
              }
            }
          }
          strand = SeqLocStrand (slp);
          if (strand > Seq_strand_both_rev && strand != Seq_strand_other) {
            strand = Seq_strand_unknown;
          }
          /*
          if (strand == Seq_strand_unknown) {
            strand = Seq_strand_plus;
          }
          */
          if (! ipp->nucsOK) {
            strand = 0;
          }
          seq = 0;
          if (ipp->sip_list != NULL && bsp != NULL) {
            for (j = 1; j <= ipp->count && seq == 0; j++) {
              if (SeqIdComp (SeqIdFindBest (bsp->id, 0), ipp->sip_list[j]) == SIC_YES) {
                seq = j;
              }
            }
          }
          
          salp_num = 0;
          salp_row = 0;
          if (seq > 0 && ipp->salp_list != NULL)
          {
            salp_num = 1;
            while (salp_num <= ipp->aln_count
                   && salp_row == 0)
            {
              tmp_sip = SeqIdPtrFromSeqAlign (ipp->salp_list [salp_num]);
              bsp = BioseqFind (ipp->sip_list [seq]);
              if (bsp != NULL)
              {
                for (bsp_id = bsp->id;
                     bsp_id != NULL && salp_row == 0; 
                     bsp_id = bsp_id->next)
                {
                  salp_row = SeqIdOrderInBioseqIdList(bsp_id, tmp_sip);
                }
              }
              if (salp_row < 1)
              {
                salp_num++;
              }
            }
            if (salp_row < 1)
            {
              salp_num = 0;
            }
            else
            {
              start = AlnMgr2MapBioseqToSeqAlign(ipp->salp_list [salp_num], start, salp_row);
              stop = AlnMgr2MapBioseqToSeqAlign(ipp->salp_list [salp_num], stop, salp_row);
              aln_strand = AlnMgr2GetNthStrand (ipp->salp_list [salp_num], salp_row);
              /* if reverse strand in alignment, reverse strand for location */
              if (aln_strand == Seq_strand_minus)
              {
                if (strand == Seq_strand_minus)
                {
                  strand = Seq_strand_plus;
                }
                else if (strand == Seq_strand_plus)
                {
                  strand = Seq_strand_minus;
                }
              }
            }
          }
          
          BuildIntervalString (ipp, fuzz_from_ch, start, fuzz_to_ch, stop,
                               strand, seq, salp_num, isInterval, isPoint,
                               str, sizeof (str));
          vnp = ValNodeNew (head);
          if (head == NULL) {
            head = vnp;
          }
          if (vnp != NULL) {
            vnp->data.ptrvalue = StringSave (str);
          }
        }
      }
      slp = next;
    }
    if (firstSlp != NULL) {
      if (firstSlp->choice == SEQLOC_INT && firstSlp->data.ptrvalue != NULL) {
        sip = (SeqIntPtr) firstSlp->data.ptrvalue;
        if (sip->strand == Seq_strand_minus || sip->strand == Seq_strand_both_rev) {
          ifp = sip->if_to;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 1) {
            partial5 = TRUE;
          }
        } else {
          ifp = sip->if_from;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 2) {
            partial5 = TRUE;
          }
        }
      } else if (firstSlp->choice == SEQLOC_PNT && firstSlp->data.ptrvalue != NULL) {
        spp = (SeqPntPtr) firstSlp->data.ptrvalue;
        if (spp->strand == Seq_strand_minus || spp->strand == Seq_strand_both_rev) {
          ifp = spp->fuzz;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 1) {
            partial5 = TRUE;
          }
        } else {
          ifp = spp->fuzz;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 2) {
            partial5 = TRUE;
          }
        }
      }
    }
    if (lastSlp != NULL) {
      if (lastSlp->choice == SEQLOC_INT && lastSlp->data.ptrvalue != NULL) {
        sip = (SeqIntPtr) lastSlp->data.ptrvalue;
        if (sip->strand == Seq_strand_minus || sip->strand == Seq_strand_both_rev) {
          ifp = sip->if_from;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 2) {
            partial3 = TRUE;
          }
        } else {
          ifp = sip->if_to;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 1) {
            partial3 = TRUE;
          }
        }
      } else if (lastSlp->choice == SEQLOC_PNT && lastSlp->data.ptrvalue != NULL) {
        spp = (SeqPntPtr) lastSlp->data.ptrvalue;
        if (spp->strand == Seq_strand_minus || spp->strand == Seq_strand_both_rev) {
          ifp = spp->fuzz;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 2) {
            partial3 = TRUE;
          }
        } else {
          ifp = spp->fuzz;
          if (ifp != NULL && ifp->choice == 4 && ifp->a == 1) {
            partial3 = TRUE;
          }
        }
      }
    }
  }
  SafeSetStatus (ipp->partial5, partial5);
  SafeSetStatus (ipp->partial3, partial3);

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

extern void SetSequenceAndStrandForIntervalPage (DialoG d)
{
  SendMessageToDialog (d, NUM_VIB_MSG + 1);
}

static Boolean 
ReadFromValueFromDialogLine 
(CharPtr line,
 Int4Ptr p_from_val,
 BoolPtr fuzz_after)
{
  CharPtr txt, ptr;
  Int4    len;
  Boolean okay;
  Int4    val;
  Int4    j;
  Char    ch;
  
  if (p_from_val == NULL || fuzz_after == NULL || StringHasNoText (line))
  {
    return FALSE;
  }

  txt = ExtractTagListColumn (line, 0);
  okay = FALSE;
  len = StringLen (txt);
  for (j = 0; j < len; j++) {
    ch = txt [j];
    if (ch != ' ' && ch != '\t' && ch != '\n') {
      okay = TRUE;
    }
  }
  if (okay) {
    *fuzz_after = FALSE;
    *p_from_val = 0;

    ptr = StringChr (txt, '<');
    if (ptr != NULL) {
      *ptr = ' ';
    }
    ptr = StringChr (txt, '>');
    if (ptr != NULL) {
      *ptr = ' ';
    }
    ptr = StringChr (txt, '^');
    if (ptr != NULL) {
      *fuzz_after = TRUE;
      *ptr = ' ';
    }
    TrimSpacesAroundString (txt);
    if (StrToLong (txt, &val)) {
      *p_from_val = val;
    } else {
      okay = FALSE;
    }
  } else {
    okay = FALSE;
  }
  MemFree (txt);
  return okay;
}

static Boolean 
ReadToValueFromDialogLine 
(CharPtr line,
 Int4Ptr p_to_val,
 Int4Ptr p_from_val,
 BoolPtr fuzz_after,
 BoolPtr isInterval,
 BoolPtr isPoint,
 Boolean partial5,
 Boolean partial3)
{
  CharPtr txt, ptr;
  Int4    val, tmp;
  Boolean okay = TRUE;
  
  if (p_to_val == NULL || p_from_val == NULL || fuzz_after == NULL
      || isInterval == NULL || isPoint == NULL)
  {
    return FALSE;
  }
  
  *p_to_val = 0;
  
  txt = ExtractTagListColumn (line, 1);
  if (! StringHasNoText (txt)) {
    ptr = StringChr (txt, '<');
    if (ptr != NULL) {
      *ptr = ' ';
    }
    ptr = StringChr (txt, '>');
    if (ptr != NULL) {
      *ptr = ' ';
    }
    ptr = StringChr (txt, '^');
    if (ptr != NULL) {
      *fuzz_after = TRUE;
      *ptr = ' ';
    }
    TrimSpacesAroundString (txt);
    if (StrToLong (txt, &val)) {
      *p_to_val = val;
      if (*fuzz_after && *p_to_val == *p_from_val + 1) {
        *isInterval = FALSE;
        *isPoint = TRUE;
        /* from++; */ /* this was causing point to be thrown off */
      } else if (*p_to_val == *p_from_val && (! partial5) && (! partial3)) {
        *isInterval = FALSE;
        *isPoint = TRUE;
      }
    } else {
      okay = FALSE;
    }
  } else {
    /*
    okay = FALSE;
    */
    *isInterval = FALSE;
    *isPoint = TRUE;
    *p_to_val = *p_from_val;
  }
  MemFree (txt);
  
  if (okay && *isInterval) {
    if (*p_from_val > *p_to_val) {
      tmp = *p_from_val;
      *p_from_val = *p_to_val;
      *p_to_val = tmp;
    }
  }
  

  return okay;
}

static Boolean 
ReadStrandFromDialogLine 
(CharPtr  line,
 Int4     strand_col,
 Uint2Ptr p_strand_val,
 Uint2Ptr p_prev_strand_val)
{
  CharPtr txt;
  Int2    val2;
  
  if (p_strand_val == NULL
      || p_prev_strand_val == NULL)
  {
    return FALSE;
  }
  
  *p_strand_val = Seq_strand_unknown;
  if (strand_col > -1) {
    txt = ExtractTagListColumn (line, strand_col);
    if (txt != NULL && StrToInt (txt, &val2)) {
      *p_strand_val = val2;
      if (*p_strand_val > Seq_strand_both_rev) {
        *p_strand_val = Seq_strand_other;
      }
      *p_prev_strand_val = *p_strand_val;
    } else {
      *p_strand_val = *p_prev_strand_val;
    }
    MemFree (txt);
  }
  if (*p_strand_val == Seq_strand_unknown) {
    *p_strand_val = Seq_strand_plus;
  }
  return TRUE;
}

static Boolean 
ReadSeqIdFromDialogLine 
(CharPtr       line, 
 Int4          seqid_col,
 SeqIdPtr PNTR p_sip_val,
 SeqIdPtr PNTR p_prev_sip_val,
 SeqIdPtr PNTR sip_list,
 Int4          sip_count)
{
  CharPtr txt;
  Int2    val2;
  Boolean okay = TRUE;
  
  if (p_sip_val == NULL || p_prev_sip_val == NULL
      || sip_count == 0 || sip_list == NULL
      || seqid_col < 0)
  {
    return FALSE;
  }
  *p_sip_val = NULL;
  txt = ExtractTagListColumn (line, seqid_col);
  if (txt != NULL) {
    if (! StrToInt (txt, &val2) || val2 <= 0)
    {
      if (*p_prev_sip_val != NULL)
      {
        *p_sip_val = *p_prev_sip_val;
      }
      else
      {
        okay = FALSE;
      }
    }
    else if (val2 <= sip_count)
    {
      *p_sip_val = sip_list [val2];
      *p_prev_sip_val = *p_sip_val;
    } else {
      okay = FALSE;
    }
  }
  else
  {
    okay = FALSE;
  }
  MemFree (txt);
  return okay;
}

static Boolean 
ReadSeqAlignFromDialogLine
(CharPtr          line,
 Int4             aln_col,
 SeqAlignPtr PNTR p_salp_val,
 SeqAlignPtr PNTR p_prev_salp_val,
 SeqAlignPtr PNTR salp_list,
 Int4             salp_count)
{
  CharPtr txt;
  Int2    val2;
  Boolean okay = TRUE;
  
  if (p_salp_val == NULL || p_prev_salp_val == NULL 
      || salp_count == 0 || salp_list == NULL
      || aln_col < 0)
  {
    return FALSE;
  }
  
  txt = ExtractTagListColumn (line, aln_col);
  if (txt != NULL) {
    if (! StrToInt (txt, &val2) || val2 <= 0)
    {
      if (*p_prev_salp_val != NULL)
      {
        *p_salp_val = *p_prev_salp_val;
      }
      else
      {
        okay = FALSE;
      }
    }
    else if (val2 <= salp_count)
    {
      *p_salp_val = salp_list [val2];
      *p_prev_salp_val = *p_salp_val;
    } else {
      okay = FALSE;
    }
  }
  else
  {
    if (*p_prev_salp_val == NULL)
    {
      okay = FALSE;
    }
    else
    {
      *p_salp_val = *p_prev_salp_val;
    }
 }
 MemFree (txt);
 return okay;
}

static Boolean 
GetBioseqAlignmentRow 
(SeqAlignPtr salp,
 BioseqPtr   bsp, 
 Int4Ptr     aln_row)
{
  SeqIdPtr  tmp_sip, bsp_id;
  if (salp == NULL || bsp == NULL || aln_row == NULL)
  {
    return FALSE;
  }
  
  *aln_row = 0;

  tmp_sip = SeqIdPtrFromSeqAlign (salp);
  
  for (bsp_id = bsp->id;
       bsp_id != NULL && *aln_row == 0; 
       bsp_id = bsp_id->next)
  {
    *aln_row = SeqIdOrderInBioseqIdList(bsp_id, tmp_sip);
  }
  if (*aln_row < 1)
  {
    return FALSE;
  }
  else
  {
    return TRUE;
  }
}

static void 
ListAlignmentsThatContainSequence 
(SeqAlignPtr PNTR salp_list, 
 Int4        salp_count,
 BioseqPtr   bsp,
 SeqIdPtr    sip,
 ValNodePtr  PNTR head)
{
  Int4       i;
  Char       str [34];
  Char       id_label [128];
  Int4       aln_row;
  ValNodePtr good_aln = NULL;
  CharPtr    none_found_fmt = "%s is not found in any alignments";
  CharPtr    one_found_fmt = "%s is found in %s";
  CharPtr    some_found_fmt = "%s is found in the following alignments";
  CharPtr    err_msg;
  CharPtr    cp;
  
  if (head == NULL || bsp == NULL || sip == NULL)
  {
    return;
  }
  SeqIdWrite (sip, id_label, PRINTID_REPORT, sizeof (id_label));
  
  /* indent the names of the alignments */
  str [0] = ' ';
  str [1] = ' ';
  str [2] = ' ';
  str [3] = ' ';
  for (i = 0; i < salp_count; i++)
  {
    if (GetBioseqAlignmentRow (salp_list [i], bsp, &aln_row))
    {
      CreateSeqAlignLabel (salp_list [i], str + 4, sizeof (str) - 4);
      ValNodeAddPointer (&good_aln, 0, StringSave (str));
    }           
  }
  if (good_aln == NULL)
  {
    err_msg = (CharPtr) MemNew ((StringLen (none_found_fmt) + StringLen (id_label))
                                * sizeof (Char));
    if (err_msg != NULL)
    {
      sprintf (err_msg, none_found_fmt, id_label);
      ValNodeAddPointer (head, 0, err_msg);
    }
  }
  else if (good_aln->next == NULL)
  {
    err_msg = (CharPtr) MemNew ((StringLen (one_found_fmt) 
                                 + StringLen (id_label)
                                 + StringLen (good_aln->data.ptrvalue))
                                * sizeof (Char));
    if (err_msg != NULL)
    {
      cp = (CharPtr) good_aln->data.ptrvalue;
      /* skip over indented space */
      cp += 4;
      sprintf (err_msg, one_found_fmt, id_label, cp);
      ValNodeAddPointer (head, 0, err_msg);
      good_aln = ValNodeFreeData (good_aln);
    }
  }
  else
  {
    err_msg = (CharPtr) MemNew ((StringLen (some_found_fmt) 
                                 + StringLen (id_label))
                                * sizeof (Char));
    if (err_msg != NULL)
    {
      sprintf (err_msg, some_found_fmt, id_label);
      ValNodeAddPointer (head, 0, err_msg);
      ValNodeLink (head, good_aln);
    }
  }
}

static void 
ListSequencesInAlignment 
(SeqIdPtr    PNTR sip_list,
 Int4        sip_count,
 SeqAlignPtr salp,
 ValNodePtr  PNTR head)
{
  SeqIdPtr   tmp_sip, aln_id;
  BioseqPtr  bsp;
  Int4       i;
  Boolean    found;
  Char       id_label [128];
  
  if (salp == NULL || head == NULL)
  {
    return;
  }

  tmp_sip = SeqIdPtrFromSeqAlign (salp);
  if (tmp_sip == NULL)
  {
    ValNodeAddPointer (head, 0, StringSave ("Selected alignment contains no sequences"));
    return;
  }
  /* indent list of sequence IDs */
  id_label [0] = ' ';
  id_label [1] = ' ';
  id_label [2] = ' ';
  id_label [3] = ' ';
  ValNodeAddPointer (head, 0, StringSave ("The selected alignment contains the following sequences:"));
  for (aln_id = tmp_sip; aln_id != NULL; aln_id = aln_id->next)
  {
    bsp = BioseqFind (aln_id);
    if (bsp == NULL)
    {
      continue;
    }
    found = FALSE;
    for (i = 0; i < sip_count && ! found; i++)
    {
      if (SeqIdIn (sip_list [i], bsp->id))
      {
        SeqIdWrite (sip_list [i], id_label + 4, PRINTID_REPORT, sizeof (id_label) - 4);
        ValNodeAddPointer (head, 0, StringSave (id_label));
        found = TRUE;
      }
    }
  }
}

extern Boolean AdjustFromForGap (Int4Ptr p_from, SeqAlignPtr salp, Int4 aln_len, Int4 aln_row)
{
  Int4 aln_from, aln_offset = 0;
  
  if (p_from == NULL || salp == NULL || aln_len == 0 || aln_row == 0
      || *p_from < 1)
  {
    return FALSE;
  }
  
  aln_from = AlnMgr2MapSeqAlignToBioseq(salp, (*p_from) - 1, aln_row);
  
  while (aln_from == -2 && (*p_from) + aln_offset < aln_len)
  {
    aln_offset ++;
    aln_from = AlnMgr2MapSeqAlignToBioseq(salp, (*p_from) + aln_offset - 1, aln_row);
  }
  if (aln_from < 0)
  {
    return FALSE;
  }
  else
  {
    *p_from = aln_from + 1;
    return TRUE;
  }
}

extern Boolean AdjustToForGap (Int4Ptr p_to, SeqAlignPtr salp, Int4 aln_row)
{
  Int4 aln_to, aln_offset = 0;
  
  if (p_to == NULL || salp == NULL || aln_row == 0
      || *p_to < 1)
  {
    return FALSE;
  }
  
  aln_to = AlnMgr2MapSeqAlignToBioseq(salp, (*p_to) - 1, aln_row);
  aln_offset = 0;
  while (aln_to == -2 && (*p_to) - 1 - aln_offset >= 0)
  {
    aln_offset ++;
    aln_to = AlnMgr2MapSeqAlignToBioseq(salp, (*p_to) - 1 - aln_offset, aln_row);
  }
  if (aln_to < 0)
  {
    return FALSE;
  }
  else
  {
    *p_to = aln_to + 1;
    return TRUE;
  }
}

static Boolean AdjustStrandForAlignment (Uint2Ptr p_strand, SeqAlignPtr salp, Int4 aln_row)
{
  Uint2 aln_strand;
  
  if (p_strand == NULL || salp == NULL || aln_row < 1)
  {
    return FALSE;
  }
  
  aln_strand = AlnMgr2GetNthStrand (salp, aln_row);
  if (aln_strand == Seq_strand_minus)
  {
    /* if alignment strand is minus, reverse strand of location */
    if (*p_strand == Seq_strand_minus)
    {
      *p_strand = Seq_strand_plus;
    }
    else if (*p_strand == Seq_strand_plus)
    {
      *p_strand = Seq_strand_minus;
    }
  }
  return TRUE;
}

static Boolean CoordinatesValidForBioseq (Int4 from, Int4 to, BioseqPtr bsp)
{
  if (bsp == NULL || from < 1 || to > bsp->length) 
  {
    return FALSE;
  }
  else
  {
    return TRUE;
  }
}

static ValNodePtr TestIntervalEditor (DialoG d)
{
  IntervalPagePtr  ipp;
  TagListPtr       tlp;
  ValNodePtr       head = NULL, vnp;
  Boolean          from_ok, to_ok, seqid_ok, salp_ok;
  Boolean          partial5, partial3;
  Int4             to, from, aln_row;
  Int4             interval_num;
  Uint2            strand, prev_strand = Seq_strand_unknown;
  SeqIdPtr         seqid, prev_seqid = NULL;
  SeqAlignPtr      salp, prev_salp = NULL;
  Char             err_msg[200];
  BioseqPtr        bsp = NULL;
  Int4             aln_len;
  Boolean          isInterval, isPoint, fuzz_before, fuzz_after;
  Int2             fuzz_from;
  Int2             fuzz_to;
  
  ipp = (IntervalPagePtr) GetObjectExtra (d);
  if (ipp == NULL) 
  {
    ValNodeAddPointer (&head, 0, StringSave ("No dialog data"));
    return head;
  }
  
  tlp = GetObjectExtra (ipp->ivals);
  if (tlp == NULL)
  {
    ValNodeAddPointer (&head, 0, StringSave ("No dialog data"));
    return head;
  }
  
  if (tlp->vnp == NULL)
  {
    ValNodeAddPointer (&head, 0, StringSave ("No location intervals listed!"));
  }

  partial5 = GetStatus (ipp->partial5);
  partial3 = GetStatus (ipp->partial3);

  for (vnp = tlp->vnp, interval_num = 1;
       vnp != NULL; 
       vnp = vnp->next, interval_num++) {
    isInterval = TRUE;
    isPoint = FALSE;
    fuzz_from = -1;
    fuzz_to = -1;
    fuzz_before = FALSE;
    fuzz_after = FALSE;
    from = 0;
    to = 0;

    from_ok = ReadFromValueFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                           &from, &fuzz_after);
    if (!from_ok)
    {
      /* we'll silently ignore lines that have no from value */
      continue;
    }
    
    to_ok = ReadToValueFromDialogLine ((CharPtr) vnp->data.ptrvalue, 
                                         &to, &from, &fuzz_after, &isInterval, &isPoint,
                                         partial5, partial3);
    if (!to_ok)
    {
      sprintf (err_msg, "Bad to value in interval %d", interval_num);
      ValNodeAddPointer (&head, 0, StringSave ("err_msg"));
    }
    strand = Seq_strand_unknown;
    ReadStrandFromDialogLine (vnp->data.ptrvalue, ipp->strand_col, 
                              &strand, &prev_strand);
    
    seqid_ok = ReadSeqIdFromDialogLine (vnp->data.ptrvalue, ipp->seqid_col,
                                         &seqid, &prev_seqid,
                                         ipp->sip_list, ipp->count);
    if (seqid_ok)
    {
      bsp = BioseqFind (seqid);
      if (bsp == NULL)
      {
        sprintf (err_msg, "Can't find bioseq for interval %d", interval_num);
        ValNodeAddPointer (&head, 0, StringSave (err_msg));
        seqid_ok = FALSE;
      }
    }
    else
    {
      sprintf (err_msg, "No sequence ID in interval %d", interval_num);
      ValNodeAddPointer (&head, 0, StringSave (err_msg));
    }

      
    /* get SeqAlign */
    salp = NULL;
    if (ipp->salp_list == NULL)
    {
      salp_ok = TRUE;
      if (bsp != NULL)
      {
        if (!CoordinatesValidForBioseq (from, to, bsp))
        {
          sprintf (err_msg, "Coordinates for interval %d are not in sequence (1-%d)",
                   interval_num, bsp->length);
          ValNodeAddPointer (&head, 0, StringSave (err_msg));
        }
      }
    }
    else
    {
      salp_ok = ReadSeqAlignFromDialogLine (vnp->data.ptrvalue, 
                                            ipp->aln_col,
                                            &salp, &prev_salp,
                                            ipp->salp_list,
                                            ipp->aln_count);
      if (!salp_ok)
      {
        sprintf (err_msg, "No alignment for interval %d", interval_num);
        ValNodeAddPointer (&head, 0, StringSave (err_msg));
      }
      
      if (salp_ok && seqid_ok)
      {
        aln_row = 0;
        if (GetBioseqAlignmentRow (salp, bsp, &aln_row))
        {
          aln_len = SeqAlignLength (salp);
          if (from < 1 || to > aln_len)
          {
            sprintf (err_msg, "Coordinates for interval %d are not in alignment interval (%d-%d)",
                     interval_num, 1, aln_len);
            ValNodeAddPointer (&head, 0, StringSave (err_msg));
          }
          else
          {
            /* check for locations in gaps */
            if (!AdjustFromForGap (&from, salp, aln_len, aln_row)
                || ! AdjustToForGap (&to, salp, aln_row))
            {
              sprintf (err_msg, "Interval %d is completely contained in a gap in the alignment.",
                       interval_num);
              ValNodeAddPointer (&head, 0, StringSave (err_msg));
            }
          }
        }
        else
        {
          sprintf (err_msg, "Sequence for interval %d not in selected alignment", interval_num);
          ValNodeAddPointer (&head, 0, StringSave (err_msg));
          ListAlignmentsThatContainSequence (ipp->salp_list, ipp->aln_count, 
                                             bsp, seqid, &head);
          ListSequencesInAlignment (ipp->sip_list, ipp->count, salp, &head);
        }
      }
    }
  }
  
  return head;
}


static Boolean 
AddLocToList 
(SeqIdPtr       seqid,
 Uint2          strand,
 Int4           from,
 Int4           to,
 Boolean        add_null,
 Int2           fuzz_from,
 Int2           fuzz_to,
 Boolean        fuzz_before,
 Boolean        fuzz_after,
 Boolean        partial5,
 Boolean        partial3,
 SeqLocPtr PNTR pslp)
{
  SeqLocPtr slp;
  SeqLocPtr tmploc1, tmploc2;
  Boolean   isInterval;
  Boolean   isPoint;
  Int4      tmp;
  
  if (pslp == NULL || seqid == NULL)
  {
    return FALSE;
  }
            
  if (add_null) {
    /* add NULL location between last location and this one */
    slp = ValNodeNew (NULL);
    if (slp != NULL) {
      slp->choice = SEQLOC_NULL;
      tmploc1 = *pslp;
      if (tmploc1 != NULL) {
        if (tmploc1->choice == SEQLOC_MIX) {
          tmploc2 = (ValNodePtr) (tmploc1->data.ptrvalue);
          if (tmploc2 != NULL) {
            while (tmploc2->next != NULL) {
              tmploc2 = tmploc2->next;
            }
            tmploc2->next = slp;
          }
        } else {
          tmploc2 = ValNodeNew (NULL);
          if (tmploc2 != NULL) {
            tmploc2->choice = SEQLOC_MIX;
            tmploc2->data.ptrvalue = (Pointer) tmploc1;
            tmploc1->next = slp;
            *pslp = tmploc2;
          }
        }
      }
    }
  }
    
  isInterval = TRUE;
  isPoint = FALSE;
  
  /* make sure from and to are in correct order */
  if (from > to)
  {
    tmp = from;
    from = to;
    to = tmp;
  }

  if (fuzz_after && to == from + 1) {
    isInterval = FALSE;
    isPoint = TRUE;
    /* from++; */ /* this was causing point to be thrown off */
  } else if (to == from && (! partial5) && (! partial3)) {
    isInterval = FALSE;
    isPoint = TRUE;
  }
          
  if (isInterval) {
    AddIntToSeqLoc (pslp, from - 1, to - 1, seqid,
                    fuzz_from, fuzz_to, strand);
  } else if (isPoint) {
    AddSeqLocPoint (pslp, seqid, from, fuzz_before, fuzz_after, strand);
  }
  return TRUE;
}

static void SetPartialsForOneLocation (SeqLocPtr master_slp, Boolean partial5, Boolean partial3)
{
  SeqLocPtr  firstSlp, lastSlp, tmp_slp;
  IntFuzzPtr ifp;
  SeqIntPtr  sip;
  SeqPntPtr  spp;
  
  if (master_slp == NULL)
  {
    return;
  }
  
  /* now set partials for location */
  firstSlp = NULL;
  lastSlp = NULL;
  tmp_slp = SeqLocFindNext (master_slp, NULL);
  while (tmp_slp != NULL) {
    if (firstSlp == NULL) {
      firstSlp = tmp_slp;
    }
    lastSlp = tmp_slp;
    tmp_slp = SeqLocFindNext (master_slp, tmp_slp);
  }
  if (firstSlp != NULL && partial5) {
    if (firstSlp->choice == SEQLOC_INT && firstSlp->data.ptrvalue != NULL) {
      sip = (SeqIntPtr) firstSlp->data.ptrvalue;
      ifp = IntFuzzNew ();
      if (ifp != NULL) {
        ifp->choice = 4;
        if (sip->strand == Seq_strand_minus || sip->strand == Seq_strand_both_rev) {
          sip->if_to = ifp;
          ifp->a = 1;
        } else {
          sip->if_from = ifp;
          ifp->a = 2;
        }
      }
    } else if (firstSlp->choice == SEQLOC_PNT && firstSlp->data.ptrvalue != NULL) {
      spp = (SeqPntPtr) firstSlp->data.ptrvalue;
      ifp = IntFuzzNew ();
      if (ifp != NULL) {
        ifp->choice = 4;
        if (spp->strand == Seq_strand_minus || spp->strand == Seq_strand_both_rev) {
          spp->fuzz = ifp;
          ifp->a = 1;
        } else {
          spp->fuzz = ifp;
          ifp->a = 2;
        }
      }
    }
  }
  if (lastSlp != NULL && partial3) {
    if (lastSlp->choice == SEQLOC_INT && lastSlp->data.ptrvalue != NULL) {
      sip = (SeqIntPtr) lastSlp->data.ptrvalue;
      ifp = IntFuzzNew ();
      if (ifp != NULL) {
        ifp->choice = 4;
        if (sip->strand == Seq_strand_minus || sip->strand == Seq_strand_both_rev) {
          sip->if_from = ifp;
          ifp->a = 2;
        } else {
          sip->if_to = ifp;
          ifp->a = 1;
        }
      }
    } else if (lastSlp->choice == SEQLOC_PNT && lastSlp->data.ptrvalue != NULL) {
      spp = (SeqPntPtr) lastSlp->data.ptrvalue;
      ifp = IntFuzzNew ();
      if (ifp != NULL) {
        ifp->choice = 4;
        if (spp->strand == Seq_strand_minus || spp->strand == Seq_strand_both_rev) {
          spp->fuzz = ifp;
          ifp->a = 2;
        } else {
          spp->fuzz = ifp;
          ifp->a = 1;
        }
      }
    }
  }
}

static SeqLocPtr 
ReadSingleSeqLoc 
(TagListPtr      tlp,
 IntervalPagePtr ipp,
 Boolean         partial5,
 Boolean         partial3, 
 Boolean         nullsBetween)
{
  Int4             from, to, aln_row, aln_len;
  Boolean          fuzz_after;
  Boolean          fuzz_before;
  Int2             fuzz_from;
  Int2             fuzz_to;
  Boolean          isInterval;
  Boolean          isPoint;
  Boolean          notFirst;
  Boolean          okay;
  SeqIdPtr         seqid, prev_sip;
  SeqLocPtr        master_slp;
  Uint2            strand, prev_strand;
  ValNodePtr       vnp;
  SeqAlignPtr      salp, prev_salp;
  BioseqPtr        bsp;

  if (tlp == NULL)
  {
    return NULL;
  }
  
  prev_sip = NULL;
  prev_salp = NULL;
  prev_strand = Seq_strand_unknown;
  master_slp = NULL;

  notFirst = FALSE;
  for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
    if (StringHasNoText (vnp->data.ptrvalue))
    {
      continue;
    }
    fuzz_from = -1;
    fuzz_to = -1;
    fuzz_before = FALSE;
    fuzz_after = FALSE;
    from = 0;
    to = 0;
    isInterval = TRUE;
    isPoint = FALSE;
    strand = Seq_strand_unknown;
    seqid = NULL;
    salp = NULL;
    aln_row = 0;
    if (!ReadFromValueFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                           &from, &fuzz_after))
    {
      continue;
    }
    okay = ReadToValueFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                         &to, &from, &fuzz_after,
                                         &isInterval, &isPoint, 
                                         partial5, partial3)
           && ReadStrandFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                        ipp->strand_col,
                                        &strand, &prev_strand)
           && ReadSeqIdFromDialogLine ((CharPtr) vnp->data.ptrvalue, ipp->seqid_col,
                                       &seqid, &prev_sip,
                                       ipp->sip_list, ipp->count)
           && (ipp->salp_list == NULL 
               || ReadSeqAlignFromDialogLine ((CharPtr) vnp->data.ptrvalue, ipp->aln_col,
                                              &salp, &prev_salp, 
                                              ipp->salp_list, ipp->aln_count))
           && ((bsp = BioseqFind (seqid)) != NULL)
           && (salp != NULL || CoordinatesValidForBioseq (from, to, bsp))
           && (salp == NULL || 
                (GetBioseqAlignmentRow (salp, bsp, &aln_row)
                 && AdjustStrandForAlignment (&strand, salp, aln_row)
                 && (aln_len = SeqAlignLength (salp)) > 0
                 && AdjustFromForGap (&from, salp, aln_len, aln_row)
                 && AdjustToForGap (&to, salp, aln_row)))
           && AddLocToList (seqid, strand, from, to,
                            (nullsBetween  && notFirst),
                            fuzz_from, fuzz_to,
                            fuzz_before, fuzz_after,
                            partial5, partial3,
                            &(master_slp));
    if (okay)
    {
      notFirst = TRUE;
    }
    else
    {
      master_slp = SeqLocFree (master_slp);
      return NULL;
    }
  }
  
  /* now set partials for location */
  SetPartialsForOneLocation (master_slp, partial5, partial3);  
  return master_slp;
}

static SeqLocPtr PNTR FreeSeqLocArray (SeqLocPtr PNTR loc_list, Int4 num_loc)
{
  Int4 j;
  if (loc_list != NULL)
  {
    for (j = 0; j < num_loc; j++)
    {
      loc_list [j] = SeqLocFree (loc_list [j]);
    }
    loc_list = MemFree (loc_list);
  }
  return loc_list;
}

static SeqLocPtr 
ReadAlignedSeqLocList
(TagListPtr      tlp,
 IntervalPagePtr ipp,
 Boolean         partial5,
 Boolean         partial3, 
 Boolean         nullsBetween)
{
  Int4             from, to, aln_from, aln_to, aln_row, aln_len = 0;
  Boolean          fuzz_after;
  Boolean          fuzz_before;
  Int2             fuzz_from;
  Int2             fuzz_to;
  Boolean          isInterval;
  Boolean          isPoint;
  Boolean          notFirst;
  Boolean          okay;
  SeqIdPtr         seqid;
  SeqLocPtr        master_slp, tmp_slp;
  Uint2            strand, prev_strand, aln_strand;
  ValNodePtr       vnp;
  SeqAlignPtr      salp, prev_salp;
  BioseqPtr        bsp;
  SeqLocPtr PNTR   loc_list;
  Int4             loc_num, max_locs;
  Boolean          asked_about_repair = FALSE;
  MsgAnswer        ans = ANS_YES;

  if (tlp == NULL || ipp == NULL || ipp->salp_list == NULL)
  {
    return NULL;
  }
  
  prev_salp = NULL;
  prev_strand = Seq_strand_unknown;
  master_slp = NULL;
  
  max_locs = 0;
  for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next)
  {
    if (!ReadSeqAlignFromDialogLine ((CharPtr) vnp->data.ptrvalue, ipp->aln_col,
                                     &salp, &prev_salp, 
                                     ipp->salp_list, ipp->aln_count))
    {
      return NULL;
    }
    else
    {
      max_locs = MAX (max_locs, salp->dim);
    }
  }
  if (max_locs == 0)
  {
    return NULL;
  }
  
  loc_list = (SeqLocPtr PNTR) MemNew (max_locs * sizeof (SeqLocPtr));
  if (loc_list == NULL)
  {
    return NULL;
  }

  prev_salp = NULL;

  okay = TRUE;
  notFirst = FALSE;
  for (vnp = tlp->vnp; vnp != NULL && okay; vnp = vnp->next) {
    if (StringHasNoText (vnp->data.ptrvalue))
    {
      continue;
    }
    fuzz_from = -1;
    fuzz_to = -1;
    fuzz_before = FALSE;
    fuzz_after = FALSE;
    from = 0;
    to = 0;
    isInterval = TRUE;
    isPoint = FALSE;
    strand = Seq_strand_unknown;
    seqid = NULL;
    salp = NULL;
    aln_row = 0;
    if (! ReadFromValueFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                           &from, &fuzz_after))
    {
      continue;
    }
    okay = ReadToValueFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                       &to, &from, &fuzz_after,
                                       &isInterval, &isPoint, 
                                       partial5, partial3)
           && ReadStrandFromDialogLine ((CharPtr) vnp->data.ptrvalue,
                                        ipp->strand_col,
                                        &strand, &prev_strand)
           && ReadSeqAlignFromDialogLine ((CharPtr) vnp->data.ptrvalue, ipp->aln_col,
                                              &salp, &prev_salp, 
                                              ipp->salp_list, ipp->aln_count);
    if (okay)
    {
      for (loc_num = 0; loc_num < salp->dim; loc_num++)
      {
        aln_row = loc_num + 1;
        aln_from = from;
        aln_to = to;
        aln_strand = strand;
        seqid = AlnMgr2GetNthSeqIdPtr (salp, aln_row);
        okay = ((bsp = BioseqFind (seqid)) != NULL)
                 && AdjustStrandForAlignment (&aln_strand, salp, aln_row)
                 && (aln_len = SeqAlignLength (salp)) > 0;
        if (okay)
        {
          if (ipp->allow_nulls_in_list)
          {
            if (! AdjustFromForGap (&aln_from, salp, aln_len, aln_row)
                || ! AdjustToForGap (&aln_to, salp, aln_row)
                || ! AddLocToList (seqid, aln_strand, aln_from, aln_to,
                                   (nullsBetween  && notFirst),
                                   fuzz_from, fuzz_to,
                                   fuzz_before, fuzz_after,
                                   partial5, partial3,
                                   &(loc_list [loc_num])))
            {
              if (loc_list[loc_num] == NULL) 
              {
                loc_list [loc_num] = ValNodeNew (NULL);
                loc_list [loc_num]->choice = SEQLOC_NULL;
              }
            }
          }
          else
          {
            okay = AdjustFromForGap (&aln_from, salp, aln_len, aln_row)
                   && AdjustToForGap (&aln_to, salp, aln_row)
                   && AddLocToList (seqid, aln_strand, aln_from, aln_to,
                              (nullsBetween  && notFirst),
                              fuzz_from, fuzz_to,
                              fuzz_before, fuzz_after,
                              partial5, partial3,
                              &(loc_list [loc_num]));
          }
        }
      }
    }
      
    notFirst = TRUE;
  }
  
  if (!okay)
  {
    loc_list = FreeSeqLocArray (loc_list, max_locs);
    return NULL;
  }
  
  /* now fix intervals that are out of order and set partials for locations */
  for (loc_num = 0; loc_num < max_locs && loc_list [loc_num] != NULL; loc_num++)
  {
    bsp = BioseqFindFromSeqLoc (loc_list [loc_num]);
    if (bsp != NULL && SeqLocBadSortOrder (bsp, loc_list [loc_num])) 
    {
      if (!asked_about_repair)
      {
        ans = Message (MSG_YN,
            "Feature location intervals are out of order.  Do you want them repaired?");
        asked_about_repair = TRUE;
      }
      if (ans == ANS_YES) {
        tmp_slp = SeqLocMerge (bsp, loc_list [loc_num], NULL, FALSE, FALSE, nullsBetween);
        loc_list [loc_num] = SeqLocFree (loc_list [loc_num]);
        loc_list [loc_num] = tmp_slp;
        if (bsp->repr == Seq_repr_seg) {
          tmp_slp = SegLocToParts (bsp, loc_list [loc_num]);
          loc_list [loc_num] = SeqLocFree (loc_list [loc_num]);
          loc_list [loc_num] = tmp_slp;
        }
        FreeAllFuzz (loc_list [loc_num]);
      }
    }
    SetSeqLocPartial (loc_list [loc_num], partial5, partial3);
  }
  
  /* now make chain */
  master_slp = loc_list [0];
  tmp_slp = loc_list [0];
  for (tmp_slp = loc_list [0], loc_num = 1; 
       tmp_slp != NULL && loc_num < max_locs;
       loc_num++)
  {
    tmp_slp->next = loc_list [loc_num];
    tmp_slp = tmp_slp->next;
  }
  
  loc_list = MemFree (loc_list);
  
  return master_slp;
}

static Pointer IntervalPageToSeqLocPtr (DialoG d)

{
  IntervalPagePtr  ipp;
  Boolean          nullsBetween;
  Boolean          partial5;
  Boolean          partial3;
  SeqLocPtr        master_slp;
  TagListPtr       tlp;

  ipp = (IntervalPagePtr) GetObjectExtra (d);
  if (ipp == NULL) return NULL;
  tlp = GetObjectExtra (ipp->ivals);
  if (tlp == NULL) return NULL;
  

  nullsBetween = GetStatus (ipp->nullsBetween);
  partial5 = GetStatus (ipp->partial5);
  partial3 = GetStatus (ipp->partial3);
  
  if (ipp->seqid_col > -1)
  {
    master_slp = ReadSingleSeqLoc (tlp, ipp, partial5, partial3, nullsBetween);
  }
  else
  {
    master_slp = ReadAlignedSeqLocList (tlp, ipp, partial5, partial3, nullsBetween);
  }
  return (Pointer) master_slp;
}

static void CleanupIntervalPage (GraphiC g, VoidPtr data)

{
  IntervalPagePtr  ipp;
  Int2             j;

  ipp = (IntervalPagePtr) data;
  if (ipp != NULL) {
    /* free seq ID list */
    if (ipp->sip_list != NULL)
    {
      for (j = 0; j <= ipp->count + 1; j++) {
        ipp->sip_list [j] = SeqIdFree (ipp->sip_list [j]);
      }
    }
    MemFree (ipp->sip_list);
    if (ipp->alist != NULL) {
      for (j = 0; j <= ipp->count + 1; j++) {
        MemFree (ipp->alist [j].name);
      }
    }
    MemFree (ipp->alist);
    MemFree (ipp->lengths);

    /* free list of alignments */
    if (ipp->salp_list != NULL)
    {
      for (j = 0; j <= ipp->aln_count + 1; j++) {
        ipp->salp_list [j] = SeqAlignFree (ipp->salp_list [j]); 
      }
    }
    MemFree (ipp->salp_list);
    /* free alignment tags */
    if (ipp->aln_alist != NULL) {
      for (j = 0; j <= ipp->aln_count + 1; j++) {
        MemFree (ipp->aln_alist [j].name);
      }
    }
    MemFree (ipp->aln_alist);
    /* free alignment lengths */
    MemFree (ipp->aln_lengths);

    
    /* free callback list */
    MemFree (ipp->callbacks);
  }
  MemFree (data);
}

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

Uint2 interval_widths [] = {
  5, 5, 0, 0, 0
};

static Boolean ReadSeqLocDialog (DialoG d, CharPtr filename)

{
  AsnIoPtr         aip;
  IntervalPagePtr  ipp;
  SeqLocPtr        slp;
  Char             path [PATH_MAX];

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  ipp = (IntervalPagePtr) GetObjectExtra (d);
  if (ipp != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      aip = AsnIoOpen (path, "r");
      if (aip != NULL) {
        slp = SeqLocAsnRead (aip, NULL);
        AsnIoClose (aip);
        if (slp != NULL) {
          SeqLocPtrToIntervalPage (ipp->dialog, slp);
          slp = SeqLocFree (slp);
          Update ();
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}

static Boolean WriteSeqLocDialog (DialoG d, CharPtr filename)

{
  AsnIoPtr         aip;
  IntervalPagePtr  ipp;
  SeqLocPtr        slp;
  Char             path [PATH_MAX];
#ifdef WIN_MAC
  FILE            *f;
#endif

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  ipp = (IntervalPagePtr) GetObjectExtra (d);
  if (ipp != NULL) {
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      f = FileOpen (path, "r");
      if (f != NULL) {
        FileClose (f);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      aip = AsnIoOpen (path, "w");
      if (aip != NULL) {
        slp = IntervalPageToSeqLocPtr (ipp->dialog);
        SeqLocAsnWrite (slp, aip, NULL);
        AsnIoClose (aip);
        slp = SeqLocFree (slp);
        return TRUE;
      }
    }
  }
  return FALSE;
}

static void SetOnlySequenceAndStrand (IntervalPagePtr ipp)
{
  TagListPtr       tlp;
  ValNodePtr       vnp;
  CharPtr          cp;
  CharPtr          tabptr;
  ValNodePtr       saved_list;
  
  if (ipp == NULL) return;
  tlp = GetObjectExtra (ipp->ivals);
  if (tlp == NULL) return;
  saved_list = tlp->vnp;
  tlp->vnp = NULL;
  SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
  tlp->vnp = saved_list;
  for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) 
  {
    cp = vnp->data.ptrvalue;
    if (cp != NULL)
    {
      tabptr = StringChr (cp, '\t');
      if (tabptr != NULL)
      {
          tabptr = StringChr (tabptr + 1, '\t');
      }
      if (tabptr != NULL)
      {
          cp[0] = '\t';
          cp++;
          while (*tabptr != 0)
          {
            *cp = *tabptr;
            cp++;
            tabptr++;
          }
          *cp = 0;
      }
    }
  }

  SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);  
}

static void ClearLocationPartialCheckboxes (IntervalPagePtr ipp)
{
  if (ipp != NULL)
  {
    SetStatus (ipp->partial5, FALSE);
    SetStatus (ipp->partial3, FALSE); 
    SetStatus (ipp->nullsBetween, FALSE); 
    if (ipp->proc != NULL) {
      ipp->proc (ipp->ffp, FALSE, FALSE, FALSE);
    }
  }
}

static void IntervalEditorMessage (DialoG d, Int2 mssg)

{
  IntervalPagePtr  ipp;

  ipp = (IntervalPagePtr) GetObjectExtra (d);
  if (ipp != NULL) {
    switch (mssg) {
      case VIB_MSG_INIT:
        SeqLocPtrToIntervalPage (d, NULL);
        break;
      case VIB_MSG_ENTER:
        SendMessageToDialog (ipp->ivals, VIB_MSG_ENTER);
        break;
      case VIB_MSG_RESET:
        /* do nothing */
        break;
      case NUM_VIB_MSG + 1:
        SetOnlySequenceAndStrand (ipp);
        break;
      case NUM_VIB_MSG + 2:
        ClearLocationPartialCheckboxes (ipp);
        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;
    }
  }
}

extern void StdFeatIntEdPartialCallback (FeatureFormPtr ffp, Boolean partial5, Boolean partial3, Boolean order)

{
  if (ffp == NULL) return;
  SafeSetStatus (ffp->partial, (partial5 || partial3 || order));
  Update ();
}

static void ChangedPartialProc (ButtoN b)

{
  IntervalPagePtr  ipp;

  ipp = (IntervalPagePtr) GetObjectExtra (b);
  if (ipp == NULL) return;
  if (ipp->proc != NULL) {
    ipp->proc (ipp->ffp, GetStatus (ipp->partial5),
               GetStatus (ipp->partial3), GetStatus (ipp->nullsBetween));
  }
}


static CharPtr MakeBlankLocationLine (CharPtr orig)
{
  CharPtr cp, rval = NULL;
  
  if (StringHasNoText (orig)) {
    return StringSave ("");
  }
  /* skip first two values, keep the rest */
  cp = StringChr (orig, '\t');
  if (cp == NULL) {
    return StringSave ("");
  }
  cp = StringChr (cp + 1, '\t');
  if (cp == NULL) {
    return StringSave ("");
  }
  cp++;

  rval = (CharPtr) MemNew (sizeof (Char) * (StringLen (cp) + 3));
  sprintf (rval, "\t\t%s", cp);
  return rval;
}


static void InsertFirstLocation (ButtoN b)
{
  DialoG ivals;
  TagListPtr tlp;
  ValNodePtr vnp;
  Int2 j;

  ivals = (DialoG) GetObjectExtra (b);
  if (ivals == NULL) {
    return;
  }
  tlp = (TagListPtr) GetObjectExtra (ivals);
  if (tlp == NULL || tlp->vnp == NULL || StringHasNoText (tlp->vnp->data.ptrvalue)) {
    return;
  }

  /* add blank line to current lines */
  vnp = ValNodeNew (NULL);
  vnp->data.ptrvalue = MakeBlankLocationLine (tlp->vnp->data.ptrvalue);
  vnp->next = tlp->vnp;
  /* disconnect lines from taglist, so they won't be erased by the reset */
  tlp->vnp = NULL;

  /* reset the dialog */
  SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);

  /* attach the lines to the dialog */
  tlp->vnp = vnp;

  /* redraw dialog */
  SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
  /* adjust scroll bars */
  for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
  }
  tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
  CorrectBarMax (tlp->bar, tlp->max);
  CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
  SendMessageToDialog (tlp->dialog, VIB_MSG_ENTER);
}

extern DialoG CreateTagListDialogEx (GrouP h, Uint2 rows, Uint2 cols,
                                     Int2 spacing, Uint2Ptr types,
                                     Uint2Ptr textWidths, EnumFieldAssocPtr PNTR alists,
                                     Boolean useBar, Boolean noExtend,
                                     ToDialogFunc tofunc, FromDialogFunc fromfunc);

extern DialoG CreateIntervalEditorDialogExEx (GrouP h, CharPtr title, Uint2 rows,
                                              Int2 spacing, SeqEntryPtr sep,
                                              Boolean nucsOK, Boolean protsOK,
                                              Boolean useBar, Boolean showPartials,
                                              Boolean allowGaps, FeatureFormPtr ffp,
                                              IntEdPartialProc proc, 
                                              Boolean use_aln, Boolean show_seqid,
                                              TaglistCallback tlp_callback, 
                                              Pointer callback_data,
                                              Boolean allow_nulls_in_list)

{
  BioseqPtr        bsp;
  Int4             count;
  GrouP            f;
  IntervalPagePtr  ipp;
  Int2             j;
  GrouP            m;
  GrouP            p;
  GrouP            btn_grp;
  PrompT           p1;
  PrompT           p2;
  PrompT           p3;
  PrompT           p_from, p_to;
  CharPtr          ptr;
  GrouP            q;
  GrouP            s;
  Boolean          showIdTags;
  SeqIdPtr         sip;
  Char             str [128];
  TagListPtr       tlp;
  SeqAlignPtr      salp_list = NULL, salp;
  Int4             num_cols;
  ButtoN           b;

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

  ipp = (IntervalPagePtr) MemNew (sizeof (IntervalPage));
  if (ipp != NULL) {

    SetObjectExtra (p, ipp, CleanupIntervalPage);
    ipp->dialog = (DialoG) p;
    ipp->todialog = SeqLocPtrToIntervalPage;
    ipp->fromdialog = IntervalPageToSeqLocPtr;
    ipp->dialogmessage = IntervalEditorMessage;
    ipp->testdialog = TestIntervalEditor;
    ipp->importdialog = ReadSeqLocDialog;
    ipp->exportdialog = WriteSeqLocDialog;

    ipp->ffp = ffp;
    ipp->proc = proc;
     
    ipp->allow_nulls_in_list = allow_nulls_in_list;

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

    ipp->nucsOK = nucsOK;
    ipp->protsOK = protsOK;
    ipp->showIdTags = FALSE;
    ipp->count = 0;
        
    if (ipp->nucsOK)
    {
      ipp->strand_col = 2;
    }
    else
    {
      ipp->strand_col = -1;
    }
    
    ipp->aln_col = -1;
    if (show_seqid)
    {
      if (ipp->nucsOK)
      {
        ipp->seqid_col = 3;
      }
      else
      {
        ipp->seqid_col = 2;
      }
      if (use_aln)
      {
        ipp->aln_col = ipp->seqid_col + 1;
      }
    }
    else
    {
      ipp->seqid_col = -1;
      if (use_aln)
      {
        if (ipp->nucsOK)
        {
          ipp->aln_col = 3;
        }
        else
        {
          ipp->aln_col = 2;
        }
      }
    }
    
    /* set up callbacks */
    ipp->callbacks = (TaglistCallback PNTR) MemNew (5 * sizeof (TaglistCallback));
    if (ipp->callbacks != NULL)
    {
      ipp->callbacks [0] = NULL;
      ipp->callbacks [1] = NULL;
      ipp->callbacks [2] = NULL;
      ipp->callbacks [3] = NULL;
      ipp->callbacks [4] = NULL;
  
      if (ipp->seqid_col > -1)
      {
        ipp->callbacks [ipp->seqid_col] = tlp_callback;
      }
      if (ipp->aln_col > -1)
      {
        ipp->callbacks [ipp->aln_col] = tlp_callback;
      }
    }

    if (sep != NULL) {
      if (use_aln)
      {
        VisitAnnotsInSep (sep, &salp_list, GetAlignmentsInSeqEntryCallback);
        count = 4;
        salp = salp_list;
        while (salp != NULL)
        {
          count++;
          salp = salp->next;
        }
        
        ipp->salp_list = (SeqAlignPtr PNTR) MemNew (sizeof (SeqAlignPtr) * count);
        ipp->aln_alist = MemNew (sizeof (EnumFieldAssoc) * (size_t) count);
        ipp->aln_lengths = MemNew (sizeof (Int4) * (size_t) count);
        ipp->aln_count = 0;
        if (ipp->salp_list != NULL && ipp->aln_alist != NULL && ipp->aln_lengths != NULL)
        {
          /* first one is NULL */
          ipp->salp_list [0] = NULL;
          ipp->aln_lengths [0] = 0;
          ipp->aln_alist [0].name = StringSave ("     ");
          ipp->aln_alist [0].value = (UIEnum) 0;
          ipp->aln_count ++;
          salp = salp_list;
          while (salp != NULL)
          {
            ipp->salp_list [ipp->aln_count] = salp;
            ipp->aln_lengths [ipp->aln_count] = SeqAlignLength (ipp->salp_list [ipp->aln_count]);
            CreateSeqAlignLabel (ipp->salp_list [ipp->aln_count], str, 30);
            ipp->aln_alist [ipp->aln_count].name = StringSave (str);
            ipp->aln_alist [ipp->aln_count].value = (UIEnum) ipp->aln_count;
                        
            salp = salp->next;
            /* sever chain */
            ipp->salp_list [ipp->aln_count]->next = NULL;
            ipp->aln_count++;
          }
          /* add end of list marker */
          ipp->aln_alist [ipp->aln_count].name = NULL;
          ipp->aln_alist [ipp->aln_count].value = (UIEnum) 0;
        }

      }
      else
      {
        ipp->salp_list = NULL;
        ipp->aln_alist = NULL;
        ipp->aln_lengths = NULL;
        ipp->aln_count = 0;
      }
      count = SegmentedEntryCount (sep);
      count += 4;
      ipp->sip_list = MemNew (sizeof (SeqIdPtr) * (size_t) count);
      ipp->alist = MemNew (sizeof (EnumFieldAssoc) * (size_t) count);
      ipp->lengths = MemNew (sizeof (Int4) * (size_t) count);
      ipp->count = 0;

      if (ipp->sip_list != NULL && ipp->alist != NULL && ipp->lengths != NULL) {
        VisitBioseqsInSep (sep, NULL, ClearBspScratch);
        SeqEntryExplore (sep, (Pointer) ipp, FillInProducts);
        VisitBioseqsInSep (sep, NULL, ClearBspScratch);
        j = 0;
        ipp->alist [j].name = StringSave ("     ");
        ipp->alist [j].value = (UIEnum) 0;
        for (j = 1; j <= ipp->count; j++) {
          sip = ipp->sip_list [j];
          if (sip != NULL) {
            bsp = BioseqFindCore (sip);
            if (bsp == NULL) {
              bsp = BioseqLockById (sip);
              BioseqUnlock (bsp);
            }
            if (bsp != NULL)
            {
              ipp->lengths [j] = bsp->length;
              sip = SeqIdFindWorst (bsp->id);
            }
            SeqIdWrite (sip, str, PRINTID_REPORT, sizeof (str));
            ptr = StringChr (str, '|');
            showIdTags = FALSE;
            if (ptr == NULL) {
              ptr = str;
            } else if (showIdTags) {
              ptr = str;
            } else {
              ptr++;
            }
            ipp->alist [j].name = StringSave (ptr);
            ipp->alist [j].value = (UIEnum) j;
          }
        }
        /* add end of list marker */
        j = ipp->count + 1;
        ipp->alist [j].name = NULL;
        ipp->alist [j].value = (UIEnum) 0;
      }
#ifdef WIN_MOTIF
      if (ipp->count > 31) {
        if (ipp->seqid_col > -1)
        {
          interval_types [ipp->seqid_col] = TAGLIST_LIST;
        }
        if (ipp->aln_col > -1)
        {
          interval_types [ipp->aln_col] = TAGLIST_LIST;
        }
      } else {
        interval_types [2] = TAGLIST_POPUP;
        interval_types [3] = TAGLIST_POPUP;
        interval_types [4] = TAGLIST_POPUP;
      }
#endif

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

      }
    }
    
    ipp->alists [0] = NULL;
    ipp->alists [1] = NULL;
    ipp->alists [2] = NULL;
    ipp->alists [3] = NULL;
    ipp->alists [4] = NULL;
    if (ipp->strand_col > -1)
    {
      ipp->alists [ipp->strand_col] = strand_alist;
    }
    if (ipp->seqid_col > -1)
    {
      ipp->alists [ipp->seqid_col] = ipp->alist;
    }
    if (ipp->aln_col > -1)
    {
      ipp->alists [ipp->aln_col] = ipp->aln_alist;
    }

    q = NULL;
    if (showPartials) {
      q = HiddenGroup (m, 4, 0, NULL);
      SetGroupSpacing (q, 20, 2);
      if (nucsOK) {
        ipp->partial5 = CheckBox (q, "5' Partial", ChangedPartialProc);
      } else {
        ipp->partial5 = CheckBox (q, "NH2 Partial", ChangedPartialProc);
      }
      SetObjectExtra (ipp->partial5, ipp, NULL);
      if (nucsOK) {
        ipp->partial3 = CheckBox (q, "3' Partial", ChangedPartialProc);
      } else {
        ipp->partial3 = CheckBox (q, "CO2H Partial", ChangedPartialProc);
      }
      SetObjectExtra (ipp->partial3, ipp, NULL);
    }

    f = HiddenGroup (m, 5, 0, NULL);
    p_from = StaticPrompt (f, "From", 5 * stdCharWidth, 0, programFont, 'c');
    p_to = StaticPrompt (f, "To", 5 * stdCharWidth, 0, programFont, 'c');
    p1 = NULL;
    p2 = NULL;
    p3 = NULL;
    if (ipp->strand_col > -1)
    {
      p1 = StaticPrompt (f, "Strand", 0, 0, programFont, 'c');
    }
    if (ipp->seqid_col > -1)
    {
      p2 = StaticPrompt (f, "SeqID", 0, 0, programFont, 'c');
    }
    if (ipp->aln_col > -1)
    {
      p3 = StaticPrompt (f, "Alignment", 0, 0, programFont, 'c');
    }

    f = HiddenGroup (m, 0, 4, NULL);
    SetGroupSpacing (f, 0, 0);

    num_cols = 2;
    if (ipp->strand_col > -1)
    {
      num_cols ++;
    }
    if (ipp->seqid_col > -1)
    {
      num_cols ++;
    }
    if (ipp->aln_col > -1)
    {
      num_cols ++;
    }
    
    ipp->ivals = CreateTagListDialogEx3 (f, rows, num_cols, spacing,
                                        interval_types, interval_widths, ipp->alists,
                                        useBar, FALSE, NULL, NULL,
                                        ipp->callbacks, callback_data, FALSE, TRUE);

    /* put back static interval_types values that may have been changed */
    interval_types [2] = TAGLIST_POPUP;
    interval_types [3] = TAGLIST_POPUP;
    interval_types [4] = TAGLIST_POPUP;
    btn_grp = HiddenGroup (m, 2, 0, NULL);
    SetGroupSpacing (btn_grp, 10, 10);
    ipp->nullsBetween = CheckBox (btn_grp, "'order' (intersperse intervals with gaps)", ChangedPartialProc);
    SetObjectExtra (ipp->nullsBetween, ipp, NULL);
    b = PushButton (btn_grp, "Insert First Location", InsertFirstLocation);
    SetObjectExtra (b, ipp->ivals, NULL);

    AlignObjects (ALIGN_CENTER, (HANDLE) ipp->ivals,
                  (HANDLE) q, (HANDLE) btn_grp, NULL);
    tlp = (TagListPtr) GetObjectExtra (ipp->ivals);
    if (tlp != NULL) {
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [0], (HANDLE) p_from, NULL);
      AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [1], (HANDLE) p_to, NULL);
      if (ipp->strand_col > -1)
      {
        AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [ipp->strand_col], (HANDLE) p1, NULL);
      }
      if (ipp->seqid_col > -1)
      {
        AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [ipp->seqid_col], (HANDLE) p2, NULL);
      }
      if (ipp->aln_col > -1)
      {
        AlignObjects (ALIGN_JUSTIFY, (HANDLE) tlp->control [ipp->aln_col], (HANDLE) p3, NULL);
      }
    }
  }

  return (DialoG) p;
}

typedef struct intervalchoice 
{
  DIALOG_MESSAGE_BLOCK
  GrouP  seq_or_aln;
  DialoG aln_dlg;
  DialoG seq_dlg;  
} IntervalChoiceData, PNTR IntervalChoicePtr;

static void IntervalChoiceCallback (Pointer userdata)
{
  IntervalChoicePtr dlg;
  SeqLocPtr         slp, slp_tmp;
  Boolean           ok_for_aln = FALSE;
  BioseqPtr         bsp;
  SeqAlignPtr       salp, salp_next;

  if (userdata == NULL)
  {
    return;
  }

  dlg = (IntervalChoicePtr) userdata;
  
  if (GetValue (dlg->seq_or_aln) == 1)
  {
    slp = DialogToPointer (dlg->seq_dlg);
    if (slp != NULL)
    {
      ok_for_aln = TRUE;
      slp_tmp = SeqLocFindNext (slp, NULL);
      while (slp_tmp != NULL && ok_for_aln)
      {
        bsp = BioseqFindFromSeqLoc (slp_tmp);
        salp = FindAlignmentsForBioseq (bsp);
        if (salp == NULL)
        {
          ok_for_aln = FALSE;
        }
        else
        {
          while (salp != NULL)
          {
            salp_next = salp->next;
            salp->next = NULL;
            salp = SeqAlignFree (salp);
            salp = salp_next;
          }
        }
        slp_tmp = SeqLocFindNext (slp, slp_tmp);
      }
    }
    if (ok_for_aln)  
    {
      Enable (dlg->seq_or_aln);
    }
    else
    {
      Disable (dlg->seq_or_aln);
    }
  }
}

static void SeqLocToIntervalChoiceEditor (DialoG d, Pointer userdata)
{
  IntervalChoicePtr dlg;
  
  dlg = (IntervalChoicePtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }
  PointerToDialog (dlg->seq_dlg, userdata);
  PointerToDialog (dlg->aln_dlg, userdata); 
  IntervalChoiceCallback (dlg);
}

static Pointer IntervalChoiceEditorToSeqLoc (DialoG d)
{
  IntervalChoicePtr dlg;
  
  dlg = (IntervalChoicePtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  if (GetValue (dlg->seq_or_aln) == 1)
  {
    return DialogToPointer (dlg->seq_dlg);
  }
  else
  {
    return DialogToPointer (dlg->aln_dlg);
  }
}


static Boolean WriteIntervalChoiceDialog (DialoG d, CharPtr filename)

{
  AsnIoPtr          aip;
  IntervalChoicePtr dlg;
  SeqLocPtr         slp;
  Char              path [PATH_MAX];
#ifdef WIN_MAC
  FILE            *f;
#endif

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  dlg = (IntervalChoicePtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (path [0] != '\0' || GetOutputFileName (path, sizeof (path), NULL)) {
#ifdef WIN_MAC
      f = FileOpen (path, "r");
      if (f != NULL) {
        FileClose (f);
      } else {
        FileCreate (path, "TEXT", "ttxt");
      }
#endif
      aip = AsnIoOpen (path, "w");
      if (aip != NULL) {
        slp = IntervalChoiceEditorToSeqLoc (dlg->dialog);
        SeqLocAsnWrite (slp, aip, NULL);
        AsnIoClose (aip);
        slp = SeqLocFree (slp);
        return TRUE;
      }
    }
  }
  return FALSE;
}


static Boolean ReadIntervalChoiceDialog (DialoG d, CharPtr filename)

{
  AsnIoPtr          aip;
  IntervalChoicePtr dlg;
  SeqLocPtr         slp;
  Char              path [PATH_MAX];

  path [0] = '\0';
  StringNCpy_0 (path, filename, sizeof (path));
  dlg = (IntervalChoicePtr) GetObjectExtra (d);
  if (dlg != NULL) {
    if (path [0] != '\0' || GetInputFileName (path, sizeof (path), "", "TEXT")) {
      aip = AsnIoOpen (path, "r");
      if (aip != NULL) {
        slp = SeqLocAsnRead (aip, NULL);
        AsnIoClose (aip);
        if (slp != NULL) {
          SeqLocToIntervalChoiceEditor (dlg->dialog, slp);
          slp = SeqLocFree (slp);
          Update ();
          return TRUE;
        }
      }
    }
  }
  return FALSE;
}


static void IntervalChoiceEditorMessage (DialoG d, Int2 mssg)

{
  IntervalChoicePtr dlg;

  dlg = (IntervalChoicePtr) GetObjectExtra (d);
  if (dlg != NULL) {  
    if (GetValue (dlg->seq_or_aln) == 1)
    {
      SendMessageToDialog (dlg->seq_dlg, mssg);
    }
    else
    {
      SendMessageToDialog (dlg->aln_dlg, mssg);
    }
  }
}

static ValNodePtr TestIntervalChoiceEditor (DialoG d)
{
  IntervalChoicePtr dlg;

  dlg = (IntervalChoicePtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  else if (GetValue (dlg->seq_or_aln) == 1)
  {
    return TestDialog (dlg->seq_dlg);
  }
  else
  {
    return TestDialog (dlg->aln_dlg);
  }
}

static void DisplayErrorMessagesOk (ButtoN b)
{
  BoolPtr pdone;
  
  pdone = (BoolPtr) GetObjectExtra (b);
  if (pdone != NULL)
  {
    *pdone = TRUE;
  }
}

extern void DisplayErrorMessages (CharPtr title, ValNodePtr err_list)
{
  WindoW     w;
  GrouP      h;
  DoC        doc;
  ButtoN     b;
  ValNodePtr vnp;
  Boolean    done = FALSE;
  
  w = MovableModalWindow(-20, -13, -10, -10, title, NULL);
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  doc = DocumentPanel (h, stdCharWidth * 27, stdLineHeight * 8);
  SetDocAutoAdjust (doc, TRUE);
  for (vnp = err_list; vnp != NULL; vnp = vnp->next)
  {
    if (!StringHasNoText (vnp->data.ptrvalue))
    {
      AppendText (doc, vnp->data.ptrvalue, NULL, NULL, programFont);
    }
  }
  b = PushButton (h, "OK", DisplayErrorMessagesOk);
  SetObjectExtra (b, &done, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) doc,
                              (HANDLE) b, 
                              NULL);
  Show (w);
  Select (w);
  while (!done)
  {
    ProcessExternalEvent ();
    Update ();
  }
  ProcessAnEvent ();
  Remove (w);
}

static void ShowIntervalChoice (IntervalChoicePtr dlg)
{
  if (dlg == NULL)
  {
    return;
  }
  if (GetValue (dlg->seq_or_aln) == 1)
  {
    Hide (dlg->aln_dlg);
    Update ();
    Show (dlg->seq_dlg);
    Update ();
  }
  else
  {
    Hide (dlg->seq_dlg);
    Update ();
    Show (dlg->aln_dlg);
    Update ();
  }
}

static void ChangeIntervalChoice (GrouP g)
{
  IntervalChoicePtr dlg;
  SeqLocPtr         slp;
  ValNodePtr        err_list = NULL;
  CharPtr           title = NULL;

  dlg = (IntervalChoicePtr) GetObjectExtra (g);
  if (dlg == NULL)
  {
    return;
  }
  if (GetValue (dlg->seq_or_aln) == 1)
  {
    title = "Unable to translate to sequence coordinates";
    err_list = TestDialog (dlg->aln_dlg);
    if (err_list == NULL)
    {
      slp = DialogToPointer (dlg->aln_dlg);
      PointerToDialog (dlg->seq_dlg, slp);
      slp = SeqLocFree (slp);
      err_list = TestDialog (dlg->seq_dlg);
    }    
    if (err_list != NULL)
    {
      SetValue (dlg->seq_or_aln, 2);       
    }
  }
  else
  {
    title = "Unable to translate to alignment coordinates";
    err_list = TestDialog (dlg->seq_dlg);
    if (err_list == NULL)
    {
      slp = DialogToPointer (dlg->seq_dlg);
      PointerToDialog (dlg->aln_dlg, slp);
      slp = SeqLocFree (slp);
      err_list = TestDialog (dlg->aln_dlg);
    }
    if (err_list != NULL)
    {
      SetValue (dlg->seq_or_aln, 1);
    }
  }
  ShowIntervalChoice (dlg);
  if (err_list != NULL)
  {
    DisplayErrorMessages (title, err_list);
    err_list = ValNodeFreeData (err_list);
  }
}

static DialoG CreateIntervalEditorDialogAlnChoice (GrouP h, CharPtr title, Uint2 rows,
                                            Int2 spacing, SeqEntryPtr sep,
                                            Boolean nucsOK, Boolean protsOK,
                                            Boolean useBar, Boolean showPartials,
                                            Boolean allowGaps, FeatureFormPtr ffp,
                                            IntEdPartialProc proc)
{
  SeqAlignPtr       salp_list = NULL;
  GrouP             p, g;
  IntervalChoicePtr dlg;
  
  VisitAnnotsInSep (sep, &salp_list, GetAlignmentsInSeqEntryCallback);
  if (salp_list == NULL)
  {
    return CreateIntervalEditorDialogExEx (h, title, rows, spacing, sep,
                                         nucsOK, protsOK, useBar, showPartials,
                                         allowGaps, ffp, proc, FALSE, TRUE,
                                         NULL, NULL, FALSE);
  }
  else
  {
    dlg = (IntervalChoicePtr) MemNew (sizeof (IntervalChoiceData));
    if (dlg == NULL)
    {
      return NULL;
    }
  
    p = HiddenGroup (h, -1, 0, NULL);
    SetGroupSpacing (p, 10, 10);
    SetObjectExtra (p, dlg, StdCleanupExtraProc);
    
    dlg->dialog = (DialoG) p;
    dlg->todialog = SeqLocToIntervalChoiceEditor;
    dlg->fromdialog = IntervalChoiceEditorToSeqLoc;
    dlg->dialogmessage = IntervalChoiceEditorMessage;
    dlg->testdialog = TestIntervalChoiceEditor;
    dlg->exportdialog = WriteIntervalChoiceDialog;
    dlg->importdialog = ReadIntervalChoiceDialog;

    g = HiddenGroup (p, 0, 0, NULL);
    dlg->seq_dlg = CreateIntervalEditorDialogExEx (g, title, rows, spacing, sep,
                                         nucsOK, protsOK, useBar, showPartials,
                                         allowGaps, ffp, proc, FALSE, TRUE,
                                         IntervalChoiceCallback, dlg, FALSE);
                                         
    dlg->aln_dlg = CreateIntervalEditorDialogExEx (g, title, rows, spacing, sep,
                                         nucsOK, protsOK, useBar, showPartials,
                                         allowGaps, ffp, proc, TRUE, TRUE,
                                         IntervalChoiceCallback, dlg, FALSE);
    AlignObjects (ALIGN_CENTER, (HANDLE)dlg->seq_dlg, (HANDLE) dlg->aln_dlg, NULL);                                         
    dlg->seq_or_aln = HiddenGroup (p, 2, 0, ChangeIntervalChoice);
    SetObjectExtra (dlg->seq_or_aln, dlg, NULL);
    RadioButton (dlg->seq_or_aln, "Sequence Coordinates");
    RadioButton (dlg->seq_or_aln, "Alignment Coordinates");
    SetValue (dlg->seq_or_aln, 1);
    
    AlignObjects (ALIGN_CENTER, (HANDLE) g, (HANDLE) dlg->seq_or_aln, NULL);
    ShowIntervalChoice (dlg);
    IntervalChoiceCallback (dlg);
    return (DialoG) p; 
  }
}

extern DialoG CreateIntervalEditorDialogEx (GrouP h, CharPtr title, Uint2 rows,
                                            Int2 spacing, SeqEntryPtr sep,
                                            Boolean nucsOK, Boolean protsOK,
                                            Boolean useBar, Boolean showPartials,
                                            Boolean allowGaps, FeatureFormPtr ffp,
                                            IntEdPartialProc proc)
{
  return CreateIntervalEditorDialogAlnChoice (h, title, rows, spacing, sep,
                                         nucsOK, protsOK, useBar, showPartials,
                                         allowGaps, ffp, proc);
}

extern DialoG CreateIntervalEditorDialog (GrouP h, CharPtr title, Uint2 rows,
                                          Int2 spacing, SeqEntryPtr sep,
                                          Boolean nucsOK, Boolean protsOK)

{
  return CreateIntervalEditorDialogEx (h, title, rows, spacing, sep,
                                       nucsOK, protsOK, TRUE, TRUE, FALSE,
                                       NULL, NULL);
}

static void ValNodePtrToVisStringDialog (DialoG d, Pointer data)

{
  ValNodePtr   head;
  Int2         j;
  ValNodePtr   list;
  CharPtr      str;
  TagListPtr   tlp;
  ValNodePtr   vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  list = (ValNodePtr) data;
  if (tlp != NULL) {
    head = NULL;
    while (list != NULL) {
      vnp = ValNodeNew (head);
      if (head == NULL) {
        head = vnp;
      }
      if (vnp != NULL) {
        str = MemNew (StringLen ((CharPtr) list->data.ptrvalue) + 3);
        if (str != NULL) {
          StringCpy (str, (CharPtr) list->data.ptrvalue);
          StringCat (str, "\n");
        }
        vnp->data.ptrvalue = str;
      }
      list = list->next;
    }
    SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
    tlp->vnp = head;
    SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
    for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
    }
    tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
    CorrectBarMax (tlp->bar, tlp->max);
    CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
  }
}

static Pointer VisStringDialogToValNodePtr (DialoG d)

{
  Char         ch;
  ValNodePtr   head;
  Int2         j;
  Int2         len;
  ValNodePtr   list;
  Boolean      okay;
  CharPtr      str;
  TagListPtr   tlp;
  ValNodePtr   vnp;

  head = NULL;
  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp != NULL && tlp->vnp != NULL) {
    list = NULL;
    for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
      str = (CharPtr) vnp->data.ptrvalue;
      okay = FALSE;
      len = StringLen (str);
      for (j = 0; j < len; j++) {
        ch = str [j];
        if (ch != ' ' && ch != '\t' && ch != '\n') {
          okay = TRUE;
        }
      }
      if (okay) {
        list = ValNodeNew (list);
        if (head == NULL) {
          head = list;
        }
        if (list != NULL) {
          list->choice = 0;
          list->data.ptrvalue = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
        }
      }
    }
  }
  return (Pointer) head;
}

static void DbtagPtrToDbtagDialog (DialoG d, Pointer data, Boolean readOnly)

{
  DbtagPtr     dp;
  ValNodePtr   head;
  Int2         j;
  size_t       len;
  ValNodePtr   list;
  ObjectIdPtr  oid;
  CharPtr      ptr;
  CharPtr      str;
  TagListPtr   tlp;
  Char         tmp [16];
  ValNodePtr   vnp;

  tlp = (TagListPtr) GetObjectExtra (d);
  list = (ValNodePtr) data;
  if (tlp != NULL) {
    head = NULL;
    while (list != NULL) {
      vnp = ValNodeNew (head);
      if (head == NULL) {
        head = vnp;
      }
      if (vnp != NULL) {
        dp = (DbtagPtr) list->data.ptrvalue;
        if (dp != NULL && dp->db != NULL && dp->tag != NULL) {
          oid = dp->tag;
          ptr = NULL;
          if (oid->str != NULL) {
            ptr = oid->str;
            if (StringNCmp (ptr, "HGNC:", 5) == 0) {
              ptr += 5;
            } else if (StringNCmp (ptr, "MGI:", 4) == 0) {
              ptr += 4;
            }
          } else {
            sprintf (tmp, "%ld", (long) oid->id);
            ptr = tmp;
          }
          len = StringLen (dp->db) + StringLen (ptr);
          str = MemNew (len + 4);
          if (str != NULL) {
            StringCpy (str, dp->db);
            StringCat (str, "\t");
            StringCat (str, ptr);
            StringCat (str, "\n");
          }
          vnp->data.ptrvalue = str;
        }
      }
      list = list->next;
    }
    SendMessageToDialog (tlp->dialog, VIB_MSG_RESET);
    tlp->vnp = head;
    SendMessageToDialog (tlp->dialog, VIB_MSG_REDRAW);
    for (j = 0, vnp = tlp->vnp; vnp != NULL; j++, vnp = vnp->next) {
    }
    if (readOnly) {
      tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows));
      CorrectBarMax (tlp->bar, tlp->max);
      CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
      if (tlp->max > 0) {
        SafeShow (tlp->bar);
      } else {
        SafeHide (tlp->bar);
      }
    } else {
      tlp->max = MAX ((Int2) 0, (Int2) (j - tlp->rows + 1));
      CorrectBarMax (tlp->bar, tlp->max);
      CorrectBarPage (tlp->bar, tlp->rows - 1, tlp->rows - 1);
    }
  }
}

static void DbtagPtrToRODbtagDialog (DialoG d, Pointer data)

{
  DbtagPtrToDbtagDialog (d, data, TRUE);
}

static void DbtagPtrToRWDbtagDialog (DialoG d, Pointer data)

{
  DbtagPtrToDbtagDialog (d, data, FALSE);
}

static Pointer DbtagDialogToDbtagPtr (DialoG d)

{
  Boolean      alldigits;
  Char         ch;
  DbtagPtr     dp;
  ValNodePtr   head;
  Int2         j;
  Boolean      leadingzero;
  Int2         len;
  ValNodePtr   list;
  Boolean      notallzero;
  Int2         num_digits;
  ObjectIdPtr  oid;
  Boolean      okay;
  CharPtr      ptr;
  CharPtr      str;
  TagListPtr   tlp;
  CharPtr      tmp;
  long int     val;
  ValNodePtr   vnp;

  head = NULL;
  tlp = (TagListPtr) GetObjectExtra (d);
  if (tlp != NULL && tlp->vnp != NULL) {
    list = NULL;
    for (vnp = tlp->vnp; vnp != NULL; vnp = vnp->next) {
      str = (CharPtr) vnp->data.ptrvalue;
      okay = FALSE;
      len = StringLen (str);
      for (j = 0; j < len; j++) {
        ch = str [j];
        if (ch != ' ' && ch != '\t' && ch != '\n') {
          okay = TRUE;
        }
      }
      if (okay) {
        list = ValNodeNew (list);
        if (head == NULL) {
          head = list;
        }
        if (list != NULL) {
          list->choice = 0;
          dp = DbtagNew ();
          list->data.ptrvalue = (Pointer) dp;
          if (dp != NULL) {
            dp->db = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 0);
            oid = ObjectIdNew ();
            dp->tag = oid;
            if (oid != NULL) {
              tmp = ExtractTagListColumn ((CharPtr) vnp->data.ptrvalue, 1);
              TrimSpacesAroundString (tmp);
              if (tmp != NULL) {
                SetObjectIdString(oid, tmp, ExistingTextOption_replace_old);
              } else {
                oid->str = StringSave ("?");
              }
            }
          }
        }
      }
    }
  }
  return (Pointer) head;
}

Uint2 visstring_types [] = {
  TAGLIST_TEXT, TAGLIST_TEXT
};

Uint2 readonlystring_types [] = {
  TAGLIST_PROMPT, TAGLIST_PROMPT
};

Uint2 visstring_widths [] = {
  0, 0, 0, 0
};

extern DialoG CreateVisibleStringDialog (GrouP h, Uint2 rows,
                                         Int2 spacing, Int2 width)

{
  visstring_widths [0] = width;
  return CreateTagListDialog (h, rows, 1, spacing,
                              visstring_types, visstring_widths, NULL,
                              ValNodePtrToVisStringDialog,
                              VisStringDialogToValNodePtr);
}

extern DialoG CreateDbtagDialog (GrouP h, Uint2 rows, Int2 spacing,
                                 Int2 width1, Int2 width2)

{
  DialoG      d;
  TagListPtr  tlp;

  visstring_widths [0] = width1;
  visstring_widths [1] = width2;
  if (GetAppProperty ("ReadOnlyDbTags") == NULL) {
    return CreateTagListDialog (h, rows, 2, spacing,
                                visstring_types, visstring_widths, NULL,
                                DbtagPtrToRWDbtagDialog,
                                DbtagDialogToDbtagPtr);
  } else {
    d = CreateTagListDialog (h, rows, 2, spacing,
                             readonlystring_types, visstring_widths, NULL,
                             DbtagPtrToRODbtagDialog,
                             DbtagDialogToDbtagPtr);
    tlp = (TagListPtr) GetObjectExtra (d);
    if (tlp != NULL) {
      Hide (tlp->bar);
    }
    return d;
  }
}

/* ValueList Editor */
static CharPtr combine_strings (CharPtr s, CharPtr add)
{
  CharPtr total;
  if (StringHasNoText (add))
  {
    return s;
  }
  if (StringHasNoText (s))
  {
    s = MemFree (s);
    s = StringSave (add);
  }
  else
  {
    total = (CharPtr) MemNew (sizeof (Char) * (StringLen (s) + StringLen (add) + 2));
    sprintf (total, "%s;%s", s, add);
    s = MemFree (s);
    s = total;
  }
  return s;
}



typedef struct simpletextvalueedit {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback change_notify;
  Pointer         change_userdata;
  TexT text;
} SimpleTextValueEditData, PNTR SimpleTextValueEditPtr;

static void SimpleTextValueEditChange (TexT t)
{
  SimpleTextValueEditPtr dlg;
  dlg = (SimpleTextValueEditPtr) GetObjectExtra (t);
  if (dlg != NULL && dlg->change_notify != NULL)
  {
    (dlg->change_notify) (dlg->change_userdata);
  }
}

static void StringToSimpleTextValueEditDialog (DialoG d, Pointer data)
{
  SimpleTextValueEditPtr dlg;
  dlg = (SimpleTextValueEditPtr) GetObjectExtra (d);
 
  if (dlg != NULL)
  {
    SetTitle (dlg->text, (CharPtr) data);
    if (dlg->change_notify != NULL)
    {
      (dlg->change_notify) (dlg->change_userdata);
    }
  }
}


static Pointer SimpleTextValueEditDialogToString (DialoG d)
{
  SimpleTextValueEditPtr dlg;
  dlg = (SimpleTextValueEditPtr) GetObjectExtra (d);
 
  if (dlg != NULL)
  {
    return SaveStringFromText (dlg->text);
  }
  else
  {
    return NULL;
  }
}

static DialoG SimpleTextValueEditDialog (GrouP h, Int2 width, ValueListParentPtr parent, TaglistCallback change_notify, Pointer change_userdata)
{
  SimpleTextValueEditPtr dlg;
  GrouP           p;

  p = HiddenGroup (h, -1, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (SimpleTextValueEditPtr) MemNew (sizeof(SimpleTextValueEditData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToSimpleTextValueEditDialog;
  dlg->fromdialog = SimpleTextValueEditDialogToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->text = DialogText (p, "", width, SimpleTextValueEditChange);
  SetObjectExtra (dlg->text, dlg, NULL);
  return (DialoG) p;
}

#define STRUCTURED_VALUE_EDIT_FIELDS \
  DIALOG_MESSAGE_BLOCK \
  TaglistCallback     change_notify; \
  Pointer             change_userdata; \
  GrouP               unparsable; \
  TexT                text; \
  ValueListParentPtr  parent; \
  DialoG              dlg; \
  ParseOK             parse_func;

typedef struct structuredvalueeditdlg {
  STRUCTURED_VALUE_EDIT_FIELDS
} StructuredValueEditDlgData, PNTR StructuredValueEditDlgPtr;


static void CopyUnparsableToNote (ButtoN b)
{
  StructuredValueEditDlgPtr dlg;
  CharPtr                   old_note, new_note;

  dlg = (StructuredValueEditDlgPtr) GetObjectExtra (b);
 
  if (dlg != NULL && dlg->parent != NULL && dlg->parent->note != NULL)
  {
    old_note = SaveStringFromText (dlg->parent->note);
    new_note = SaveStringFromText (dlg->text);
    old_note = combine_strings (old_note, new_note);
    SetTitle (dlg->parent->note, old_note); 
    new_note = MemFree (new_note);
    old_note = MemFree (old_note);
    SetTitle (dlg->text, "");
    Hide (dlg->unparsable);
    if (dlg != NULL && dlg->change_notify != NULL)
    {
      (dlg->change_notify) (dlg->change_userdata);
    }
  }
}


static void EraseUnparsable (ButtoN b)
{
  StructuredValueEditDlgPtr dlg;

  dlg = (StructuredValueEditDlgPtr) GetObjectExtra (b);

  if (dlg != NULL)
  {
    SetTitle (dlg->text, "");
    Hide (dlg->unparsable);
    if (dlg != NULL && dlg->change_notify != NULL)
    {
      (dlg->change_notify) (dlg->change_userdata);
    }
  }
}


static void StringToStructuredValueEditDialog (DialoG d, Pointer data)
{
  StructuredValueEditDlgPtr dlg;
  CharPtr                   txt;

  dlg = (StructuredValueEditDlgPtr) GetObjectExtra (d);

  txt = (CharPtr) data;
 
  if (dlg != NULL)
  {
    if (StringHasNoText (txt) || (dlg->parse_func != NULL && (dlg->parse_func) (txt)))
    {
      PointerToDialog (dlg->dlg, txt);
      SetTitle (dlg->text, "");
      Hide (dlg->unparsable);
    }
    else
    {
      SetTitle (dlg->text, (CharPtr) data);
      Show (dlg->unparsable);
    }
    if (dlg->change_notify != NULL)
    {
      (dlg->change_notify) (dlg->change_userdata);
    }
  }
}


static Pointer StructuredValueEditDialogToString (DialoG d)
{
  StructuredValueEditDlgPtr dlg;
  CharPtr                   txt = NULL, tmp;

  dlg = (StructuredValueEditDlgPtr) GetObjectExtra (d);

  if (dlg != NULL)
  {
    if (dlg->dlg == NULL)
    {
      txt = StringSave ("");
    }
    else
    {
      txt = DialogToPointer (dlg->dlg);
    }
    
    if (!TextHasNoText (dlg->text))
    {
      tmp = SaveStringFromText (dlg->text);
      txt = combine_strings (txt, tmp);
      tmp = MemFree (tmp);
    }
  }
  return txt;

}


static void StructuredValueEditChange (TexT t)
{
  StructuredValueEditDlgPtr dlg;
  CharPtr                    txt, tmp;

  dlg = (StructuredValueEditDlgPtr) GetObjectExtra (t);

  if (TextHasNoText (dlg->text))
  {
    Hide (dlg->unparsable);
  }
  else if (dlg->dlg != NULL)
  {
    txt = SaveStringFromText(dlg->text);
    tmp = DialogToPointer (dlg->dlg);
    if (StringHasNoText (tmp) && dlg->parse_func != NULL && (dlg->parse_func)(txt))
    {
      PointerToDialog (dlg->dlg, txt);
      SetTitle (dlg->text, "");
      Hide (dlg->unparsable);
    }
    txt = MemFree (txt);
    tmp = MemFree (tmp);
  }

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



typedef struct truefalsevalueedit {
  STRUCTURED_VALUE_EDIT_FIELDS
} TrueFalseValueEditData, PNTR TrueFalseValueEditPtr;

static DialoG TrueFalseValueEditDialog (GrouP h, Int2 width, ValueListParentPtr parent, TaglistCallback change_notify, Pointer change_userdata)
{
  TrueFalseValueEditPtr dlg;
  GrouP           p;
  ButtoN          b;

  p = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (TrueFalseValueEditPtr) MemNew (sizeof(TrueFalseValueEditData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToStructuredValueEditDialog;
  dlg->fromdialog = StructuredValueEditDialogToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->parent = parent;

  StaticPrompt (p, "TRUE", 0, 0, programFont, 'c');

  dlg->unparsable = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (dlg->unparsable, 10, 10);
  dlg->text = DialogText (dlg->unparsable, "", width - 15, StructuredValueEditChange);
  SetObjectExtra (dlg->text, dlg, NULL);
 
  b = PushButton (dlg->unparsable, "Copy to Note", CopyUnparsableToNote);
  SetObjectExtra (b, dlg, NULL);

  b = PushButton (dlg->unparsable, "Erase", EraseUnparsable);
  SetObjectExtra (b, dlg, NULL);

  Hide (dlg->unparsable);

  return (DialoG) p;
}


typedef struct latlondlg {
  DIALOG_MESSAGE_BLOCK

  PopuP dir_ns;
  PopuP dir_ew;
  TexT  deg_ns;
  TexT  deg_ew;

  TaglistCallback change_notify;
  Pointer change_userdata;

} LatLonDlgData, PNTR LatLonDlgPtr;


static void ResetLatLonDlg (LatLonDlgPtr dlg)
{
  if (dlg == NULL) return;

  SetTitle (dlg->deg_ns, "");
  SetValue (dlg->dir_ns, 1);
  SetTitle (dlg->deg_ew, "");
  SetValue (dlg->dir_ew, 1);
}


static void StringToLatLonDlg (DialoG d, Pointer data)
{
  LatLonDlgPtr dlg;
  CharPtr      str, ns, ew, tmp;
  Int4         len;

  dlg = (LatLonDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  str = (CharPtr) data;
  if (str == NULL)
  {
    ResetLatLonDlg (dlg);
    return;
  }

  ew = str + StringLen (str) - 1;
  if (*ew != 'E' && *ew != 'W')
  {
    ResetLatLonDlg (dlg);
    return;
  }
  
  ns = StringChr (str, 'N');
  if (ns == NULL)
  {
    ns = StringChr (str, 'S');
  }
  if (ns == NULL)
  {
    ResetLatLonDlg (dlg);
    return;
  }

  len = ns - str + 1;
  tmp = (CharPtr) MemNew (sizeof (Char) * len);
  StringNCpy (tmp, str, len - 1);
  tmp [len - 1] = 0;
  TrimSpacesAroundString (tmp);
  SetTitle (dlg->deg_ns, tmp);
  tmp = MemFree (tmp);
  SetValue (dlg->dir_ns, *ns == 'N' ? 1 : 2);
  
  len = ew - ns;
  tmp = (CharPtr) MemNew (sizeof (Char) * len);
  StringNCpy (tmp, ns + 1, len - 1);
  tmp [len - 1] = 0;
  TrimSpacesAroundString (tmp);
  SetTitle (dlg->deg_ew, tmp);
  tmp = MemFree (tmp);
  SetValue (dlg->dir_ew, *ew == 'E' ? 1 : 2);
  
}


static Pointer LatLonDlgToString (DialoG d)
{
  LatLonDlgPtr dlg;
  CharPtr      str, ns, ew;
  Int4         len;

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

  if (TextHasNoText (dlg->deg_ns) && TextHasNoText (dlg->deg_ew))
  {
    return NULL;
  }  

  ns = SaveStringFromText (dlg->deg_ns);
  ew = SaveStringFromText (dlg->deg_ew);
  len = StringLen (ns) + StringLen (ew) + 6;
  str = (CharPtr) MemNew (sizeof(Char) * len);
  sprintf (str, "%s %c %s %c", 
           ns == NULL ? "" : ns,
           GetValue (dlg->dir_ns) == 1 ? 'N' : 'S',
           ew == NULL ? "" : ew,
           GetValue (dlg->dir_ew) == 1 ? 'E' : 'W');
  ns = MemFree (ns);
  ew = MemFree (ew);
  return str;
}

static void LatLonTextChange (TexT t)
{
  LatLonDlgPtr dlg;

  dlg = (LatLonDlgPtr) GetObjectExtra (t);
  if (dlg == NULL) return;


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


static void LatLonPopupChange (PopuP p)
{
  LatLonDlgPtr dlg;

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


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


static DialoG LatLonDialog (GrouP h, TaglistCallback change_notify, Pointer change_userdata)
{
  LatLonDlgPtr dlg;
  GrouP        p;

  p = HiddenGroup (h, 4, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (LatLonDlgPtr) MemNew (sizeof(LatLonDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToLatLonDlg;
  dlg->fromdialog = LatLonDlgToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->deg_ns = DialogText (p, "", 3, LatLonTextChange);
  SetObjectExtra (dlg->deg_ns, dlg, NULL);
  dlg->dir_ns = PopupList (p, TRUE, LatLonPopupChange);
  SetObjectExtra (dlg->dir_ns, dlg, NULL);
  PopupItem (dlg->dir_ns, "N");
  PopupItem (dlg->dir_ns, "S");
  SetValue (dlg->dir_ns, 1);

  dlg->deg_ew = DialogText (p, "", 3, LatLonTextChange);
  SetObjectExtra (dlg->deg_ew, dlg, NULL);
  dlg->dir_ew = PopupList (p, TRUE, LatLonPopupChange);
  SetObjectExtra (dlg->dir_ew, dlg, NULL);
  PopupItem (dlg->dir_ew, "E");
  PopupItem (dlg->dir_ew, "W");

  SetValue (dlg->dir_ew, 1);
  
  return (DialoG) p;
}


static Boolean ParseLatLonOk (CharPtr str)
{
  CharPtr ns, ew, cp;

  if (StringHasNoText (str))
  {
    return TRUE;
  }
  ew = str + StringLen (str) - 1;
  if (*ew != 'E' && *ew != 'W')
  {
    return FALSE;
  }
  ns = str;
  while (ns < ew && !isalpha (*ns))
  {
    ns++;
  }
  if (*ns != 'N' && *ns != 'S')
  {
    return FALSE;
  }

  cp = ns + 1;
  while (cp < ew)
  {
    if (isalpha (*cp))
    {
      return FALSE;
    }
    cp++;
  }
  return TRUE;
}


static DialoG ValueListLatLonDialog (GrouP h, Int2 width, ValueListParentPtr parent, TaglistCallback change_notify, Pointer change_userdata)
{
  StructuredValueEditDlgPtr dlg;
  GrouP           p;
  ButtoN          b;


  p = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (StructuredValueEditDlgPtr) MemNew (sizeof(StructuredValueEditDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToStructuredValueEditDialog;
  dlg->fromdialog = StructuredValueEditDialogToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->parent = parent;
 
  dlg->dlg = LatLonDialog (p, change_notify, change_userdata);
  dlg->parse_func = ParseLatLonOk;
  
  dlg->unparsable = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (dlg->unparsable, 10, 10);
  dlg->text = DialogText (dlg->unparsable, "", width - 15, StructuredValueEditChange);
  SetObjectExtra (dlg->text, dlg, NULL);
 
  b = PushButton (dlg->unparsable, "Copy to Note", CopyUnparsableToNote);
  SetObjectExtra (b, dlg, NULL);

  b = PushButton (dlg->unparsable, "Erase", EraseUnparsable);
  SetObjectExtra (b, dlg, NULL);

  Hide (dlg->unparsable);

  return (DialoG) p;
}


typedef struct specimenvoucherdlg {
  DIALOG_MESSAGE_BLOCK

  TexT  institution_code;
  TexT  collection_code;
  TexT  free_text;

  TaglistCallback change_notify;
  Pointer change_userdata;

} SpecimenVoucherDlgData, PNTR SpecimenVoucherDlgPtr;


static void ResetSpecimenVoucherDlg (SpecimenVoucherDlgPtr dlg)
{
  if (dlg == NULL) return;

  SetTitle (dlg->institution_code, "");
  SetTitle (dlg->collection_code, "");
  SetTitle (dlg->free_text, "");
}


static void StringToSpecimenVoucherDlg (DialoG d, Pointer data)
{
  SpecimenVoucherDlgPtr dlg;
  CharPtr      str, ptr, cp;

  dlg = (SpecimenVoucherDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  ResetSpecimenVoucherDlg (dlg);
  str = (CharPtr) data;
  if (str == NULL)
  {
    return;
  }

  /* make copy so we don't worry about changing data */
  str = StringSave (str);

  cp = StringChr (str, ':');
  if (cp == NULL)
  {
    SetTitle (dlg->free_text, str);
  }
  else 
  {
    *cp = 0;
    SetTitle (dlg->institution_code, str);
    ptr = cp + 1;
    cp = StringChr (ptr, ':');
    if (cp == NULL) 
    {
      SetTitle (dlg->free_text, ptr);
    }
    else 
    {
      *cp = 0;
      SetTitle (dlg->collection_code, ptr);
      SetTitle (dlg->free_text, cp + 1);
    }
  }
  str = MemFree (str);
}


static Pointer SpecimenVoucherDlgToString (DialoG d)
{
  SpecimenVoucherDlgPtr dlg;
  CharPtr      str;
  Int4         len;
  CharPtr      inst, coll, free_text;

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

  if (TextHasNoText (dlg->institution_code) && TextHasNoText (dlg->collection_code) && TextHasNoText (dlg->free_text))
  {
    return NULL;
  }  

  inst = SaveStringFromText (dlg->institution_code);
  coll = SaveStringFromText (dlg->collection_code);
  free_text = SaveStringFromText (dlg->free_text);

  len = StringLen (inst) + StringLen (coll) + StringLen (free_text) + 3;
  str = (CharPtr) MemNew (sizeof(Char) * len);
  if (StringHasNoText (inst) && StringHasNoText (coll))
  {
    sprintf (str, "%s", free_text);
  }
  else if (StringHasNoText (coll))
  {
    sprintf (str, "%s:%s", inst, free_text == NULL ? "" : free_text);
  }
  else
  {
    sprintf (str, "%s:%s:%s", inst == NULL ? "" : inst,
                              coll == NULL ? "" : coll,
                              free_text == NULL ? "" : free_text);
  }
  inst = MemFree (inst);
  coll = MemFree (coll);
  free_text = MemFree (free_text);
  return str;
}

static void SpecimenVoucherTextChange (TexT t)
{
  SpecimenVoucherDlgPtr dlg;

  dlg = (SpecimenVoucherDlgPtr) GetObjectExtra (t);
  if (dlg == NULL) return;


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


static DialoG SpecimenVoucherDialog (GrouP h, Int2 width, ValueListParentPtr parent, TaglistCallback change_notify, Pointer change_userdata)
{
  SpecimenVoucherDlgPtr dlg;
  GrouP        p;

  p = HiddenGroup (h, 6, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (SpecimenVoucherDlgPtr) MemNew (sizeof(SpecimenVoucherDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToSpecimenVoucherDlg;
  dlg->fromdialog = SpecimenVoucherDlgToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  StaticPrompt (p, "Inst", 0, 0, programFont, 'r');
  dlg->institution_code = DialogText (p, "", 4, SpecimenVoucherTextChange);
  SetObjectExtra (dlg->institution_code, dlg, NULL);
  StaticPrompt (p, "Coll", 0, 0, programFont, 'r');
  dlg->collection_code = DialogText (p, "", 4, SpecimenVoucherTextChange);
  SetObjectExtra (dlg->collection_code, dlg, NULL);

  StaticPrompt (p, "SpecID/Text", 0, 0, programFont, 'r');
  dlg->free_text = DialogText (p, "", width - 8, SpecimenVoucherTextChange);
  SetObjectExtra (dlg->free_text, dlg, NULL);
  
  return (DialoG) p;
}


static Boolean ParseSpecimenVoucherOk (CharPtr str)
{
  return TRUE;
}


typedef struct altitudedlg {
  DIALOG_MESSAGE_BLOCK

  TexT  val;

  TaglistCallback change_notify;
  Pointer change_userdata;

} AltitudeDlgData, PNTR AltitudeDlgPtr;


static void ResetAltitudeDlg (AltitudeDlgPtr dlg)
{
  if (dlg == NULL) return;

  SetTitle (dlg->val, "");
}


static void StringToAltitudeDlg (DialoG d, Pointer data)
{
  AltitudeDlgPtr dlg;
  CharPtr      str, stp, tmp;
  Int4         len;

  dlg = (AltitudeDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  str = (CharPtr) data;
  if (str == NULL)
  {
    ResetAltitudeDlg (dlg);
    return;
  }

  stp = StringChr (str, 'm');
  if (stp == NULL) {
    ResetAltitudeDlg (dlg);
    return;
  }

  len = stp - str + 1;
  tmp = (CharPtr) MemNew (sizeof (Char) * len);
  StringNCpy (tmp, str, len - 1);
  tmp [len - 1] = 0;
  TrimSpacesAroundString (tmp);
  SetTitle (dlg->val, tmp);
  tmp = MemFree (tmp);
}


static Boolean ParseAltitudeNumberOnly (CharPtr str)
{
  CharPtr cp;
  Boolean any_digit = FALSE;

  if (StringHasNoText (str))
  {
    return TRUE;
  }
  cp = str;
  if (*cp == '-') {
    /* allow negative */
    cp++;
  }
  while (isdigit(*cp)) {
    any_digit = TRUE;
    cp++;
  }
  if (*cp == '.') {
    cp++;
    while (isdigit(*cp)) {
      cp++;
      any_digit = TRUE;
    }
  }
  if (!any_digit || *cp != 0) {
    return FALSE;
  }
  return TRUE;
}


static Pointer AltitudeDlgToString (DialoG d)
{
  AltitudeDlgPtr dlg;
  CharPtr      str, val;
  CharPtr      m = " m.";
  Int4         len;

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

  if (TextHasNoText (dlg->val))
  {
    return NULL;
  }  

  val = SaveStringFromText (dlg->val);
  if (ParseAltitudeNumberOnly(val)) {
    len = StringLen (val) + StringLen (m) + 1;
    str = (CharPtr) MemNew (sizeof(Char) * len);
    sprintf (str, "%s%s", val, m);
    val = MemFree (val);
  } else {
    str = val;
  }
  return str;
}

static void AltitudeTextChange (TexT t)
{
  AltitudeDlgPtr dlg;

  dlg = (AltitudeDlgPtr) GetObjectExtra (t);
  if (dlg == NULL) return;


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


static DialoG AltitudeDialog (GrouP h, TaglistCallback change_notify, Pointer change_userdata)
{
  AltitudeDlgPtr dlg;
  GrouP        p;

  p = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (AltitudeDlgPtr) MemNew (sizeof(AltitudeDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToAltitudeDlg;
  dlg->fromdialog = AltitudeDlgToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;

  dlg->val = DialogText (p, "", 3, AltitudeTextChange);
  SetObjectExtra (dlg->val, dlg, NULL);
  StaticPrompt (p, "meters", 0, 0, systemFont, 'l');

  return (DialoG) p;
}


static Boolean ParseAltitudeOk (CharPtr str)
{
  CharPtr cp;
  Boolean any_digit = FALSE;

  if (StringHasNoText (str))
  {
    return TRUE;
  }
  cp = str;
  if (*cp == '-') {
    /* allow negative */
    cp++;
  }
  while (isdigit(*cp)) {
    any_digit = TRUE;
    cp++;
  }
  if (*cp == '.') {
    cp++;
    while (isdigit(*cp)) {
      cp++;
      any_digit = TRUE;
    }
  }
  if (!any_digit) {
    return FALSE;
  }
  while (isspace(*cp)) {
    cp++;
  }
  if (StringCmp (cp, "m") == 0
      || StringCmp (cp, "m.") == 0
      || StringCmp (cp, "meters") == 0
      || StringCmp (cp, "metres") == 0) {
    return TRUE;
  } else {
    return FALSE;
  }
}


static DialoG ValueListAltitudeDialog (GrouP h, Int2 width, ValueListParentPtr parent, TaglistCallback change_notify, Pointer change_userdata)
{
  StructuredValueEditDlgPtr dlg;
  GrouP           p;
  ButtoN          b;


  p = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (StructuredValueEditDlgPtr) MemNew (sizeof(StructuredValueEditDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = StringToStructuredValueEditDialog;
  dlg->fromdialog = StructuredValueEditDialogToString;
  dlg->testdialog = NULL;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->parent = parent;
 
  dlg->dlg = AltitudeDialog (p, change_notify, change_userdata);
  dlg->parse_func = ParseAltitudeOk;
  
  dlg->unparsable = HiddenGroup (p, 3, 0, NULL);
  SetGroupSpacing (dlg->unparsable, 10, 10);
  dlg->text = DialogText (dlg->unparsable, "", width - 15, StructuredValueEditChange);
  SetObjectExtra (dlg->text, dlg, NULL);
 
  b = PushButton (dlg->unparsable, "Copy to Note", CopyUnparsableToNote);
  SetObjectExtra (b, dlg, NULL);

  b = PushButton (dlg->unparsable, "Erase", EraseUnparsable);
  SetObjectExtra (b, dlg, NULL);

  Hide (dlg->unparsable);

  return (DialoG) p;
}


typedef DialoG (*MakeValueEditDialogFunc) PROTO ((GrouP, Int2, ValueListParentPtr, TaglistCallback, Pointer));

static MakeValueEditDialogFunc value_edit_dialog_list[] = {
  SimpleTextValueEditDialog,
  TrueFalseValueEditDialog,
  ValueListLatLonDialog,
  SpecimenVoucherDialog,
  ValueListAltitudeDialog
};


extern NameValuePairPtr NameValuePairFree (NameValuePairPtr nvp)
{
  if (nvp != NULL)
  {
    nvp->name_vnp = ValNodeFreeData (nvp->name_vnp);
    nvp->value = MemFree (nvp->value);
    nvp = MemFree (nvp);
  }
  return nvp;
}


extern NameValuePairPtr NameValuePairCopy (NameValuePairPtr nvp)
{
  NameValuePairPtr cpy = NULL;

  if (nvp != NULL)
  {
    cpy = (NameValuePairPtr) MemNew (sizeof (NameValuePairData));
    if (nvp->name_vnp != NULL)
    {
      cpy->name_vnp = ValNodeNew (NULL);
      cpy->name_vnp->choice = nvp->name_vnp->choice;
      cpy->name_vnp->data.ptrvalue = StringSave (nvp->name_vnp->data.ptrvalue);
    }
    cpy->value = StringSave (nvp->value);
  }
  return cpy;
}


extern ValNodePtr NameValuePairListFree (ValNodePtr vnp)
{
  ValNodePtr vnp_next;

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


typedef struct valuelistrowdialog {
  DIALOG_MESSAGE_BLOCK

  TaglistCallback change_notify;
  Pointer         change_userdata;
  DialoG          parent_dlg;
  DialoG          name_dlg;
  DialoG          editors[eNumValueEditors];
  Int4            current_editor;
} ValueListRowDialogData, PNTR ValueListRowDialogPtr;


static void ChangeValueListRowName (Pointer data)
{
  ValueListRowDialogPtr dlg;
  ValNodePtr            vnp;
  Int4                  i;
  CharPtr               value = NULL;

  dlg = (ValueListRowDialogPtr) data;
  if (dlg == NULL) return;

  if (dlg->current_editor > -1)
  {
    value = (CharPtr) DialogToPointer (dlg->editors[dlg->current_editor]);
  }
  for (i = 0; i < eNumValueEditors; i++)
  {
    Hide (dlg->editors[i]);
  }
  vnp = (ValNodePtr) DialogToPointer (dlg->name_dlg);
  if (vnp != NULL && vnp->choice > 0)
  {
    dlg->current_editor = vnp->choice - 1;
    Show (dlg->editors[dlg->current_editor]);
    PointerToDialog (dlg->editors[dlg->current_editor], value);
    vnp = ValNodeFree (vnp);
  } else {
    dlg->current_editor = -1;
  }
  value = MemFree (value);

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


static void NameValuePairToDialog (DialoG d, Pointer data)
{
  ValueListRowDialogPtr dlg;
  NameValuePairPtr      nvp;
  ValNode               vn;
  Int4                  i;

  dlg = (ValueListRowDialogPtr) GetObjectExtra (d);
  nvp = (NameValuePairPtr) data;

  if (dlg == NULL) return;
  if (nvp == NULL)
  {
    vn.next = NULL;
    vn.choice = 0;
    vn.data.ptrvalue = " ";
    PointerToDialog (dlg->name_dlg, &vn);
    for (i = 0; i < eNumValueEditors; i++)
    {
      PointerToDialog (dlg->editors[i], NULL);
    }
    ChangeValueListRowName (dlg);
  }
  else
  {
    PointerToDialog (dlg->name_dlg, nvp->name_vnp);
    ChangeValueListRowName (dlg);
    if (dlg->current_editor > -1)
    {
      PointerToDialog (dlg->editors[dlg->current_editor], nvp->value);
      if (dlg->change_notify != NULL)
      {
        (dlg->change_notify) (dlg->change_userdata);
      }
    }
  }    
}


static Pointer DialogToNameValuePair (DialoG d)
{
  ValueListRowDialogPtr dlg;
  NameValuePairPtr      nvp;
  ValNodePtr            name_vnp;

  dlg = (ValueListRowDialogPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->current_editor < 0) return NULL;

  name_vnp = DialogToPointer (dlg->name_dlg);
  if (name_vnp == NULL) return NULL;

  nvp = (NameValuePairPtr) MemNew (sizeof (NameValuePairData));
  nvp->name_vnp = name_vnp;
  nvp->value = DialogToPointer (dlg->editors[dlg->current_editor]);
  return nvp;
}


static ValNodePtr CopyChoiceList (ValNodePtr orig)
{
  ValNodePtr vnp, cpy = NULL, prev = NULL;

  while (orig != NULL)
  {
    vnp = ValNodeNew (NULL);
    vnp->choice = orig->choice;
    vnp->data.ptrvalue = StringSave (orig->data.ptrvalue);
    if (prev == NULL)
    {
      cpy = vnp;
    }
    else
    {
      prev->next = vnp;
    }
    prev = vnp;
    orig = orig->next;
  }
  return cpy;
}

static ValNodePtr TestValueListRowDialog (DialoG d)
{
  ValueListRowDialogPtr dlg;
  ValNodePtr            name_vnp, err_list = NULL;
  CharPtr               val, err_str;
  CharPtr               err_fmt = "No modifier type selected for data %s";

  dlg = (ValueListRowDialogPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->current_editor < 0) return NULL;

  val = DialogToPointer (dlg->editors[dlg->current_editor]);
  if (!StringHasNoText (val))
  {
    name_vnp = DialogToPointer (dlg->name_dlg);
    if (name_vnp == NULL || name_vnp->choice == 0)
    {
      err_str = (CharPtr) MemNew (sizeof (Char) * (StringLen (err_fmt) + StringLen (val)));
      sprintf (err_str, err_fmt, val);
      err_list = ValNodeNew(NULL);
      err_list->choice = 0;     
      err_list->data.ptrvalue = err_str;
    }
    name_vnp = ValNodeFree (name_vnp);
  }
  val = MemFree (val);
  return err_list;
}


static void ClearValueListRow (ButtoN b)
{
  ValueListRowDialogPtr dlg;

  dlg = (ValueListRowDialogPtr) GetObjectExtra (b);

  if (dlg != NULL)
  {  
    PointerToDialog (dlg->dialog, NULL);
    if (dlg->change_notify != NULL) 
    {
      (dlg->change_notify) (dlg->change_userdata);
    }
    SendMessageToDialog (dlg->parent_dlg, VIB_MSG_REDRAW);
  }
}
 
 
static DialoG ValueListRowDialog (GrouP h, Int2 width, ValNodePtr choice_list, ValueListParentPtr parent,
                                  TaglistCallback change_notify, Pointer change_userdata, DialoG parent_dlg)
{
  ValueListRowDialogPtr dlg;
  GrouP           p, g;
  Int4            i;
  ValNode         vn;
  ButtoN          b;

  p = HiddenGroup (h, 3, 0, NULL);
  SetGroupSpacing (p, 2, 2);
  dlg = (ValueListRowDialogPtr) MemNew (sizeof(ValueListRowDialogData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = NameValuePairToDialog;
  dlg->fromdialog = DialogToNameValuePair;
  dlg->testdialog = TestValueListRowDialog;
  dlg->dialogmessage = NULL;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  dlg->parent_dlg = parent_dlg;
  
  dlg->name_dlg = ValNodeSelectionDialog (p, CopyChoiceList(choice_list), 1, 
                                     ValNodeStringName,
                                     ValNodeSimpleDataFree, ValNodeStringCopy,
                                     ValNodeStringMatch, NULL,
                                     ChangeValueListRowName, dlg, FALSE);
  vn.next = NULL;
  vn.choice = 0;
  vn.data.ptrvalue = " ";
  PointerToDialog (dlg->name_dlg, &vn);

  g = HiddenGroup (p, 0, 0, NULL);
  for (i = 0; i < eNumValueEditors; i++)
  {
    dlg->editors[i] = (value_edit_dialog_list[i]) (g, width, parent, change_notify, change_userdata);
  }

  b = PushButton (p, "X", ClearValueListRow);
  SetObjectExtra (b, dlg, NULL);

  ChangeValueListRowName (dlg);
    
  return (DialoG) p;
}

typedef struct valuelistdialog {
  DIALOG_MESSAGE_BLOCK
  TaglistCallback change_notify;
  Pointer         change_userdata;
  Int4            num_rows;
  DialoG *        rows;
  BaR             left_bar;
  BaR             right_bar;
  ValNodePtr      choice_list;
  ValNodePtr      value_list;
  Int4            scroll_pos;
  Boolean         suppress_notify;
} ValueListDialogData, PNTR ValueListDialogPtr;


static void CleanupValueListDialog (GraphiC g, VoidPtr data)

{
  ValueListDialogPtr dlg;
  
  dlg = (ValueListDialogPtr) data;
  if (dlg != NULL)
  {
    dlg->rows = MemFree (dlg->rows);
  }

  StdCleanupExtraProc (g, data);
}


static NameValuePairPtr GetNthNameValuePair (Int4 n, ValNodePtr list)
{
  while (n > 0 && list != NULL)
  {
    n--;
    list = list->next;
  }
  if (list == NULL)
  {
    return NULL;
  }
  else 
  {
    return (NameValuePairPtr) list->data.ptrvalue;
  }
}

static void PopulateValueListRows (ValueListDialogPtr dlg)
{
  Int4 n;
  ValNodePtr vnp;

  n = dlg->scroll_pos;
  vnp = dlg->value_list;

  dlg->suppress_notify = TRUE;

  while (n > 0 && vnp != NULL)
  {
    n--;
    vnp = vnp->next;
  }
  for (n = 0; n < dlg->num_rows; n++)
  {
    if (vnp == NULL)
    {  
      PointerToDialog (dlg->rows[n], NULL);
    }
    else 
    {
      PointerToDialog (dlg->rows[n], vnp->data.ptrvalue);
      vnp = vnp->next;
    }
  }
  dlg->suppress_notify = FALSE;
}


static Boolean IsNameValuePairEmpty (NameValuePairPtr nvp)
{
  if (nvp == NULL) 
  {
    return TRUE;
  }
  else if ((nvp->name_vnp == NULL || nvp->name_vnp->choice == 0 || StringHasNoText (nvp->name_vnp->data.ptrvalue)) /* no choice */
            && StringHasNoText (nvp->value)) /* no value */
  {
    return TRUE;
  }
  else 
  {
    return FALSE;
  }
}

/* Only trim blanks from the end of the list */
static void RemoveBlanks (ValNodePtr PNTR pvnp)
{
  ValNodePtr vnp, last_non_blank = NULL;

  if (pvnp == NULL) return;

  vnp = *pvnp;
  while (vnp != NULL) 
  {
    if (!IsNameValuePairEmpty ((NameValuePairPtr) vnp->data.ptrvalue)) 
    {
      last_non_blank = vnp;
    }
    vnp = vnp->next;
  }
  if (last_non_blank != NULL) {
    last_non_blank->next = NameValuePairListFree (last_non_blank->next);
  }
}

static void ValueListPairToDialog (DialoG d, Pointer data)
{
  ValueListDialogPtr dlg;
  Int4               num_vals;

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

  dlg->value_list = NameValuePairListFree (dlg->value_list);
  dlg->value_list = (ValNodePtr) data;
  RemoveBlanks (&(dlg->value_list));

  num_vals = ValNodeLen (dlg->value_list);
  if (num_vals >= dlg->num_rows)
  {
    SetBarMax (dlg->left_bar, num_vals + 1 - dlg->num_rows);
    SetBarMax (dlg->right_bar, num_vals + 1 - dlg->num_rows);
  }
  else
  {
    SetBarMax (dlg->left_bar, 0);
    SetBarMax (dlg->right_bar, 0);
  }
  if (GetValue (dlg->left_bar) > GetBarMax (dlg->left_bar))
  {
    CorrectBarValue (dlg->left_bar, 0);
    CorrectBarValue (dlg->right_bar, 0);
  }
  PopulateValueListRows (dlg);
}


static void ScrollValueListProc (BaR b, GraphiC g, Int2 _new, Int2 _old)
{
  ValueListDialogPtr dlg;

  dlg = (ValueListDialogPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  /* synchronize left and right scroll bars */
  if (b == dlg->right_bar && dlg->left_bar != NULL)
  {
    CorrectBarValue (dlg->left_bar, GetBarValue (dlg->right_bar));
  }
  else if (b == dlg->left_bar && dlg->right_bar != NULL)
  {
    CorrectBarValue (dlg->right_bar, GetBarValue (dlg->left_bar));      
  }

  dlg->scroll_pos = _new;
  PopulateValueListRows (dlg);
}


static void ValueListDialogMessage (DialoG d, Int2 mssg)

{
  ValNodePtr         new_value_list;

  switch (mssg) {
    case VIB_MSG_REDRAW :
      new_value_list = DialogToPointer (d);
      PointerToDialog (d, new_value_list);
      break;
    default :
      break;
  }
}


static void ChangeValueRow (Pointer data)
{
  ValueListDialogPtr dlg;
  NameValuePairPtr   nvp;
  Int4               max, i;
  ValNodePtr         vnp;

  dlg = (ValueListDialogPtr) data;

  if (dlg == NULL) return;

  if (dlg->suppress_notify) return;

  /* copy values into list */
  /* first, skip over rows scrolled past */
  vnp = dlg->value_list;
  i = 0;
  while (i < dlg->scroll_pos)
  {
    if (vnp == NULL)
    {
      vnp = ValNodeAddPointer (&dlg->value_list, 0, NULL);
    }
    vnp = vnp->next;
    i++;
  }
  
  /* now copy in rows we can see */    
  for (i = 0; i < dlg->num_rows; i++)
  {
    if (vnp == NULL)
    {
      vnp = ValNodeAddPointer (&dlg->value_list, 0, DialogToPointer (dlg->rows[i]));
    }
    else
    {
      vnp->data.ptrvalue = NameValuePairFree (vnp->data.ptrvalue);
      vnp->data.ptrvalue = DialogToPointer (dlg->rows[i]);
    }
    vnp = vnp->next;
  }
  

  /* if editing last row, extend scrollbar for new row */
  max = GetBarMax (dlg->left_bar);
  if (dlg->scroll_pos == max)
  {
    nvp = DialogToPointer (dlg->rows[dlg->num_rows - 1]);
    if (!IsNameValuePairEmpty (nvp))
    {
      SetBarMax (dlg->left_bar, max + 1);
      SetBarMax (dlg->right_bar, max + 1); 
    }
    nvp = NameValuePairFree(nvp);    
  }   

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


static Pointer DialogToNameValuePairList (DialoG d)
{
  ValueListDialogPtr dlg;
  ValNodePtr         value_list = NULL, vnp;

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

  for (vnp = dlg->value_list; vnp != NULL; vnp = vnp->next)
  {
    if (vnp->data.ptrvalue != NULL) 
    {
      ValNodeAddPointer (&value_list, 0, NameValuePairCopy (vnp->data.ptrvalue));
    }
  }
  return (Pointer) value_list;
}


static ValNodePtr TestValueListDialog (DialoG d)
{
  ValNodePtr       err_list = NULL, value_list = NULL, vnp;
  NameValuePairPtr nvp;
  CharPtr          err_fmt = "No modifier type selected for data %s";
  CharPtr          err_str;

  value_list = DialogToPointer (d);

  for (vnp = value_list; vnp != NULL; vnp = vnp->next)
  {
    nvp = (NameValuePairPtr) vnp->data.ptrvalue;
    if (nvp != NULL 
        && !StringHasNoText (nvp->value) 
        && (nvp->name_vnp == NULL || StringHasNoText (nvp->name_vnp->data.ptrvalue)))
    {
      err_str = (CharPtr) MemNew (sizeof (Char) * (StringLen (err_fmt) + StringLen (nvp->value)));
      sprintf (err_str, err_fmt, nvp->value);
      ValNodeAddPointer (&err_list, 0, err_str);
    }
  }
  value_list = NameValuePairListFree (value_list);
  return err_list;
}

extern DialoG ValueListDialog (GrouP h, Uint2 num_rows, Int2 width, ValNodePtr choice_list, ValueListParentPtr parent, TaglistCallback change_notify, Pointer change_userdata)
{
  ValueListDialogPtr dlg;
  GrouP           p, g;
  Int4            i;

  p = HiddenGroup (h, 3, 0, NULL);
  SetGroupSpacing (p, 3, 3);
  dlg = (ValueListDialogPtr) MemNew (sizeof(ValueListDialogData));

  SetObjectExtra (p, dlg, CleanupValueListDialog);
  dlg->dialog = (DialoG) p;
  dlg->todialog = ValueListPairToDialog;
  dlg->fromdialog = DialogToNameValuePairList;
  dlg->testdialog = TestValueListDialog;
  dlg->dialogmessage = ValueListDialogMessage;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  
  dlg->num_rows = num_rows;

  /* navigation bar on left */
  dlg->left_bar = ScrollBar (p, 0, dlg->num_rows, ScrollValueListProc);
  SetObjectExtra (dlg->left_bar, dlg, NULL);
  CorrectBarPage (dlg->left_bar, dlg->num_rows - 1, dlg->num_rows - 1);

  g = HiddenGroup (p, 1, 0, NULL);
  dlg->rows = (DialoG *) MemNew (num_rows * sizeof (DialoG));
  for (i = 0; i < dlg->num_rows; i++)
  {
    dlg->rows[i] = ValueListRowDialog (g, width, choice_list, parent, ChangeValueRow, dlg, (DialoG) p);

  }

  /* navigation bar on right */
  dlg->right_bar = ScrollBar (p, 0, dlg->num_rows, ScrollValueListProc);
  SetObjectExtra (dlg->right_bar, dlg, NULL);
  CorrectBarPage (dlg->right_bar, dlg->num_rows - 1, dlg->num_rows - 1);

  AlignObjects (ALIGN_LOWER, (HANDLE) g, (HANDLE) dlg->left_bar, (HANDLE) dlg->right_bar, NULL);


  return (DialoG) p;
}


typedef struct modifierlistdlg {
  VALUE_LIST_PARENT_FIELDS

  DialoG dlg;
  ButtoN type_strain;
  TexT   taxname;

  EnumFieldAssocPtr al;
} ModifierListDlgData, PNTR ModifierListDlgPtr;


static ValNodePtr TestModifierListDlg (DialoG d)
{
  ModifierListDlgPtr dlg = (ModifierListDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return NULL;
  return TestDialog (dlg->dlg);
}



static ValNodePtr SubSourceListToNameValueList (SubSourcePtr ssp)
{
  ValNodePtr val_list = NULL;
  NameValuePairPtr nvp;

  while (ssp != NULL)
  {
    if (ssp->subtype != SUBSRC_other)
    {
      nvp = (NameValuePairPtr) MemNew (sizeof (NameValuePairData));
      nvp->name_vnp = ValNodeNew(NULL);
      nvp->name_vnp->choice = 1;
      nvp->name_vnp->data.ptrvalue = StringSave (GetSubsourceQualName (ssp->subtype));
      nvp->value = StringSave (ssp->name);
      ValNodeAddPointer (&val_list, 0, nvp);
    }
    ssp = ssp->next;
  }
  return val_list;

}


static void SubSourceListToDialog (DialoG d, Pointer data)
{
  ModifierListDlgPtr dlg;
  SubSourcePtr ssp;
  ValNodePtr   vnp;
  CharPtr      note_txt = NULL;

  dlg = (ModifierListDlgPtr) GetObjectExtra (d);

  if (dlg == NULL) return;

  ssp = (SubSourcePtr) data;

  vnp = SubSourceListToNameValueList (ssp);
  PointerToDialog (dlg->dlg, vnp);

  while (ssp != NULL) 
  {
    if (ssp->subtype == SUBSRC_other)
    {
      note_txt = combine_strings (note_txt, ssp->name);
    }
    ssp = ssp->next;
  }
  SetTitle (dlg->note, note_txt);

}


static Pointer DialogToSubSourceList (DialoG d)
{
  ModifierListDlgPtr dlg;
  ValNodePtr   val_list, vnp;
  NameValuePairPtr nvp;
  SubSourcePtr     ssp_list = NULL, ssp_prev = NULL, ssp_new;
  Uint1            subtype;
  CharPtr          comment_str;

  dlg = (ModifierListDlgPtr) GetObjectExtra (d);

  if (dlg == NULL) return NULL;

  val_list = (ValNodePtr) DialogToPointer (dlg->dlg);
  for (vnp = val_list; vnp != NULL; vnp = vnp->next)
  {
    if (vnp->data.ptrvalue != NULL) 
    {
      nvp = (NameValuePairPtr) vnp->data.ptrvalue;
      if (nvp != NULL && nvp->name_vnp != NULL)
      {        
        subtype = EquivalentSubSourceEx (nvp->name_vnp->data.ptrvalue, TRUE);
        if (subtype != 0)
        {
          ssp_new = SubSourceNew ();
          ssp_new->subtype = subtype;
          ssp_new->name = StringSave (nvp->value == NULL ? "" : nvp->value);
          if (ssp_prev == NULL)
          {
            ssp_list = ssp_new;
          }
          else
          {
            ssp_prev->next = ssp_new;
          }
          ssp_prev = ssp_new;
        }
      }
    }
  }
  val_list = NameValuePairListFree (val_list);

  /* add comment */
  comment_str = SaveStringFromText (dlg->note);
  if (!StringHasNoText (comment_str))
  {
    ssp_new = SubSourceNew();
    ssp_new->subtype = SUBSRC_other;
    ssp_new->name = StringSave (comment_str);
    if (ssp_prev == NULL)
    {
      ssp_list = ssp_new;
    }
    else
    {
      ssp_prev->next = ssp_new;
    }
    ssp_prev = ssp_new;
  }

  return ssp_list;
}


static ValNodePtr GetSubSourceChoicesForValueList (EnumFieldAssocPtr al)
{
  ValNodePtr choice_list = NULL;
  EnumFieldAssocPtr efap;

  efap = al;
  while (efap->name != NULL)
  {
    if (StringHasNoText (efap->name))
    {
      ValNodeAddStr (&choice_list, eValueEditSimpleText + 1, StringSave (efap->name));
    }
    else if (efap->value == SUBSRC_lat_lon)
    {
      ValNodeAddStr (&choice_list, eValueEditLatLon + 1, StringSave (efap->name));
    }
    else if (efap->value == SUBSRC_altitude)
    {
      ValNodeAddStr (&choice_list, eValueEditAltitude + 1, StringSave (efap->name));
    }
    else if (IsNonTextModifier (efap->name))
    {
      ValNodeAddStr (&choice_list, eValueEditTrueFalse + 1, StringSave (efap->name));
    }
    else
    {
      ValNodeAddStr (&choice_list, eValueEditSimpleText + 1, StringSave (efap->name));
    }
    efap ++;
  }
  return choice_list;
}


static CharPtr subsource_extra_prompts [] = {
  "Additional", "Source", "Information", NULL
};


extern DialoG CreateSubSourceDialog (GrouP h, EnumFieldAssocPtr al)
{
  ModifierListDlgPtr dlg;
  GrouP        p, g, x;
  ValNodePtr   choice_list;
  Int2         max;
  ButtoN       b;


  p = HiddenGroup (h, -1, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (ModifierListDlgPtr) MemNew (sizeof(ModifierListDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = SubSourceListToDialog;
  dlg->fromdialog = DialogToSubSourceList;
  dlg->testdialog = TestModifierListDlg;
  dlg->dialogmessage = NULL;

  choice_list = GetSubSourceChoicesForValueList (al);
  dlg->dlg = ValueListDialog (p, 3, 23, choice_list, (ValueListParentPtr) dlg, NULL, NULL);  
  choice_list = ValNodeFreeData (choice_list);

  g = HiddenGroup (p, 3, 0, NULL);
  SelectFont (programFont);
  max = MaxStringWidths (subsource_extra_prompts) + 2;
  x = MultiLinePrompt (g, "Additional Source Information", max, programFont);
  dlg->note = ScrollText (g, 20, 3, programFont, TRUE, NULL);
  b = PushButton (g, "X", ClearTextBtn);
  SetObjectExtra (b, dlg->note, NULL);

  AlignObjects (ALIGN_MIDDLE, (HANDLE) x, (HANDLE) dlg->note, (HANDLE) b, NULL);

  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->dlg, (HANDLE) g, NULL);

  return (DialoG) p;
}


static ValNodePtr OrgModListToNameValueList (OrgModPtr mod)
{
  ValNodePtr val_list = NULL;
  NameValuePairPtr nvp;

  while (mod != NULL)
  {
    if (mod->subtype != ORGMOD_gb_acronym
        && mod->subtype != ORGMOD_gb_anamorph
        && mod->subtype != ORGMOD_gb_synonym
        && mod->subtype != ORGMOD_type_material
        && mod->subtype != ORGMOD_other)
    {
      nvp = (NameValuePairPtr) MemNew (sizeof (NameValuePairData));
      nvp->name_vnp = ValNodeNew(NULL);
      nvp->name_vnp->choice = 1;
      nvp->name_vnp->data.ptrvalue = StringSave (GetOrgModQualName (mod->subtype));
      nvp->value = StringSave (mod->subname);
      ValNodeAddPointer (&val_list, 0, nvp);
    }
    mod = mod->next;
  }
  return val_list;

}


static void OrgModListToDialog (DialoG d, Pointer data)
{
  ModifierListDlgPtr dlg;
  OrgModPtr          mod;
  ValNodePtr         vnp;
  CharPtr            note_txt = NULL;

  dlg = (ModifierListDlgPtr) GetObjectExtra (d);

  if (dlg == NULL) return;

  mod = (OrgModPtr) data;

  vnp = OrgModListToNameValueList (mod);
  PointerToDialog (dlg->dlg, vnp);

  while (mod != NULL) 
  {
    if (mod->subtype == ORGMOD_other)
    {
      note_txt = combine_strings (note_txt, mod->subname);
    }
    mod = mod->next;
  }
  SetTitle (dlg->note, note_txt);

}


static Pointer DialogToOrgModList (DialoG d)
{
  ModifierListDlgPtr dlg;
  ValNodePtr   val_list, vnp;
  NameValuePairPtr nvp;
  OrgModPtr        mod_list = NULL, mod_prev = NULL, mod_new;
  Uint1            subtype;
  CharPtr          comment_str;

  dlg = (ModifierListDlgPtr) GetObjectExtra (d);

  if (dlg == NULL) return NULL;

  val_list = (ValNodePtr) DialogToPointer (dlg->dlg);
  for (vnp = val_list; vnp != NULL; vnp = vnp->next)
  {
    if (vnp->data.ptrvalue != NULL) 
    {
      nvp = (NameValuePairPtr) vnp->data.ptrvalue;
      if (nvp != NULL && nvp->name_vnp != NULL)
      {        
        subtype = EquivalentOrgModEx (nvp->name_vnp->data.ptrvalue, TRUE);
        if (subtype != 0)
        {
          mod_new = OrgModNew ();
          mod_new->subtype = subtype;
          mod_new->subname = StringSave (nvp->value == NULL ? "" : nvp->value);
          if (mod_prev == NULL)
          {
            mod_list = mod_new;
          }
          else
          {
            mod_prev->next = mod_new;
          }
          mod_prev = mod_new;
        }
      }
    }
  }
  val_list = NameValuePairListFree (val_list);

  /* add comment */
  comment_str = SaveStringFromText (dlg->note);
  if (!StringHasNoText (comment_str))
  {
    mod_new = OrgModNew();
    mod_new->subtype = ORGMOD_other;
    mod_new->subname = StringSave (comment_str);
    if (mod_prev == NULL)
    {
      mod_list = mod_new;
    }
    else
    {
      mod_prev->next = mod_new;
    }
    mod_prev = mod_new;
  }

  return mod_list;
}


extern Boolean IsNonTextModifier (CharPtr mod_name)
{
  if (StringICmp (mod_name, "transgenic") == 0
      || StringICmp (mod_name, "germline") == 0
      || StringICmp (mod_name, "metagenomic") == 0
      || StringICmp (mod_name, "environmental-sample") ==0
      || StringICmp (mod_name, "rearranged") == 0)
  {
    return TRUE;  
  }
  else
  {
    return FALSE;
  }
}


static ValNodePtr GetOrgModChoicesForValueList (EnumFieldAssocPtr al)
{
  ValNodePtr choice_list = NULL;
  EnumFieldAssocPtr efap;

  efap = al;
  while (efap->name != NULL)
  {
    if (StringHasNoText (efap->name))
    {
      ValNodeAddStr (&choice_list, eValueEditSimpleText + 1, StringSave (efap->name));
    }
    else if (IsNonTextModifier (efap->name))
    {
      ValNodeAddStr (&choice_list, eValueEditTrueFalse + 1, StringSave (efap->name));
    }
    else if (efap->value == ORGMOD_specimen_voucher || efap->value == ORGMOD_culture_collection || efap->value == ORGMOD_bio_material)
    {
      ValNodeAddStr (&choice_list, eValueEditSpecimenVoucher + 1, StringSave (efap->name));
    }
    else
    {
      ValNodeAddStr (&choice_list, eValueEditSimpleText + 1, StringSave (efap->name));
    }
    efap ++;
  }
  return choice_list;
}


static CharPtr orgmod_extra_prompts [] = {
  "Additional", "Organism", "Information", NULL
};

static void ChangeOrgmodComment (TexT t)
{
  ModifierListDlgPtr dlg;
  CharPtr str;

  dlg = (ModifierListDlgPtr) GetObjectExtra (t);
  if (dlg == NULL) return;

  str = SaveStringFromText (t);
  if ( StringStr (str, "type strain of ")) {
    Disable (dlg->type_strain);
  }

}  


static void AddTypeStrainProc (ButtoN b)
{
  ModifierListDlgPtr dlg;
  CharPtr        old_orgcomment;
  Int4           old_orgcomment_len;
  CharPtr        org_name;
  Int4           org_name_len;
  const CharPtr  ts = "type strain of ";
  const CharPtr  sep = "; ";
  CharPtr        new_orgcomment;

  dlg = (ModifierListDlgPtr) GetObjectExtra (b);
  if (dlg == NULL) return;

  old_orgcomment_len = TextLength (dlg->note) + 1;
  old_orgcomment = MemNew (old_orgcomment_len + 1);
  if (old_orgcomment == NULL) return;
  org_name_len = TextLength (dlg->taxname) + 1;
  org_name = MemNew (org_name_len + 1);
  if (org_name == NULL) 
  {
    MemFree (old_orgcomment);
    return;
  }
  new_orgcomment = MemNew (old_orgcomment_len
            + StringLen (sep)
            + StringLen (ts)
            + org_name_len
            + 1);
  if (new_orgcomment == NULL)
  {
    MemFree (old_orgcomment);
    MemFree (org_name);
  }

  GetTitle (dlg->note, old_orgcomment, old_orgcomment_len);
  TrimSpacesAroundString (old_orgcomment);
  GetTitle (dlg->taxname, org_name, org_name_len);
  TrimSpacesAroundString (org_name);
  if (old_orgcomment[0] != 0)
  {
    StringCpy(new_orgcomment, old_orgcomment);
    StringCat(new_orgcomment, sep);
  }
  else if (new_orgcomment != NULL)
  {
    new_orgcomment[0] = 0;
  }
    
  StringCat (new_orgcomment, ts);
  StringCat (new_orgcomment, org_name);
  SetTitle (dlg->note, new_orgcomment);
  MemFree (org_name);
  MemFree (old_orgcomment);
  MemFree (new_orgcomment);
  Disable (b);
}


extern DialoG CreateOrgModDialog (GrouP h, EnumFieldAssocPtr al, TexT taxname)
{
  ModifierListDlgPtr dlg;
  GrouP        p, g, x;
  ValNodePtr   choice_list;
  Int2         max;
  ButtoN       b;


  p = HiddenGroup (h, -1, 0, NULL);
  SetGroupSpacing (p, 10, 10);
  dlg = (ModifierListDlgPtr) MemNew (sizeof(ModifierListDlgData));

  SetObjectExtra (p, dlg, StdCleanupExtraProc);
  dlg->dialog = (DialoG) p;
  dlg->todialog = OrgModListToDialog;
  dlg->fromdialog = DialogToOrgModList;
  dlg->testdialog = TestModifierListDlg;
  dlg->dialogmessage = NULL;

  choice_list = GetOrgModChoicesForValueList (al);
  dlg->dlg = ValueListDialog (p, 3, 23, choice_list, (ValueListParentPtr) dlg, NULL, NULL);  
  choice_list = ValNodeFreeData (choice_list);

  g = HiddenGroup (p, 3, 0, NULL);
  SelectFont (programFont);
  max = MaxStringWidths (orgmod_extra_prompts) + 2;
  x = MultiLinePrompt (g, "Additional Organism Information", max, programFont);
  dlg->note = ScrollText (g, 20, 3, programFont, TRUE, ChangeOrgmodComment);
  SetObjectExtra (dlg->note, dlg, NULL);
  b = PushButton (g, "X", ClearTextBtn);
  SetObjectExtra (b, dlg->note, NULL);
  AlignObjects (ALIGN_MIDDLE, (HANDLE) x, (HANDLE) dlg->note, (HANDLE) b, NULL);

  dlg->taxname = taxname;
  if (dlg->taxname != NULL)
  {
    dlg->type_strain = PushButton (p, "Add type strain to comment", AddTypeStrainProc);
    SetObjectExtra (dlg->type_strain, dlg, NULL);
  }
  else
  {
    dlg->type_strain = NULL;
  }

  AlignObjects (ALIGN_CENTER, (HANDLE) dlg->dlg, (HANDLE) g, (HANDLE) dlg->type_strain, NULL);

  return (DialoG) p;
}


extern void CreateStandardEditMenu (WindoW w)
{
  MenU m;
  IteM i;

  /* Edit Menu */
  m = PulldownMenu (w, "Edit");
  i = CommandItem (m, "Cut", StdCutTextProc);
  i = CommandItem (m, "Copy", StdCopyTextProc);
  i = CommandItem (m, "Paste", StdPasteTextProc);
  i = CommandItem (m, "Clear", StdDeleteTextProc);
}


static ValNodePtr sqnTempFiles = NULL;

extern void RememberSqnTempFile (CharPtr file)

{
  ValNodePtr  vnp;

  if (StringHasNoText (file)) return;

  vnp = ValNodeCopyStr (NULL, 0, file);
  if (vnp == NULL) return;

  vnp->next = sqnTempFiles;
  sqnTempFiles = vnp;
}

extern void FreeSqnTempFiles (void)

{
  CharPtr     file;
  ValNodePtr  list;
  ValNodePtr  vnp;

  if (sqnTempFiles == NULL) return;

  list = sqnTempFiles;
  sqnTempFiles = NULL;

  for (vnp = list; vnp != NULL; vnp = vnp->next) {
    file = (CharPtr) vnp->data.ptrvalue;
    if (StringHasNoText (file)) continue;
    FileRemove (file);
  }

  ValNodeFreeData (list);
}


/* This gets a list of the open views */
NLM_EXTERN ValNodePtr GetBaseFormList (void)

{
  Uint4          j;
  Uint4          num;
  ObjMgrPtr      omp;
  ObjMgrDataPtr  omdp;
  ObjMgrDataPtr  PNTR omdpp;
  OMUserDataPtr  omudp;
  BaseFormPtr    bfp;
  ValNodePtr     base_form_list = NULL;

  omp = ObjMgrGet ();
  if (omp == NULL) return NULL;
  num = omp->currobj;
  for (j = 0, omdpp = omp->datalist; j < num && omdpp != NULL; j++, omdpp++) {
    omdp = *omdpp;
    if (omdp->parentptr == NULL) {
      for (omudp = omdp->userdata; omudp != NULL; omudp = omudp->next) {
        if (omudp->proctype == OMPROC_VIEW) {
          bfp = (BaseFormPtr) omudp->userdata.ptrvalue;
          ValNodeAddPointer (&base_form_list, 0, bfp);
        }
      }
    }
  }
  return base_form_list;
}


/* This gets a list of the top SeqEntry for each open view */
NLM_EXTERN ValNodePtr GetViewedSeqEntryList (void)
{
  ValNodePtr  sep_list = NULL, base_form_list, vnp;
  BaseFormPtr bfp;
  SeqEntryPtr sep;
  
  base_form_list = GetBaseFormList();
  for (vnp = base_form_list; vnp != NULL; vnp = vnp->next) {
    bfp = (BaseFormPtr) vnp->data.ptrvalue;
    if (bfp != NULL && bfp->input_entityID != 0) {
      sep = GetTopSeqEntryForEntityID (bfp->input_entityID);
      ValNodeAddPointer (&sep_list, 0, sep);
    }
  }
  base_form_list = ValNodeFree (base_form_list);
  return sep_list;
}


static void AddCitSubFromSeqSubmit (SeqEntryPtr sep, SeqSubmitPtr ssp)
{
  SeqDescPtr    sdp;
  PubdescPtr    pdp;

  if (sep == NULL || ssp == NULL || ssp->sub == NULL || ssp->sub->cit == NULL) {
    return;
  }

  pdp = PubdescNew ();
  ValNodeAddPointer (&(pdp->pub), PUB_Sub, ssp->sub->cit);
  ssp->sub->cit = NULL;

  sdp = CreateNewDescriptor (sep, Seq_descr_pub);
  sdp->data.ptrvalue = pdp;
}


NLM_EXTERN SeqEntryPtr RestoreFromFileEx (CharPtr path, Boolean convert_seqsubmit_to_pub)

{
  BioseqPtr     bsp;
  BioseqSetPtr  bssp;
  Pointer       dataptr;
  Uint2         datatype;
  Uint2         entityID;
  SeqEntryPtr   rsult;
  SeqEntryPtr   sep;
  SeqSubmitPtr  ssp;

  rsult = NULL;
  if (path != NULL && path [0] != '\0') {
    dataptr = ObjMgrGenericAsnTextFileRead (path, &datatype, &entityID);
    if (dataptr != NULL && entityID > 0) {
      if (datatype == OBJ_SEQSUB || datatype == OBJ_SEQENTRY ||
          datatype == OBJ_BIOSEQ || datatype == OBJ_BIOSEQSET) {
        WatchCursor ();
        sep = GetTopSeqEntryForEntityID (entityID);
        if (sep == NULL) {
          sep = SeqEntryNew ();
          if (sep != NULL) {
            if (datatype == OBJ_BIOSEQ) {
              bsp = (BioseqPtr) dataptr;
              sep->choice = 1;
              sep->data.ptrvalue = bsp;
              SeqMgrSeqEntry (SM_BIOSEQ, (Pointer) bsp, sep);
            } else if (datatype == OBJ_BIOSEQSET) {
              bssp = (BioseqSetPtr) dataptr;
              sep->choice = 2;
              sep->data.ptrvalue = bssp;
              SeqMgrSeqEntry (SM_BIOSEQSET, (Pointer) bssp, sep);
            } else {
              sep = SeqEntryFree (sep);
            }
          }
          sep = GetTopSeqEntryForEntityID (entityID);
        }
        if (sep != NULL) {
          rsult = SeqEntryNew ();
          if (rsult != NULL) {
            rsult->choice = sep->choice;
            rsult->data.ptrvalue = sep->data.ptrvalue;
            sep->data.ptrvalue = NULL;
            if (datatype == OBJ_SEQSUB) {
              ssp = (SeqSubmitPtr) dataptr;
              if (convert_seqsubmit_to_pub) {
                AddCitSubFromSeqSubmit(rsult, ssp);
              }
              SeqSubmitFree ((SeqSubmitPtr) dataptr);
            } else {
              SeqEntryFree (sep);
            }
            /*Message (MSG_OK, "Call new object manager free function");*/
          }
        }
        ArrowCursor ();
      }
      /*
      ObjMgrDelete (entityID);
      */
    }
  }
  return rsult;
}


NLM_EXTERN SeqEntryPtr RestoreFromFile (CharPtr path)
{
  return RestoreFromFileEx (path, FALSE);
}


NLM_EXTERN Uint2 RestoreEntityIDFromFileEx (CharPtr path, Uint2 entityID, Boolean convert_seqsubmit_to_pub)
{
  SeqEntryPtr scope, oldsep, currsep;
  Uint2 newid = 0;

  scope = SeqEntrySetScope (NULL);
  oldsep = RestoreFromFileEx (path, convert_seqsubmit_to_pub);
  currsep = GetTopSeqEntryForEntityID (entityID);
  if (oldsep == NULL || currsep == NULL) {
    SeqEntrySetScope (scope);
  } else {  
    ReplaceSeqEntryWithSeqEntry (currsep, oldsep, TRUE);
    SeqEntrySetScope (NULL);
    newid = ObjMgrGetEntityIDForChoice (currsep);
  }
  return newid;
}


NLM_EXTERN Uint2 RestoreEntityIDFromFile (CharPtr path, Uint2 entityID)
{
  return RestoreEntityIDFromFileEx (path, entityID, FALSE);
}


NLM_EXTERN void CloseLog (LogInfoPtr lip)
{
  if (lip == NULL || lip->fp == NULL)
  {
    return;
  }
  FileClose (lip->fp);
  lip->fp = NULL;
  if (lip->data_in_log)
  {
    LaunchGeneralTextViewer (lip->path, lip->display_title);
  }
  FileRemove (lip->path);  
}


NLM_EXTERN void ShowLog (LogInfoPtr lip)
{
  if (lip == NULL || lip->fp == NULL)
  {
    return;
  }
  FileClose (lip->fp);
  lip->fp = NULL;
  if (lip->data_in_log)
  {
    LaunchGeneralTextViewer (lip->path, lip->display_title);
  }
  lip->fp = FileOpen (lip->path, "a");
}


static Boolean EditIdList (SeqIdPtr PNTR sip_list, SeqIdPtr all_list)
{
  ModalAcceptCancelData acd;
  DialoG                dlg;
  WindoW                w;
  ButtoN                b;
  GrouP                 h, c;
  ValNodePtr            tmp;
  
  if (sip_list == NULL || *sip_list == NULL) return FALSE;

  w = MovableModalWindow(-20, -13, -10, -10, "Constraint", NULL);
  h = HiddenGroup (w, -1, 0, NULL);
  SetGroupSpacing (h, 10, 10);

  /* create dialog with list of sequence IDs */
  dlg = ValNodeSelectionDialogExEx (h, ValNodeSeqIdListCopy(all_list), TALL_SELECTION_LIST, ValNodeSeqIdName,
                                           ValNodeSeqIdFree, ValNodeSeqIdCopy,
                                           ValNodeSeqIdMatch, "location", 
                                           NULL, NULL, TRUE, TRUE, FALSE, NULL);
  
  /* populate */
  tmp = SeqIdListToValNodeSeqIdList (*sip_list);
  PointerToDialog (dlg, tmp);
  tmp = ValNodeSeqIdListFree(tmp);

  c = HiddenGroup (h, 2, 0, NULL);
  SetGroupSpacing (c, 10, 10);
  b = PushButton (c, "Accept", ModalAcceptButton);
  SetObjectExtra (b, &acd, NULL);
  b = PushButton (c, "Cancel", ModalCancelButton);
  SetObjectExtra (b, &acd, NULL);
  AlignObjects (ALIGN_CENTER, (HANDLE) dlg,
                              (HANDLE) c, 
                              NULL);

  Show (w);
  Select (w);
  acd.accepted = FALSE;
  acd.cancelled = FALSE;
  while (!acd.accepted && ! acd.cancelled)
  {
    ProcessExternalEvent ();
    Update ();
  }
  ProcessAnEvent ();
  if (!acd.cancelled)
  {
    tmp = DialogToPointer (dlg);
    *sip_list = ValNodeSeqIdListToSeqIdList (tmp);
    tmp = ValNodeSeqIdListFree(tmp);
  }
  Remove (w);
  return acd.accepted;
}


static Boolean EditOneDescriptor (SeqDescPtr sdp)
{
  Boolean rval = FALSE;

  if (sdp == NULL) 
  {
    return FALSE;
  }
  else if (sdp->choice == Seq_descr_pub) 
  {
    rval = EditPubdescInPlace(sdp);
  }
  return rval;
}

typedef struct descriptorstreameditordlg {
  DIALOG_MESSAGE_BLOCK

  DoC    pub_doc;

  ValNodePtr               desc_stream_list;
  ValNodePtr               sip_list;
  Nlm_ChangeNotifyProc     change_notify;
  Pointer                  change_userdata;
} DescriptorStreamEditorDlgData, PNTR DescriptorStreamEditorDlgPtr;


#define k_WideColumnWidth 50


static CharPtr GetDescriptorFlatFileText (SeqDescPtr sdp)
{
  Asn2gbJobPtr   ajp;
  BaseBlockPtr   bbp;
  XtraBlock      extra;
  BioseqPtr      bsp;
  ErrSev         level;
  CharPtr        desc_text = NULL, cp;
  Int4           index;

  
  if (sdp == NULL) 
  {
    return NULL;
  }
  bsp = BioseqNew ();
  bsp->descr = AsnIoMemCopy (sdp, (AsnReadFunc) SeqDescAsnRead, (AsnWriteFunc) SeqDescAsnWrite);
  bsp->repr = Seq_repr_raw;

  level = ErrSetMessageLevel (SEV_MAX);

  MemSet ((Pointer) &extra, 0, sizeof (XtraBlock));
  ajp = asn2gnbk_setup (bsp, NULL, NULL, (FmtType)GENBANK_FMT, SEQUIN_MODE, NORMAL_STYLE, 0, 0, 0, &extra);
  if (ajp != NULL) 
  {
    for (index = 0; index < ajp->numParagraphs; index++) 
    {
      bbp = ajp->paragraphArray [index];
      if (bbp->blocktype == REFERENCE_BLOCK)
      {
        desc_text = asn2gnbk_format (ajp, (Int4) index);
        if (desc_text != NULL) {
          /* skip first line */
          cp = StringChr (desc_text, 10);
          if (cp != NULL) {
            StringCpy (desc_text, cp + 1);
            while ((cp = StringChr (desc_text, 10)) != NULL) {
              *cp = '~';
            }
          }
        }
      }
    }
  }

  ErrSetMessageLevel (level);    

  ajp = asn2gnbk_cleanup (ajp);
  bsp = BioseqFree (bsp);
  return desc_text;  
}


static CharPtr GetDescriptorText (SeqDescPtr sdp, StdPrintOptionsPtr spop)
{
  CharPtr text = NULL, tmp;
  ValNode pub_field;
  PubdescPtr pdp;

  if (sdp == NULL) {
    return NULL;
  } else if (sdp->choice == Seq_descr_pub) {
    MemSet (&pub_field, 0, sizeof (ValNode));
    pub_field.choice = FieldType_pub;
    pub_field.data.intvalue = Publication_field_title;
    text = GetFieldValueForObject (OBJ_SEQDESC, sdp, &pub_field, NULL);
    if (text == NULL) {
      pub_field.data.intvalue = Publication_field_cit;
      text = GetFieldValueForObject (OBJ_SEQDESC, sdp, &pub_field, NULL);
      if (text == NULL) {
        pub_field.data.intvalue = Publication_field_authors;
        text = GetFieldValueForObject (OBJ_SEQDESC, sdp, &pub_field, NULL);
      }
    }
    if ((pdp = (PubdescPtr) sdp->data.ptrvalue) != NULL
        && pdp->pub != NULL
        && pdp->pub->choice == PUB_Sub) {
      if (text == NULL) {
        text = StringSave ("CitSub");
      } else {
        tmp = (CharPtr) MemNew (sizeof (Char) * (StringLen (text) + 8));
        sprintf (tmp, "CitSub:%s", text);
        text = MemFree (text);
        text = tmp;
      }
    }
    if (text == NULL) {
      text = StringSave ("No information for pub");
    }
  } else {
    text = GetDescriptorFlatFileText (sdp);
    if (text == NULL) 
    {
      if (StdFormatPrint ((Pointer) sdp, (AsnWriteFunc) SeqDescAsnWrite,
                                        "StdSeqDesc", spop))
      {
        if (StringNICmp (spop->ptr, "citation;", 9) == 0)
        {
          text = StringSave (spop->ptr + 9);
        }
        else
        {
          text = StringSave (spop->ptr);
        }
        spop->ptr = MemFree (spop->ptr);
      } else {
        text = StringSave ("Unable to format");
      }
    }
  }
  return text;
}


static void AddOnePublicationToPubDoc (DoC doc,  DescStreamPtr d, ParPtr ParFmt, ColPtr ColFmt)
{
  Char    txt[300];
  CharPtr tmp;
  Char    buf[k_WideColumnWidth];

  if (doc == NULL || d == NULL) {
    return;
  }


  if (d->replace == NULL) {
    sprintf (txt, " \tUndelete\t");
  } else {
    sprintf (txt, "Edit\tDelete\t");
  }

  if (d->orig == NULL) {
    StringCat (txt, "New\t");
  } else {
    tmp = GetDescriptorText (d->orig, spop);
    if (StringLen (tmp) > k_WideColumnWidth) {
      tmp[k_WideColumnWidth] = 0;
    }
    StringCat (txt, tmp);
    StringCat (txt, "\t");
    tmp = MemFree (tmp);
  }

  if (d->replace == NULL) {
    StringCat (txt, "Deleted\t");
  } else {
    tmp = GetDescriptorText (d->replace, spop);
    if (StringLen (tmp) > k_WideColumnWidth) {
      tmp[k_WideColumnWidth] = 0;
    }
    StringCat (txt, tmp);
    StringCat (txt, "\t");
    tmp = MemFree (tmp);
  }

  if (d->owners == NULL) {
    StringCat (txt, "No owners - will be deleted\t");
  } else if (d->on_all) {
    StringCat (txt, "All\t");
  } else {
    SeqIdWrite (d->owners, buf, PRINTID_FASTA_ALL, sizeof (buf) - 1);
    StringCat (txt, buf);
    StringCat (txt, "\t");
  }

  if (d->num_dependent == 0) {
    StringCat (txt, " \n");
  } else {
    sprintf (buf, "%d featcit\n", d->num_dependent);
    StringCat (txt, buf);
  }

  AppendText (doc, txt, ParFmt, ColFmt, programFont);
}

static void PublicationListDialogRedraw (DescriptorStreamEditorDlgPtr dlg)
{
  ValNodePtr         vnp;
  RecT               r;
  WindoW             temport;
  Int4               i;
  ParData            ParFmt = {FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0};
  ColData            ColFmt[] = 
  {
    {0, 0, 5, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, 9, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, k_WideColumnWidth, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, k_WideColumnWidth, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, k_WideColumnWidth, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, k_WideColumnWidth, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, FALSE},
    {0, 0, 20, 0, NULL, 'l', TRUE, FALSE, FALSE, FALSE, TRUE}
  };

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

  SelectFont (programFont);
  for (i = 0; i < sizeof (ColFmt) / sizeof (ColData); i++) {
    ColFmt[i].pixWidth = ColFmt[i].charWidth * CharWidth('0');
  }
  
  /* make row list for table display and update table display */
  for (vnp = dlg->desc_stream_list;
       vnp != NULL;
       vnp = vnp->next)
  {
    AddOnePublicationToPubDoc (dlg->pub_doc, vnp->data.ptrvalue, &ParFmt, ColFmt);
  }
  AppendText (dlg->pub_doc, " \t \tAdd new publication\n", &ParFmt, ColFmt, programFont);


  temport = SavePort (dlg->pub_doc);
  Select (dlg->pub_doc);
  ObjectRect (dlg->pub_doc, &r);
  InvalRect (&r);  
  RestorePort (temport);
  Update ();


}


static Boolean ShadePubButtons (DoC d, Int2 item, Int2 row, Int2 col)

{
  DescriptorStreamEditorDlgPtr dlg;
  Boolean    shade = FALSE;

  dlg = (DescriptorStreamEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL || dlg->desc_stream_list == NULL) {
    return FALSE;
  }
  if (item < 1) {
    shade = FALSE;
  } else if (item == ValNodeLen (dlg->desc_stream_list) + 1) {
    if (col == 3) {
      shade = TRUE;
    } else {
      shade = FALSE;
    }
  } else if (col == 1 || col == 2) {
    shade = TRUE;
  }
  return shade;  
}


static void ClickPubDoc (DoC d, PoinT pt)
{
  DescriptorStreamEditorDlgPtr dlg;
  Int2                         item, row, col;
  RecT                         rct;
  DescStreamPtr                ds;
  ValNodePtr                   vnp;
  Boolean                      changed = FALSE;
  Int2                         d_num;
  
  dlg = (DescriptorStreamEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL) return;
  
  MapDocPoint (d, pt, &item, &row, &col, &rct);
  if (item == 0) return;
  for (vnp = dlg->desc_stream_list, d_num = 1;
       vnp != NULL && d_num < item;
       vnp = vnp->next, d_num++)
  {
  }

  if (vnp == NULL || (ds = (DescStreamPtr) vnp->data.ptrvalue) == NULL) {
    /* add new publication */
    ds = DescStreamNew (NULL, NULL);
    ds->replace = SeqDescrNew (NULL);
    ds->replace->choice = Seq_descr_pub;
    ds->replace->data.ptrvalue = PubdescNew ();
    if (EditOneDescriptor (ds->replace))
    {
      ds->owners = ValNodeSeqIdListToSeqIdList (dlg->sip_list);
      ValNodeAddPointer (&(dlg->desc_stream_list), 0, ds);
      changed = TRUE;
    }
    else
    {
      ds = DescStreamFree (ds);
    }
  } else {

    switch (col) {
      case 1:
      case 3:
      case 4:
        /* edit */
        changed = EditOneDescriptor (ds->replace);
        break;
      case 2:
        /* delete */
        if (ds->replace == NULL) {
          ds->replace = AsnIoMemCopy (ds->orig, (AsnReadFunc) SeqDescAsnRead, (AsnWriteFunc) SeqDescAsnWrite);
        } else {
          if (ANS_YES != Message (MSG_YN, "Are you sure you want to delete the publication?"))
          {
            return;
          }
          ds->replace = SeqDescFree (ds->replace);
          changed = TRUE;
        }
        break;
      case 5:
        /* edit owner list */
        if (EditIdList (&(ds->owners), dlg->sip_list)) {
          ds->on_all = IdListsMatch(ds->owners, dlg->sip_list);
          changed = TRUE;
        }
        break;
    } 
  }


  if (changed) {
    PublicationListDialogRedraw (dlg);
  }
}


static void DescriptorStreamToDialog (DialoG d, Pointer userdata)
{
  DescriptorStreamEditorDlgPtr dlg;
  
  dlg = (DescriptorStreamEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return;
  }

  dlg->desc_stream_list = (ValNodePtr) userdata;

  PublicationListDialogRedraw(dlg);


}

static Pointer DialogToDescriptorStream (DialoG d)
{
  DescriptorStreamEditorDlgPtr dlg;
  ValNodePtr                   list = NULL;
  
  dlg = (DescriptorStreamEditorDlgPtr) GetObjectExtra (d);
  if (dlg == NULL)
  {
    return NULL;
  }
  
  return (Pointer) list;
}


static void CleanupDescriptorStreamEditorDlg (GraphiC g, VoidPtr data)
{
  DescriptorStreamEditorDlgPtr dlg;

  dlg = (DescriptorStreamEditorDlgPtr) data;
  if (dlg != NULL) {
    dlg->sip_list = ValNodeSeqIdListFree(dlg->sip_list);
  } 
  StdCleanupExtraProc (g, data);
}


NLM_EXTERN DialoG DescriptorStreamEditor (GrouP h,  Nlm_ChangeNotifyProc change_notify, Pointer change_userdata)
{
  DescriptorStreamEditorDlgPtr dlg;
  GrouP           p;
  
  dlg = (DescriptorStreamEditorDlgPtr) MemNew (sizeof (DescriptorStreamEditorDlgData));
  if (dlg == NULL)
  {
    return NULL;
  }

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

  dlg->dialog = (DialoG) p;
  dlg->todialog = DescriptorStreamToDialog;
  dlg->fromdialog = DialogToDescriptorStream;
  dlg->change_notify = change_notify;
  dlg->change_userdata = change_userdata;
  SelectFont (programFont);

  dlg->pub_doc = DocumentPanel (p, CharWidth('0') * 150, stdLineHeight * 12);
  SetObjectExtra (dlg->pub_doc, dlg, NULL);
  SetDocProcs (dlg->pub_doc, ClickPubDoc, NULL, NULL, NULL);
  SetDocShade (dlg->pub_doc, NULL, NULL, ShadePubButtons, NULL);

  return (DialoG) p;
}


NLM_EXTERN void SetDescriptorStreamEditorIdList (DialoG d, SeqIdPtr sip_list)
{
  DescriptorStreamEditorDlgPtr dlg;
  
  dlg = (DescriptorStreamEditorDlgPtr) GetObjectExtra (d);
  if (dlg != NULL) {
    dlg->sip_list = ValNodeSeqIdListFree (dlg->sip_list);
    dlg->sip_list = SeqIdListToValNodeSeqIdList (sip_list);
  }
}


