Index: trunk/hiptop/pester/locale/en_US/Pester.rsrc
===================================================================
--- trunk/hiptop/pester/locale/en_US/Pester.rsrc	(revision 252)
+++ trunk/hiptop/pester/locale/en_US/Pester.rsrc	(revision 253)
@@ -130,4 +130,5 @@
 			alignBaseline = ID_IN_BUTTON
 			positionToRight = ID_IN_BUTTON : 1
+			className = "net.sabi.pester.EventfulNumberEditor"
 	
 		popupMenu
Index: trunk/hiptop/pester/net/sabi/pester/Alarm.java
===================================================================
--- trunk/hiptop/pester/net/sabi/pester/Alarm.java	(revision 252)
+++ trunk/hiptop/pester/net/sabi/pester/Alarm.java	(revision 253)
@@ -78,8 +78,10 @@
 	    dataStream.writeInt(mDate.getUnixTimeGMT());
 	    dataStream.writeInt(mType);
+	    dataStream.writeInt(mAlert == null ? 0 : mAlert.getID());
 	    dataStream.flush();
 	    return byteStream.toByteArray();
 	} catch (Exception e) {
 	    // XXX do something
+	    DEBUG.p("failed to write alarm:" + e);
 	}
 	return null;
@@ -97,7 +99,11 @@
 	    mMessage = dataStream.readUTF();
 	    mPeriod = dataStream.readInt();
+	    mDate = new Date(dataStream.readInt());
 	    mType = dataStream.readInt();
+	    int alertID = dataStream.readInt();
+	    mAlert = (alertID == 0 ? null : new RingToneObject(alertID));
 	} catch (Exception e) {
 	    // XXX do something
+	    DEBUG.p("failed to read alarm:" + e);
 	}
     }
@@ -116,4 +122,5 @@
     }
 
+    // XXX trigger on EVENT_TIME_CHANGED
     void resume() {
 	mState = STATE_SCHEDULED;
Index: trunk/hiptop/pester/net/sabi/pester/AlarmSetDialog.java
===================================================================
--- trunk/hiptop/pester/net/sabi/pester/AlarmSetDialog.java	(revision 252)
+++ trunk/hiptop/pester/net/sabi/pester/AlarmSetDialog.java	(revision 253)
@@ -42,5 +42,7 @@
     private Alarm mEditingAlarm;
     private boolean mAlarmIsNew;
-    private boolean haveShownWindow = false;
+    private boolean mHaveShownWindow = false;
+
+    private danger.app.Alarm mValidateAlarm;
 
     public static AlarmSetDialog getDialog() {
@@ -82,4 +84,6 @@
 	mStatusBox.setAutoResize(false);
 
+	mValidateAlarm = new danger.app.Alarm(1, this);
+
 	super.onDecoded();
     }
@@ -100,5 +104,5 @@
 	}
 
-	Date date = alarm.getDate();
+	Date date = new Date(alarm.getDate());
 	mTimeEditor.setDate(date);
 	mDateEditor.setDate(date);
@@ -124,5 +128,5 @@
 	validate();
 	show();
-	if (!haveShownWindow) {
+	if (!mHaveShownWindow) {
 	    Rect alarmRect = mSetAlarmButton.getFrame();
 	    Rect contentRect = getContentRect();
@@ -131,5 +135,5 @@
 	    mStatusBox.show();
 	    addChild(mStatusBox);
-	    haveShownWindow = true;
+	    mHaveShownWindow = true;
 	    setAutoSize(false); // don't resize to contain it next time
 	}
@@ -170,16 +174,33 @@
 	mDateEditor.min(now);
 	mDatePicker.min(now);
-	if (!isIn() && editingDate().compareTo(now) <= 0) {
-	    mStatusBox.setText("Specify a future date.");
-	    return false;
-	}
-	mStatusBox.setText(Alarm.dateTimeString(editingDate(), true));
-	// XXX invalidate @ alarm date if isIn()
-	// XXX invalidate in 1 sec if isAt()
+	Date editingDate = editingDate();
+	if (isIn()) {
+	    mValidateAlarm.resetWake(60 - now.getSeconds());
+	} else {
+	    int secondsUntilDate =
+		editingDate.getDangerTimeGMT() - now.getDangerTimeGMT();
+	    if (secondsUntilDate <= 0) {
+		mStatusBox.setText("Specify a future date.");
+		return false;
+	    }
+	    mValidateAlarm.resetWake(secondsUntilDate);
+	}
+	mStatusBox.setText(Alarm.dateTimeString(editingDate, true));
 	return true;
     }
 
+    // XXX make a timer-only version of this which doesn't try to extract
+    // XXX the editing date (it can cause odd behavior by forcing the 
+    // XXX NumberEditor to spit out a value)
     protected void validate() {
-	mSetAlarmButton.setEnabled(isValid());
+	boolean isValid = isValid();
+	mSetAlarmButton.setEnabled(isValid);
+	if (!isValid)
+	    mValidateAlarm.deactivate();
+    }
+
+    protected void stopEditing() {
+	mEditingAlarm = null;
+	mValidateAlarm.deactivate();
     }
 
@@ -200,15 +221,20 @@
 	    mEditingAlarm.schedule();
 	    if (mAlarmIsNew) Alarms.addAlarm(mEditingAlarm);
-	    mEditingAlarm = null;
+	    stopEditing();
 	    return true;
 	case EVENT_CANCEL_ALARM:
 	    if (!mAlarmIsNew) mEditingAlarm.resume();
-	    mEditingAlarm = null;
+	    stopEditing();
 	    return true;
 	case EVENT_DISCARD_ALARM:
-	    // XXX confirmation
-	    Alarms.removeAlarm(mEditingAlarm);
-	    mEditingAlarm = null;
-	    return true;
+	    // XXX prompt for confirmation
+	    stopEditing();
+	    return true;
+	case Event.EVENT_TIME_CHANGED:
+	    // XXX this isn't being delivered on the simulator, why?
+	    DEBUG.p("time changed");
+	    validate();
+	    return true;
+	case Event.EVENT_ALARM:
  	case EVENT_VALIDATE: // something changed
 	    validate();
Index: trunk/hiptop/pester/net/sabi/pester/EventfulNumberEditor.java
===================================================================
--- trunk/hiptop/pester/net/sabi/pester/EventfulNumberEditor.java	(revision 253)
+++ trunk/hiptop/pester/net/sabi/pester/EventfulNumberEditor.java	(revision 253)
@@ -0,0 +1,21 @@
+package net.sabi.pester;
+
+import danger.app.Event;
+import danger.ui.NumberEditor;
+import danger.util.DEBUG;
+
+public class EventfulNumberEditor extends NumberEditor {
+
+    // Note: because NumberEditor temporarily holds invalid values to
+    // ease editing, it doesn't send its event immediately, but after
+    // a delay or exit.  The one exception is when you exit the
+    // picker; it should send an event but doesn't.
+
+    public boolean receiveEvent(Event e) {
+	boolean retval = super.receiveEvent(e);
+	if (e.type == -33268) { // value returned by picker
+	    getEvent().sendToTarget();
+	}
+	return retval;
+    }
+}
