/* List - A simple database.
Originally was CD List database of CD titles and artists */

/* The "usual" Pilot includes */
#include <Common.h>
#include <System/SysAll.h>
#include <UI/UIAll.h>
#include <ExgMgr.h>

#include "listrc.h"	/* include resource ids */
#include "list.h"	/* structures and defines */

/* Global variables */
DmOpenRef CurrentDB;  /* Pointer to the database */
Word CurrentRecord;	  /* The currrent record number */
Word TopVisibleRecord;	  /* index of the top most record being displayed (main form) */
Word TopVisibleField;	  /* Index of the top most field in the table (edit form) */
Word CurrentCategory;		/* The currrent category */
char CategoryName[dmCategoryLength]; /* Holder of the current category */
char ListDBName[dmDBNameLength];  /* The database name */
char CustomField1[CUSTOM_FIELD_SIZE];	  /* Artist */
char CustomField2[CUSTOM_FIELD_SIZE];	  /* Album	*/
Byte DisplayStyle; /* how to display the fields */
Byte WriteProtect; /* cached value of write-protect */
Word ViewPage;	/* The current "page" of information the view mode is showing */
Boolean MultipleLists;	 /* true if there is more than a single list */
char ViewTitle[32]; /* Storage used for the view title "View ", number, " of ", number */
char NoteText[16]; /* Storage used for note text (allow translation) */
char LocalFindText[32]; /* Text used for local finds */

ListAppInfoType DefaultListApplicationInfo = {
	0x000e, /* renamed categories */

	/* Category labels */
	"Unfiled",
	"", "", "", "", "", "", "",
	"", "", "", "", "", "", "", "",

	/* Category Uniq IDs */
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 

	15,	 /* Last Uniq ID */

	/* End of AppInfoType structure, start of List specific data */

	/* Display style */
	0,

	/* Write-Protect */
	0,

	/* Category */
	dmAllCategories,

	/* Custom fields */
	"Field1",
	"Field2"
};

/* 
PilotMain is called by the startup code and implements a simple event handling loop.
*/
DWord PilotMain(Word cmd, Ptr cmdPBP, Word launchFlags)
{
	Word error = 0;
	UInt attr;
	ULong uniqueID;
	EventType gotoEvent;
	CALLBACK_PROLOGUE;	 /* Work around PilotGCC 0.5.0 bug */

	switch( cmd ) 
	{ 
				case sysAppLaunchCmdNormalLaunch:
					/* A normal launch */

					if(( error = StartApplication()) != 0 ) {
						CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
								return( error );
					}
					EventLoop();

					StopApplication();
					break;

				case sysAppLaunchCmdFind:
					/* System find command */
					CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
							ListSearch( (FindParamsPtr) cmdPBP	);
					break;

				case sysAppLaunchCmdGoTo:
					if( launchFlags & sysAppLaunchFlagNewGlobals ) {
						/* The application being launched */
						{
							LocalID dbID;
							UInt cardNo;
							DmSearchStateType dbState;
							Word count = 0;

							if( DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
								&cardNo, &dbID ) == 0) {
								do { count++;
								} while( DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
									&cardNo, &dbID ) == 0);
							}		
							MultipleLists = count > 1;
						}

						/* We are doing a goto, we know that the database exists, just open it */
						CurrentDB = DmOpenDatabase( ((GoToParamsPtr) cmdPBP)->dbCardNo, 
							((GoToParamsPtr) cmdPBP)->dbID, dmModeReadWrite );
						ReadDatabaseInfo();
						CurrentRecord = ((GoToParamsPtr) cmdPBP)->recordNum;
						DmRecordInfo( CurrentDB, CurrentRecord, &attr, NULL, NULL );
						/* change the current category if it is not appropriate */
						if( CurrentCategory != dmAllCategories ) {
							CurrentCategory = attr & dmRecAttrCategoryMask;
						}
					} else {
						/* The application is running.	Close the current database */
						CloseDatabase();

						/* Open the one with the "found" record */
						CurrentDB = DmOpenDatabase( ((GoToParamsPtr) cmdPBP)->dbCardNo, 
							((GoToParamsPtr) cmdPBP)->dbID, dmModeReadWrite );
						ReadDatabaseInfo();

						/* Close all the forms to clean up where we were in the application,
						use the uniqueID to avoid losing track of the record */
						CurrentRecord = ((GoToParamsPtr) cmdPBP)->recordNum;
						DmRecordInfo( CurrentDB, CurrentRecord, &attr, &uniqueID, NULL );
						/* change the current category if it is not appropriate */
						if( CurrentCategory != dmAllCategories ) {
							CurrentCategory = attr & dmRecAttrCategoryMask;
						}
						FrmCloseAllForms(); 
						DmFindRecordByID( CurrentDB, uniqueID, &CurrentRecord );
					}

					/*	Send an event to load the form we want to goto */
					MemSet( &gotoEvent, sizeof(EventType), 0 );
					gotoEvent.eType = frmLoadEvent;
					gotoEvent.data.frmLoad.formID = idViewForm;
					EvtAddEventToQueue (&gotoEvent);

					/*	Send an event to goto a form and select the matching text. */
					gotoEvent.eType = frmGotoEvent;
					gotoEvent.data.frmGoto.formID = idViewForm;
					gotoEvent.data.frmGoto.recordNum = CurrentRecord;

					EvtAddEventToQueue (&gotoEvent);


					if( launchFlags & sysAppLaunchFlagNewGlobals ) {
						EventLoop ();
						StopApplication ();	
					}

					break;

				case sysAppLaunchCmdSyncNotify:
					/* sent to application after a hot sync has been performed */
					ListHandleSync();
					break;
					
				case sysAppLaunchCmdExgReceiveData:
					if(( launchFlags & sysAppLaunchFlagSubCall) && (CurrentDB != 0)) {
						/* The List application is running */
						CloseDatabase(); /* in case we are recieving a replacement */
					}
					
					ListReceiveData( (ExgSocketPtr)cmdPBP );

					if( launchFlags & sysAppLaunchFlagSubCall ) {
						/* The List application is running */
						if( QueryPalmOSVersion() < 2 ) {
							FrmGotoForm( idSelectFormV10 );
						} else {
							FrmGotoForm( idSelectFormV20 );
						}
					}

					break;
	}

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


void EventLoop() 
{
	EventType event;
	Word error;

	do {
		/* Doze until an event arrives */
		EvtGetEvent(&event, evtWaitForever);

		/* System gets first chance to handle the event */
		if(! SysHandleEvent( &event )) {

			/* Menu handler gets second chance to handle the event */
			if(! MenuHandleEvent(NULL, &event, &error) ) {

				/* Application handler gets third chance to handle the event */
				if( !ApplicationHandleEvent(&event) ) {

					/*	Dispatch the event to the applications handler for the form */
					FrmDispatchEvent( &event );
				}
			}
		}
	}  while( event.eType != appStopEvent );
}


/* 
Start up the application initializing the database if needed.
Note: It is possible for this routine to fail, at this point we should
return an error code and bail out of the application.
*/
Word StartApplication() 
{
	ListPreferences listPreferences;
	DmSearchStateType dbState;	
	UInt cardNo;
	LocalID dbID;
	Word count = 0;
	char dbName[dmDBNameLength];

	if(!PrefGetAppPreferencesV10( ListType, ListVersion, &listPreferences, sizeof(ListPreferences))) {
		/* Set the default */
		listPreferences.automateBackup = true;
		listPreferences.selectDatabaseOnStartup = false;
	}

	/* Count how many databases there are */
	if( DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
										&cardNo, &dbID ) == 0) {
		do { count++;
		} while( DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
			&cardNo, &dbID ) == 0);
	}
	MultipleLists = count > 1;

	if( count == 0 ) {
		/* There are no databases */
		CreateDatabase();
		ReadDatabaseInfo();
		FrmGotoForm( idCustomForm );
		return( 0 );
	}

	if( !listPreferences.selectDatabaseOnStartup ) {
		/* Find the database that was running last */
		if( DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
											&cardNo, &dbID ) == 0) {
			do {
				/* Is the current Database the one we are looking for? */
				DmDatabaseInfo( cardNo, dbID, dbName, NULL, NULL, NULL, NULL, NULL, NULL,
								NULL, NULL, NULL, NULL );
				if( StrCompare( dbName, listPreferences.lastDBName ) == 0 ) {
					/* Yes it is, open and bail out */
					CurrentDB = DmOpenDatabase( cardNo, dbID, dmModeReadWrite );
					ReadDatabaseInfo();
					FrmGotoForm( idMainForm ); 
					return( 0 );
				}
			} while( DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
				&cardNo, &dbID ) == 0);
		} 
	}

	if( count == 1 ) {
		/* A single database */
		CurrentDB = DmOpenDatabaseByTypeCreator( ListType, ListCreator, dmModeReadWrite );
		ReadDatabaseInfo();
		FrmGotoForm( idMainForm ); 
	} else {
		/* Multiple databases */
		if( QueryPalmOSVersion() < 2 ) {
			FrmGotoForm( idSelectFormV10 );
		} else {
			FrmGotoForm( idSelectFormV20 );
		}
	}

	return( 0 );
}


/* 
Stop the application, closing resources (database).
*/
void StopApplication()
{
	ListPreferences listPreferences;

	/* Remember the state of the application for the next startup */
	PrefGetAppPreferencesV10( ListType, ListVersion, &listPreferences, sizeof(ListPreferences));
	StrCopy( listPreferences.lastDBName, ListDBName );
	PrefSetAppPreferencesV10( ListType, ListVersion, &listPreferences, sizeof( ListPreferences ));

	/* Send a frmSave event to all open forms */
	FrmSaveAllForms();

	/* Close all the open forms */

	FrmCloseAllForms();

	/* Close the database */
	CloseDatabase();
}


/*
Boolean ApplicationHandleEvent(EventType *event)

This routine loads form resources and set the event
handler for the form loaded.

Return true if the event has handle and should not be passed
to a higher level handler.
*/
Boolean ApplicationHandleEvent(EventPtr event)
{
	Word formId;
	FormPtr form;

	if( event->eType == frmLoadEvent ) {

		/* Load the form resource */
		formId = event->data.frmLoad.formID;
		form = FrmInitForm (formId);
		FrmSetActiveForm (form);

		switch( formId ) 
		{
			case idMainForm:
				FrmSetEventHandler( form, &MainFormEventHandler );
				break;

			case idViewForm:
				FrmSetEventHandler( form, &ViewFormEventHandler );
				break;

			case idEditForm: 
				FrmSetEventHandler( form, &EditFormEventHandler );
				break;

			case idNoteEditForm: 
				FrmSetEventHandler( form, &NoteEditFormEventHandler );
				break;

			case idCustomForm: 
				FrmSetEventHandler( form, &CustomFormEventHandler );
				break;

			case idSelectFormV10: 
				FrmSetEventHandler( form, &SelectFormEventHandlerV10 );
				break;
				
			case idSelectFormV20: 
				FrmSetEventHandler( form, &SelectFormEventHandlerV20 );
				break;

			case idFindForm: 
				FrmSetEventHandler( form, &FindFormEventHandler );
				break;

			case idFindResultsForm: 
				FrmSetEventHandler( form, &FindResultsFormEventHandler );
				break;

			default:
				return( false );
				break;
		}

		return( true );
	}
	return( false );
}

/* A portable version of StrNCaselessCompare */
static Int ListStrNCaselessCompare( Char *source, Char* dest, Int length ) {
	Int i;
	Char destination[16];
	
	if( QueryPalmOSVersion() >= 2 ) {
		return( StrNCaselessCompare( source, dest, length ));
	}

	/* Oh well, do it the long hand version */
	/* Copy length, or less from the dest into destination and zero terminate */
	for( i=0; i<length; i++ ) {
		destination[i] = source[i];
		if( source[i] == 0 )
			break; /* abort copy */
	}
	return( StrCaselessCompare( source, destination ));
}

Boolean QuickScroll( EventPtr event ) {
	VoidHand recordHandle;
	Int numRecords, match;
	ListDBHeader *record;
	TablePtr table;
	FormPtr form;
	FieldPtr field;
	UInt fieldIndex;
	Char *alphaSrc;
	UInt base, probe;
	Boolean alphaScrollMatch = false;
	Char alphaDst[16];
	Word quickTextLength, qi, alphaHighlight;
	/* Some random key has been input -- if there exists a record
	in the current category whose first letter in the display
	style (ie: sort order) matches, then scroll such that this
	record is visible (top of the screen?) */

	/* Update the quick scroll text line */
	form = FrmGetActiveForm();
	table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainTable ));
	fieldIndex = FrmGetObjectIndex( form, idMainLookupText );
	FrmSetFocus( form, fieldIndex );
	field = FrmGetObjectPtr( form, fieldIndex );

	if( FldHandleEvent( field, event ) || (event->eType == fldChangedEvent) ) {
		alphaSrc = FldGetTextPtr( field );
		quickTextLength = FldGetTextLength( field );

		/* if the text is of zero length, unhighlight any entry in the table */
		if( quickTextLength == 0 ) {
			TblUnhighlightSelection( table );
			return true;
		}

		/* binary search for a "close" record */
		base = 0;
		probe = 0;
		numRecords = DmNumRecords( CurrentDB );
		while( numRecords > 0 ) {
			numRecords = numRecords >> 1; /* Quick divide by 2 */
			probe = base + numRecords;
			recordHandle = DmQueryRecord( CurrentDB, probe );
			if( recordHandle ) {
				record = MemHandleLock( recordHandle );
				match = ListStrNCaselessCompare( alphaSrc,
											 (DisplayStyle == 0 ?
											  ((CharPtr)record + record->field1 ) :
											  ((CharPtr)record + record->field2 )),
											 quickTextLength );
				MemHandleUnlock( recordHandle );
				
				if( match > 0 ) {
					/* probe not far enough, move forward */
					base = probe;
					}
			}
		}
		/* now linear scan to find first match */
		for(;;) {
			recordHandle = DmQueryNextInCategory( CurrentDB, &probe, CurrentCategory );
			if( !recordHandle ) {
				/* No more records */
				break;
			}
			record = MemHandleLock( recordHandle );
			match = ListStrNCaselessCompare( alphaSrc,
										 (DisplayStyle == 0 ?
										  ((CharPtr)record + record->field1 ) :
										  ((CharPtr)record + record->field2 )),
										 quickTextLength );
			MemHandleUnlock( recordHandle );
			if(match == 0) {
				TopVisibleRecord = probe;
				alphaScrollMatch = true;
				break;
			}
			probe++;
		}

		if( alphaScrollMatch == true ) {
			CurrentRecord = TopVisibleRecord; /* highlight element */
			alphaHighlight = MainLoadTable();
			TblEraseTable( table );
			TblDrawTable( table );
			TblSelectItem( table, alphaHighlight, 0 );
		} else {
			/* No match? delete the new character -- it is invalid */
			FldDelete( field, FldGetInsPtPosition( field ) - 1, quickTextLength );
		}
		return true;
	}
	return false;
}

/*
MainFormEventHandler
*/

Boolean MainFormEventHandler(EventPtr event)
{
	FormPtr form;
	FieldPtr field;
	Boolean handled = false;
	TablePtr table;
	VoidHand recordHandle;
	ListDBHeader *record;
	ControlPtr ctl;
	Word i;
	UInt attr;
	Word category; 

	CALLBACK_PROLOGUE;	 /* Work around PilotGCC 0.5.0 bug */

	switch( event->eType )
	{
				case ctlSelectEvent:
					switch( event->data.ctlSelect.controlID ) 
					{
						case idMainNewButton:
							if( WriteProtect ) {
								FrmAlert( idWriteProtect );
							} else {
								CurrentRecord = -1;
								/* Create a new (empty) record, it is automatically attached to the database */
								recordHandle = DmNewRecord( CurrentDB, &CurrentRecord, sizeof(ListDBHeader) );
								if( recordHandle == NULL ) {
									FrmAlert( DeviceFullAlert );
									break;	/* probably not the right kind of bail out */
								}

								/* Initialize the record */
								record = MemHandleLock( recordHandle );
								DmSet( record, 0, sizeof(ListDBHeader), 0 );
								MemHandleUnlock( recordHandle );
								DmReleaseRecord( CurrentDB, CurrentRecord, true );

								/* Set category information in database record.. */
								if( CurrentCategory != dmAllCategories ) {
									DmRecordInfo( CurrentDB, CurrentRecord, &attr, NULL, NULL );
									attr &= ~dmRecAttrCategoryMask;
									attr |= CurrentCategory;
									DmSetRecordInfo( CurrentDB, CurrentRecord, &attr, NULL );
								}

								FrmGotoForm( idEditForm );
							}
							handled = true;
							break;

						case idMainCategoryTrigger:
							/* The category pop down menu is being clicked on */
							form = FrmGetActiveForm();
							category = CurrentCategory;
							CategorySelectV10( CurrentDB, form, idMainCategoryTrigger, idMainCategoryList, 
											   true, &category, CategoryName );
categoryChange:
							if( category != CurrentCategory ) {
								/* Nuke the quick find text if any */
								field = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainLookupText ));
								FldDelete( field, 0, FldGetTextLength( field ));
								
								/* We changed categories, update the list to reflect the change */
								CurrentCategory = category;
								TopVisibleRecord = 0;
								CurrentRecord = -1;
								MainLoadTable();
								table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainTable ));
								TblEraseTable( table );
								TblDrawTable( table );
							}
							break;

						case idMainSelectButton:
							/* Close the current db and goto the select form */
							CloseDatabase();
							if( QueryPalmOSVersion() < 2 ) {
								FrmGotoForm( idSelectFormV10 );
							} else {
								FrmGotoForm( idSelectFormV20 );
							}
							handled = true;
							break;

						case idMainFindButton:
							FrmPopupForm( idFindForm );
							handled = true;
							break;
					}
					break;

				case fldChangedEvent:
					form = FrmGetActiveForm();
					handled = QuickScroll( event );
					break;

				case ctlRepeatEvent:
					/* Do not set handle = true for repeat events so they will repeat automatically */ 
					form = FrmGetActiveForm();
					table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainTable ));
					switch( event->data.ctlRepeat.controlID ) {
						case idMainUpButton:
							/* Scroll Up: Backup either mainTableRows - 1 elements, or as far as we can */
							ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainUpButton ));
							if( ctl->attr.visible && CtlEnabled( ctl ) ) {
								TopVisibleRecord = TblGetRowID( table, 0 );
								for( i=0; i<mainTableRows-1; i++ ) {
									if(DmSeekRecordInCategory( CurrentDB, &TopVisibleRecord, 1, dmSeekBackward, CurrentCategory ))
										break;
								}
							}
							break;
							
						case idMainDownButton:
							/* Scroll Down: make the top visible record the bottom record currently displayed */
							ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainDownButton ));
							if( ctl->attr.visible && CtlEnabled( ctl ) ) {
								TopVisibleRecord = TblGetRowID( table, mainTableRows - 1 );
							}
							break;
					}
					if( ctl->attr.visible && CtlEnabled( ctl ) ) { 
						CurrentRecord = -1;
						MainLoadTable();
						TblEraseTable( table );
						TblDrawTable( table );
					}
					break;
					
				case keyDownEvent: 
					form = FrmGetActiveForm();
					if( ChrIsHardKey( event->data.keyDown.chr ) ) {
						/* Cycle categories */
						category = CategoryGetNext( CurrentDB, CurrentCategory );

						ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainCategoryTrigger ));
						CategoryGetName( CurrentDB, category, CategoryName );
						CategorySetTriggerLabel( ctl, CategoryName );

						handled = true;
						goto categoryChange; /* ROOHACK: goto's are evil (partII) */
					} else {
						
						table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainTable ));
						ctl = NULL;

						switch( event->data.keyDown.chr ) {
							case pageUpChr:
								/* Scroll Up: Backup either mainTableRows - 1 elements, or as far as we can */
								ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainUpButton ));
								if( ctl->attr.visible && CtlEnabled( ctl ) ) {
									TopVisibleRecord = TblGetRowID( table, 0 );
									for( i=0; i<mainTableRows-1; i++ ) {
										if(DmSeekRecordInCategory( CurrentDB, &TopVisibleRecord, 1, dmSeekBackward, CurrentCategory ))
											break;
									}
								}
								break;

							case pageDownChr:
								/* Scroll Down: make the top visible record the bottom record currently displayed */
								ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainDownButton ));
								if( ctl->attr.visible && CtlEnabled( ctl ) ) {
									TopVisibleRecord = TblGetRowID( table, mainTableRows - 1 );
								}
								break;

							default:
								handled = QuickScroll( event );
						}

						if( ctl && ctl->attr.visible && CtlEnabled( ctl ) ) { 
							CurrentRecord = -1;
							MainLoadTable();
							TblEraseTable( table );
							TblDrawTable( table );
						}
					}
					break;

				case frmOpenEvent:
					form = FrmGetActiveForm();

					/* Initialize the CD List table */
					table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainTable ));
					for( i = 0; i < mainTableRows; i++ ) {
						TblSetItemStyle( table, i, 0, customTableItem );
						TblSetRowUsable( table, i, false );
					}

					TblSetColumnUsable( table, 0, true );

					/* Set the callback routine that will draw the records */
					TblSetCustomDrawProcedure( table, 0, &MainTableDrawRecord );

					i = MainLoadTable();

					ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainCategoryTrigger ));
					CategoryGetName( CurrentDB, CurrentCategory, CategoryName );
					CategorySetTriggerLabel( ctl, CategoryName );

					/* Make the title of this form the name of the database */
					FrmSetTitle( form, ListDBName );

					FrmDrawForm( form );

					/* Null out the quick scroll text */
					/* ROOHACK?? */

					/* Hide/show the list select pushbutton */
					if( MultipleLists ) {
						ctl = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainSelectButton ));
						CtlShowControl( ctl );
						WinDrawLine( 123, 149, 129, 149 );
						WinDrawLine( 123, 151, 129, 151 );
						WinDrawLine( 123, 153, 129, 153 );
						WinDrawLine( 123, 155, 129, 155 );
					}

					if( i != -1 ) {
						TblSelectItem( table, i, 0 );	
					}

					/* Give the quick lookup text the focus */
					FrmSetFocus( form, FrmGetObjectIndex( form, idMainLookupText ));
					field = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainLookupText ));
					FldGrabFocus( field );

					handled = true;
					break;

				case tblSelectEvent:
					CurrentRecord = TblGetRowID( event->data.tblSelect.pTable, event->data.tblSelect.row );
					FrmGotoForm (idViewForm);
					handled = true;
					break;

				case menuEvent:
					switch( event->data.menu.itemID ) 
					{
						case idMainMenuSelectDB:
							/* Close the current db and goto the select form */
							CloseDatabase();
							if( QueryPalmOSVersion() < 2 ) {
								FrmGotoForm( idSelectFormV10 );
							} else {
								FrmGotoForm( idSelectFormV20 );
							}
							handled = true;
							break;
						case idMainMenuNewDB:
							/* Close the current db, create a new one and configure it */
							CloseDatabase();

							/* Verify we don't already have 12 databases */
							{
								LocalID dbID;
								UInt cardNo;
								DmSearchStateType dbState;

								i = 0;
								if( DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
									&cardNo, &dbID ) == 0) {
									do { i++;
									} while( DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
										&cardNo, &dbID ) == 0);
								}		
								if( (QueryPalmOSVersion() < 2 ) && (i > 12) ) {
									FrmAlert( idMaxNumberOfDBs );
									FrmGotoForm( idSelectFormV10 );
								} else { 
									int errorID;

									if((errorID = CreateDatabase()) != 0) {
										/* failed to create a new one, goto the form that switches databases */
										if( errorID == idDuplicateDB ) {
											DmSearchStateType dbState;
											UInt cardNo;
											LocalID dbID;
											char dbName[dmDBNameLength];
											char defaultDbName[dmDBNameLength];
											/* This is the "New List" name duplication error, select the database named "New List" */
											SysCopyStringResource( defaultDbName, idStringNewDatabase );
											if( DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
												&cardNo, &dbID ) == 0) {
												do {
													/* Is the current Database the one we are looking for? */
													DmDatabaseInfo( cardNo, dbID, dbName, NULL, NULL, NULL, NULL, NULL, NULL,
														NULL, NULL, NULL, NULL );
													if( StrCompare( dbName, defaultDbName ) == 0 ) {
														/* Yes it is, open and bail out */
														CurrentDB = DmOpenDatabase( cardNo, dbID, dmModeReadWrite );
														ReadDatabaseInfo();
														FrmAlert( idDuplicateDB );
														FrmGotoForm( idMainForm );
														break;
													}
												} while( DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
													&cardNo, &dbID ) == 0);
											}
										} else {
											FrmAlert( errorID );
											if( QueryPalmOSVersion() < 2 ) {
												FrmGotoForm( idSelectFormV10 );
											} else {
												FrmGotoForm( idSelectFormV20 );
											}
										}
									} else {
										MultipleLists = i > 0;
										/* Successfully created a new database */
										ReadDatabaseInfo();
										FrmGotoForm( idCustomForm );
									}
								}
							}
							handled = true;
							break;
						case idMainMenuDelDB:
							/* Verify the request */
							if( FrmAlert( idVerifyDeleteDB ) == 0 ) {
								UInt cardNo;
								LocalID dbID;
								/* User selected the OK button, delete the database */
								DmOpenDatabaseInfo( CurrentDB, &dbID, NULL, NULL, &cardNo, NULL );
								DmCloseDatabase( CurrentDB );
								DmDeleteDatabase( cardNo, dbID );

								CurrentDB = DmOpenDatabaseByTypeCreator( ListType, ListCreator, dmModeReadWrite );
								if( CurrentDB == 0 ) {
									/* if we've delete the only database, then automatically create a new one */
									CreateDatabase(); /* ROOHACK: ignore return code? */
									ReadDatabaseInfo();
									FrmGotoForm( idCustomForm );
								} else {
									DmCloseDatabase( CurrentDB );
									if( QueryPalmOSVersion() < 2 ) {
										FrmGotoForm( idSelectFormV10 );
									} else {
										FrmGotoForm( idSelectFormV20 );
									}
								}
							}
							handled = true;
							break;

						case idMainMenuBeamDB:
							/* Note also handle keyDown even sendData elsewhere? */
							if( QueryPalmOSVersion() >= 3 ) {
								Err ListSocketWrite( void* buffer, ULong* sizeP, ExgSocketPtr s );
								ExgSocketType socket;
								LocalID dbID;
								UInt cardNo;
								Err error = 0;
								UInt dbAttr;

								/* Ensure that this database is not copy protected */
								DmOpenDatabaseInfo( CurrentDB, &dbID, NULL, NULL, &cardNo, NULL );
								DmDatabaseInfo( cardNo, dbID, NULL, &dbAttr, NULL, NULL, NULL, NULL,
												NULL, NULL, NULL, NULL, NULL );
								if( dbAttr &= dmHdrAttrCopyPrevention ) {
									FrmAlert( idCopyProtected );
								} else {
									MemSet(&socket,sizeof(socket),0);
									socket.description = ListDBName;
									socket.target = ListCreator;
									error = ExgPut(&socket);
									if (!error) {
										error = ExgDBWrite( (ExgDBWriteProcPtr)&ListSocketWrite, &socket, ListDBName, dbID, cardNo );
										ExgDisconnect( &socket, error );
									}
								}
							} else {
								FrmAlert( idBeamNotSupported );
							}
							handled = true;
							break;

						case idMainMenuAbout:
							FrmAlert( idAbout );
							handled = true;
							break;
						case idMainMenuCustom:
							FrmGotoForm (idCustomForm);
							handled = true;
							break;
					}
					break;
	}
	CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
			return( handled );
}

/* This draws the list view of the database entries.  
In the future we will have three(?) different draw policies based
on the database info.  For now we will always use a single draw
policy */
void MainTableDrawRecord(VoidPtr table, Word row, Word column, RectanglePtr bounds )
{
	VoidHand recordHandle;
	ListDBHeader *record;
	UInt recordNumber;
	CALLBACK_PROLOGUE;	 /* Work around PilotGCC 0.5.0 bug */

	recordNumber = TblGetRowID( table, row );  /* the row ID contains the record # */
	recordHandle = DmQueryRecord( CurrentDB, recordNumber );

	/* If the DmQueryRecord fails, don't use the pointer */
	if( recordHandle ) {
		record = MemHandleLock( recordHandle );
		ListDrawEntry( record, bounds, DisplayStyle );
		MemHandleUnlock( recordHandle  ); /*  remember to unlock what we lock down */
	}

	CALLBACK_EPILOGUE; /* Work around PilotGCC 0.5.0 bug */
}


/* This routine is used to update the main form table.	It is used when:
- intializing the table
- scrolling

Answers the row to highlight if the current record is shown, if the current
row is not shown it answers -1.
*/
Word MainLoadTable()
{
	Word recordNumber;
	FormPtr form;
	TablePtr table;
	Word i;
	Err error;
	Word select;
	Word upIndex, downIndex;
	Boolean scrollUp, scrollDown;

	form = FrmGetActiveForm();
	table = FrmGetObjectPtr( form, FrmGetObjectIndex( form, idMainTable ));

	select = -1;
retryTableLoad:
	recordNumber = TopVisibleRecord;
	/* For every table row, fetch the associated record */
	for( i = 0; i < mainTableRows; i++ ) {

		/*	Check we have a record of this number */
		error = DmSeekRecordInCategory( CurrentDB, &recordNumber, 0, dmSeekForward, CurrentCategory );

		if( error != 0 ) {
			/* Do not use the row if we get an error, the rest of the rows will also get errors too */
			TblSetRowUsable( table, i, false );
		} else {
			/* Make the row usable */
			TblSetRowUsable( table, i, true );
			/* Mark it invalid so it will be redrawn */
			TblMarkRowInvalid( table, i );
			/*	Set the row id to be the record number */
			TblSetRowID( table, i, recordNumber );

			if( recordNumber == CurrentRecord ) {
				select = i;
			}
		}

		recordNumber++;
	}

	/* If the current record is not visible, try again */
	if((CurrentRecord != -1) && (select == -1)) {
		TopVisibleRecord = CurrentRecord;
		goto retryTableLoad;
	}

	TblUnhighlightSelection( table );

	/* Determine what the scroll buttons should look like */
	if( error == 0) {
		error = DmSeekRecordInCategory( CurrentDB, &recordNumber, 0, dmSeekForward, CurrentCategory );
	}
	scrollDown = (error == 0);

	recordNumber = TopVisibleRecord;
	error = DmSeekRecordInCategory( CurrentDB, &recordNumber, 1, dmSeekBackward, CurrentCategory );
	scrollUp = (error == 0);

	upIndex = FrmGetObjectIndex( form, idMainUpButton );
	downIndex = FrmGetObjectIndex( form, idMainDownButton );

	FrmUpdateScrollers( form, upIndex, downIndex, scrollUp, scrollDown ); 

	return( select );
}


/* Create a new database, making it the current database */
int CreateDatabase() {
	Err error;
	LocalID dbID;
	UInt cardNo; 
	LocalID appInfoID;
	VoidHand appInfoHandle;
	ListAppInfoType *appInfo;
	DmSearchStateType dbState;
	char dbName[dmDBNameLength];
	char defaultDbName[dmDBNameLength]; 

	/* create database */
	SysCopyStringResource( defaultDbName, idStringNewDatabase );
	error = DmCreateDatabase( 0, defaultDbName, ListCreator, ListType, 0 );
	if( error == dmErrAlreadyExists ) {
		return( idDuplicateDB );
	}
	if( error ) {
		FrmAlert(bad);	/*ROOHACK: ?? what about this case? */
		return( bad );
	}
	/* new code below */
	DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
									&cardNo, &dbID );
	DmDatabaseInfo( cardNo, dbID, dbName, NULL, NULL, NULL, NULL, NULL, NULL,
					NULL, NULL, NULL, NULL );
	while( StrCompare( dbName, defaultDbName ) != 0 ) {
		DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
										&cardNo, &dbID );
		DmDatabaseInfo( cardNo, dbID, dbName, NULL, NULL, NULL, NULL, NULL, NULL,
						NULL, NULL, NULL, NULL );
	}
	CurrentDB = DmOpenDatabase( cardNo, dbID, dmModeReadWrite );

	/* Initialize the database info structure */
	DmOpenDatabaseInfo( CurrentDB, &dbID, NULL, NULL, &cardNo, NULL );
	appInfoHandle = DmNewHandle( CurrentDB, sizeof(DefaultListApplicationInfo) );
	if( !appInfoHandle ) {
		/*ROOHACK: Out of memory error */
		FrmAlert( DeviceFullAlert );
		return( bad );
	}
	appInfoID = MemHandleToLocalID( appInfoHandle );
	DmSetDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, 
					  NULL, &appInfoID, NULL, NULL, NULL);

	/* Copy in the defaults */
	appInfo = MemHandleLock( appInfoHandle );
	SysCopyStringResource( DefaultListApplicationInfo.categoryLabels[0], idStringUnfiled );
	SysCopyStringResource( DefaultListApplicationInfo.customField1, idStringField1 );
	SysCopyStringResource( DefaultListApplicationInfo.customField2, idStringField2 );
	DmWrite(appInfo, 0, &DefaultListApplicationInfo,  sizeof(DefaultListApplicationInfo) );
	MemHandleUnlock( appInfoHandle );
	return( 0 );
}

/* what a bad name */
void ReadDatabaseInfo() {
	LocalID dbID;
	UInt cardNo; 
	LocalID appInfoID;
	ListAppInfoType *appInfo;
	Boolean resort = false;

	/* Read the name from the current database */
	DmOpenDatabaseInfo( CurrentDB, &dbID, NULL, NULL, &cardNo, NULL );
	DmDatabaseInfo( cardNo, dbID, ListDBName, NULL, NULL, NULL, NULL, NULL,
					NULL, &appInfoID, NULL, NULL, NULL );
	appInfo = MemLocalIDToLockedPtr( appInfoID, cardNo );
	StrCopy( CustomField1, appInfo->customField1 );
	StrCopy( CustomField2, appInfo->customField2 );
	CurrentCategory = appInfo->lastCategory;
	DisplayStyle = appInfo->displayStyle;

	if( DisplayStyle & 0x80 ) {
		Byte displayStyle;

		DisplayStyle &= 0x7F;

		/* Bugfix: write just what changed -- Thanks to Christopher Antos: HandyShopper sources */
		displayStyle = DisplayStyle;
		DmWrite( appInfo, ((ULong)&(appInfo->displayStyle) - (ULong)appInfo), &displayStyle, 1 );

		resort = true;
	}
	WriteProtect = appInfo->writeProtect;
	MemPtrUnlock( appInfo );

	/* Some information like current category/current record should be stored
	in the application info? */
	CurrentRecord = -1;

	/* resort the database if its a newly created one */
	if( resort ) {
		RectangleType box = { 20, 60, 120, 40 };
		WinDrawRectangle( &box, 4 );
		FntSetFont( boldFont );
		WinEraseChars( "Please Wait..", 13, 48, 74 );
		DmQuickSort( CurrentDB, (DmComparF *)&ListCompareRecords, DisplayStyle );
		WinEraseWindow(); /* clean up */
	}

}

/* Close the CurrrentDB and update database specific info (current category) */
void CloseDatabase()
{
	LocalID dbID;
	UInt cardNo;
	LocalID appInfoID;
	ListAppInfoType *appInfo;
	Byte lastCategory;

	/* Remember the current category (current record? ROOHACK ) */
	DmOpenDatabaseInfo( CurrentDB, &dbID, NULL, NULL, &cardNo, NULL );
	DmDatabaseInfo( cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, 
					NULL, &appInfoID, NULL, NULL, NULL );
	appInfo = MemLocalIDToLockedPtr( appInfoID, cardNo );

	/* Bugfix: write just what changed -- Thanks to Christopher Antos: HandyShopper sources */
	lastCategory = CurrentCategory;
	DmWrite( appInfo, ((ULong)&(appInfo->lastCategory) - (ULong)appInfo), &lastCategory, 1 );
	/*	DmWrite( appInfo, 0, &localCopy, sizeof( ListAppInfoType ) ); */

	MemPtrUnlock( appInfo );
	/* Actually close the database */
	DmCloseDatabase( CurrentDB );
	/* Clear cached info */
	CurrentDB = 0;
	TopVisibleRecord = 0;
	CurrentRecord = -1;
}

/* Walk all the list databases, clearing the backup bit */
void ListHandleSync() 
{
	ListPreferences listPreferences;
	DmSearchStateType dbState;
	UInt cardNo;
	LocalID dbID;
	UInt dbAttr;

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

		/* Find the "first" database */ 
		if( DmGetNextDatabaseByTypeCreator( true, &dbState, ListType, ListCreator, false,
											&cardNo, &dbID ) != 0) {
			/* There are no databases */
			return;
		} 

		/* There is at least one database */
		do {
			/* Clear the backup bit */
			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 );

			/* keep going until we've done all the databases */
		} while( DmGetNextDatabaseByTypeCreator( false, &dbState, ListType, ListCreator, false,
			&cardNo, &dbID ) == 0);
	}
	return;
}

Err ListSocketWrite( void* buffer, ULong* size, ExgSocketPtr socket )
{
	Err error;
	*size = ExgSend( socket, buffer, *size, &error );
	return error;
}

Err	ListSocketRead( void* buffer, ULong* size, ExgSocketPtr socket )
{
	Err error;
	*size = ExgReceive( socket, buffer, *size, &error );
	return error;
}

Boolean	ListSocketDeleteDB( const char* nameP, Word version, Int cardNo, LocalID dbID, void* userDataP )
{
	if( FrmAlert( idVerifyReplaceDB ) == 0 ) {
		return( !DmDeleteDatabase( cardNo, dbID ) );
	}
	/* Fail */
	return false;
}

int ListReceiveData( ExgSocketPtr socket )
{
	LocalID dbID;
	UInt cardNo = 0;
	Boolean reset;
	Err error;

	error = ExgAccept( socket );
	if (!error) {
		error = ExgDBRead( (ExgDBReadProcPtr)&ListSocketRead, &ListSocketDeleteDB, socket, &dbID, cardNo, &reset, false );
		ExgDisconnect( socket, error );
		if( !error ) {
			socket->goToCreator = NULL; /* do not launch app */
		}
		if( reset ) {
			FrmAlert(bad); /* Unexpected, we should never have to reset */
		}
	}
}

