Index: /trunk/hiptop/pester/build.xml
===================================================================
--- /trunk/hiptop/pester/build.xml	(revision 237)
+++ /trunk/hiptop/pester/build.xml	(revision 237)
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+
+<project name="Pester" default="run">
+    <property environment="env"/>
+    <property name="DANGER_HOME" value="${env.DANGER_HOME}"/>
+
+    <property name="APP_CLASS_NAME"  value="Pester"/>
+    <property name="APP_PACKAGE_NAME" value="net.sabi.Pester"/>
+
+    <property name="APP_VERSION_MAJOR" value="1"/>
+    <property name="APP_VERSION_MINOR" value="0"/>
+    <property name="APP_VERSION_BUILD" value="1"/>
+
+    <import file="${DANGER_HOME}/tools/build_common.xml"/>
+</project>
Index: /trunk/hiptop/pester/locale/en_US/Pester.rsrc
===================================================================
--- /trunk/hiptop/pester/locale/en_US/Pester.rsrc	(revision 237)
+++ /trunk/hiptop/pester/locale/en_US/Pester.rsrc	(revision 237)
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+
+package "net.sabi.pester"
+
+interface "{source}net/sabi/pester/Resources.java"
+events "{source}net/sabi/pester/Commands.java"
+
+string ID_APP_NAME "Pester"
+string ID_APP_CLASS "net.sabi.pester.Pester"
+string ID_APP_FLAGS ""
+string ID_LOCALE "en_US"
+
+bitmap ID_LARGE_ICON "{base}LargeIcon.png"
+bitmap ID_SMALL_ICON "{base}SmallIcon.png"
+
+bitmap ID_SPLASH_SCREEN_NO_TITLE	"{base}SplashScreenTransparent.png"
+
+string	ID_VERSION	"V@APP_VERSION_MAJOR@.@APP_VERSION_MINOR@"
+
+splashScreen ID_SPLASH_SCREEN_RESOURCE
+	image
+	        bitmap = ID_SPLASH_SCREEN_NO_TITLE
+                alignWithParentTop = 0
+		alignWithParentLeft = 0
+        staticTextBox
+                text = ID_APP_NAME
+        	alignWithParentLeft = 2
+        	alignWithParentTop = 0
+        	fillToRight = 2
+        	transparent
+        	doesNotHaveBorder
+endSplashScreen
+
+menu ID_ALARM_LIST_MENU
+	menuItem
+		title = "New Alarm"
+		shortcut = 'N'
+		icon = soundIcon
+		event = EVENT_NEW_ALARM
+
+	divider
+
+	menuItem 
+		id = ID_DISCARD_ITEM
+		title = "Discard"
+		shortcut = deleteKey
+		event = EVENT_DISCARD_ALARM
+endMenu
+screen ID_ALARM_LIST_SCREEN
+	title = "Pester"
+	actionMenu = ID_ALARM_LIST_MENU
+	className = "net.sabi.pester.AlarmListWindow"
+	icon = ID_SMALL_ICON
+
+	activeListView
+		fillToBottom = 0
+		id = ID_ALARM_LIST_VIEW
+		alignWithParentLeft = 0
+		alignWithParentTop = 0
+		initialFocus
+		widthPct = 100
+		itemHeight = 12
+		className = "net.sabi.pester.AlarmListView"
+		
+endScreen
+dialog ID_ALARM_SET_DIALOG
+
+	title = "Set Alarm"
+	showCancel
+#	className = "net.sabi.pester.AlarmSetDialog"
+	widthPct = 100
+
+	staticText
+		text = "Message"
+		id = ID_MESSAGE_LABEL
+		alignWithParentTop = 3
+		alignWithParentLeft = 5
+		justifyRight
+		font = boldSysFont
+
+	typeAheadTextField
+		fillToRight = 0
+		id = ID_MESSAGE_FIELD
+		alignBaseline = ID_MESSAGE_LABEL
+		positionToRight = ID_MESSAGE_LABEL : 3	
+		autoCap
+		initiallySelectAll
+		initialFocus
+
+	button
+		position = buttonBottom1
+		title = "Set"
+		shortcut = doneButton
+		event = EVENT_SET_ALARM
+endDialog
Index: /trunk/hiptop/pester/net/sabi/pester/Alarm.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/Alarm.java	(revision 237)
+++ /trunk/hiptop/pester/net/sabi/pester/Alarm.java	(revision 237)
@@ -0,0 +1,102 @@
+package net.sabi.pester;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import danger.internal.Date;
+import danger.util.StdActiveObject;
+
+public class Alarm extends StdActiveObject {
+    private static final int VERSION_1 = 1;
+
+    // persisted
+    private String mMessage;
+    private boolean mUsesPeriod;
+    private long mPeriod;
+    private Date mDate;
+
+    // transient
+    private int mStatus;
+
+    public Alarm() {
+	mStatus = STATUS_INVALID;
+    }
+
+    public String getMessage() {
+	return mMessage;
+    }
+    public long getPeriod() {
+	return mPeriod;
+    }
+    public boolean getUsesPeriod() {
+	return mUsesPeriod;
+    }
+    public Date getDate() {
+	return mDate;
+    }
+
+    public void setMessage(String message) {
+	mMessage = message;
+    }
+    public void setPeriod(long period) {
+	mUsesPeriod = true;
+	mPeriod = period;
+    }
+    public void setDate(Date date) {
+	mUsesPeriod = false;
+	mDate = date;
+    }
+
+    public byte[] toByteArray() {
+	try {
+	    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+	    DataOutputStream dataStream = new DataOutputStream(byteStream);
+
+	    dataStream.writeByte(VERSION_1);
+	    dataStream.writeUTF(mMessage);
+	    dataStream.writeLong(mPeriod);
+	    dataStream.writeInt(mDate.getUnixTimeGMT());
+	    dataStream.writeBoolean(mUsesPeriod);
+	    dataStream.flush();
+	    return byteStream.toByteArray();
+	} catch (Exception e) {
+	    // XXX do something
+	}
+	return null;
+    }
+    
+    public void fromByteArray(byte[] data) {
+	try {
+	    ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
+	    DataInputStream dataStream = new DataInputStream(byteStream);
+	    
+	    byte version = dataStream.readByte();
+	    if (version != VERSION_1) {
+		// XXX barf
+	    }
+	    mMessage = dataStream.readUTF();
+	    mPeriod = dataStream.readLong();
+	    mUsesPeriod = dataStream.readBoolean();
+	} catch (Exception e) {
+	    // XXX do something
+	}
+    }
+
+    void beginEditing() {
+	mStatus = STATUS_EDITING;
+    }
+
+    void endEditing() {
+	mStatus = STATUS_SCHEDULED;
+    }
+    
+    public String toString() {
+	return mMessage;
+    }
+
+    public static final int STATUS_INVALID = 0;
+    public static final int STATUS_EDITING = 1;
+    public static final int STATUS_SCHEDULED = 2;
+    public static final int STATUS_EXPIRED = 3;
+}
Index: /trunk/hiptop/pester/net/sabi/pester/AlarmListView.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/AlarmListView.java	(revision 237)
+++ /trunk/hiptop/pester/net/sabi/pester/AlarmListView.java	(revision 237)
@@ -0,0 +1,109 @@
+package net.sabi.pester;
+
+import danger.app.Application;
+import danger.app.Event;
+import danger.ui.ActiveListView;
+import danger.ui.DialogWindow;
+import danger.ui.Rect;
+import danger.ui.TypeAheadTextField;
+import danger.util.ActiveList;
+import danger.util.StdActiveList;
+import danger.util.DEBUG;
+
+public class AlarmListView extends ActiveListView
+    implements Resources, Commands {
+    private static Pester sPester;
+    private static AlarmListWindow sAlarmListWindow;
+    // XXX some of these shouldn't conceptually be static, but who cares
+    private static StdActiveList sAlarmList;
+    private static Alarms sAlarms;
+
+    private static DialogWindow sEditWindow;
+    private TypeAheadTextField sMessageField;
+
+    private static Alarm sFocusedAlarm, sEditingAlarm;
+
+    private static final int EVENT_CANCEL_ALARM = -1; // XXX move this?
+    
+    public void onDecoded() {
+	sPester = (Pester)Application.getCurrentApp();
+	sAlarmListWindow = (AlarmListWindow)getWindow();
+	sAlarmList = new StdActiveList();
+	setList(sAlarmList);
+
+	sEditWindow = sPester.getDialog(ID_ALARM_SET_DIALOG, this);
+	sEditWindow.setCancelButtonEvent(new Event(this, EVENT_CANCEL_ALARM));
+	sMessageField = (TypeAheadTextField)sEditWindow.getDescendantWithID(ID_MESSAGE_FIELD);
+	sEditWindow.disableBottomRightButtonOnEmptyField(sMessageField);
+
+	super.onDecoded();
+    }
+
+    public boolean eventWidgetUp(int widget, Event e) {
+	switch (widget) {
+	case Event.DEVICE_BUTTON_BACK:
+	case Event.DEVICE_BUTTON_CANCEL:
+	    sPester.returnToLauncher();
+	    return true;
+	}
+	return super.eventWidgetUp(widget, e);
+    }
+
+    void editAlarm(Alarm alarm) {
+	sEditingAlarm = alarm;
+	alarm.beginEditing();
+	sMessageField.setText(sEditingAlarm.getMessage());
+	sEditWindow.show();
+    }
+
+    public boolean receiveEvent(Event e) {
+	switch (e.type) {
+	case EVENT_NEW_ALARM:
+	    editAlarm(new Alarm());
+	    return true;
+	case EVENT_SET_ALARM:
+	    if (sEditingAlarm != sFocusedAlarm) { // new alarm
+		sAlarmList.addItem(sEditingAlarm);
+		setFocusedItem(sEditingAlarm);
+	    }
+	    sEditingAlarm.setMessage(sMessageField.toString());
+	    sEditingAlarm.update();
+	case EVENT_CANCEL_ALARM:
+	    // XXX (re)schedule alarm
+	    sEditingAlarm.endEditing();
+	    return true;
+	case EVENT_DISCARD_ALARM:
+	    // XXX confirmation
+	    sAlarmList.removeItem(sFocusedAlarm);
+	    return true;
+	}
+	return super.receiveEvent(e);
+    }
+
+    protected void itemActivated(Object item) {
+	// XXX deschedule alarm
+	editAlarm(sFocusedAlarm);
+    }
+    protected void itemFocused(Object item) {
+	sFocusedAlarm = (Alarm)item;
+    }
+
+    public int alarmsSet() {
+	return getListSize();
+    }
+    public void onItemAdded(ActiveList list, Object item, int index) {
+	sAlarmListWindow.updateAlarmCount();
+	super.onItemAdded(list, item, index);
+    }
+    public void onItemRemoved(ActiveList list, Object item, int index) {
+	sAlarmListWindow.updateAlarmCount();
+	super.onItemRemoved(list, item, index);
+    }
+
+    protected CharSequence getToolTipForItem(Object item) {
+	if (item == null) // this is dumb, why do we get asked for a tooltip?
+	    return null;
+	// XXX only set a tooltip if the message is truncated
+	return ((Alarm)item).getMessage();
+    }
+}
Index: /trunk/hiptop/pester/net/sabi/pester/AlarmListWindow.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/AlarmListWindow.java	(revision 237)
+++ /trunk/hiptop/pester/net/sabi/pester/AlarmListWindow.java	(revision 237)
@@ -0,0 +1,29 @@
+package net.sabi.pester;
+
+import danger.ui.ScreenWindow;
+import danger.ui.Menu;
+import danger.ui.MenuItem;
+
+public class AlarmListWindow extends ScreenWindow
+    implements Resources, Commands {
+    private static AlarmListView sAlarmListView;
+
+    public void onDecoded() {
+	sAlarmListView = (AlarmListView)getDescendantWithID(ID_ALARM_LIST_VIEW);
+	updateAlarmCount();
+	super.onDecoded();
+    }
+
+    public void updateAlarmCount() {
+	int size = sAlarmListView.alarmsSet();
+	StringBuffer sb = new StringBuffer().append(size).append(" alarm");
+	if (size != 1) sb.append('s');
+	sb.append(" set");
+	setSubTitle(sb.toString());
+    }
+
+    public void adjustActionMenuState(Menu actionMenu) {
+	boolean haveFocusedItem = (sAlarmListView.getFocusedItem() != null);
+	actionMenu.getItemWithID(ID_DISCARD_ITEM).setEnabled(haveFocusedItem);
+    }
+}
Index: /trunk/hiptop/pester/net/sabi/pester/Alarms.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/Alarms.java	(revision 237)
+++ /trunk/hiptop/pester/net/sabi/pester/Alarms.java	(revision 237)
@@ -0,0 +1,32 @@
+package net.sabi.pester;
+
+import java.util.Comparator;
+import danger.app.DataStore;
+
+public class Alarms {
+    // max # records in a datastore
+    private static final int MAX_ALARM_COUNT = 50;
+
+    private static Alarms sAlarms = null;
+    private static DataStore sDataStore;
+
+    private Alarms() {
+	sDataStore = DataStore.createDataStore("alarms", true /* auto sync */);
+    }
+    
+    public static Alarms getInstance() {
+	if (sAlarms == null)
+	    sAlarms = new Alarms();
+
+	return sAlarms;
+    }
+
+    /*
+    private static Alarm sAlarm0 = new Alarm();
+    private static Alarm sAlarm1 = new Alarm();
+    public int compare(Object arg0, Object arg1) {
+	
+	return Collator.getInstance().compare(
+    }
+    */
+}
Index: /trunk/hiptop/pester/net/sabi/pester/Pester.java
===================================================================
--- /trunk/hiptop/pester/net/sabi/pester/Pester.java	(revision 237)
+++ /trunk/hiptop/pester/net/sabi/pester/Pester.java	(revision 237)
@@ -0,0 +1,13 @@
+package net.sabi.pester;
+
+import danger.app.Application;
+import danger.ui.ScreenWindow;
+
+public class Pester extends Application implements Resources, Commands {
+    public Pester() {
+	alarmListWindow = (ScreenWindow)getResources().getScreen(ID_ALARM_LIST_SCREEN);
+	alarmListWindow.show();
+    }
+
+    static private ScreenWindow alarmListWindow;
+}
