//	First attempt at a GPS waypoint editor
//
//	Paul Nash
//	paul@frcs.alt.za

//
//	$Header: /usr/home/pilot/gypsy-1.6/RCS/gps.c,v 1.53 1999/08/30 06:35:32 paul Exp $
//
//	$Log: gps.c,v $
//	Revision 1.53  1999/08/30 06:35:32  paul
//	Added Jaque Andre's patches
//
//	Revision 1.52  1999/06/13 17:26:13  paul
//	Changed lat & long edit to use N/S E/W list (button) input, rather
//	than text.  This allows the ':' separator to use ' ' or '-'.
//
//	Revision 1.51  1999/06/12 17:30:52  paul
//	Removed "track to GPS" from menu; confirm clear track log
//
//	Revision 1.50  1999/06/12 14:38:39  paul
//	changed track log to allow cumulative logging.  Not tested yet
//
//	Revision 1.49  1999/05/12 08:48:05  paul
//	Ready for release as Gypsy 1.4.  Major changes include:
//	*  Rudimentary track/route buffer to transfer data between GPS
//	   and PC (a'la gps2p2pc)
//	*  Protocol fix for DLE escaping bug
//	*  Code changes from <andre@drfmc.ceng.cea.fr> to be able to
//	   compile a PalmOS2 version
//	*  Garmin protocol tries a bit harder to recover from comms
//	   errors (but is still *very* dumb)
//
//


#pragma pack(2)

#include "gps.h"
#include <Common.h>
#include <System/SysAll.h>
#include <System/SerialMgr.h>
#include <UI/UIAll.h>
//	This is only needed for the GNU C/C++ 0.5 cross-compiler
#include "callback.h"


//
//	Get in global vars, etc, plus reserve space for them
//

#define EXTERN
#include "globals.h"
struct prefs {
   char name[32];
   UInt card;
};


//
//	Local function prototypes
//

static int StartApp (void);
static void StopApp (void);
static void ProcessLoop (void);
static Boolean HandleWaypointListEvent (EventPtr e);
static Boolean HandleWaypointEditEvent (EventPtr e);
static void DisplayTable ();
static Err descrTableLoadName (VoidPtr t, Word r, Word c, Boolean e,
                        VoidHand *d, Word *o, Word *s, FieldPtr f);
static Boolean descrTableSaveName (VoidPtr t, Word r, Word c);
static void descrTableDrawDescr (VoidPtr tbl, Word row, Word column,
                          RectanglePtr bounds);
static void uppercase (char *c, int strip_blanks);
static void deleteWaypoints (int cmd);
static void changeFlags (int cmd);
static int CompareWaypoints (struct waypoint *wpt1, struct waypoint *wpt2,
                      int unused, SortRecordInfoPtr un1,
		      SortRecordInfoPtr un2, VoidHand appInfo);
static int EditButtons (Word button);
static int HandleEditControls (Word menu_item);
static int HandleEditOpen ();
static int HandleListMenu (Word menu_item);
static int HandleListButton (Word button);


//
//	and internal globals

static char *rcs_id = "$Id: gps.c,v 1.53 1999/08/30 06:35:32 paul Exp $";
extern char *version_info;
extern char *compile_date;
DWord romVersion;

//
//	main routine, does very little ...
//

DWord PilotMain(Word cmd, Ptr cmdPBP, Word launchFlags)
{
   int err;

   if (cmd == sysAppLaunchCmdNormalLaunch) {
      err = StartApp ();
      if (err) {
	 return err;
      }
      FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
      ProcessLoop ();
      StopApp ();
   }
   return 0;
}



//
//	Load database, set the initial form, etc
//

int StartApp (void)
{
   int err = 0;
   Word i = 0;
   struct prefs p;
   ULong creator, type;
   LocalID id;

   FrmGotoForm (gpsWptListFrm);
   table_top = 0;
   wpt_copy_buf = NULL;
   if (SysLibFind ("Serial Library", &serial_lib)) {
      serial_lib = -1;
      FrmAlert (gpsSerialAlert);
   }
   serial_port_open = false;

   waypoint_db = NULL;
   i = sizeof (struct prefs);
   if (PrefGetAppPreferences (DB_APPID, 0, &p, &i, true)
       && i == sizeof (struct prefs)) {
      if (p.name[0] != '\0') {
	 id = DmFindDatabase (p.card, p.name);
	 if (id) {
	    DmDatabaseInfo (p.card, id, NULL, NULL, NULL, NULL, NULL,
			    NULL, NULL, NULL, NULL, &type, &creator);
	    if (type == DB_TYPE && creator == DB_APPID) {
	       waypoint_db = DmOpenDatabase (p.card, id, dmModeReadWrite);
	    }
	 }
      }
   }
   if (!waypoint_db) {
      waypoint_db = DmOpenDatabaseByTypeCreator (DB_TYPE, DB_APPID,
						 dmModeReadWrite);
   }
   if (!waypoint_db) {
      err = DmCreateDatabase (0, DB_NAME, DB_APPID, DB_TYPE, false);
      if (!err) {
	 waypoint_db = DmOpenDatabaseByTypeCreator (DB_TYPE, DB_APPID,
						    dmModeReadWrite);
      }
   }
   num_records = DmNumRecords (waypoint_db);
   return err;
}


//
//	Shut down and go home
//

void StopApp (void)
{
   struct prefs p;

   if (waypoint_db) {
      DmOpenDatabaseInfo (waypoint_db, NULL, NULL, NULL, &p.card, NULL);
      StrCopy (p.name, currentDBName());
      DmCloseDatabase (waypoint_db);
      PrefSetAppPreferences (DB_APPID, 0, 1, &p, sizeof (struct prefs), true);
      waypoint_db = NULL;
   }
   if (serial_port_open) {
      SerClose (serial_lib);
   }
}


//
//	Look for "load form" events, otherwise punt to form handlers
//

void ProcessLoop (void)
{
   Word err = 0;
   Word formID;
   FormPtr form;
   EventType e;
   Long timeout = evtWaitForever;

   do {
      EvtGetEvent(&e, timeout);
      if (SysHandleEvent(&e))  {
	 continue;
      }
      if (MenuHandleEvent((void *)0, &e, &err)) {
	 continue;
      }

      if (e.eType == frmLoadEvent) {
	 formID = e.data.frmLoad.formID;
	 form = FrmInitForm (formID);
	 FrmSetActiveForm (form);
	 timeout = evtWaitForever;
	 switch (formID) {
	    case gpsWptListFrm:
	       FrmSetEventHandler (form, HandleWaypointListEvent);
	       break;
	    case gpsWptEditFrm:
	       FrmSetEventHandler (form, HandleWaypointEditEvent);
	       break;
	    case gpsFileOpenFrm:
	    case gpsFileMergeFrm:
	    case gpsFileUtilFrm:
	       FrmSetEventHandler (form, HandleFileListEvent);
	       break;
	    case gpsFileNewFrm:
	       FrmSetEventHandler (form, HandleFileNewEvent);
	       break;
	    case garminHostFrm:
	       timeout = 0L;
	       FrmSetEventHandler (form, HandleHostEvent);
	 }
      }
      FrmDispatchEvent (&e);
   } while (e.eType != appStopEvent);
}


//
//	Waypoint list:
//
//	Allow the user to select a waypoint, then "EDIT"; or
//	Allow the user to go to "XFER"; or
//	Allow the user to create a "NEW" entry (special case of edit)
//

Boolean HandleWaypointListEvent (EventPtr e)
{
   int handled = false;
   FormPtr form;
   TablePtr table;
   Word row;
   struct waypoint *wpt, *new_wpt;
   VoidHand rec_handle;

   CALLBACK_PROLOGUE;

   form = FrmGetActiveForm ();
   switch (e->eType) {
      case frmOpenEvent:
	 setField (dbName, currentDBName ());
	 FrmDrawForm (form);
	 DisplayTable ();
	 current_record = -1;
	 handled = true;
	 break;
      case menuEvent:
         handled = HandleListMenu (e->data.menu.itemID);
	 break;
      case tblSelectEvent:
	 table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, wptTable ));
	 row = e->data.tblSelect.row;
	 current_record = TblGetRowID (table, row);
	 new_wpt = MemPtrNew (sizeof (struct waypoint));
	 rec_handle = DmGetRecord (waypoint_db, current_record);
	 wpt = MemHandleLock (rec_handle);
	 MemMove (new_wpt, wpt, sizeof (struct waypoint));
	 new_wpt->flagged = TblGetItemInt (table, row, 0);
	 DmWrite (wpt, 0, new_wpt, sizeof (struct waypoint));
	 MemHandleUnlock (rec_handle);
	 MemPtrFree (new_wpt);
	 DmReleaseRecord (waypoint_db, current_record, true);
	 handled = true;
	 break;
      case ctlSelectEvent:
         handled =  HandleListButton (e->data.ctlEnter.controlID);
	 break;
      case keyDownEvent:
	 switch (e->data.keyDown.chr) {
	    case pageUpChr:
	       table_top -= TABLE_SCROLL;
	       DisplayTable ();
	       handled = true;
	       break;
	    case pageDownChr:
	       table_top += TABLE_SCROLL;
	       DisplayTable ();
	       handled = true;
	       break;
	 }
         break;
      default:
	break;
   }
   CALLBACK_EPILOGUE;
   return handled;
}


//
//	Waypoint edit:
//
//	Fill in fields from selected entry
//	On "OK", transfer back to list
//	On "CANCEL", just go back
//

Boolean HandleWaypointEditEvent (EventPtr e)
{
   int handled = false;

   CALLBACK_PROLOGUE;
   switch (e->eType) {
      case frmOpenEvent:
         handled =  HandleEditOpen ();
	 break;
      case menuEvent:
         handled = HandleEditControls (e->data.menu.itemID);
	 break;
      case ctlSelectEvent:
         handled = EditButtons (e->data.ctlEnter.controlID);
	 break;
      default:
	break;
   }
   CALLBACK_EPILOGUE;
   return handled;
}


//
//	Display a table of waypoints
//
//	For now, just fill in from memory, but later this will 
//	read from a database file
//

void DisplayTable ()
{
   int i;
   FormPtr form;
   TablePtr table;
   Word rec_num;
   VoidHand rec_handle;
   struct waypoint *wpt;

   // first, check that we're displaying from a logical position
   num_records = DmNumRecords (waypoint_db);
   if (table_top > num_records - TABLE_ROWS) {
      table_top = num_records - TABLE_ROWS;
   }
   if (table_top < 0) {
      table_top = 0;
   }

   form = FrmGetActiveForm();
   table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, wptTable ));
   TblEraseTable (table);
   TblSetColumnUsable (table, 0, true);
   TblSetColumnUsable (table, 1, true);
   TblSetColumnUsable (table, 2, true);
   TblSetColumnUsable (table, 3, true);
   TblSetLoadDataProcedure (table, 2, descrTableLoadName);
   TblSetSaveDataProcedure (table, 2, descrTableSaveName);
   TblSetCustomDrawProcedure (table, 3, descrTableDrawDescr);
   for (i = 0; i < TABLE_ROWS; ++i) {
      rec_num = i + table_top;
      if (rec_num >= num_records) {
	 TblSetRowUsable (table, i, false);
      }
      else {
	 rec_handle = DmQueryRecord (waypoint_db, rec_num);
	 wpt = MemHandleLock (rec_handle);
	 TblSetRowID (table, i, rec_num);
	 TblSetItemStyle (table, i, 0, checkboxTableItem);
	 TblSetItemInt (table, i, 0, wpt->flagged);
	 TblSetItemStyle (table, i, 1, numericTableItem);
	 TblSetItemInt (table, i, 1, rec_num);
	 TblSetItemStyle (table, i, 2, textTableItem);
	 TblSetItemStyle (table, i, 3, customTableItem);
	 MemHandleUnlock (rec_handle);
	 TblSetRowUsable (table, i, true);
	 TblSetRowSelectable (table, i, true);
	 TblMarkRowInvalid (table, i);
      }
   }
   TblDrawTable (table);
}


//
//	Used to put the waypoint NAME into a table field, so
//	that it can be edited.  This is fairly simple ...
//

Err descrTableLoadName (VoidPtr tbl, Word row, Word column,
                        Boolean editable, VoidHand *dataH, Word *offset,
			Word *size, FieldPtr fID)
{
   char *txt;
   struct waypoint *wpt;
   Word rec_num = 0;
   VoidHand mem_handle,
            rec_handle;

   CALLBACK_PROLOGUE;
   rec_num = TblGetRowID (tbl, row);
   rec_handle = DmQueryRecord (waypoint_db, rec_num);
   if (rec_handle == NULL) {
      *dataH = NULL;
      *offset = *size = 0;
      CALLBACK_EPILOGUE;
      return 0;
   }
   wpt = MemHandleLock (rec_handle);
   mem_handle = MemHandleNew (GARMIN_NAME_LEN + 1);
   txt = MemHandleLock (mem_handle);
   StrNCopy (txt, wpt->name, GARMIN_NAME_LEN);
   txt[GARMIN_NAME_LEN] = '\0';
   *dataH = mem_handle;
   *offset = 0;
   *size = StrLen(txt);
   FldSetMaxChars (fID, GARMIN_NAME_LEN);
   MemHandleUnlock (rec_handle);
   MemHandleUnlock (mem_handle);
   CALLBACK_EPILOGUE;
   return 0;
}


//
//	... and save it once it has changed
//

Boolean descrTableSaveName (VoidPtr tbl, Word row, Word col)
{
   char *txt;
   struct waypoint *wpt,
		   *new_wpt;
   Word rec_num = 0;
   VoidHand rec_handle;
   VoidHand mem_handle;
   FieldPtr field;

   CALLBACK_PROLOGUE;
   field = TblGetCurrentField (tbl);
   if (field && FldDirty (field)) {
      rec_num = TblGetRowID (tbl, row);
      rec_handle = DmGetRecord (waypoint_db, rec_num);
      if (rec_handle == NULL) {
	 CALLBACK_EPILOGUE;
	 return 0;
      }
      wpt = MemHandleLock (rec_handle);
      new_wpt = MemPtrNew (sizeof (struct waypoint));
      MemMove (new_wpt, wpt, sizeof (struct waypoint));
      mem_handle = (VoidHand) FldGetTextHandle (TblGetCurrentField (tbl));
      txt = MemHandleLock (mem_handle);
      StrNCopy (new_wpt->name, txt, GARMIN_NAME_LEN);
      new_wpt->name[GARMIN_NAME_LEN] = '\0';
      uppercase (new_wpt->name, true);
      new_wpt->changed = true;
      DmWrite (wpt, 0, new_wpt, sizeof (struct waypoint));
      MemHandleUnlock (rec_handle);
      MemHandleUnlock (mem_handle);
      DmReleaseRecord (waypoint_db, rec_num, true);
   }
   CALLBACK_EPILOGUE;
   return 0;
}


//
//	Custom draw procedure for the description, which isn't
//	editable.  This forces it to select as a chunk.
//

void descrTableDrawDescr (VoidPtr tbl, Word row, Word column,
                          RectanglePtr bounds)
{
   char txt[16];
   struct waypoint *wpt;
   Word rec_num = 0;
   VoidHand rec_handle;
   FontID old_font;

   CALLBACK_PROLOGUE;
   rec_num = TblGetRowID (tbl, row);
   rec_handle = DmQueryRecord (waypoint_db, rec_num);
   if (rec_handle == NULL) {
      CALLBACK_EPILOGUE;
      return;
   }
   wpt = MemHandleLock (rec_handle);
   StrNCopy (txt, wpt->descr, 15);
   txt[15] = '\0';
   old_font = FntSetFont (0);
   WinDrawChars (txt, StrLen(txt), bounds->topLeft.x, bounds->topLeft.y);
   FntSetFont (old_font);
   MemHandleUnlock (rec_handle);
   CALLBACK_EPILOGUE;
   return;
}


//
//	Set an editable field's contents ...
//

void setField (Word fieldname, char *contents)
{
   FormPtr form;
   FieldPtr field;
   VoidHand handle;
   char *txt;

   form = FrmGetActiveForm ();
   field = FrmGetObjectPtr( form, FrmGetObjectIndex( form, fieldname));
   handle = MemHandleNew (StrLen (contents) + 1);
   txt = MemHandleLock (handle);
   StrCopy (txt, contents);
   FldSetText(field, handle, 0, StrLen(contents));
   MemHandleUnlock (handle);
}

//
//	... and retrieve it
//

void getField (Word fieldname, char *contents)
{
   FormPtr form;
   FieldPtr field;
   VoidHand handle;
   char *txt;

   form = FrmGetActiveForm ();
   field = FrmGetObjectPtr( form, FrmGetObjectIndex( form, fieldname));
   handle = (VoidHand) FldGetTextHandle (field);
   if (handle == NULL) {
      *contents = '\0';
   }
   else {
      txt = MemHandleLock (handle);
      StrCopy (contents, txt);
      MemHandleUnlock (handle);
   }
}


//
//	Convert a string to UPPERCASE in place, plus remove
//	non-Garmin characters.
//

void uppercase (char *c, int strip_blanks)
{
   for (; *c != '\0'; ++c) {
      if (*c >= 'a' && *c <= 'z') {
	 *c = *c - 'a' + 'A';
      }
      if (*c == ' ' && strip_blanks) {
	 *c = '-';
      }
      if ((*c < 'A' || *c > 'Z') && (*c < '0' || *c > '9') &&
	   *c != ' ' && *c != '-') {
	 *c = '-';
      }
   }
}
      

//
//	Delete a whole bunch of entries (either clear the lot or 
//	check whether they are flagged before deleting).
//

void deleteWaypoints (int cmd)
{
   int i;
   int flag;
   VoidHand handle;
   struct waypoint *wpt;

   for (i = 0; i < num_records; ++i) {
      handle = DmQueryRecord (waypoint_db, i);
      if (! handle)
	 break;
      wpt = MemHandleLock (handle);
      flag = wpt->flagged;
      MemHandleUnlock (handle);
      switch (cmd) {
	 case menuClrAll:
	    DmRemoveRecord (waypoint_db, i--);
	    break;
	 case menuClrFlagged:
	    if (flag)
	       DmRemoveRecord (waypoint_db, i--);
	    break;
	 case menuClrUnflagged:
	    if (! flag)
	       DmRemoveRecord (waypoint_db, i--);
	    break;
      }
   }
   DisplayTable ();
}



//
//	manipulate the "flag" field of the database, either set all,
//	clear all or toggle.  A tad more complex than above, but not
//	by much ...
//

void changeFlags (int cmd)
{
   int i;
   VoidHand handle;
   struct waypoint *wpt;
   struct waypoint *new_wpt;

   new_wpt = MemPtrNew (sizeof (struct waypoint));
   for (i = 0; i < num_records; ++i) {
      handle = DmGetRecord (waypoint_db, i);
      wpt = MemHandleLock (handle);
      MemMove (new_wpt, wpt, sizeof (struct waypoint));
      switch (cmd) {
	 case menuFlag:
	    new_wpt->flagged = true;
	    break;
	 case menuUnflag:
	    new_wpt->flagged = false;
	    break;
	 case menuToggleFlag:
	    if (new_wpt->flagged)
	       new_wpt->flagged = false;
	    else
	       new_wpt->flagged = true;
	    break;
	 case menuFlagChanged:
	    if (new_wpt->changed)
	       new_wpt->flagged = true;
	    break;
	 case menuClearChanged:
	    new_wpt->changed = false;
	    break;
      }
      DmWrite (wpt, 0, new_wpt, sizeof (struct waypoint));
      MemHandleUnlock (handle);
      DmReleaseRecord (waypoint_db, i, true);
   }
   MemPtrFree (new_wpt);
   DisplayTable ();
}


//
//	Compare two waypoints for a sort
//

int CompareWaypoints (struct waypoint *wpt1, struct waypoint *wpt2,
                      int unused, SortRecordInfoPtr un1,
		      SortRecordInfoPtr un2, VoidHand appInfo)
{
   int result;

   result = StrCompare (wpt1->name, wpt2->name);
   if (!result) {
      result = StrCompare (wpt1->descr, wpt2->descr);
   }
   return result;
}


//
//	Handle edit "open form" event
//

int HandleEditOpen ()
{
   FormPtr form;
   FieldPtr field;
   struct waypoint *wpt;
   VoidHand rec_handle;
   int i,
       new = false,
       grab_focus = false;

   form = FrmGetActiveForm ();
   setField (dbName, currentDBName ());
   if (current_record >= 0 && current_record < num_records) {
      rec_handle = DmQueryRecord (waypoint_db, current_record);
      wpt = MemHandleLock (rec_handle);
      setField (editName, wpt->name);
      setField (editDescr, wpt->descr);
      setField (editLatitude, GarminLongToPos (wpt->latitude));
      setField (editLongitude, GarminLongToPos (wpt->longitude));
      LstSetSelection (FrmGetObjectPtr (form, FrmGetObjectIndex (form, listNS)),
		       wpt->latitude < 0 ? 1 : 0);
      LstSetSelection (FrmGetObjectPtr (form, FrmGetObjectIndex (form, listEW)),
		       wpt->longitude < 0 ? 1 : 0);
      if (StrLen (wpt->name) == 3 &&
	  wpt->name[0] >= '0' && wpt->name[0] <= '9') {
	 grab_focus = true;
      }
      MemHandleUnlock (rec_handle);
   }
   else {
      new = grab_focus = true;
      setField (editName, "WAYPT");
      i = StrLen ("WAYPY");
      setField (editDescr, "");
      setField (editLatitude, GarminLongToPos (0L));
      setField (editLongitude, GarminLongToPos (0L));
   }
   FrmSetFocus (form, FrmGetObjectIndex (form, editName));
   if (grab_focus) {
      field = FrmGetObjectPtr( form, FrmGetObjectIndex( form, editName));
      FldSetSelection (field, 0, 6);
   }
   FrmDrawForm (form);
   return true;
}


//
//	Process edit "menu" events
//

int HandleEditControls (Word menu_item)
{
   int handled = false;
   FormPtr form;
   FieldPtr field;
   Word fID, start, end;

   form = FrmGetActiveForm ();
   fID = FrmGetFocus (form);
   field = FrmGetObjectPtr (form, fID);
   switch (menu_item) {
      case menuDB:
	 FrmCustomAlert (whoAmI, currentDBName(), " ", " ");
	 handled = true;
	 break;
      case menuCut:
	 FldCut (field);
	 handled = true;
	 break;
      case menuCopy:
	 FldCopy (field);
	 handled = true;
	 break;
      case menuPaste:
	 FldPaste (field);
	 handled = true;
	 break;
      case menuCopySpecial:
	 // grab all fields from the screen & internal & save
	 handled = true;
	 break;
      case menuPasteSpecial:
	 // must update waypoint + all fields from saved buffer
	 handled = true;
	 break;
      case menuUndo:
	 FldUndo (field);
	 handled = true;
	 break;
      case menuSelectAll:
	 FldSetSelection (field, 0, FldGetTextLength (field));
	 handled = true;
	 break;
      case menuDelete:
	 FldGetSelection (field, &start, &end);
	 FldDelete (field, start, end);
	 handled = true;
	 break;
      default:
	 FrmAlert (gpsAlert1);
	 break;
   }
   return handled;
}


//
//	... and process edit button taps
//

int EditButtons (Word button)
{
   int handled = false;
   FormPtr form;
   struct waypoint *wpt;
   struct waypoint *new_wpt;
   VoidHand rec_handle;
   char tmp[15];
   int dirty;
   long lat, lon;

   form = FrmGetActiveForm ();
   switch (button) {
      case gpsOkBtn:
	 dirty = false;
	 if (current_record >= 0 && current_record < num_records) {
	    rec_handle = DmGetRecord (waypoint_db, current_record);
	    wpt = MemPtrNew (sizeof (struct waypoint));
	    MemSet (wpt, sizeof (struct waypoint), 0);
	    new_wpt = MemHandleLock (rec_handle);
	    MemMove (wpt, new_wpt, sizeof (struct waypoint));
	 }
	 else {
	    current_record = num_records + 1;
	    rec_handle = DmNewRecord (waypoint_db, &current_record,
				      sizeof (struct waypoint));
	    num_records = DmNumRecords (waypoint_db);
	    table_top = num_records - TABLE_ROWS;
	    wpt = MemPtrNew (sizeof (struct waypoint));
	    MemSet (wpt, sizeof (struct waypoint), 0);
	    new_wpt = MemHandleLock (rec_handle);
	 }
	 wpt->changed = true;
	 getField (editName, wpt->name);
	 getField (editDescr, wpt->descr);
	 getField (editLatitude, tmp);
	 wpt->latitude = GarminPosToLong (tmp);
	 if (LstGetSelection (FrmGetObjectPtr (form,
			      FrmGetObjectIndex (form, listNS))) == 1) {
	    wpt->latitude *= -1L;
	 }
	 getField (editLongitude, tmp);
	 wpt->longitude = GarminPosToLong (tmp);
	 if (LstGetSelection (FrmGetObjectPtr (form,
			      FrmGetObjectIndex (form, listEW))) == 1) {
	    wpt->longitude *= -1L;
	 }
	 uppercase (wpt->name, true);
	 uppercase (wpt->descr, false);
	 DmWrite (new_wpt, 0, wpt, sizeof (struct waypoint));
	 MemHandleUnlock (rec_handle);
	 MemPtrFree (wpt);
	 DmReleaseRecord (waypoint_db, current_record, true);
	 FrmGotoForm (gpsWptListFrm);
	 handled = true;
	 break;
      case gpsNokBtn:
	 FrmGotoForm (gpsWptListFrm);
	 handled = true;
	 break;
      case gpsKillBtn:
	 if (current_record >= 0 && current_record < num_records) {
	    DmRemoveRecord (waypoint_db, current_record);
	 }
	 FrmGotoForm (gpsWptListFrm);
	 handled = true;
	 break;
      case gpsHereBtn:
	 if (! GarminGetPos (&lat, &lon)) {
	    setField (editLatitude, GarminLongToPos (lat));
	    setField (editLongitude, GarminLongToPos (lon));
	    LstSetSelection (FrmGetObjectPtr (form,
			     FrmGetObjectIndex (form, listNS)),
			     lat < 0 ? 1 : 0);
	    LstSetSelection (FrmGetObjectPtr (form,
			     FrmGetObjectIndex (form, listEW)),
			     lon < 0 ? 1 : 0);
	    FrmDrawForm (form);
	 }
	 break;
      default:
	 FrmAlert (gpsAlert1);
	 handled = true;
	 break;
   }
   return handled;
}


//
//	Handle list view menu events
//

int HandleListMenu (Word menu_item)
{
   int handled = false;
   struct waypoint *wpt, *sel_wpt;
   VoidHand record;

   switch (menu_item) {
      case menuAbout:
	 FrmCustomAlert (gpsAboutAlert, version_info, compile_date, " ");
	 handled = true;
	 break;
      case menuDB:
	 FrmCustomAlert (whoAmI, currentDBName(), " ", " ");
	 handled = true;
	 break;
      case menuNew:
	 newfile_camefrom = gpsWptListFrm;
	 FrmPopupForm (gpsFileNewFrm);
	 handled = true;
	 break;
      case menuOpen:
	 file_merge = false;
	 FrmGotoForm (gpsFileOpenFrm);
	 handled = true;
	 break;
      case menuMerge:
	 file_merge = true;
	 FrmGotoForm (gpsFileMergeFrm);
	 handled = true;
	 break;
      case menuFileUtil:
	 file_merge = false;
	 FrmGotoForm (gpsFileUtilFrm);
	 handled = true;
	 break;
      case menuClrAll:
      case menuClrFlagged:
      case menuClrUnflagged:
	 deleteWaypoints (menu_item);
	 handled = true;
	 break;
      case menuFlag:
      case menuFlagChanged:
      case menuToggleFlag:
      case menuUnflag:
      case menuClearChanged:
	 changeFlags (menu_item);
	 handled = true;
	 break;
      case menuSelectAll:
	 changeFlags (menuFlag);
	 handled = true;
	 break;
      case menuSort:
	 DmQuickSort (waypoint_db, (DmComparF *) CompareWaypoints, 0);
	 current_record = table_top = 0;
	 DisplayTable ();
	 handled = true;
	 break;
      case menuFetch:
	 GarminGetAll (GPS_WAYPOINT, 0);
	 num_records = DmNumRecords (waypoint_db);
	 table_top = num_records - TABLE_ROWS;
	 DisplayTable ();
	 handled = true;
	 break;
      case menuXferTrackIn:
	 GarminGetAll (GPS_TRACK, 0);
	 break;
      case menuClearTrackLog:
	 if (FrmCustomAlert (fileDeleteAlert, "Track Log", " ", " ") == 0) {
	    GarminClearTrackLog ();
	 }
	 break;
      case menuXferRouteIn:
	 GarminGetAll (GPS_ROUTE, 0);
	 break;
      case menuXferTrackOut:
	 GarminSendTrack ();
	 break;
      case menuXferRouteOut:
	 GarminSendRaw (menu_item);
	 break;
      case menuSendAll:
      case menuSendFlagged:
      case menuSendChanged:
	 GarminSend (menu_item);
	 handled = true;
	 break;
      case menuGarminHost:
	 FrmGotoForm (garminHostFrm);
	 handled = true;
	 break;
      default:
	 FrmAlert (gpsAlert1);
	 break;
      case menuDelete:
	 if (current_record >= 0 && current_record < num_records) {
	    DmRemoveRecord (waypoint_db, current_record);
	 }
	 FrmGotoForm (gpsWptListFrm);
	 handled = true;
	 break;
      case menuCopySpecial:
	 if (current_record >= 0 && current_record < num_records) {
	    if (wpt_copy_buf == NULL) {
	       wpt_copy_buf = MemHandleNew (sizeof (struct waypoint));
	    }
	    wpt = MemHandleLock (wpt_copy_buf);
	    record = DmQueryRecord (waypoint_db, current_record);
	    sel_wpt = MemHandleLock (record);
	    MemMove (wpt, sel_wpt, sizeof (struct waypoint));
	    MemHandleFree (wpt_copy_buf);
	    MemHandleFree (record);
	 }
	 else {
	    if (wpt_copy_buf != NULL) {
	       MemHandleFree (wpt_copy_buf);
	       wpt_copy_buf = NULL;
	    }
	 }
	 handled = true;
	 break;
   }
   return handled;
}


//
//	List view button selections
//

int HandleListButton (Word button)
{
   int handled = false;

   switch (button) {
      case gpsOkBtn:
	 if (current_record >= 0 && current_record < num_records) 
	    FrmGotoForm (gpsWptEditFrm);
	 handled = true;
	 break;
      case gpsNewBtn:
	 current_record = -1;
	 FrmGotoForm (gpsWptEditFrm);
	 handled = true;
	 break;
      case gpsKillBtn:
	 if (current_record >= 0 && current_record < num_records) {
	    DmRemoveRecord (waypoint_db, current_record);
	    DisplayTable ();
	 }
	 handled = true;
	 break;
      case gpsSortBtn:
	 DmQuickSort (waypoint_db, (DmComparF *) CompareWaypoints, 0);
	 current_record = table_top = 0;
	 DisplayTable ();
	 handled = true;
	 break;
      default:
	 FrmAlert (gpsAlert1);
	 break;
   }
   return handled;
}


//
//	Get the name of the currently active database
//

char *currentDBName (void)
{
   LocalID id;
   UInt card;
   static char name[32];

   name[0] = '\0';
   if (waypoint_db) {
      DmOpenDatabaseInfo (waypoint_db, &id, NULL, NULL, &card, NULL);
      DmDatabaseInfo (card, id, name, NULL, NULL, NULL, NULL, NULL,
				      NULL, NULL, NULL, NULL, NULL);
   }
   return name;
}
