# Palm::ToDo.pm
# 
# Perl class for dealing with Palm ToDo databases. 
#
#	Copyright (C) 1999, 2000, Andrew Arensburger.
#	You may distribute this file under the terms of the Artistic
#	License, as specified in the README file.
#
# $Id: ToDo.pm,v 1.12 2002/11/07 14:12:10 arensb Exp $

# XXX - Bug: apparently, the first ToDo item shows up with a category
# of "unfiled"

use strict;
package Palm::ToDo;
use Palm::Raw();
use Palm::StdAppInfo();

use vars qw( $VERSION @ISA );

# One liner, to allow MakeMaker to work.
$VERSION = do { my @r = (q$Revision: 1.12 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };

@ISA = qw( Palm::StdAppInfo Palm::Raw );

=head1 NAME

Palm::ToDo - Handler for Palm ToDo databases.

=head1 SYNOPSIS

    use Palm::ToDo;

=head1 DESCRIPTION

The ToDo PDB handler is a helper class for the Palm::PDB package. It
parses ToDo databases.

=head2 AppInfo block

The AppInfo block begins with standard category support. See
L<Palm::StdAppInfo> for details.

Other fields include:

    $pdb->{appinfo}{dirty_appinfo}
    $pdb->{appinfo}{sortOrder}

I don't know what these are.

=head2 Sort block

    $pdb->{sort}

This is a scalar, the raw data of the sort block.

=head2 Records

    $record = $pdb->{records}[N]

    $record->{due_day}
    $record->{due_month}
    $record->{due_year}

The due date of the ToDo item. If the item has no due date, these are
undefined.

    $record->{completed}

This is defined and true iff the item has been completed.

    $record->{priority}

An integer. The priority of the item.

    $record->{description}

A text string. The description of the item.

    $record->{note}

A text string. The note attached to the item. Undefined if the item
has no note.

=cut
#'

sub import
{
	&Palm::PDB::RegisterPDBHandlers(__PACKAGE__,
		[ "todo", "DATA" ],
		);
}

=head2 new

  $pdb = new Palm::ToDo;

Create a new PDB, initialized with the various Palm::ToDo fields
and an empty record list.

Use this method if you're creating a ToDo PDB from scratch.

=cut
#'

# new
# Create a new Palm::ToDo database, and return it
sub new
{
	my $classname	= shift;
	my $self	= $classname->SUPER::new(@_);
			# Create a generic PDB. No need to rebless it,
			# though.

	$self->{name} = "ToDoDB";	# Default
	$self->{creator} = "todo";
	$self->{type} = "DATA";
	$self->{attributes}{resource} = 0;
				# The PDB is not a resource database by
				# default, but it's worth emphasizing,
				# since ToDoDB is explicitly not a PRC.

	# Initialize the AppInfo block
	$self->{appinfo} = {
		dirty_appinfo	=> undef,	# ?
		sortOrder	=> undef,	# ?
	};

	# Add the standard AppInfo block stuff
	&Palm::StdAppInfo::seed_StdAppInfo($self->{appinfo});

	# Give the PDB a blank sort block
	$self->{sort} = undef;

	# Give the PDB an empty list of records
	$self->{records} = [];

	return $self;
}

=head2 new_Record

  $record = $pdb->new_Record;

Creates a new ToDo record, with blank values for all of the fields.

C<new_Record> does B<not> add the new record to C<$pdb>. For that,
you want C<$pdb-E<gt>append_Record>.

=cut

# new_Record
# Create a new, initialized record.
sub new_Record
{
	my $classname = shift;
	my $retval = $classname->SUPER::new_Record(@_);

	# Item has no due date by default.
	$retval->{due_day} = undef;
	$retval->{due_month} = undef;
	$retval->{due_year} = undef;

	$retval->{completed} = 0;	# Not completed
	$retval->{priority} = 1;

	# Empty description, no note.
	$retval->{description} = "";
	$retval->{note} = undef;

	return $retval;
}

# ParseAppInfoBlock
# Parse the AppInfo block for ToDo databases.
sub ParseAppInfoBlock
{
	my $self = shift;
	my $data = shift;
	my $dirtyAppInfo;
	my $sortOrder;
	my $appinfo = {};
	my $std_len;

	# Get the standard parts of the AppInfo block
	$std_len = &Palm::StdAppInfo::parse_StdAppInfo($appinfo, $data);

	$data = $appinfo->{other};		# Look at the non-category part

	# Get the rest of the AppInfo block
	my $unpackstr =		# Argument to unpack()
		"x2" .		# Reserved
		"n" .		# XXX - Dirty AppInfo (what is this?)
		"Cx";		# Sort order

	($dirtyAppInfo, $sortOrder) = unpack $unpackstr, $data;

	$appinfo->{dirty_appinfo} = $dirtyAppInfo;
	$appinfo->{sort_order} = $sortOrder;

	return $appinfo;
}

sub PackAppInfoBlock
{
	my $self = shift;
	my $retval;

	# Pack the application-specific part of the AppInfo block
	$self->{appinfo}{other} = pack("x2 n Cx",
		$self->{appinfo}{dirty_appinfo},
		$self->{appinfo}{sort_order});

	# Pack the AppInfo block
	$retval = &Palm::StdAppInfo::pack_StdAppInfo($self->{appinfo});

	return $retval;
}

sub ParseRecord
{
	my $self = shift;
	my %record = @_;
	my $data = $record{data};

	delete $record{offset};		# This is useless
	delete $record{data};		# No longer necessary

	my $date;
	my $priority;

	($date, $priority) = unpack "n C", $data;
	$data = substr $data, 3;	# Remove the stuff we've already seen

	if ($date != 0xffff)
	{
		my $day;
		my $month;
		my $year;

		$day   =  $date       & 0x001f;	# 5 bits
		$month = ($date >> 5) & 0x000f;	# 4 bits
		$year  = ($date >> 9) & 0x007f;	# 7 bits (years since 1904)
		$year += 1904;

		$record{due_day} = $day;
		$record{due_month} = $month;
		$record{due_year} = $year;
	}

	my $completed;		# Boolean

	$completed = $priority & 0x80;
	$priority &= 0x7f;	# Strip high bit

	$record{completed} = 1 if $completed;
	$record{priority} = $priority;

	my $description;
	my $note;

	($description, $note) = split /\0/, $data;

	$record{description} = $description;
	$record{note} = $note unless $note eq "";

	return \%record;
}

sub PackRecord
{
	my $self = shift;
	my $record = shift;
	my $retval;
	my $rawDate;
	my $priority;

	if (defined($record->{due_day}))
	{
		$rawDate = ($record->{due_day} & 0x001f) |
			(($record->{due_month} & 0x000f) << 5) |
			((($record->{due_year} - 1904) & 0x007f) << 9);
	} else {
		$rawDate = 0xffff;
	}
	$priority = $record->{priority} & 0x7f;
	$priority |= 0x80 if $record->{completed};

	$retval = pack "n C",
		$rawDate,
		$priority;
	$retval .= $record->{description} . "\0";
	$retval .= $record->{note} . "\0";

	return $retval;
}

1;
__END__

=head1 AUTHOR

Andrew Arensburger E<lt>arensb@ooblick.comE<gt>

=head1 SEE ALSO

Palm::PDB(3)

Palm::StdAppInfo(3)

=cut
