PalmOS开发教程-7

类别:编程语言 点击:0 评论:0 推荐:
第七章 列表框和排序      在这一章中,将接触到一些新的控件和数据库操作技巧。我们先生成一个窗体来显示contact数据库中的所有记录,然后创建一个下拉框供选择排序标准,最后添加代码进行排序,并使新创建或修改过的记录也能够在列表中正确排列。

保存工程

按我们的习惯先保存工程,步骤如下:

1. 运行Windows浏览器;
2. 找到保存现有工程的文件夹;
3. 按下CTRL-C复制;
4. 选择目的文件夹;
5. 按下CTRL-V将现有工程粘贴到目的文件夹;
6. 将其重命名为容易记忆的名字,我将其命名为Contacts CH.6。

列表框
列表框能够显示许多文本条目并允许从中选择,我们可以对每个条目进行浏览或选择。我们还可以通过调用函数浏览列表或选择条目。当然,我们可以画出自己的列表框,在缺省情况下,列表框由Palm系统绘制。

Contacts.rsrc的内容添加
在这一部分中,我们将向应用程序添加一个新的窗体。此窗体将以列表的形式显示数据库记录,并允许我们浏览或选择。如果选中了其中的一条记录,记录内容将从Contact Detail 窗体上显示出来。

Contact Detail 窗体的内容添加
由于Contact Detail 窗体被调用来返回一条记录内容,所以我们需要添加一个Done按钮。步骤如下:

1. 运行构造器;
2. 打开资源文件Contact.rsrc,它位于工程文件夹的src文件夹里面;
3. 双击打开;
4. 选择Windows | Catalog打开控件模板;
5. 向Contact Detail 窗体拖动一个按钮;
6. 设置按钮属性Object Identifier为Done,Left Origin=1,Top Origin=147,Label为Done;
7. 添加完成后,所得按钮如下图所示,选择右上角的X按钮关闭编辑器。



在Contact List窗体上创建菜单栏

在创建Contact List窗体前,我们先创建一个菜单;这样当设置窗体的属性时,就可以直接写入此菜单栏的ID了。

1. 在Resource Type and Name 列表框中选择Menu Bars并按下CTRL-K,于是一个新的菜单就产生了,将其重命名为Contact List;
2. 双击打开;
3. 拖动Option菜单到Contact List菜单栏上;
4. 完成后应如下图所示,点击右上角的X按钮关闭菜单栏。


创建Contact List窗体

现在我们来创建Contact List 窗体,步骤如下:
1. 点击Forms,并按下CTRL-K创建一个新的窗体;
2. 点击窗体名称将其命名为Contact List;
3. 在窗体编辑器中双击打开Contact List 窗体;
4. 为Contact List 添加标题,并使菜单栏的ID和Contact List 窗体的菜单栏的ID相匹配。
创建列表框(List)
1. 从Catalog窗口中拖动一个列表框(List)到窗体上。它的属性如表7-1所示;
Object Identifier 在资源头文件中,构造器用之代表资源ID
List ID 列表框的资源ID
Left Origin 水平方向上控件的最左端位置
Top Origin 垂直方向上控件的最顶端位置
Width 列表的宽度
Usable 决定此控件是否可见能用,如果没有选中,也可在通过函数调用来实现其可见
Font 列表项的字体
Visible Items 列表框可显示的行数。注意,实际的列表条目数比此值可多可少
List Items 动态的初始化列表条目,如果想添加条目,按CTRL-K
2. 设置属性Object Identifier 为List,Left Origin=0,Top Origin=12,Width=160,Visible Items=12;
3. 完成后窗体如下图所示,按下右上角的X按钮关闭窗体,并选File | Save保存所做修改。



添加内存错误警告

在这一章里,我们将开始添加错误处理代码。在内存溢出时,系统将发出警告信息:
1. 点击Resource Type and Name列表中的Alerts 并按下CTRL-K创建一个新的警报;
2. 将其命名为MemoryError;
3. 双击,在编辑器中打开;
4. 设置属性Alert Type 为Error,标题(title)为Fatal Error,信息(message)为“I have run out of memory.”
5. 完成后,如下图所示:




Contact.c的内容添加

现在添加代码以完成新窗体的功能。首先,先在前几章中异常处理的忽略处添加代码进行异常处理。
异常处理
在这一章中,有很多地方可能会造成内存溢出。因此,我们必须添加严密的异常处理程序。下面我们将利用异常处理器(Error Manager)来完成,在The Palm OS SDK Reference 中对异常处理器有详细的论述。
首先,在PilotMain()前,定义异常退出(Exit)宏:

// CH.7 The error exit macro
#define errorExit(alert) { ErrThrow( alert ); }

异常处理函数ErrThrow()有些与众不同,它与宏ErrTry和ErrCatch()配合作用,类似C++和Java的形式给出异常处理。如果在ErrTry模块中的任何代码调用了ErrThrow()函数,控件就会立即调用ErrCatch()函数。下面我们看看在PilotMain()中做了哪些修改。
在事件loop 产生前先添加宏ErrTry:
// CH.7 Begin the try block
ErrTry {

// CH.2 Our event loop
do
{

结束宏ErrTry并添加ErrCatch() 函数:

// CH.7 End the try block and do the catch block
}
ErrCatch( errorAlert )
{
// CH.7 Display the appropriate alert
FrmAlert( errorAlert );
} ErrEndCatch

此函数将根据ErrThrow()传给ErrCatch()的ID号显示其相应的异常警报。然后,应用程序将在catch模块后正常退出,正常执行关闭数据库诸如此类的操作。最后,PilotMain()将控制权归还给Palm OS。

Contact List 窗体的切换
下面,我们添加代码来实现Contact List 窗体和Contact Detail窗体之间的切换。在PilotMain()中,修改初始化Contact Detail窗体的代码如下:

// CH.7 Choose our starting page
// CH.5 If there are no records, create one
if( numRecords == 0 )
{
newRecord();
FrmGotoForm( ContactDetailForm );
}
else
FrmGotoForm( ContactListForm );
在切换到Contact List窗体时,判断如果数据库中没有记录,就要创建一条新记录;一般情况下,数据库中都是有记录的,我们可以直接调用Contact List窗体。

为使Contact List窗体能正确的初始化,在loop事件中的frmLoadEvent处理代码中再添加case项:

// CH.7 Contact List form
case ContactListForm:
form = FrmInitForm( ContactListForm );
FrmSetEventHandler( form, contactListHandleEvent );
break;

下面的代码是如何处理Contact Detail 窗体中的Done按钮事件:

// CH.7 Done button
case ContactDetailDoneButton:
{
// CH.7 Load the contact list
FrmGotoForm( ContactListForm );
}
break;

为了在窗体间切换,而不是弹出窗体,我们使用了函数FrmGotoform()来初始化。这个函数首先调用frmCoseEvent关闭前一个窗体,然后调用frmLoadEvent和frmOpenEvent打开新的窗体。

Contact List窗体事件处理函数

下面我们为Contact List 窗体来添加事件处理函数。首先,在文件的开头加入原型:

static Boolean contactListHandleEvent( EventPtr event );

另外,还需要两个变量表示给列表框分配内存的句柄:

// CH.7 Contact list variables
static VoidHand hchoices; // CH.7 Handle to packed choices
static VoidHand hpchoices; // CH.7 Handle to pointers

下面是事件处理函数:

// CH.7 Our Contact List form event handler function
static Boolean contactListHandleEvent( EventPtr event )
{
FormPtr form; // CH.7 A form structure pointer

// CH.7 Get our form pointer
form = FrmGetActiveForm();

// CH.7 Parse events
switch( event->eType )
{
// CH.7 Form open event
case frmOpenEvent:
{
// CH.7 Draw the form
FrmDrawForm( form );

// CH.7 Build the list
buildList();
}
break;

// CH.7 Form close event
case frmCloseEvent:
{
// CH.7 Unlock and free things here
MemHandleUnlock( hpchoices );
MemHandleFree( hpchoices );
MemHandleUnlock( hchoices );
MemHandleFree( hchoices );
hchoices = 0;
}
break;

// CH.7 Respond to a list selection
case lstSelectEvent:
{
// CH.7 Set the database cursor to the selected contact
cursor = event->data.lstSelect.selection;

// CH.7 Go to contact details
FrmGotoForm( ContactDetailForm );
}
break;

// CH.7 Respond to a menu event
case menuEvent:
return( menuEventHandler( event ) );

// CH.7 Respond to the popup trigger
case popSelectEvent:
{
// CH.7 If there is no change, we're done
if( sortBy == event->data.popSelect.selection )
return( true );

// CH.7 Modify sort order variable
sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria
DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.7 Rebuild the list
buildList();
}
break;

} // CH.7 End of the event switch statement

// CH.7 We're done
return( false );
}

事件处理函数以十分标准的形式开始:首先建立了一个窗体指针变量,它将在调用frmOpenEvent时被调用;然后窗体被绘制并调用了一个新的函数buildList(),这个函数在下一部分将详细讨论。

我们调用frmCloseEvent释放用来储存列表框内容的内存。之所以在关闭(close)事件中释放内存,是因为只要列表框可见、窗体在使用的时候,这一部分内存就一直被Palm OS使用。在这一点上,列表控件和其它控件有所不同,一般的控件都有自己的内存,而对于列表控件来说,它需要另外分配内存。

下面,讲述一个新的事件处理函数——lstSelectEvent。当列表的条目被选中时,此事件将被触发。变量selection标明哪一个条目被选中,列表得记录从0开始。这样我们使用起来就十分方便了,只要将当前记录等于列表变量selection的值,然后调用Contact Detail窗体,窗体就会使用前面所设置的变量cursor产生正确的记录。

最后,对本程序菜单事件的处理和对Contact Detail窗体的菜单处理几乎相同。这样,对Contact List窗体的处理程序就算完成了。

函数buildList()

下面我们来讨论这个实用函数buildList(),这个函数通过浏览数据库,为每条记录创建一文本字符串,然后使用这些字符串填充List对象的各个条目。

static void buildList( void )
{
FormPtr form; // CH.6 A form structure pointer
Int choice; // CH.7 The list choice we're doing
CharPtr precord; // CH.7 Pointer to a record
Char listChoice[dateStringLength + 1 + // CH.7 We
timeStringLength + 1 + // build
DB_FIRST_NAME_SIZE + // list
DB_LAST_NAME_SIZE]; // choices here
// CH.7 The current list choice
CharPtr pchoices; // CH.7 Pointer to packed choices
UInt offset; // CH.7 Offset into packed strings
VoidPtr ppchoices; // CH.7 Pointer to pointers to choices

// CH.6 Get our form pointer
form = FrmGetActiveForm();
在声明变量后,函数象往常一样获取窗体指针。这里面最有趣的变量是char array ,它将保证列表字符串的连续性。为使阵列(array)有足够的大小来保存任何Palm OS的系统时间和日期,可以使用Palm OS常量dateStringLength和timeStringLength。

// CH.7 Put the list choices in a packed string
for( choice = 0; choice < numRecords; choice++ )
{
// CH.7 Get the record
hrecord = DmQueryRecord( contactsDB, choice );
precord = MemHandleLock( hrecord );

// CH.7 Get the date and time
MemMove( &dateTime, precord + DB_DATE_TIME_START,
sizeof( dateTime ) );

首先,浏览数据库的所有记录,然后获取记录将变量dateTime设置为在所得记录中的时间和日期。注意到我们使用了DmQueryRecord()函数来获取记录句柄。此函数只给我们提供一个只读的记录副本。

下面,我们创建表示记录的字符串:

// CH.7 Clear the list choice string
*listChoice = '\0';

// CH.7 Add the date string if any
if( dateTime.year != NO_DATE )
{
DateToAscii( dateTime.month, dateTime.day,
dateTime.year,
(DateFormatType)PrefGetPreference(
prefDateFormat ), listChoice );
StrCat( listChoice, " " );
}

// CH.7 Add the time string if any
if( dateTime.hour != NO_TIME )
{
TimeToAscii( dateTime.hour, dateTime.minute,
(TimeFormatType)PrefGetPreference(
prefTimeFormat ), listChoice +
StrLen( listChoice ) );
StrCat( listChoice, " " );
}

// CH.7 Append the first name
StrCat( listChoice, precord + DB_FIRST_NAME_START );
StrCat( listChoice, " " );

// CH.7 Append the last name
StrCat( listChoice, precord + DB_LAST_NAME_START );

在这里我们将date,time,first name和last name都写入了字符串。注意到函数DateToAscii()和函数TimeToAscii()运行需要花很多时间。对本程序来说调用这两个函数还算可以,但是如果要提高处理记录的速度,就必须考虑将时间和日期保存在单个记录来提高速度。

下一步,我们将为这些字符串分配内存,这样做是因为列表框在填充新条目时需要一定的格式。

// CH.7 Allocate memory for the list entry string
// CH.7 If this is the first choice
if( hchoices == 0 )
{
// CH.7 Allocate the storage for the choice
if( (hchoices = MemHandleNew(
StrLen( listChoice ) + 1 )) == 0 )
errorExit( MemoryErrorAlert );

// CH.7 Initial offset points to the start
offset = 0;
}


上面就是字符串没有创建时的代码,在给字符串分配内存时,如果失败了,就调用新的错误处理函数来处理。然后我们初始化偏移(offset)说明在哪里将字符串置零。
下面是字符串已经创建好的情况:

else
// CH.7 If this is a subsequent choice
{
// CH.7 Unlock
MemHandleUnlock( hchoices );

// CH.7 Resize
if( MemHandleResize( hchoices, offset +
StrLen( listChoice ) + 1 ) )
errorExit( MemoryErrorAlert );

}

在这里我们将锁定的内存解锁(Unlock),使之能储存下一个字符串。

接着我们将字符串写入包列表(Packed List):

// CH.7 Lock
pchoices = MemHandleLock( hchoices );

// CH.7 Copy the string into the memory
StrCopy( pchoices + offset, listChoice );
offset += StrLen( listChoice ) + 1;

// CH.7 Unlock the record
MemHandleUnlock( hrecord );
}


首先我们锁定主块(chunk),然后将新建字符串拷入。做完这些工作后,解锁我们所使用的数据库记录句柄。注意,我们没有调用函数DmReleaseRecord()是因为我们用函数DmQueryRecord()代替了DmGetRecord()。

循环操作完成后,我们已有了一个包含每个数据库记录的列表字符串包。现在我们将这些选项发送到列表对象显示:
// CH.7 Create a pointer array from the packed string list
if( (hpchoices = SysFormPointerArrayToStrings( pchoices,
numRecords )) == 0 )
errorExit( MemoryErrorAlert );
ppchoices = MemHandleLock( hpchoices );

// CH.7 Set the list choices
LstSetListChoices( getObject( form, ContactListListList ),
ppchoices, numRecords );
// CH.7 Draw the list
LstDrawList( getObject( form, ContactListListList ) );

// CH.7 We're done
return;
}

我们使用函数SysFormPointerArrayToStrings()建立了一个指向所建包列表的指针。这样包列表的指针就确定了。

函数LstSetListChoice()利用包列表的指针来填充每个条目。注意,此函数将清空所有已存在的条目,所以在向此函数发送列表项时,必须是完全的包列表条目。

记住在程序的最后,通过窗体关闭(Form Close)事件来释放所有的内存。

在这一部分的最后,我们绘出了列表框。
调试

在这一类程序的调试中,最好是使用单步调试来检查程序是否能够正常运行。首先调试刚创建的新函数builList()。在函数的开始处设置一个断点,单步运行,看程序能否顺利运行,特别注意一下内存在最后是否得到释放。

从列表中选中一条记录,看是否能从Contact Detail窗体正确显示,再按下Contact Detail窗体上的Done按钮返回。在Contact Detail窗体上新添加一条记录,再从列表中选择此记录,看能否在Contact Detail窗体上正确显示。

排序

这一部分除了讲述排序外,还将接触到两个新控件,即弹出触发按钮(pop-up triggers)和弹出列表框(pop-up lists)。把弹出列表框加入Contact Detail窗体中可允许我们从其中选择不同的排序选项作为排序标准。最后,我们将添加代码创建或修改在Contact Detail窗体的相应记录,使之能够根据排序标准插入到正确的位置。

弹出触发按钮(pop-up triggers)

弹出触发按钮和普通按钮很相似。它们都有一个标签并响应触发事件。它的特殊之处在于它能够和一个弹出列表框相关联。当按下弹出触发按钮时,弹出列表框就会显示。如果选中列表框上的条目,就会产生事件popSelectEvent。当然,我们还可以利用弹出触发按钮和列表框相关联做一些其它的工作。

对Contacts.rsrc内容的添加

现在向Contact List窗体添加弹出列表框。它将允许我们依据三个排序标准进行排序:时间日期、姓(first name)、名(last name)。

1. 运行构造器;
2. 打开资源文件Contact.rsrc,它位于工程文件夹的Src文件夹中;
3. 双击打开Contact List窗体;
4. 选择Windows | Catalog打开控件面板;
5. 从Catalog窗口中拖动一个标签到窗体上。修改属性为:Left Origin=0,Top origin=149,Label为Sort By:;
6. 拖动一个弹出列表框到窗体上。修改属性维:Object Identifier 为SortList,Left Origin=40,Top Origin=125,Visible Items=3。在下面我们会看到它的位置和新添加的弹出触发按钮正好对齐;
7. 单击选中List Items,然后按下CTRL-K产生第一个条目。修改条目文本为Date and Time;
8. 单击选中List Items,然后按下CTRL-K产生第二个条目。修改条目文本为First Name;
9. 单击选中List Items,然后按下CTRL-K产生第三个条目。修改条目文本为Last Name。

创建弹出触发按钮
1. 拖动一个弹出触发按钮到Contact List 窗体上,其属性如表7-2所示;
Object Identifier 在资源头文件中,构造器用之代表资源ID
Popup ID 弹出触发按钮的资源ID
Left Origin 水平方向上控件的最左端位置
Top Origin 垂直方向上控件的最顶端位置
Width 按钮的宽度
Height 按钮的高度
Usable 用来定义控件是否可见及可用,如果不设置,也可在通过函数调用来实现其可见
Anchor Left 决定当文本长度改变时,按钮文本是以左侧还是右侧为锚点扩展,选中时,按钮文本将向右侧扩展。
Font 标签文本的字体
Label 标签上的文本内容
List ID 和弹出触发按钮相关联的弹出列表框的ID

2.修改属性为:Object Identifier为Trigger,Left Origin=40,Top Origin=149,Width=80,Label 为Date and Time,将List ID设置为刚创建的弹出列表框的ID;
3. 在对Contact List窗体修改完毕,其图如下所示。单击窗体编辑器右上角的X按钮关闭,选择File | Save 保存所做修改。





对Contacts.c内容的添加
首先,在文件头添加排序所必需的变量和常量:

// CH.7 The sort order variable and constants
static Int sortBy;
// CH.7 NOTE: These items match the popup list entries!
#define SORTBY_DATE_TIME 0
#define SORTBY_FIRST_NAME 1
#define SORTBY_LAST_NAME 2

变量sortBy表示在三个排序标准中的当前值。三个常量代表了弹出列表框中的三个排序标准,注意使它们和列表框的每个选项相对应。

排序的初始化
为了建立排序标准,必须添加代码来处理popSelectEvent事件,此事件在弹出列表框的列表选项被选中时触发。下面的代码是如何根据所选项进行排序:

case popSelectEvent:
{
// CH.7 If there is no change, we're done
if( sortBy == event->data.popSelect.selection )
return( true );

// CH.7 Modify sort order variable
sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria
DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.7 Rebuild the list
buildList();
}
break;

首先,在排序标准没有改变时,避免重复排序。然后保存排序标准,因为在Contact Detail窗体中当有新记录或修改记录后,排序发生了改变,这时又用到了排序标准。最后调用函数DmQuickSort(),此函数将排序标准sortBy传递给函数sortFunc()。在数据库排序完成后,绘制列表框显示。
下面我们研究一下函数sortFunc()。这个函数可以比较前后两个条目的大小,在将数据库中的两条记录比较后,函数返回一个整数。如果此数大于零,则第一个记录排在前面;如果小于零则第二条记录排在前面;如果为零则说明两记录相同。

// CH.7 This function is called by Palm OS to sort records
static Int sortFunc( CharPtr precord1, CharPtr precord2, Int sortBy )
{
Int sortResult;

// CH.7 Switch based on sort criteria
switch( sortBy )
{
// CH.7 Sort by date and time
case SORTBY_DATE_TIME:
{
DateTimePtr pdateTime1;
DateTimePtr pdateTime2;
Long lDiff;

pdateTime1 = (DateTimePtr)(precord1 + DB_DATE_TIME_START);
pdateTime2 = (DateTimePtr)(precord2 + DB_DATE_TIME_START);

// CH.7 Compare the dates and times
lDiff = (Long)(TimDateTimeToSeconds( pdateTime1 ) / 60 ) -
(Long)(TimDateTimeToSeconds( pdateTime2 ) / 60 );

// CH.7 Date/time #1 is later
if( lDiff > 0 )
sortResult = 1;

else
// CH.7 Date/time #2 is later
if( lDiff < 0 )
sortResult = -1;

else
// CH.7 They are equal
sortResult = 0;
}
break;

代码中首先将时间和日期从所选记录中抽出,然后将其转换成秒进行比较。在这个算法中会将无日期的记录放在列表框的顶部,而将无时间的记录放在相同日期的最底部。由于所转换的秒值有可能超过16位整值,所以我们采用32位长整数来进行比较,相应的设置变量sortResult为16位。

// CH.7 Sort by first name
case SORTBY_FIRST_NAME:
{
sortResult = StrCompare( precord1 + DB_FIRST_NAME_START,
precord2 + DB_FIRST_NAME_START );
}
break;

// CH.7 Sort by last name
case SORTBY_LAST_NAME:
{
sortResult = StrCompare( precord1 + DB_LAST_NAME_START,
precord2 + DB_LAST_NAME_START );
}
break;


调用函数StrCompare()为first name和last name 排序,此函数定义在Developing Palm OS 3.0 Applications Part II:Sytem Management中。它和ANSI C的函数strcmp()十分相似,直接给Palm OS返回一个整值。

排序的记录写入列表框
在移动到另一个记录或退出Contact Detail窗体时,函数getField()都要被调用,因此应该在此函数中添加代码,来保证在添加新记录或修改记录后重新排序。
为达到要求,这些代码应添加在记录内存已被释放但“脏(dirty)”位还没有清除的位置。

// CH.5 If the record has been modified
if( isDirty )
{
CharPtr precord; // CH.5 Points to the DB record

// CH.7 Detach the record from the database
DmDetachRecord( contactsDB, cursor, &hrecord );

// CH.5 Lock the record
precord = MemHandleLock( hrecord );

// CH.5 Get the text for the First Name field
getText( getObject( form, ContactDetailFirstNameField ),
precord, DB_FIRST_NAME_START );

// CH.5 Get the text for the Last Name field
getText( getObject( form, ContactDetailLastNameField ),
precord, DB_LAST_NAME_START );

// CH.5 Get the text for the Phone Number field
getText( getObject( form, ContactDetailPhoneNumberField ),
precord, DB_PHONE_NUMBER_START );

// CH.7 Find the proper position
cursor = DmFindSortPosition( contactsDB, precord, NULL,
(DmComparF*)sortFunc, sortBy );

// CH.5 Unlock the record
MemHandleUnlock( hrecord );

// CH.7 Reattach the record
DmAttachRecord( contactsDB, &cursor, hrecord, NULL );
}

既然只是在记录被改变时重新排序,因此可以先使用isDirty位来判断记录是否已被修改。然后将“脏”的记录从数据库中临时分离出来并将之锁定,最后调用函数DmFindSortPosition()决定应该在列表框的什么地方插入。此函数的前提示列表已经有了正确的排序,由于我们一次只改变或添加一条记录,因此可以满足要求。最后解锁该记录并将之插入到数据库的新位置。

调试
分别在popSelectEvent事件的顶部、函数sortBy()的顶部、函数getField()的if语句的开始处设置断点。检查程序是否能正常运行。下面是一些功能的测试:
l 用三个排序标准分别排序,看数据库是否能根据各个标准正确的排序;
l 向数据库中添加一个记录,看其是否能排在正确的位置;
l 修改一个现存记录,看其是否能排在正确的位置。


下一步做什么
下一章我们将添加更多新的控件,例如表和浏览栏。这一章我们使用列表框显示程序的记录信息已是个不错的方法,下一章我们将修改程序,用表控件来做到这一点。

程序列表
// CH.2 The super-include for the Palm OS
#include <Pilot.h>

// CH.5 Added for the call to GrfSetState()
#include <Graffiti.h>

// CH.3 Our resource file
#include "Contacts_res.h"

// CH.4 Prototypes for our event handler functions
static Boolean contactDetailHandleEvent( EventPtr event );
static Boolean aboutHandleEvent( EventPtr event );
static Boolean enterTimeHandleEvent( EventPtr event );
static Boolean contactListHandleEvent( EventPtr event );
static Boolean menuEventHandler( EventPtr event );

// CH.4 Constants for ROM revision
#define ROM_VERSION_2 0x02003000
#define ROM_VERSION_MIN ROM_VERSION_2

// CH.5 Prototypes for utility functions
static void newRecord( void );
static VoidPtr getObject( FormPtr, Word );
static void setFields( void );
static void getFields( void );
static void setText( FieldPtr, CharPtr );
static void getText( FieldPtr, VoidPtr, Word );
static void setDateTrigger( void );
static void setTimeTrigger( void );
static void setTimeControls( void );
static void buildList( void );
static Int sortFunc( CharPtr, CharPtr, Int );

// CH.5 Our open database reference
static DmOpenRef contactsDB;
static ULong numRecords;
static UInt cursor;
static Boolean isDirty;
static VoidHand hrecord;

// CH.5 Constants that define the database record
#define DB_ID_START 0
#define DB_ID_SIZE (sizeof( ULong ))
#define DB_DATE_TIME_START (DB_ID_START +\
DB_ID_SIZE)
#define DB_DATE_TIME_SIZE (sizeof( DateTimeType ))
#define DB_FIRST_NAME_START (DB_DATE_TIME_START +\
DB_DATE_TIME_SIZE)
#define DB_FIRST_NAME_SIZE 16
#define DB_LAST_NAME_START (DB_FIRST_NAME_START +\
DB_FIRST_NAME_SIZE)
#define DB_LAST_NAME_SIZE 16
#define DB_PHONE_NUMBER_START (DB_LAST_NAME_START +\
DB_LAST_NAME_SIZE)
#define DB_PHONE_NUMBER_SIZE 16
#define DB_RECORD_SIZE (DB_PHONE_NUMBER_START +\
DB_PHONE_NUMBER_SIZE)

// CH.6 Storage for the record's date and time in expanded form
static DateTimeType dateTime;
static Word timeSelect;
#define NO_DATE 0
#define NO_TIME 0x7fff

// CH.7 The error exit macro
#define errorExit(alert) { ErrThrow( alert ); }

// CH.7 Contact list variables
static VoidHand hchoices; // CH.7 Handle to packed choices
static VoidHand hpchoices; // CH.7 Handle to pointers

// CH.7 The sort order variable and constants
static Int sortBy;
// CH.7 NOTE: These items match the popup list entries!
#define SORTBY_DATE_TIME 0
#define SORTBY_FIRST_NAME 1
#define SORTBY_LAST_NAME 2

// CH.2 The main entry point
DWord PilotMain( Word cmd, Ptr, Word )
{
DWord romVersion; // CH.4 ROM version
FormPtr form; // CH.2 A pointer to our form structure
EventType event; // CH.2 Our event structure
Word error; // CH.3 Error word

// CH.4 Get the ROM version
romVersion = 0;
FtrGet( sysFtrCreator, sysFtrNumROMVersion, &romVersion );

// CH.4 If we are below our minimum acceptable ROM revision
if( romVersion < ROM_VERSION_MIN )
{
// CH.4 Display the alert
FrmAlert( LowROMVersionErrorAlert );

// CH.4 PalmOS 1.0 will continuously re-launch this app
// unless we switch to another safe one
if( romVersion < ROM_VERSION_2 )
{
AppLaunchWithCommand( sysFileCDefaultApp,
sysAppLaunchCmdNormalLaunch, NULL );
}
return( 0 );
}

// CH.2 If this is not a normal launch, don't launch
if( cmd != sysAppLaunchCmdNormalLaunch )
return( 0 );

// CH.5 Create a new database in case there isn't one
if( ((error = DmCreateDatabase( 0, "ContactsDB-PPGU", 'PPGU', 'ctct',
false )) != dmErrAlreadyExists) && (error != 0) )
{
// CH.5 Handle db creation error
FrmAlert( DBCreationErrorAlert );
return( 0 );
}

// CH.5 Open the database
contactsDB = DmOpenDatabaseByTypeCreator( 'ctct', 'PPGU',
dmModeReadWrite );

// CH.5 Get the number of records in the database
numRecords = DmNumRecords( contactsDB );

// CH.5 Initialize the record number
cursor = 0;

// CH.7 Choose our starting page
// CH.5 If there are no records, create one
if( numRecords == 0 )
{
newRecord();
FrmGotoForm( ContactDetailForm );
}
else
FrmGotoForm( ContactListForm );

// CH.7 Begin the try block
ErrTry {

// CH.2 Our event loop
do
{
// CH.2 Get the next event
EvtGetEvent( &event, -1 );

// CH.2 Handle system events
if( SysHandleEvent( &event ) )
continue;

// CH.3 Handle menu events
if( MenuHandleEvent( NULL, &event, &error ) )
continue;

// CH.4 Handle form load events
if( event.eType == frmLoadEvent )
{
// CH.4 Initialize our form
switch( event.data.frmLoad.formID )
{
// CH.4 Contact Detail form
case ContactDetailForm:
form = FrmInitForm( ContactDetailForm );
FrmSetEventHandler( form, contactDetailHandleEvent );
break;

// CH.4 About form
case AboutForm:
form = FrmInitForm( AboutForm );
FrmSetEventHandler( form, aboutHandleEvent );
break;

// CH.6 Enter Time form
case EnterTimeForm:
form = FrmInitForm( EnterTimeForm );
FrmSetEventHandler( form, enterTimeHandleEvent );
break;

// CH.7 Contact List form
case ContactListForm:
form = FrmInitForm( ContactListForm );
FrmSetEventHandler( form, contactListHandleEvent );
break;
}
FrmSetActiveForm( form );
}

// CH.2 Handle form events
FrmDispatchEvent( &event );

// CH.2 If it's a stop event, exit
} while( event.eType != appStopEvent );

// CH.7 End the try block and do the catch block
}
ErrCatch( errorAlert )
{
// CH.7 Display the appropriate alert
FrmAlert( errorAlert );
} ErrEndCatch

// CH.5 Close all open forms
FrmCloseAllForms();

// CH.5 Close the database
DmCloseDatabase( contactsDB );

// CH.2 We're done
return( 0 );
}

// CH.4 Our Contact Detail form handler function
static Boolean contactDetailHandleEvent( EventPtr event )
{
FormPtr form; // CH.3 A pointer to our form structure
VoidPtr precord; // CH.6 Points to a database record

// CH.3 Get our form pointer
form = FrmGetActiveForm();

// CH.4 Parse events
switch( event->eType )
{
// CH.4 Form open event
case frmOpenEvent:
{
// CH.2 Draw the form
FrmDrawForm( form );

// CH.5 Draw the database fields
setFields();
}
break;

// CH.5 Form close event
case frmCloseEvent:
{
// CH.5 Store away any modified fields
getFields();
}
break;

// CH.5 Parse the button events
case ctlSelectEvent:
{
// CH.5 Store any field changes
getFields();

switch( event->data.ctlSelect.controlID )
{
// CH.5 First button
case ContactDetailFirstButton:
{
// CH.5 Set the cursor to the first record
if( cursor > 0 )
cursor = 0;
}
break;

// CH.5 Previous button
case ContactDetailPrevButton:
{
// CH.5 Move the cursor back one record
if( cursor > 0 )
cursor--;
}
break;

// CH.5 Next button
case ContactDetailNextButton:
{
// CH.5 Move the cursor up one record
if( cursor < (numRecords - 1) )
cursor++;
}
break;

// CH.5 Last button
case ContactDetailLastButton:
{
// CH.5 Move the cursor to the last record
if( cursor < (numRecords - 1) )
cursor = numRecords - 1;
}
break;

// CH.5 Delete button
case ContactDetailDeleteButton:
{
// CH.5 Remove the record from the database
DmRemoveRecord( contactsDB, cursor );

// CH.5 Decrease the number of records
numRecords--;

// CH.5 Place the cursor at the first record
cursor = 0;

// CH.5 If there are no records left, create one
if( numRecords == 0 )
newRecord();
}
break;

// CH.5 New button
case ContactDetailNewButton:
{
// CH.5 Create a new record
newRecord();
}
break;

// CH.7 Done button
case ContactDetailDoneButton:
{
// CH.7 Load the contact list
FrmGotoForm( ContactListForm );
}
break;

// CH.6 Date selector trigger
case ContactDetailDateSelTrigger:
{
// CH.6 Initialize the date if necessary
if( dateTime.year == NO_DATE )
{
DateTimeType currentDate;

// CH.6 Get the current date
TimSecondsToDateTime( TimGetSeconds(),
&currentDate );

// CH.6 Copy it
dateTime.year = currentDate.year;
dateTime.month = currentDate.month;
dateTime.day = currentDate.day;
}

// CH.6 Pop up the system date selection form
SelectDay( selectDayByDay, &(dateTime.month),
&(dateTime.day), &(dateTime.year),
"Enter Date" );

// CH.6 Get the record
hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Lock it down
precord = MemHandleLock( hrecord );

// CH.6 Write the date time field
DmWrite( precord, DB_DATE_TIME_START, &dateTime,
sizeof( DateTimeType ) );

// CH.6 Unlock the record
MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty
isDirty = true;
}
break;

// CH.6 Time selector trigger
case ContactDetailTimeSelTrigger:
{
// CH.6 Pop up our selection form
FrmPopupForm( EnterTimeForm );
}
break;
}

// CH.5 Sync the current record to the fields
setFields();
}
break;

// CH.5 Respond to field tap
case fldEnterEvent:
isDirty = true;
break;

// CH.3 Parse menu events
case menuEvent:
return( menuEventHandler( event ) );
break;
}

// CH.2 We're done
return( false );
}

// CH.4 Our About form event handler function
static Boolean aboutHandleEvent( EventPtr event )
{
FormPtr form; // CH.4 A pointer to our form structure

// CH.4 Get our form pointer
form = FrmGetActiveForm();

// CH.4 Respond to the Open event
if( event->eType == frmOpenEvent )
{
// CH.4 Draw the form
FrmDrawForm( form );
}

// CH.4 Return to the calling form
if( event->eType == ctlSelectEvent )
{
FrmReturnToForm( 0 );

// CH.4 Always return true in this case
return( true );
}

// CH.4 We're done
return( false );
}

// CH.6 Our Enter Time form event handler function
static Boolean enterTimeHandleEvent( EventPtr event )
{
FormPtr form; // CH.6 A form structure pointer
static DateTimeType oldTime; // CH.6 The original time

// CH.6 Get our form pointer
form = FrmGetActiveForm();

// CH.6 Switch on the event
switch( event->eType )
{
// CH.6 Initialize the form
case frmOpenEvent:
{
// CH.6 Store the time value
oldTime = dateTime;

// CH.6 Draw it
FrmDrawForm( form );

// CH.6 Set the time controls
setTimeControls();
}
break;

// CH.6 If a button was repeated
case ctlRepeatEvent:
// CH.6 If a button was pushed
case ctlSelectEvent:
{
Word buttonID; // CH.6 The ID of the button

// CH.6 Set the ID
buttonID = event->data.ctlSelect.controlID;

// CH.6 Switch on button ID
switch( buttonID )
{
// CH.6 Hours button
case EnterTimeHoursPushButton:
// CH.6 Minute Tens button
case EnterTimeMinuteTensPushButton:
// CH.6 Minute Ones button
case EnterTimeMinuteOnesPushButton:
{
// CH.6 If no time was set
if( dateTime.hour == NO_TIME )
{
// CH.6 Set the time to 12 PM
dateTime.hour = 12;
dateTime.minute = 0;

// CH.6 Set the controls
setTimeControls();
}

// CH.6 Clear the old selection if any
if( timeSelect )
CtlSetValue( getObject( form, timeSelect ),
false );

// CH.6 Set the new selection
CtlSetValue( getObject( form, buttonID ), true );
timeSelect = buttonID;
}
break;

// CH.6 Up button
case EnterTimeTimeUpRepeating:
{
// CH.6 If there's no time, do nothing
if( dateTime.hour == NO_TIME )
break;

// CH.6 Based on what push button is selected
switch( timeSelect )
{
// CH.6 Increase hours
case EnterTimeHoursPushButton:
{
// CH.6 Increment hours
dateTime.hour++;

// CH.6 If it was 11 AM, make it 12 AM
if( dateTime.hour == 12 )
dateTime.hour = 0;

// CH.6 If it was 11 PM, make it 12 PM
if( dateTime.hour == 24 )
dateTime.hour = 12;
}
break;

// CH.6 Increase tens of minutes
case EnterTimeMinuteTensPushButton:
{
// CH.6 Increment minutes
dateTime.minute += 10;

// CH.6 If it was 5X, roll over
if( dateTime.minute > 59 )
dateTime.minute -= 60;
}
break;

// CH.6 Increase minutes
case EnterTimeMinuteOnesPushButton:
{
// CH.6 Increment minutes
dateTime.minute++;

// CH.6 If it is zero, subtract ten
if( (dateTime.minute % 10) == 0 )
dateTime.minute -= 10;
}
break;
}

// Revise the controls
setTimeControls();
}
break;

// CH.6 Down button
case EnterTimeTimeDownRepeating:
{

// CH.6 If there's no time, do nothing
if( dateTime.hour == NO_TIME )
break;

// CH.6 Based on what push button is selected
switch( timeSelect )
{
// CH.6 Decrease hours
case EnterTimeHoursPushButton:
{
// CH.6 Decrement hours
dateTime.hour--;

// CH.6 If it was 12 AM, make it 11 AM
if( dateTime.hour == -1 )
dateTime.hour = 11;

// CH.6 If it was 12 PM, make it 11 PM
if( dateTime.hour == 11 )
dateTime.hour = 23;
}
break;

// CH.6 Decrease tens of minutes
case EnterTimeMinuteTensPushButton:
{
// CH.6 Decrement minutes
dateTime.minute -= 10;

// CH.6 If it was 0X, roll over
if( dateTime.minute < 0 )
dateTime.minute += 60;
}
break;

// CH.6 Decrease minutes
case EnterTimeMinuteOnesPushButton:
{
// CH.6 Decrement minutes
dateTime.minute--;

// CH.6 If it is 9, add ten
if( (dateTime.minute % 10) == 9 )
dateTime.minute += 10;

// CH.6 If less than zero, make it 9
if( dateTime.minute < 0 )
dateTime.minute = 9;
}
break;
}

// CH.6 Revise the controls
setTimeControls();
}
break;

// CH.6 AM button
case EnterTimeAMPushButton:
{
// CH.6 If no time was set
if( dateTime.hour == NO_TIME )
{
// CH.6 Set the time to 12 AM
dateTime.hour = 0;
dateTime.minute = 0;

// CH.6 Set the controls
setTimeControls();
}

// CH.6 If it is PM
if( dateTime.hour > 11 )
{
// CH.6 Change to AM
dateTime.hour -= 12;

// CH.6 Set the controls
setTimeControls();
}
}
break;

// CH.6 PM button
case EnterTimePMPushButton:
{
// CH.6 If no time was set
if( dateTime.hour == NO_TIME )
{
// CH.6 Set the time to 12 PM
dateTime.hour = 12;
dateTime.minute = 0;

// CH.6 Set the controls
setTimeControls();
}

// CH.6 If it is AM
if( dateTime.hour < 12 )
{
// CH.6 Change to PM
dateTime.hour += 12;

// CH.6 Set the controls
setTimeControls();
}
}
break;

// CH.6 No Time checkbox
case EnterTimeNoTimeCheckbox:
{
// CH.6 If we are unchecking the box
if( dateTime.hour == NO_TIME )
{
// CH.6 Set the time to 12 PM
dateTime.hour = 12;
dateTime.minute = 0;

// CH.6 Set the controls
setTimeControls();

// CH.6 Set the new selection
timeSelect = EnterTimeHoursPushButton;
CtlSetValue( getObject( form, timeSelect ),
true );
}

else
// CH.6 If we are checking the box
dateTime.hour = NO_TIME;

// CH.6 Set the controls
setTimeControls();
}
break;

// CH.6 Cancel button
case EnterTimeCancelButton:
{
// CH.6 Restore time
dateTime = oldTime;

// CH.6 Return to calling form
FrmReturnToForm( 0 );
}
// CH.6 Always return true
return( true );

// CH.6 OK button
case EnterTimeOKButton:
{
VoidPtr precord; // CH.6 Points to the record

// CH.6 Lock it down
precord = MemHandleLock( hrecord );

// CH.6 Write the date time field
DmWrite( precord, DB_DATE_TIME_START, &dateTime,
sizeof( DateTimeType ) );

// CH.6 Unlock the record
MemHandleUnlock( hrecord );

// CH.6 Mark the record dirty
isDirty = true;

// CH.6 Return to the Contact Details form
FrmReturnToForm( 0 );

// CH.6 Update the field
setTimeTrigger();
}
// CH.6 Always return true
return( true );
}
}
break;
}

// CH.6 We're done
return( false );
}

// CH.7 Our Contact List form event handler function
static Boolean contactListHandleEvent( EventPtr event )
{
FormPtr form; // CH.7 A form structure pointer

// CH.7 Get our form pointer
form = FrmGetActiveForm();

// CH.7 Parse events
switch( event->eType )
{
// CH.7 Form open event
case frmOpenEvent:
{
// CH.7 Draw the form
FrmDrawForm( form );

// CH.7 Build the list
buildList();
}
break;

// CH.7 Form close event
case frmCloseEvent:
{
// CH.7 Unlock and free things here
MemHandleUnlock( hpchoices );
MemHandleFree( hpchoices );
MemHandleUnlock( hchoices );
MemHandleFree( hchoices );
hchoices = 0;
}
break;

// CH.7 Respond to a list selection
case lstSelectEvent:
{
// CH.7 Set the database cursor to the selected contact
cursor = event->data.lstSelect.selection;

// CH.7 Go to contact details
FrmGotoForm( ContactDetailForm );
}
break;

// CH.7 Respond to a menu event
case menuEvent:
return( menuEventHandler( event ) );

// CH.7 Respond to the popup trigger
case popSelectEvent:
{
// CH.7 If there is no change, we're done
if( sortBy == event->data.popSelect.selection )
return( true );

// CH.7 Modify sort order variable
sortBy = event->data.popSelect.selection;

// CH.7 Sort the contact database by the new criteria
DmQuickSort( contactsDB, (DmComparF*)sortFunc, sortBy );

// CH.7 Rebuild the list
buildList();
}
break;

} // CH.7 End of the event switch statement

// CH.7 We're done
return( false );
}

// CH.3 Handle menu events
Boolean menuEventHandler( EventPtr event )
{
FormPtr form; // CH.3 A pointer to our form structure
Word index; // CH.3 A general purpose control index
FieldPtr field; // CH.3 Used for manipulating fields

// CH.3 Get our form pointer
form = FrmGetActiveForm();

// CH.3 Erase the menu status from the display
MenuEraseStatus( NULL );

// CH.4 Handle options menu
if( event->data.menu.itemID == OptionsAboutContacts )
{
// CH.4 Pop up the About form as a Dialog
FrmPopupForm( AboutForm );
return( true );
}

// CH.3 Handle graffiti help
if( event->data.menu.itemID == EditGraffitiHelp )
{
// CH.3 Pop up the graffiti reference based on
// the graffiti state
SysGraffitiReferenceDialog( referenceDefault );
return( true );
}

// CH.3 Get the index of our field
index = FrmGetFocus( form );

// CH.3 If there is no field selected, we're done
if( index == noFocus )
return( false );

// CH.3 Get the pointer of our field
field = FrmGetObjectPtr( form, index );

// CH.3 Do the edit command
switch( event->data.menu.itemID )
{
// CH.3 Undo
case EditUndo:
FldUndo( field );
break;

// CH.3 Cut
case EditCut:
FldCut( field );
break;

// CH.3 Copy
case EditCopy:
FldCopy( field );
break;

// CH.3 Paste
case EditPaste:
FldPaste( field );
break;

// CH.3 Select All
case EditSelectAll:
{
// CH.3 Get the length of the string in the field
Word length = FldGetTextLength( field );

// CH.3 Sound an error if appropriate
if( length == 0 )
{
SndPlaySystemSound( sndError );
return( false );
}

// CH.3 Select the whole string
FldSetSelection( field, 0, length );
}
break;

// CH.3 Bring up the keyboard tool
case EditKeyboard:
SysKeyboardDialogV10();
break;
}

// CH.3 We're done
return( true );
}

// CH.5 This function creates and initializes a new record
static void newRecord( void )
{
VoidPtr precord; // CH.5 Pointer to the record

// CH.7 Create the database record and get a handle to it
if( (hrecord = DmNewRecord( contactsDB, &cursor,
DB_RECORD_SIZE )) == NULL )
errorExit( MemoryErrorAlert );

// CH.5 Lock down the record to modify it
precord = MemHandleLock( hrecord );

// CH.5 Clear the record
DmSet( precord, 0, DB_RECORD_SIZE, 0 );

// CH.6 Initialize the date and time
MemSet( &dateTime, sizeof( dateTime ), 0 );
dateTime.year = NO_DATE;
dateTime.hour = NO_TIME;
DmWrite( precord, DB_DATE_TIME_START, &dateTime,
sizeof( DateTimeType ) );

// CH.5 Unlock the record
MemHandleUnlock( hrecord );

// CH.5 Clear the busy bit and set the dirty bit
DmReleaseRecord( contactsDB, cursor, true );

// CH.5 Increment the total record count
numRecords++;

// CH.5 Set the dirty bit
isDirty = true;

// CH.5 We're done
return;
}

// CH.5 A time saver: Gets object pointers based on their ID
static VoidPtr getObject( FormPtr form, Word objectID )
{
Word index; // CH.5 The object index

// CH.5 Get the index
index = FrmGetObjectIndex( form, objectID );

// CH.5 Return the pointer
return( FrmGetObjectPtr( form, index ) );
}

// CH.5 Gets the current database record and displays it
// in the detail fields
static void setFields( void )
{
FormPtr form; // CH.5 The contact detail form
CharPtr precord; // CH.5 A record pointer
Word index; // CH.5 The object index

// CH.5 Get the contact detail form pointer
form = FrmGetActiveForm();

// CH.5 Get the current record
hrecord = DmQueryRecord( contactsDB, cursor );

// CH.6 Initialize the date and time variable
precord = MemHandleLock( hrecord );
MemMove( &dateTime, precord + DB_DATE_TIME_START,
sizeof( dateTime ) );

// CH.6 Initialize the date control
setDateTrigger();

// CH.6 Initialize the time control
setTimeTrigger();

// CH.5 Set the text for the First Name field
setText( getObject( form, ContactDetailFirstNameField ),
precord + DB_FIRST_NAME_START );

// CH.5 Set the text for the Last Name field
setText( getObject( form, ContactDetailLastNameField ),
precord + DB_LAST_NAME_START );

// CH.5 Set the text for the Phone Number field
setText( getObject( form, ContactDetailPhoneNumberField ),
precord + DB_PHONE_NUMBER_START );
MemHandleUnlock( hrecord );

// CH.5 If the record is already dirty, it's new, so set focus
if( isDirty )
{
// CH.3 Get the index of our field
index = FrmGetObjectIndex( form, ContactDetailFirstNameField );

// CH.3 Set the focus to the First Name field
FrmSetFocus( form, index );

// CH.5 Set upper shift on
GrfSetState( false, false, true );
}

// CH.5 We're done
return;
}

// CH.5 Puts any field changes in the record
void getFields( void )
{
FormPtr form; // CH.5 The contact detail form

// CH.5 Get the contact detail form pointer
form = FrmGetActiveForm();

// CH.5 Turn off focus
FrmSetFocus( form, -1 );

// CH.5 If the record has been modified
if( isDirty )
{
CharPtr precord; // CH.5 Points to the DB record

// CH.7 Detach the record from the database
DmDetachRecord( contactsDB, cursor, &hrecord );

// CH.5 Lock the record
precord = MemHandleLock( hrecord );

// CH.5 Get the text for the First Name field
getText( getObject( form, ContactDetailFirstNameField ),
precord, DB_FIRST_NAME_START );

// CH.5 Get the text for the Last Name field
getText( getObject( form, ContactDetailLastNameField ),
precord, DB_LAST_NAME_START );

// CH.5 Get the text for the Phone Number field
getText( getObject( form, ContactDetailPhoneNumberField ),
precord, DB_PHONE_NUMBER_START );

// CH.7 Find the proper position
cursor = DmFindSortPosition( contactsDB, precord, NULL,
(DmComparF*)sortFunc, sortBy );

// CH.5 Unlock the record
MemHandleUnlock( hrecord );

// CH.7 Reattach the record
DmAttachRecord( contactsDB, &cursor, hrecord, NULL );
}

// CH.5 Reset the dirty bit
isDirty = false;

// CH.5 We're done
return;
}

// CH.5 Set the text in a field
static void setText( FieldPtr field, CharPtr text )
{
VoidHand hfield; // CH.5 Handle of field text
CharPtr pfield; // CH.5 Pointer to field text

// CH.5 Get the current field handle
hfield = FldGetTextHandle( field );

// CH.5 If we have a handle
if( hfield != NULL )
{
// CH.5 Resize it
if( MemHandleResize( hfield, StrLen( text ) + 1 ) != 0 )
errorExit( MemoryErrorAlert );
}

else
// CH.5 Allocate a handle for the string
{
hfield = MemHandleNew( StrLen( text ) + 1 );
if( hfield == NULL )
errorExit( MemoryErrorAlert );
}

// CH.5 Lock it
pfield = MemHandleLock( hfield );

// CH.5 Copy the string
StrCopy( pfield, text );

// CH.5 Unlock it
MemHandleUnlock( hfield );

// CH.5 Give it to the field
FldSetTextHandle( field, hfield );

// CH.5 Draw the field
FldDrawField( field );

// CH.5 We're done
return;
}

// CH.5 Get the text from a field
static void getText( FieldPtr field, VoidPtr precord, Word offset )
{
CharPtr pfield; // CH.5 Pointer to field text

// CH.5 Get the text pointer
pfield = FldGetTextPtr( field );

// CH.5 Copy it
DmWrite( precord, offset, pfield, StrLen( pfield ) );

// CH.5 We're done
return;
}

// CH.6 Set the Contact Detail date selector trigger
static void setDateTrigger( void )
{
FormPtr form; // CH.5 The contact detail form

// CH.6 Get the contact detail form pointer
form = FrmGetActiveForm();

// CH.6 If there is no date
if( dateTime.year == NO_DATE )
{
CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ),
" " );
}

else
// CH.6 If there is a date
{
Char dateString[dateStringLength];

// CH.6 Get the date string
DateToAscii( dateTime.month, dateTime.day, dateTime.year,
(DateFormatType)PrefGetPreference( prefDateFormat ), dateString );

// CH.6 Set the selector trigger label
CtlSetLabel( getObject( form, ContactDetailDateSelTrigger ),
dateString );

}

// CH.6 We're done
return;
}

// CH.6 Set the Contact Detail time selector trigger
static void setTimeTrigger( void )
{
FormPtr form; // CH.5 The contact detail form

// CH.6 Get the contact detail form pointer
form = FrmGetActiveForm();

// CH.6 If there's no time
if( dateTime.hour == NO_TIME )
{
CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ),
" " );
}

else
// CH.6 If there is a time
{
Char timeString[timeStringLength];

// CH.6 Get the time string
TimeToAscii( dateTime.hour, dateTime.minute,
(TimeFormatType)PrefGetPreference( prefTimeFormat ), timeString );

// CH.6 Set the selector trigger label
CtlSetLabel( getObject( form, ContactDetailTimeSelTrigger ),
timeString );

}

// CH.6 We're done
return;
}

// CH.6 Set the controls in the Enter Time form based on dateTime
static void setTimeControls( void )
{
FormPtr form;
ControlPtr hourButton;
ControlPtr minuteTensButton;
ControlPtr minuteOnesButton;
ControlPtr amButton;
ControlPtr pmButton;
ControlPtr noTimeCheckbox;
Char labelString[3];
SWord hour;

// CH.6 Get the form
form = FrmGetActiveForm();

// CH.6 Get the control pointers
hourButton = getObject( form, EnterTimeHoursPushButton );
minuteTensButton = getObject( form,
EnterTimeMinuteTensPushButton );
minuteOnesButton = getObject( form,
EnterTimeMinuteOnesPushButton );
amButton = getObject( form, EnterTimeAMPushButton );
pmButton = getObject( form, EnterTimePMPushButton );
noTimeCheckbox = getObject( form, EnterTimeNoTimeCheckbox );

// CH.6 If there is a time
if( dateTime.hour != NO_TIME )
{
// CH.6 Update the hour
hour = dateTime.hour % 12;
if( hour == 0 )
hour = 12;
CtlSetLabel( hourButton,
StrIToA( labelString, hour ) );

// CH.6 Update the minute tens
CtlSetLabel( minuteTensButton,
StrIToA( labelString, dateTime.minute / 10 ) );

// CH.6 Update the minute ones
CtlSetLabel( minuteOnesButton,
StrIToA( labelString, dateTime.minute % 10 ) );

// CH.6 Update AM
CtlSetValue( amButton, (dateTime.hour < 12) );

// CH.6 Update PM
CtlSetValue( pmButton, (dateTime.hour > 11) );

// CH.6 Uncheck the no time checkbox
CtlSetValue( noTimeCheckbox, false );
}

else
// If there is no time
{
// CH.6 Update the hour
CtlSetValue( hourButton, false );
CtlSetLabel( hourButton, "" );

// CH.6 Update the minute tens
CtlSetValue( minuteTensButton, false );
CtlSetLabel( minuteTensButton, "" );

// CH.6 Update the minute ones
CtlSetValue( minuteOnesButton, false );
CtlSetLabel( minuteOnesButton, "" );

// CH.6 Update AM
CtlSetValue( amButton, false );

// CH.6 Update PM
CtlSetValue( pmButton, false );

// CH.6 Uncheck the no time checkbox
CtlSetValue( noTimeCheckbox, true );
}

// CH.6 We're done
return;
}

// CH.7 Builds the contact list
static void buildList( void )
{
FormPtr form; // CH.6 A form structure pointer
Int choice; // CH.7 The list choice we're doing
CharPtr precord; // CH.7 Pointer to a record
Char listChoice[dateStringLength + 1 + // CH.7 We
timeStringLength + 1 + // build
DB_FIRST_NAME_SIZE + // list
DB_LAST_NAME_SIZE]; // choices here
// CH.7 The current list choice
CharPtr pchoices; // CH.7 Pointer to packed choices
UInt offset; // CH.7 Offset into packed strings
VoidPtr ppchoices; // CH.7 Pointer to pointers to choices

// CH.6 Get our form pointer
form = FrmGetActiveForm();

// CH.7 Put the list choices in a packed string
for( choice = 0; choice < numRecords; choice++ )
{
// CH.7 Get the record
hrecord = DmQueryRecord( contactsDB, choice );
precord = MemHandleLock( hrecord );

// CH.7 Get the date and time
MemMove( &dateTime, precord + DB_DATE_TIME_START,
sizeof( dateTime ) );

// CH.7 Clear the list choice string
*listChoice = '\0';

// CH.7 Add the date string if any
if( dateTime.year != NO_DATE )
{
DateToAscii( dateTime.month, dateTime.day,
dateTime.year,
(DateFormatType)PrefGetPreference(
prefDateFormat ), listChoice );
StrCat( listChoice, " " );
}

// CH.7 Add the time string if any
if( dateTime.hour != NO_TIME )
{
TimeToAscii( dateTime.hour, dateTime.minute,
(TimeFormatType)PrefGetPreference(
prefTimeFormat ), listChoice +
StrLen( listChoice ) );
StrCat( listChoice, " " );
}

// CH.7 Append the first name
StrCat( listChoice, precord + DB_FIRST_NAME_START );
StrCat( listChoice, " " );

// CH.7 Append the last name
StrCat( listChoice, precord + DB_LAST_NAME_START );

// CH.7 Allocate memory for the list entry string
// CH.7 If this is the first choice
if( hchoices == 0 )
{
// CH.7 Allocate the storage for the choice
if( (hchoices = MemHandleNew(
StrLen( listChoice ) + 1 )) == 0 )
errorExit( MemoryErrorAlert );

// CH.7 Initial offset points to the start
offset = 0;
}

else
// CH.7 If this is a subsequent choice
{
// CH.7 Unlock
MemHandleUnlock( hchoices );

// CH.7 Resize
if( MemHandleResize( hchoices, offset +
StrLen( listChoice ) + 1 ) )
errorExit( MemoryErrorAlert );

}

// CH.7 Lock
pchoices = MemHandleLock( hchoices );

// CH.7 Copy the string into the memory
StrCopy( pchoices + offset, listChoice );
offset += StrLen( listChoice ) + 1;

// CH.7 Unlock the record
MemHandleUnlock( hrecord );
}

// CH.7 Create a pointer array from the packed string list
if( (hpchoices = SysFormPointerArrayToStrings( pchoices,
numRecords )) == 0 )
errorExit( MemoryErrorAlert );
ppchoices = MemHandleLock( hpchoices );

// CH.7 Set the list choices
LstSetListChoices( getObject( form, ContactListListList ),
ppchoices, numRecords );

// CH.7 Draw the list
LstDrawList( getObject( form, ContactListListList ) );

// CH.7 We're done
return;
}

// CH.7 This function is called by Palm OS to sort records
static Int sortFunc( CharPtr precord1, CharPtr precord2, Int sortBy )
{
Int sortResult;

// CH.7 Switch based on sort criteria
switch( sortBy )
{
// CH.7 Sort by date and time
case SORTBY_DATE_TIME:
{
DateTimePtr pdateTime1;
DateTimePtr pdateTime2;
Long lDiff;

pdateTime1 = (DateTimePtr)(precord1 + DB_DATE_TIME_START);
pdateTime2 = (DateTimePtr)(precord2 + DB_DATE_TIME_START);

// CH.7 Compare the dates and times
lDiff = (Long)(TimDateTimeToSeconds( pdateTime1 ) / 60 ) -
(Long)(TimDateTimeToSeconds( pdateTime2 ) / 60 );

// CH.7 Date/time #1 is later
if( lDiff > 0 )
sortResult = 1;

else
// CH.7 Date/time #2 is later
if( lDiff < 0 )
sortResult = -1;

else
// CH.7 They are equal
sortResult = 0;
}
break;

// CH.7 Sort by first name
case SORTBY_FIRST_NAME:
{
sortResult = StrCompare( precord1 + DB_FIRST_NAME_START,
precord2 + DB_FIRST_NAME_START );
}
break;

// CH.7 Sort by last name
case SORTBY_LAST_NAME:
{
sortResult = StrCompare( precord1 + DB_LAST_NAME_START,
precord2 + DB_LAST_NAME_START );
}
break;
}

// CH.7 We're done
return( sortResult );
}

本文地址:http://com.8s8s.com/it/it30190.htm