/* edit.c contains the editing view related code
 */

/* Includes required */
#include <Common.h>
#include <System/SysAll.h>
#include <System/Graffiti.h>
#include <UI/UIAll.h>
#include "listrc.h"
#include "list.h"

/* Ugly globals to remember memory allocation in TableLoadData procedure */
static VoidHand field1Memory;
static VoidHand field2Memory;
static VoidHand noteMemory;

Boolean EditFormEventHandler(EventPtr event)
{
  FormPtr form;
  Word category;
  Boolean edited;
  Boolean handled = false;
  TablePtr table;
  ControlPtr ctl;
  FieldPtr fld;
  UInt attr;
  Word i;
  Word labelWidth;
  Word row, col;
  CALLBACK_PROLOGUE;   /* Work around PilotGCC 0.5.0 bug */  

  switch( event->eType ) 
  {
		  
	  case ctlSelectEvent:
		  switch( event->data.ctlSelect.controlID ) {

			  case idEditDoneButton:
				  if( EditCurrentRecordEmpty() ) {
					  DmRemoveRecord( CurrentDB, CurrentRecord );
					  CurrentRecord = -1;
					  FrmGotoForm( idMainForm );
				  } else {
					  FrmGotoForm( idViewForm );
				  }
				  handled = true;
				  break;

			  case idEditDeleteButton:
				  /* prompt for confirmation */
				  if( FrmAlert( idVerifyDeleteRecord ) == 0 ) {
					  /* User selected the OK button, delete the record */
					  DmRemoveRecord( CurrentDB, CurrentRecord );
					  CurrentRecord = -1;
					  FrmGotoForm( idMainForm );
				  }
				  handled = true;
				  break;
				  
			  case idEditNoteButton:
				  FrmGotoForm( idNoteEditForm );
				  handled = true;
				  break;
				  
			  case idEditCategoryTrigger:
				  DmRecordInfo( CurrentDB, CurrentRecord, &attr, NULL, NULL );
				  category = attr & dmRecAttrCategoryMask;

				  /* The category pop down menu is being clicked on */
				  form = FrmGetActiveForm();
				  /* Crash under PalmOS emulator with 2.0 rom? -- bad rom file? */
				  edited = CategorySelectV10( CurrentDB, form, idEditCategoryTrigger, idEditCategoryList, 
											  false, &category, CategoryName );
				  
				  if( edited || (category != (attr & dmRecAttrCategoryMask))) {
					  /* We changed categories, update the record to reflect the change */
					  attr &= ~dmRecAttrCategoryMask;
					  attr |= category;
					  DmSetRecordInfo( CurrentDB, CurrentRecord, &attr, NULL );
					  if( CurrentCategory != dmAllCategories ) {
						  CurrentCategory = category;
					  }
					  /* If a category was deleted what happens? */
				  }
				  ListMarkDirty();
				  /* ROOHACK should I be setting handled = true here? */
				  break;
		  }
		  break;
	  
	  case tblSelectEvent:
		  form = FrmGetActiveForm();
		  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));
		  
		  if( event->data.tblSelect.column == editTableLabelCol ) {
			  TblReleaseFocus( table );
			  TblUnhighlightSelection( table );
			  TblGrabFocus( table, event->data.tblSelect.row, editTableDataCol );
			  fld = TblGetCurrentField( table );
			  FldGrabFocus ( fld );
			  FldMakeFullyVisible( fld ); 
		  } else {
			  fld = TblGetCurrentField( table );
		  }

		  if( FldGetTextLength( fld ) == 0 ) {
			  GrfSetState( false, false, true ); /* Force uppercase */
		  }
		  break;

	  case fldHeightChangedEvent:
		  /* One of the rows in the edit table has changed height */
		  form = FrmGetActiveForm();
		  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));
		  /* Dispatch the event to the table */
		  TblHandleEvent( table, event );
		  /* If the table scrolled, the first row will be updated with the correct item ID */
		  TopVisibleField = TblGetRowID( table, 0 );
		  /* Re-draw the table */
		  EditLoadTable( table, false );
		  TblRedrawTable( table );
		  break;

	  case keyDownEvent: 
		  if( ChrIsHardKey( event->data.keyDown.chr ) ) {
			  FrmGotoForm( idMainForm );
			  handled = true;
			  break;
		  }
	
		  form = FrmGetActiveForm();
		  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));
		  if( (event->data.keyDown.chr == nextFieldChr) || (event->data.keyDown.chr == prevFieldChr) ) {
			  if(!TblEditing(table)) 
				  break;
			  TblGetSelection( table, &row, &col );
			  i = TblGetRowID( table, row );
			  if( event->data.keyDown.chr == prevFieldChr ) {
				  /* move up event */
				  if( i == 0 ) {
					  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
					  return( handled );
				  }
				  i--;
			  } else {
				  /* move down event */
				  if( i == editTableRows - 1 ) {
					  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
					  return( handled );
				  }
				  i++;
			  }
			  TblReleaseFocus( table );
			  
			  while(!TblFindRowID( table, i, &row )) {
				  TopVisibleField = i;
				  EditLoadTable( table, true );
				  TblRedrawTable( table );
			  }
			  TblGrabFocus( table, row, editTableDataCol );
			  fld = TblGetCurrentField( table );
			  FldSetInsPtPosition( fld, 0 );
			  FldGrabFocus( fld );
			  FldMakeFullyVisible( fld );
			  break;
		  }
      
		  if(! ((event->data.keyDown.chr == pageUpChr) || (event->data.keyDown.chr == pageDownChr)) ) {
			  /* Only handle page up/down key events specially */
			  break;
		  }
		  /* Change the focus to not be on the table element since we are about to
		  scroll and we want to force and update */
		  TblReleaseFocus( table );
		  /* Fall into the scrolling code */
	  case ctlRepeatEvent:

		  form = FrmGetActiveForm();
		  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));
		  if( (event->data.keyDown.chr == pageUpChr) || 
			  (event->data.ctlRepeat.controlID == idEditUpButton) ) {
			  /* Scroll Up: backup one field */
			  ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditUpButton ));
			  if( ctl->attr.visible && CtlEnabled( ctl ) ) {
				  TopVisibleField--; 
			  }
		  }
		  if( (event->data.keyDown.chr == pageDownChr) || 
			  (event->data.ctlRepeat.controlID == idEditDownButton) ) {
			  /* Scroll Down: move forward one field */
			  ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditDownButton ));
			  if( ctl->attr.visible && CtlEnabled( ctl ) ) {
				  TopVisibleField++; 
			  }
		  }
		  if( ctl->attr.visible && CtlEnabled( ctl ) ) { 
			  EditLoadTable( table, true );
			  TblRedrawTable( table );
		  }
		  break;

#if 0
		  case frmCloseEvent:
		  form = FrmGetActiveForm();
		  FrmHandleEvent( form, event );
		  if( field1Memory ) MemHandleFree( field1Memory );
		  if( field2Memory ) MemHandleFree( field2Memory );
		  if( noteMemory ) MemHandleFree( noteMemory );
		  handled = true;
		  break;
#endif

	  case frmOpenEvent:
		  field1Memory = NULL;
		  field2Memory = NULL;
		  noteMemory = NULL;
		  TopVisibleField = 0;
		  form = FrmGetActiveForm();
		  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));

		  for( i = 0; i < editTableRows; i++ ) {
			  TblSetItemStyle( table, i, editTableDataCol, textTableItem );
			  TblSetRowUsable( table, i, false );
		  }

		  TblSetColumnUsable( table, editTableLabelCol, true );
		  TblSetColumnUsable( table, editTableDataCol, true );

		  TblSetColumnSpacing( table, editTableLabelCol, editTableSpacing );

		  /* Set the callback routines that will load and save the description field. */
		  TblSetLoadDataProcedure( table, editTableDataCol, &EditGetRecordField );
		  TblSetSaveDataProcedure( table, editTableDataCol, &EditSetRecordField );

		  /* Dynamically set the column widths to adapt to custom field names */
		  SysCopyStringResource( NoteText, idStringNote );
		  labelWidth = FntCharsWidth( NoteText, StrLen( NoteText ));
		  labelWidth = max( labelWidth, FntCharsWidth( CustomField1, StrLen( CustomField1 )));
		  labelWidth = max( labelWidth, FntCharsWidth( CustomField2, StrLen( CustomField2 )));
		  labelWidth = min( labelWidth, 80 ); /* Make it no greater than half the screen */

		  TblSetColumnWidth( table, editTableLabelCol, labelWidth + 3 );
		  TblSetColumnWidth( table, editTableDataCol, 157 - labelWidth );

		  EditLoadTable( table, true );

		  /* Read the category for this record from database */
		  DmRecordInfo( CurrentDB, CurrentRecord, &attr, NULL, NULL );
		  category = attr & dmRecAttrCategoryMask;
		  ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditCategoryTrigger ));
		  CategoryGetName( CurrentDB, category, CategoryName );
		  CategorySetTriggerLabel( ctl, CategoryName );

		  FrmDrawForm( form );

		  /* Give the table focus */
      
		  FrmSetFocus( form, FrmGetObjectIndex( form, idEditTable ));
		  TblGrabFocus( table, 0, editTableDataCol );
		  fld = TblGetCurrentField( table );
		  FldGrabFocus( fld );
		  FldMakeFullyVisible( fld );
		  if( FldGetTextLength( fld ) == 0 ) {
			  GrfSetState( false, false, true ); /* Force uppercase */
		  }
		  handled = true;
		  break;

	  case menuEvent:
		  switch( event->data.menu.itemID ) 
		  {
			  case idEditMenuUndo:
			  case idEditMenuCut:
			  case idEditMenuCopy:
			  case idEditMenuPaste:
			  case idEditMenuSelectAll:
				  /* Common code for the edit operations */
				  form = FrmGetActiveForm();
				  i = FrmGetFocus (form);
				  /* ROOHACK: There seems to be a lot of error checking, it may not be needed */
				  if (i == noFocus) {
					  break;
				  }
	  
				  if (FrmGetObjectType (form, i) != frmTableObj) {
					  break;
				  }
				  
				  fld = TblGetCurrentField (FrmGetObjectPtr (form, i));
				  if (fld == NULL) {
					  break;
				  }
				  
				  /* A second switch to actually do the operation */
				  switch( event->data.menu.itemID)
				  {
					  case idEditMenuUndo:
						  FldUndo (fld);
						  break;
					  case idEditMenuCut:
						  FldCut (fld);
						  break;
					  case idEditMenuCopy:
						  FldCopy (fld);
						  break;
					  case idEditMenuPaste:
						  FldPaste (fld);
						  break;
					  case idEditMenuSelectAll:
						  FldSetSelection (fld, 0, FldGetTextLength (fld));
						  break;
				  }
				  handled = true;
				  break;
			  case idEditMenuKeyboard:
				  SysKeyboardDialogV10();
				  handled = true;
				  break;
		  }
		  break;

	  case appStopEvent:
		  /* We are switching applications, if the current record is empty -- delete it */
		  if( EditCurrentRecordEmpty() ) {
			  DmRemoveRecord( CurrentDB, CurrentRecord );
			  CurrentRecord = -1;
		  }
		  break;
  }
  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
  return( handled );
}

void NoteEditSave() {
	FormPtr form;
	FieldPtr field;
	VoidHand textHandle;
	CharPtr text;

	form = FrmGetActiveForm();
	field = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idNoteEditField ));
	textHandle = (VoidHand)FldGetTextHandle( field );

	if( FldDirty( field ) ) {
		ListMarkDirty();
		text = MemHandleLock( textHandle );
		UpdateRecord( 2, text ); /* 2 is the note field index */
		MemHandleUnlock( textHandle );
	}
}

Boolean NoteEditFormEventHandler(EventPtr event)
{
	FormPtr form;
	Word category;
	Boolean edited;
	Boolean handled = false;
	TablePtr table;
	ControlPtr ctl;
	FieldPtr fld;
	VoidHand recordHandle, textHandle;
	ListDBHeader *record;
	CharPtr string;
	UInt attr;
	Word i, stringLength;
	Word labelWidth;
	Word row, col;
	CALLBACK_PROLOGUE;   /* Work around PilotGCC 0.5.0 bug */  

	switch( event->eType ) 
	{
		case ctlSelectEvent:
			if( event->data.ctlSelect.controlID == idEditDoneButton ) {
				NoteEditSave();
				FrmGotoForm( idEditForm );
				handled = true;
			}
			break;

		case frmOpenEvent:
			form = FrmGetActiveForm();
			fld = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idNoteEditField ));
			recordHandle = DmQueryRecord( CurrentDB, CurrentRecord );
			/* lock record so we can read it */
			record = MemHandleLock( recordHandle );
			if( record->note != 0 ) {
				string = (CharPtr) record + record->note;
				stringLength = StrLen( string );
				textHandle = MemHandleNew( stringLength + 1 );
				if( textHandle == NULL ) {
					FrmAlert( bad ); /* a poor way to deal with out of memory */
				}
				StrCopy( MemHandleLock( textHandle ), string );
				MemHandleUnlock( textHandle );
				FldSetText( fld, textHandle, 0, stringLength + 1);
			}
			/* Remember to unlock the handle */
			MemHandleUnlock( recordHandle );

			/* Update scrollers */
			FrmUpdateScrollers( form,
								FrmGetObjectIndex( form, idEditUpButton ),
								FrmGetObjectIndex( form, idEditDownButton ),
								FldScrollable( fld, up ),
								FldScrollable( fld, down ));
			FrmDrawForm( form );
			handled = true;
			break;

		case menuEvent:
			switch( event->data.menu.itemID ) 
			{
				case idEditMenuUndo:
				case idEditMenuCut:
				case idEditMenuCopy:
				case idEditMenuPaste:
				case idEditMenuSelectAll:
				  /* Common code for the edit operations */
					form = FrmGetActiveForm();
					i = FrmGetFocus (form);
					/* If there is no focus, then we have no field to act on */
					if (i == noFocus) {
						break;
					}
					fld = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idNoteEditField ));
					/* If we have no field, bail */
					if (fld == NULL) {
						break;
					}

				  /* A second switch to actually do the operation */
					switch( event->data.menu.itemID)
					{
						case idEditMenuUndo:
							FldUndo (fld);
							break;
						case idEditMenuCut:
							FldCut (fld);
							break;
						case idEditMenuCopy:
							FldCopy (fld);
							break;
						case idEditMenuPaste:
							FldPaste (fld);
							break;
						case idEditMenuSelectAll:
							FldSetSelection (fld, 0, FldGetTextLength (fld));
							break;
					}
					handled = true;
					break;
				case idEditMenuKeyboard:
					SysKeyboardDialogV10();
					handled = true;
					break;
			}
			break;

		case keyDownEvent: 
			if( ChrIsHardKey( event->data.keyDown.chr ) ) {
				NoteEditSave();
				FrmGotoForm( idMainForm );
				handled = true;
				break;
			}
			form = FrmGetActiveForm();
			fld = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idNoteEditField ));			
			i = FldGetVisibleLines( fld ) - 1;
			if (event->data.keyDown.chr == pageUpChr) {
				if( FldScrollable( fld, up ) ) {
					FldScrollField( fld, i, up );
				}				
				handled = true;
			}
			if (event->data.keyDown.chr == pageDownChr) {
				if( FldScrollable( fld, down ) ) {
					FldScrollField( fld, i, down );
				}
				handled = true;
			}
			if( !handled ) {
				/* Just process each keystroke and update the scrollers */
				FrmHandleEvent( form, event );
				handled = true;
				}
			FrmUpdateScrollers( form,
								FrmGetObjectIndex( form, idEditUpButton ),
								FrmGetObjectIndex( form, idEditDownButton ),
								FldScrollable( fld, up ),
								FldScrollable( fld, down ));
			break;

		case ctlRepeatEvent:
			form = FrmGetActiveForm();
			fld = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idNoteEditField ));
			if( event->data.ctlRepeat.controlID == idEditUpButton ) {
				/* Scroll up, one line */
				if( FldScrollable( fld, up ) ) {
					FldScrollField( fld, 1, up );
				}
			}
			if( event->data.ctlRepeat.controlID == idEditDownButton ) {
				/* Scroll down, one line */
				if( FldScrollable( fld, down ) ) {
					FldScrollField( fld, 1, down );
				}
			}
			FrmUpdateScrollers( form,
								FrmGetObjectIndex( form, idEditUpButton ),
								FrmGetObjectIndex( form, idEditDownButton ),
								FldScrollable( fld, up ),
								FldScrollable( fld, down ));
			/* Leave unhandled to enable auto-repeat */
			break;
			
		case fldChangedEvent: /* good for drag scroll updates, etc.. */
			form = FrmGetActiveForm();
			fld = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idNoteEditField ));
			/* Update scrollers */
			FrmUpdateScrollers( form,
								FrmGetObjectIndex( form, idEditUpButton ),
								FrmGetObjectIndex( form, idEditDownButton ),
								FldScrollable( fld, up ),
								FldScrollable( fld, down ));
			handled = true;
			break;

		case appStopEvent:
			NoteEditSave();
			/* Do not handle, let system clean up */
			break;
	}
	CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
	return( handled );
}


/* This routine is used to update the edit table.  It is used when:
	o A field height changes (Typed text wraps to the next line)
	o Scrolling
	o Advancing to the next field causes scrolling
	o The focus moves to another field
	o A custom label changes
*/
void EditLoadTable(TablePtr table, Boolean initializeTable )
{
  VoidHand recordHandle;
  ListDBHeader *record;
  RectangleType bounds;
  Word tableHeight;
  Word columnWidth;
  Word height;
  Word dataHeight;
  Word fieldIndex;
  Word lastFieldIndex;
  Word row;
  FormPtr form;
  Word upIndex, downIndex;
  Boolean scrollUp = false, scrollDown = false;

  /* Hang onto the record */
  recordHandle = DmQueryRecord( CurrentDB, CurrentRecord );
  record = MemHandleLock( recordHandle );

  /* Determine the height of the table and the width of the data column */
  TblGetBounds( table, &bounds );
  tableHeight = bounds.extent.y;
  columnWidth = TblGetColumnWidth( table, editTableDataCol );

  row = 0;
  dataHeight = 0;
  fieldIndex = TopVisibleField;
  lastFieldIndex = fieldIndex;

  /* Load up the table */
  for( ; fieldIndex < editTableRows ; fieldIndex++ ) {
    if( dataHeight >= tableHeight ) {
      /* Bail if we run out of space */
      scrollDown = true;
      break;
    }
    
    height = EditGetFieldHeight( fieldIndex, columnWidth, record );
    dataHeight += height;
	/* ROOHACK: height is limited to the max height available for the table,
	   it is not clear that this is necessary -- and it prevents us from
	   knowing if the field did not fit into the display.  In this case
	   (the note field) we should enable an "edit note" button to allow
	   the user to go an edit the note in a more traditional manner */

    if( initializeTable ) {
      /*  initialize the row */
      TblSetRowUsable( table, row, true );
      /* set the field index as the row id */
      TblSetRowID( table, row, fieldIndex );
      /* mark the row invalid so it is redrawn */
      TblMarkRowInvalid( table, row );
      /* Set the height of the field */
      TblSetRowHeight(table, row, height );
      /* set the label for the column */
      TblSetItemStyle( table, row, 0, labelTableItem );
      switch( fieldIndex )
	{
	case 0:
	  TblSetItemPtr ( table, row, 0, CustomField1 );
	  break;
	case 1:
	  TblSetItemPtr ( table, row, 0, CustomField2 );
	  break;
	case 2:
      SysCopyStringResource( NoteText, idStringNote );
	  TblSetItemPtr ( table, row, 0, NoteText);
	  break;
	}
    }

    lastFieldIndex = fieldIndex;
    row++;
  }
  
  /* If we ran out of space to draw the table, then hide the remaining rows */
  while( row < editTableRows ) {
    TblSetRowUsable( table, row, false );
    row++;
  }
  /* Update the scroll buttons */
  scrollUp = TopVisibleField != 0;
  scrollDown =  dataHeight >= tableHeight;
  form = FrmGetActiveForm();
  upIndex = FrmGetObjectIndex( form, idEditUpButton );
  downIndex = FrmGetObjectIndex( form, idEditDownButton );
  FrmUpdateScrollers( form, upIndex, downIndex, scrollUp, scrollDown );

  /* Unlock the record */
  MemHandleUnlock( recordHandle );
}

/* Return the height required to display the text in the record
   with the given contraints. */
Word EditGetFieldHeight( Word field, Word width, ListDBHeader *record )
{
  CharPtr string = NULL;
  Word height;

  switch( field ) 
    {
    case 0:
      if( record->field1 != 0 ) {
	string = (CharPtr) record + record->field1;
      } 
      break;
    case 1:
      if( record->field2 != 0 ) {
	string = (CharPtr) record + record->field2;
      } 
      break;
    case 2:
      if( record->note != 0 ) {
	string = (CharPtr) record + record->note;
      } 
    }
  if( string != NULL ) {
    height = FldCalcFieldHeight( string, width );
    height = min( height, maxFieldLines ) * FntLineHeight();
    return( height );
  }
  return( FntLineHeight() );
}


/* Update the form to allow or disallow extended note editing */
void EditEnableNoteEdit( char * noteString ) {
	FormPtr form;
	ControlPtr noteButton;
	TablePtr table;
	Word columnWidth;
	Word x, y;
	
	form = FrmGetActiveForm();

	/* Determine if we show the edit note button or not */
	noteButton = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditNoteButton ));
	table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));	
	columnWidth = TblGetColumnWidth( table, editTableDataCol );

	if( FldCalcFieldHeight( noteString, columnWidth ) > 9 ) {
		/* Enable note button */
		CtlShowControl( noteButton );
	}
}

/* Note: editing is a byte field, but we to maintain word alignment on the
       stack we push a word. (only valid when hacking assembly, GCC does it for us)
       - Note: List was originally written in assembly (way way back)

       Check that this routine frees the memory somewhere.  Does the OS do it for us? (yes)
	   Actually the above statement is false -- the OS does NOT free the memory for us
	   so we have a work-around in frmCloseEvent
       */
Err EditGetRecordField(VoidPtr table, Word row, Word col, Boolean editing, VoidHand *textHandle,
		       WordPtr textOffset, WordPtr textSize, FieldPtr field )
{
  Word fieldIndex;
  VoidHand recordHandle;
  ListDBHeader *record;
  CharPtr string;
  Word length;
  int version;
  CALLBACK_PROLOGUE;   /* Work around PilotGCC 0.5.0 bug */

  version = QueryPalmOSVersion();

  /* the row id is the field # we want to get */
  fieldIndex = TblGetRowID( table, row );

  recordHandle = DmQueryRecord( CurrentDB, CurrentRecord );
  /* check for failure to locate record? - we're editing it, it better be there */

  /* initialize the return values */
  *textHandle = NULL;
  *textOffset = 0;
  *textSize = 0;

  /* lock record so we can read it */
  record = MemHandleLock( recordHandle );
  switch( fieldIndex ) 
    {
    case 0: /* field1 */

      if( version >= 2 ) { /* apparently in version 1 of PalmOS
				       the field parameter is not passed
				       in to this function */
		  FldSetMaxChars( field, 120 ); 
      }
      if( record->field1 != 0 ) {
		  string = (CharPtr)record + record->field1;
		  length = StrLen( string );
		  if( field1Memory ) MemHandleFree( field1Memory );
		  field1Memory = MemHandleNew( length + 1 );
		  *textHandle = field1Memory;
		  /* add fail code if allocation fails */
		  StrCopy( MemHandleLock( *textHandle ), string );
		  MemHandleUnlock( *textHandle );
		  *textOffset = 0;
		  *textSize = length + 1;
      }
      break;
    case 1: /* field2 */
      if( version >= 2 )  FldSetMaxChars( field, 120 ); 
      if( record->field2 != 0 ) {
		  string = (CharPtr)record + record->field2;
		  length = StrLen( string );
		  if( field2Memory ) MemHandleFree( field2Memory );
		  field2Memory = MemHandleNew( length + 1 );
		  *textHandle = field2Memory;
		  /* add fail code if allocation fails */
		  StrCopy( MemHandleLock( *textHandle ), string );
		  MemHandleUnlock( *textHandle );
		  *textOffset = 0;
		  *textSize = length + 1;
      }
      break;
    case 2: /* note */
      if( version >= 2 )  FldSetMaxChars( field, 4096 );
      if( record->note != 0 ) {
		  string = (CharPtr)record + record->note;
		  length = StrLen( string );

		  EditEnableNoteEdit( string );

		  if( noteMemory ) MemHandleFree( noteMemory );
		  noteMemory = MemHandleNew( length + 1 );
		  *textHandle = noteMemory;
		  /* add fail code if allocation fails */
		  StrCopy( MemHandleLock( *textHandle ), string );
		  MemHandleUnlock( *textHandle );
		  *textOffset = 0;
		  *textSize = length + 1;
      }
      break;
    }
  /* release the lock on the datatbase record */
  MemHandleUnlock( recordHandle );

  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
  return( 0 );
}

Boolean EditSetRecordField( VoidPtr table, Word row, Word col )
{
  FieldPtr fieldPointer;
  VoidHand textHandle;
  CharPtr text;
  Word fieldIndex;
  UInt attr;
  LocalID dbID;
  UInt cardNo;
  UInt dbAttr;
  CALLBACK_PROLOGUE;   /* Work around PilotGCC 0.5.0 bug */

  fieldPointer = TblGetCurrentField( table );
  textHandle = (VoidHand)FldGetTextHandle( fieldPointer );

  /*  the row id is the field # */
  fieldIndex = TblGetRowID( table, row );
  
  /* some stuff I dont really get.  Has to do with redrawing the field
     if it is not visible? */
  
  /* Make sure there any selection is removed since we will free
     the text memory before the callee can remove the selection. */
  FldSetSelection( fieldPointer, 0, 0 );
  
  if( FldDirty( fieldPointer ) ) {
    /* Skip empty (ie: NULL) text fields */
    if( textHandle != NULL ) {
      
      ListMarkDirty();
      
      text = MemHandleLock( textHandle );
      
      /*  Update the record */
      UpdateRecord( fieldIndex, text );

	  if( fieldIndex == 2 )  { /* note field */
		  EditEnableNoteEdit( text );
	  }
      
      MemHandleUnlock( textHandle );
    }
  }
  
  /* Free the memory used for the fields text because the table suppresses it. */
  FldFreeMemory( fieldPointer );
  switch(fieldIndex){
	  case 0:
		  field1Memory = NULL;
		  break;
	  case 1:
		  field2Memory = NULL;
		  break;
	  case 2:
		  noteMemory = NULL;
		  break;
  }

  CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
  return( true ); /* no redraw */
}



/* Update the current records field with the given text *
 *
 * field is one of 0, 1, or 2. */
void UpdateRecord( Word field, CharPtr newText )
{
  VoidHand oldRecordHandle, newRecordHandle;
  ListDBHeader *oldRecord, *newRecord, *record;
  CharPtr source, dest;
  Word currentOffset;
  ULong recordID;
  
  oldRecordHandle = DmQueryRecord( CurrentDB, CurrentRecord );
  oldRecord = MemHandleLock( oldRecordHandle );	/*  lock to allow us to read memory */

  newRecord = MemPtrNew( MAX_RECORD_SIZE );
  /* Check for no memory situation */
  if( newRecord == NULL ) { 
    FrmAlert( idNoMemUpdate ); 
    return;
  } 

  currentOffset = sizeof( ListDBHeader );

  if( field == 0 ) {
    /* we are updating field1 */
    source = newText;
  } else {
    /* verify that field1 is not empty */
    if( oldRecord->field1 != 0 ) {
      /* copy the old field1 data */
      source = (CharPtr)oldRecord + oldRecord->field1;
    } else {
      source = NULL;
    }
  }
  if( source != NULL ) {
    dest = (CharPtr)newRecord + currentOffset;
    StrCopy( dest, source );
    newRecord->field1 = currentOffset;
    currentOffset += (StrLen( source ) + 1);
  } else {
    newRecord->field1 = 0;
  }
  
  if( field == 1 ) {
    /* we are updating field2 */
    source = newText;
  } else {
    /* verify that field2 is not empty */
    if( oldRecord->field2 != 0 ) {
      /* copy the old field2 data */
      source = (CharPtr)oldRecord + oldRecord->field2;
    } else {
      source = NULL;
    }
  }
  if( source != NULL ) {
    dest = (CharPtr)newRecord + currentOffset;
    StrCopy( dest, source );
    newRecord->field2 = currentOffset;
    currentOffset += (StrLen( source ) + 1);
  } else {
    newRecord->field2 = 0;
  }
  
  if( field == 2 ) {
    /* we are updating the note */
    source = newText;
  } else {
    /* verify that the note is not empty */
    if( oldRecord->note != 0 ) {
      /* copy the old note data */
      source = (CharPtr)oldRecord + oldRecord->note;
    } else {
      source = NULL;
    }
  }   
  if( source != NULL ) {
    dest = (CharPtr)newRecord + currentOffset;
    StrCopy( dest, source );
    newRecord->note = currentOffset;
    currentOffset += (StrLen( source ) + 1);
  } else {
    newRecord->note = 0;
  }
	   
  /* Now we know how big the new record will be */
  
  newRecordHandle = DmNewHandle( CurrentDB, currentOffset );
  /*  Check for errors (out of memory) */
  if( newRecordHandle == NULL ) { 
    MemPtrFree( newRecord );   /* clean up */
    MemHandleUnlock( oldRecordHandle );
    MemHandleFree( oldRecordHandle );
    FrmAlert( idNoMemUpdate ); 
    return; 
  } 

  record = MemHandleLock( newRecordHandle );
  DmWrite( record, 0, newRecord, currentOffset );
  /* error checking? */

  /* Attach the new record in place over the old one */
  DmAttachRecord( CurrentDB, &CurrentRecord, (Handle)newRecordHandle, (Handle *)&oldRecordHandle );
  /* ROOHACK check for errors? */

  MemHandleUnlock( newRecordHandle ); /* unlock the new record */
  MemPtrFree( newRecord );
  MemHandleUnlock( oldRecordHandle );	/*  unlock the record */
  MemHandleFree( oldRecordHandle );

  /* Do an insertion sort to keep things in order */
  DmRecordInfo( CurrentDB, CurrentRecord, NULL, &recordID, NULL );
  DmInsertionSort( CurrentDB, (DmComparF *)&ListCompareRecords, DisplayStyle );
  DmFindRecordByID( CurrentDB, recordID, &CurrentRecord );

}

void ListMarkDirty()
{
  ListPreferences listPreferences;
  LocalID dbID;
  UInt cardNo;
  UInt dbAttr;
  
  PrefGetAppPreferencesV10( ListType, ListVersion, &listPreferences, sizeof(ListPreferences));
  /* only automate the backup if we have been asked to do so */
  if( listPreferences.automateBackup ) {

    /* Tag the database so it will be backup'd next hot sync */
    DmOpenDatabaseInfo( CurrentDB, &dbID, NULL, NULL, &cardNo, NULL );
    DmDatabaseInfo( cardNo, dbID, NULL, &dbAttr, NULL, NULL, NULL, NULL,
		    NULL, NULL, NULL, NULL, NULL );
    dbAttr |= dmHdrAttrBackup;
    DmSetDatabaseInfo( cardNo, dbID, NULL, &dbAttr, NULL, NULL, NULL, NULL, 
		       NULL, NULL, NULL, NULL, NULL );
  }
  return;
}

/* Return true if the current record is empty, false otherwise */
Boolean EditCurrentRecordEmpty()
{
  FormPtr form;
  TablePtr table;
  FieldPtr fieldPointer;
  VoidHand textHandle;
  CharPtr text;
  VoidHand recordHandle;
  ListDBHeader *record;

  /* Check to see if the current text field is dirty, if so then the 
     record is NOT empty, but has not been saved into the database */
  form = FrmGetActiveForm();
  table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idEditTable ));
  fieldPointer = TblGetCurrentField( table );
  if( fieldPointer != NULL ) { /* table was not the focus, no field was selected */
	  textHandle = (VoidHand)FldGetTextHandle( fieldPointer );
	  if( FldDirty( fieldPointer )) {
		  return( false );
	  }
  }

  recordHandle = DmQueryRecord( CurrentDB, CurrentRecord );
  record = MemHandleLock( recordHandle );
  if( (record->field1 == 0) && (record->field2 == 0) && (record->note == 0) ) {
    MemHandleUnlock( recordHandle );
    return( true );
  }
  MemHandleUnlock( recordHandle );
  return( false );
}

/* Return 1, 2, or 3 to indicate the PalmOS version.  This is used
   to enable/disable certain features in the List program depending
   on what is available on this Palm device */
int QueryPalmOSVersion() {
	DWord version;
	if(FtrGet('psys', 1, &version)) {
		/* The version query call should never fail */
		FrmAlert(bad);
	}
	if( version >= 0x03000000 ) {
		return 3; /* PalmOS 3.0 or greater */
	}
	if( version >= 0x02000000 ) {
		return 2; /* PalmOS 2.0 or greater */
	}
	return 1; /* default to PalmOS 1.0 */
}