栏目搜索
 
 
 
 
你的位置:首页 > 移动开发 > Palm OS应用程序设计指南之五 >
 

Palm OS应用程序设计指南之五

发布者:[本站编辑] | 来源:[]

Palm OS应用程序设计指南之五_电脑维修资料库



  在上文中我们通过为contacr detail程序添加对话框来展示了palm os应用程序的风格,本文我们将继续研究contact detail 程序,让它具有显示并设置日期时间的功能。我们要添加一个窗体来设置时间。这个时间和日期是可选的以供下次联系使用。
  为实现此功能,我们将使用新的资源:选择触发器(selector triggers),开关按钮(push buttons),重复按钮(repeating buttons)。连同前面的按钮,它们都是palm os的控件。它们具有类似的属性,并且在触发时发出相同的事件。它们都可拥有自己的标签,它们都是被单击触发的。在单击后它们的形状都有所改变,不过有的只是瞬间改变就恢复了而已。

  保存你的工程

  当在修改工程之前,最好先制作一个它的副本。这样当出现问题时,你就可以拿出的工程的副本重新开始。步骤如下:

  1. 打开windows 资源管理器;

  2. 找到工程所在的文件夹;

  3. 选中工程,按下ctrl+c拷贝文件夹;

  4. 选择想要保存到的文件夹;

  5. 按下ctrl+v保存;

  6. 将工程重新命名,以便你能清楚记忆。我将其命名为contacts ch.5。

  对contatcs.rsrc文件内容的添加

  这一部分我们为contact detail添加日期和时间的控件。我们还将创建一个用来改变时间的窗体。对改变日期,我们将调用palm os的标准对话框。

  添加日期时间选择触发控件

  向contact detail窗体添加两个标签和两个选择触发器(selector triggers)。我们将使用选择触发来显示下一次调用此contact的日期和时间。选择触发控件处理事件和按钮很相似,只是外形有很大不同。它被一个点壮矩形所环绕。和按钮相比,这个矩形在宽度和高度上都占有一个象素的额外空间,这一点在放置此控件时要考虑。有关选择触发器(selector triggers)的属性见表1。

object identifier 构造器用来代表资源头文件id的常量selector trigger id选择触发控件的资源id;left origin 水平方向上控件的最左端位置;top origin垂直方向上控件的最顶端位置;width控件的最大宽度。此属性很少使用,因为控件的右边界会随着标签文本的长度改变而改变;height控件的高度;usable决定次控件是否可见能用。如果没有选中,也可在通过函数调用来实现其可见;anchor left决定当文本长度改变时,控件的左侧或右侧是否做相应的伸缩;font标签使用的字体;label 标签的缺省文本;
  以下是添加控件的步骤:

  1. 打开资源构造器;

  2. 打开文件contacts.rsrc。它在src文件夹中;

  3. 双击contacts detail窗体;

  4. 选择window | catalog,产生控件模板;

  5. 将一个标签拖到窗体上。置标签文本为next call date。将它放在phone number 标签底下。设置left origin为0、top origin为60,文本字体为粗体;

  6. 将一个选择触发器(selector triggers)拖到窗体上。设置object identifier为date,left origin为81,top origin 为60,width为78。向标签输入10个空格,这样可保证在缺省的情况下,当被finger按下时有充足的空间;

  7. 向窗体上再拖一个标签。置标签文本为next call time。将它放在next call date 标签底下。设置left origin为12、top origin为80,文本字体为粗体;

  8. 向窗体上再拖一个选择触发器(selector triggers)。设置object identifier为time,left origin为81,top origin 为80,width为78。也向标签输入10个空格。

  9. 添加控件后,按下右上角的x按钮,关闭窗体。





  创建一个新的设置时间窗体

  现在创建一个窗体:

  1. 点击资源(resource)中的窗体(forms)选项,按下ctrl-k创建一个新的窗体;

  2. 点击name框并重命名为enter time;

  3. 双击打开窗体进行编辑;

  4. 首先设置窗体属性。复选中属性modal和save behind。我们将此窗体作为对话框的形式出现;此窗体是我们接触到的第一个不是全屏显示的窗体;我们修改其宽度(width)为156,其高度(height)为53;

  5. 为实现modal边框可见,我们需要在窗体和屏幕边界留出2个象素的宽度。所以宽度设为156,而左初始边界应为2,顶端初始边界应为105,这样就保证了两个象素的余度;

  6. 修改窗体的名字(name)属性为enter time。

  添加开关按钮(push buttons)

  开关按钮(push buttons)在表现为按下的状态时,不同于通常淡的底色和黑的文字,而是黑的底色和淡的文字。我们使用开关按钮(push buttons)显示小时、分钟、和上午/下午(am/pm)。在放置开关按钮(push buttons)时,我们必须考虑它的边界所占用的一个象素的宽度。开关按钮(push buttons)的属性如表6-2所示:

object identifier资源构造器用来代表资源头文件id的常量push button id开关按钮(push buttons)的资源idleft origin 水平方向上按钮的最左端位置;top origin 垂直方向上按钮的最顶端位置;width按钮的宽度height按钮的高度usable用来定义控件是否可见及可用,如果没有选中,也可在通过函数调用来实现其可见group id表示当按钮按下时是否突出 显示。如果此数字为0,则按钮当被按下是将在“按下”和“没有按下”两个状态间切换。当此数字不为0时,按钮被按下是保持原来的状态。在每一组中所用的组(group)id应是唯一的,因为在后面的代码中将使用到组idfont 标签显示文字的字体label 标签本身的文字
  添加步骤:

  1. 将一个开关按钮(push buttons)拖到enter time 窗体上;

  2. 既然此按钮显示时间,可将object identifier设置为hours。设置属性:left origin=5,top origin=17,width为18,group id为1。font属性为bold,清除标签内的文字;

  3. 复制hours开关按钮(push buttons)。可选中hours开关按钮(push buttons)后,按下ctrl-d进行复制。修改object identifier为minutestens。设置属性:left origin=34,top origin=17,width为12,因为它只包含一个数字;

  4. 复制minutestens按钮。可选中minutestens按钮后,按下ctrl-d进行复制。修改object identifier设置为minutesones。设置属性:left origin=50,top origin=17;

  5. 再拖动一个开关按钮(push buttons)到窗体上。修改object identifier设置为am。设置属性:left origin=109,top origin=17,width为20,group id为2。设置标签内的文字为am;

  6. 复制am开关按钮(push buttons)。修改object identifier为pm。设置属性:left origin=130,top origin=17,注意am 按钮和pm按钮重叠是为了使他们之间只有一个象素的间隔,这就是相关的开关按钮(push buttons)如何归为一组的方法。设置标签内的文字为pm;

名字描述object identifier构造器用来代表资源头文件id的常量。push button id开关按钮(push buttons)标志号。left origin开关按钮(push buttons)的左边缘的水平起始位置。top origin 开关按钮(push buttons)的顶边缘的垂直起始位置。width开关按钮(push buttons)的宽度。height 开关按钮(push buttons)的高度。usable 该参数定义该控件是否可视和被激活。如该参数未被选择,你可通过一函数调用使该控件可视和被激活。group id 该参数影响开关按钮(push buttons)被选时是否仍保持加亮状态。如该参数为0,开关按钮(push buttons)被按下时,其状态在“on”和“off”之间切换。如果你要在程序中使用组号,则该组号应为唯一。font标签显示文字的字体label 标签本身的文字

  向设置时间窗体添加重复按钮(repeating buttons)

  如果输入笔按在repeating按钮上的时间超过半秒,该按钮将连续发ctlrepeatevent事件。头半秒之后,ctlrepeatevent事件每十分之一秒发一次。我们使用重复按钮(repeating buttons)来构造up和down箭头以调整时间。如同一般的按钮一样,重复按钮(repeating buttons)可有一边框。在我们的事例中,这些按钮不使用边框,因此,它们像标签和文本框一样排列。

  重复按钮(repeating buttons)的属性如下表:

名字描述object identifier构造器用来代表资源头文件id的常量。button id 重复按钮(repeating buttons)标志号。left origin按钮的左边缘的水平起始位置。top origin 按钮的顶边缘的垂直起始位置。width 按钮的宽度。height按钮的高度。usable 该参数定义该控件是否可视和被激活。如该参数未被选择,你可通过一函数调用使该控件可视和被激活。anchor left 如该特性被选择,按钮被程序重定大小时向右扩张。frame 如该特性被选择,按钮将有一边框。non-bold frame 如该特性被选择,按钮边框粗细为1像素。font 标签显示文字的字体label 标签本身的文字
  向设置时间窗体添加一个repeating按钮

  1. 将一个repeating按钮拖放至设置时间窗体。将object identifier改为timeup。参考位置为left origion = 109, top origion = 17。参考大小为width=20,height =8。不选frame特性。

  2. 选字体为symble 7,在label上选hex box。键入01。这样,你在form上看到的是空白标签,而运行代码后将显示一个向上的箭头。不要被hex 21所迷惑。当你运行代码时,hex 21显示为一个复选框。

  3. 通过选择repeating按钮并按ctl-d来复制该按钮。将将object identifier改为timedown。此按钮的位置应为left origion = 69, top origion = 25。将label设置为hex 02,则程序运行时显示一个向下的箭头。

  向设置时间窗体添加一个复选框

  复选框的左端有一个小框可被选择以指示某些事。在设置时间窗体中,该小框可让用户选择不输入时间。复选框没有边框,所以它们的排列如同一个域一样。复选框的属性如下表所示:

名字描述object identifier资源构造器用来代表资源头文件id的常量。checkbox id复选框标志号。left origin复选框的左边缘的水平起始位置。top origin复选框的顶边缘的垂直起始位置。width复选框的宽度。height复选框的高度。usable该参数定义该控件是否可视和被激活。如该参数未被选中,你可通过一函数调用使该控件可视和被激活。selected如该参数被选择,则复选框被画时缺省为被选。group id 该参数影响复选框被选时是否仍保持加亮状态。如该参数为0,复选框被按下时,其状态在被选和未选之间切换。如果你要在程序中使用组号,则该组号应为唯一。font标签显示文字的字体。label 标签本身的文字
  现在向设置时间窗体添加一个复选框:

  1. 从catalog窗拖放一个复选框到设置时间窗体。

  2. 将object identifier属性设置为notime。参考位置为left origion = 53, top origion = 37。设width为50。选selected。设group id为0因为并未成组。设label为notime。


  完善设置时间窗体

  现在让我们来添加一些熟悉的控件:

  1. 小时和分钟之间用冒号隔开会更好看。从catalog窗体拖放一个label控件到设置时间窗体中。位置为left origion = 27, top origion = 17。设字体为bold。加一个冒号。

  2. 每一个对话框都需要一个ok按钮。从catalog窗体拖放一个button控件到设置时间窗体中。位置为left origion = 5, top origion = 37。注意left origion设为5使按钮与其上的push按钮对齐,并在左边缘留出4象素的空间。

  3. 有一个cancel按钮也会很好。拖放一个button控件到窗体中。改设object identifier为cancel。其位置为left origion = 115, top origion = 37。改设label为cancel。

  4. 使cancel按钮为此窗体的缺省按钮。记下cancel按钮的button id。点击窗体背景的任意处以显示设置时间窗体的属性表。将窗体缺省按钮的id设为cancel按钮的button id。

  你已经完成了设置时间窗体的构建。点击右上角的x来关闭窗口。选file | save来保存你的改变。

  向contacts.c添加代码

  现在,为了在数据库、控件以及我们才添加的新窗体中支持日期和时间,我们向contacts.c添加代码。

  在数据库中初始化和保存日期和时间

  为了在内部和数据库中保存和定义日期和时间,你需要一些变量和常量的定义。

// 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
  变量datetime保存目前正被处理的记录的日期和时间。设置时间窗体使用timeselect变量完成同样功能。常量no_time和no_datae同样使用datatime来表示没有日期或没有时间或二者兼备。

  在newrecord()功能中,加入代码设date和time的初始状态为没有日期和没有时间。

// 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 ) );
  注意我们使用memset()将整个记录清零。如果我们不这样做,记录中的域将有垃圾并且域功能将崩溃,因为我们并没有如域功能希望的那样发送以零定界的字符串。然后,我们把变量datetime作为临时变量来初始化该记录。

  在setfields()功能中,从记录中载入datetime变量的值。

// ch.6 initialize the date and time variable
precord = memhandlelock( hrecord );
memmove( &datetime, precord + db_date_time_start,
sizeof( datetime ) );
  并且,设置日期和时间(selector triggers)的外观。我们将在讨论选择触发器时详细研究这些功能。

// ch.6 initialize the date control
setdatetrigger();

// ch.6 initialize the time control
settimetrigger();

  支持日期和时间选择按钮

  加入的第一行代码应是contact detail窗体事件处理句柄,对日期选择按钮来说,然后再添加向处理普通按钮那样事件处理过程。

// 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;
  如果以前没有填入时间,把时间设为当前时间。获取当前时间是通过调用函数timgetseconds()并通过函数timseconstodatetime()将其输出转换为日期。这些函数是时间处理器的一部分,在palm os的reference.pdf文件中有详细的描述。

  时间初始化完成后,我们调用函数selectday()以产生palm os内置控件来选择日期。当选定时间后,我们锁定数据库当前记录,将新的时间值写入。

  选择时间有些不一样。因为palm os中没有可以直接选择时间的控件,那么我们弹出enter time 窗体来和用户交互。

// ch.6 time selector trigger
case contactdetailtimeseltrigger:
{
// ch.6 pop up our selection form
frmpopupform( entertimeform );
}
break;
  在contact detail事件处理过程中我们所做的就是调出enter time 窗体。修改数据库的工作就由后者来处理。

  函数setdatetrigger()用来更新日期选择按钮的外观,代码如下:

// 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;

// 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;
}
  如果没有时间,我们控件中写入10个空格。由于选择触发按钮能够根据标签自动调整大小,这样就可保证触发按钮足够大可用手指来选择。记住一定要保证控件标签(包括触发按钮)中的文本大小不要超过最初在构造器中定义的数量。

  如果已有时间,那我们返回时间。系统内的优先权将决定我们使用什么格式来显示时间。我们调用带参数predateformat的函数prefgetpreference()来获取日期的优先权。我们选择类型dateformat的原因是pregetpreference()能够返回许多不同的优先权,我们选最普遍的一种。

  函数datetoascii()用来将日期变量转换为我们定义的短时间格式,这是由我们从系统中获得的。当我们获得时间后,我们就将其写入触发按钮标签内。

  函数settimetrigger()用来设置时间触发按钮。它和setdatetrigger()很相似,除了用它自己相应的函数外。


  支持开关按钮(push buttons)

  像在contact details所做的那样,我们也将建立switch语句来处理ctlselectevent。case语句建立在不同button id值上,它可用真正的控件id来表示。

  首先来处理小时和分钟开关按钮(push buttons):

// ch.6 hours button
case entertimehourspushbutton:
// ch.6 minute tens button
case entertimeminutetenspushbutton:
// ch.6 minute o-nes 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;
  按钮将在它们的标签上显示各自的值。哪一个值和上或下箭头相关联是根据哪一个被选中决定的。如果当按钮被选中后但没有时间显示,12pm将被设置并显示。如果先前已选择了一个按钮,那么我们先清除这个选择,然后选中我们刚刚点击的那个按钮。

  在函数settimecontrol()中,我们看它是如何设置标签文本的。实际上,它和其他任何控件处理标签一样。

// 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 o-nes
ctlsetlabel( minuteonesbutton,
stritoa( labelstring, datetime.minute % 10 ) );
  时间是以24小时格式显示,我们把它转换为12小时格式并在按钮上显示。

  在函数entertimehandleevent()中也有两个push按钮,用来进行am/pm设置。它们的代码如下:

// 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;
  在am case语句中,如果没有时间,那么我们设置时间为12 am;如果时间是pm,那么我们从24小时格式中减去12而变为am。

  pm按钮处理和am差不多。

  在settimecontrols()中显示am和pm,代码如下:

// ch.6 update am
ctlsetvalue( ambutton, (datetime.hour < 12) );

// ch.6 update pm
ctlsetvalue( pmbutton, (datetime.hour > 11) );
  在需要布尔值的地方根据其逻辑变换布尔值是个很有意思的事。

  支持重复按钮(repeating buttons)

  repeating按钮允许时间被增加或减少。为了使其有效,我们必须像处理ctlselectevent那样处理ctlrepeatevent。在函数entertimehandleevent()我们必须也要从事件句柄那里返回false,否则就不能产生repeating 事件。

// ch.6 up button
case entertimetimeuprepeating:
{
// ch.6 if there's no time, do nothing
if( datetime.hour == no_time )
break;

// ch.6 based o-n 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;
  如果没有时间显示,上下箭头不做任何事情。有时间显示时,根据所选择的开关按钮(push buttons),重复按钮(repeating buttons)将增加小时、分钟的十位、分钟的个位。其中也处理了必要的循环情况。

  减少按钮和增加按钮基本相似。


  支持复选框

  复选框的处理和其它按钮一样。当被触发时,产生一个ctlselectevent。

// 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;
  为方便起见,如果没选复选框,我们将选中小时push按钮,然后其它的事由函数settimecontrol()来处理。当选中复选框时将清空所有的控件内容。

  结束enter time窗体

  还有一些其它的窗口处理事件需要讨论。在entertimehandleevent()中处理frmopenevent。

// 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;
  当打开窗体时,我们用函数settimecontrols()初始化窗体。我们也保存了当前时间以便按下了cancel能够恢复。

// 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 );
  cancel按钮事件储存了老的时间,并重新返回给contact detail窗体.

  ok按钮有些复杂。我们必须根据新时间相应的更新数据库和contact detail窗体。

  在这里有几个重点。这个代码块表明了为什么变量hrecord在函数中是公用的。在这里由于hrecord是有效的,所有我们就用它来将新的时间值写入数据库中。

  注意到在函数frmreturntoform()后调用的settimetrigger(),它用来触发contact details窗体的时间选择触发按钮。能够实现触发的原因是因为当执行frmreturntoform()后,活动窗体就变为了contact details窗体。这就使弹出窗体的数据能够顺利的传递到调用窗体上。

  通常情况下,在调用frmreturntoform()后返回true,这是因为调用后老的窗体结构已经消失。然而如果返回了false,palm os就试图访问按钮结构以做更多的素材。由于窗口已经不在了,这样就会使程序崩溃。

  调试

  首先,你应该保证数据库记录能被正确的创建和修改。如果顺利的话,你所加的显示函数能够正确的显示所得结果。你的已有记录表现为no date,在界面上会显示12 am,但不会造成危险。

  下一步操作contact detail窗体上的日期触发按钮,看是否能按设计程序正常工作。对日期内置控件来说,这应比较容易。

  对于时间控制,你需要调试enter time窗体。仔细的调试各个控件直到都能够可靠的工作。当窗体能够顺利运行后,你可验证数据库记录和时间选择触发按钮是否被正确的修改。

  下一步

  下一步,我们将向contacts应用程序添加一个列表框窗体。然后修改代码使我们能够根据first name、last name、date 和time进行排序。


  程序清单

  下面是完整的contacts.c程序清单。

// 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 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 );

// 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.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 o-ne
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 o-ne
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.5 if there are no records, create o-ne
if( numrecords == 0 )
newrecord();

// ch.4 go to our starting page
frmgotoform( contactdetailform );

// 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;
}
frmsetactiveform( form );
}

// ch.2 handle form events
frmdispatchevent( &event );

// ch.2 if it's a stop event, exit
} while( event.etype != appstopevent );

// 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 o-ne record
if( cursor > 0 )
cursor--;
}
break;

// ch.5 next button
case contactdetailnextbutton:
{
// ch.5 move the cursor up o-ne 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 o-ne
if( numrecords == 0 )
newrecord();
}
break;

// ch.5 new button
case contactdetailnewbutton:
{
// ch.5 create a new record
newrecord();
}
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 o-n 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 o-n button id
switch( buttonid )
{
// ch.6 hours button
case entertimehourspushbutton:
// ch.6 minute tens button
case entertimeminutetenspushbutton:
// ch.6 minute o-nes 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 o-n 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 o-n 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.3 handle menu events
static 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 o-n
// 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.5 create the database record and get a handle to it
hrecord = dmnewrecord( contactsdb, &cursor, db_record_size );

// 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 o-n 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 o-n
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.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.5 unlock the record
memhandleunlock( hrecord );
}

// 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
memhandleresize( hfield, strlen( text ) + 1 );
}

else
// ch.5 allocate a handle for the string
hfield = memhandlenew( strlen( text ) + 1 );

// 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;

// 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;

// 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 o-n 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 o-nes
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 o-nes
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;
} </t