Index: trunk/hiptop/pester/locale/en_US/Pester.rsrc
===================================================================
--- trunk/hiptop/pester/locale/en_US/Pester.rsrc	(revision 250)
+++ trunk/hiptop/pester/locale/en_US/Pester.rsrc	(revision 251)
@@ -20,5 +20,5 @@
 
 int EVENT_CANCEL_ALARM 1
-int EVENT_TIME_EDITOR_LOST_FOCUS 2
+int EVENT_DATE_EDITOR_TOOK_FOCUS 2
 
 splashScreen ID_SPLASH_SCREEN_RESOURCE
@@ -72,13 +72,13 @@
 	menuItem
 		title = "second(s)"
-		event = EVENT_PERIOD_UNIT
+		event = EVENT_VALIDATE
 		eventData = 1
 	menuItem
 		title = "minute(s)"
-		event = EVENT_PERIOD_UNIT
+		event = EVENT_VALIDATE
 		eventData = 60
 	menuItem
 		title = "hour(s)"
-		event = EVENT_PERIOD_UNIT
+		event = EVENT_VALIDATE
 		eventData = 3600
 endMenu
@@ -101,4 +101,5 @@
 	typeAheadTextField
 		id = ID_MESSAGE_FIELD
+		event = EVENT_VALIDATE
 		autoCap
 		initiallySelectAll
@@ -123,4 +124,5 @@
 		numberEditor
 			id = ID_PERIOD_FIELD
+			event = EVENT_VALIDATE
 			dontWrap
 			minValue = 1
@@ -161,5 +163,4 @@
 			alignBaseline = ID_AT_BUTTON
 			positionToRight = ID_AT_BUTTON : 1
-			className = "net.sabi.pester.AlarmTimeEditor"
 	
 		dateEditor
@@ -168,4 +169,5 @@
 			positionToRight = ID_TIME_EDITOR : 1
 			alignBaseline = ID_TIME_EDITOR
+			className = "net.sabi.pester.AlarmDateEditor"
 		
 		datePicker
Index: trunk/hiptop/pester/net/sabi/pester/AlarmDateEditor.java
===================================================================
--- trunk/hiptop/pester/net/sabi/pester/AlarmDateEditor.java	(revision 251)
+++ trunk/hiptop/pester/net/sabi/pester/AlarmDateEditor.java	(revision 251)
@@ -0,0 +1,16 @@
+package net.sabi.pester;
+
+import danger.app.Event;
+import danger.ui.DateTimeEditor;
+import danger.util.DEBUG;
+
+public class AlarmDateEditor extends DateTimeEditor
+    implements Resources, Commands {
+
+    public void takeFocus(boolean inFromAbove) {
+	Event event = getEvent();
+	event.type = EVENT_DATE_EDITOR_TOOK_FOCUS;
+	event.sendToTarget();
+	super.takeFocus(inFromAbove);
+    }
+}
Index: trunk/hiptop/pester/net/sabi/pester/AlarmSetDialog.java
===================================================================
--- trunk/hiptop/pester/net/sabi/pester/AlarmSetDialog.java	(revision 251)
+++ trunk/hiptop/pester/net/sabi/pester/AlarmSetDialog.java	(revision 251)
@@ -0,0 +1,247 @@
+package net.sabi.pester;
+
+import danger.app.Application;
+import danger.app.Event;
+import danger.audio.ToneClass;
+import danger.audio.ToneRights;
+import danger.internal.Date;
+import danger.ui.Button;
+import danger.ui.CheckBox;
+import danger.ui.Color;
+import danger.ui.DateTimeEditor;
+import danger.ui.DatePicker;
+import danger.ui.DialogWindow;
+import danger.ui.Font;
+import danger.ui.Layout;
+import danger.ui.MenuItem;
+import danger.ui.NumberEditor;
+import danger.ui.Pen;
+import danger.ui.PopupMenu;
+import danger.ui.RadioButton;
+import danger.ui.Rect;
+import danger.ui.RingTonePicker;
+import danger.ui.StaticTextBox;
+import danger.ui.TypeAheadTextField;
+import danger.util.DEBUG;
+
+public class AlarmSetDialog extends DialogWindow
+    implements Resources, Commands {
+
+    private TypeAheadTextField mMessageField;
+    private RadioButton mInButton, mAtButton;
+    private NumberEditor mPeriodField;
+    private PopupMenu mPeriodUnitsPopup;
+    // XXX don't allow <5 second repeating alarms
+    private CheckBox mRepeatCheckBox;
+    private DateTimeEditor mTimeEditor, mDateEditor;
+    private DatePicker mDatePicker;
+    private RingTonePicker mAlertPicker;
+    private Button mDiscardAlarmButton, mSetAlarmButton;
+    private StaticTextBox mStatusBox;
+
+    private Alarm mEditingAlarm;
+    private boolean mAlarmIsNew;
+    private boolean haveShownWindow = false;
+
+    public static AlarmSetDialog getDialog() {
+	AlarmSetDialog dialog = (AlarmSetDialog)
+	    Application.getCurrentApp().getDialog(ID_ALARM_SET_DIALOG, null);
+	return dialog;
+    }
+
+    public void onDecoded() {
+	setCancelButtonEvent(new Event(this, EVENT_CANCEL_ALARM));
+	mMessageField =
+	    (TypeAheadTextField)getDescendantWithID(ID_MESSAGE_FIELD);
+	disableBottomRightButtonOnEmptyField(mMessageField);
+	mInButton = (RadioButton)getDescendantWithID(ID_IN_BUTTON);
+	mAtButton = (RadioButton)getDescendantWithID(ID_AT_BUTTON);
+	mPeriodField = (NumberEditor)getDescendantWithID(ID_PERIOD_FIELD);
+	mPeriodUnitsPopup =
+	    (PopupMenu)getDescendantWithID(ID_PERIOD_UNITS_POPUP);
+	mRepeatCheckBox =
+	    (CheckBox)getDescendantWithID(ID_PERIOD_REPEAT_CHECKBOX);
+	mTimeEditor = (DateTimeEditor)getDescendantWithID(ID_TIME_EDITOR);
+	mDateEditor = (DateTimeEditor)getDescendantWithID(ID_DATE_EDITOR);
+	mDatePicker = (DatePicker)getDescendantWithID(ID_DATE_PICKER);
+	mAlertPicker = (RingTonePicker)getDescendantWithID(ID_ALERT_PICKER);
+	mSetAlarmButton = (Button)getDescendantWithID(ID_SET_ALARM_BUTTON);
+	mDiscardAlarmButton =
+	    (Button)getDescendantWithID(ID_DISCARD_ALARM_BUTTON);
+	mInButton.setValue(1);
+	mDateEditor.limitToDangerEpoch();
+	mAlertPicker.setGroupFilter(ToneClass.FOREGROUND | ToneClass.CUSTOM);
+	mAlertPicker.setRights(ToneRights.FULL);
+	// this doesn't work properly yet
+	// mAlertPicker.setCanRecord(true);
+
+	mStatusBox = new StaticTextBox();
+	mStatusBox.setTransparent(true);
+	mStatusBox.setHasBorder(false);
+	mStatusBox.setFont(Font.findSystemFont());
+	mStatusBox.setAutoResize(false);
+
+	super.onDecoded();
+    }
+
+    protected void editDateOrPeriod(Alarm alarm) {
+        boolean usesPeriod = alarm.getUsesPeriod();
+	if (usesPeriod) {
+	    int period = alarm.getPeriod();
+	    for (int i = mPeriodUnitsPopup.itemCount() - 1 ; i >= 0 ; --i) {
+		int unitsPerSec = mPeriodUnitsPopup.getItem(i).getEvent().data;
+		if (period % unitsPerSec == 0) {
+		    mPeriodUnitsPopup.setValue(i);
+		    mPeriodField.setValue(period / unitsPerSec);
+		    break;
+		}
+	    }
+	    mRepeatCheckBox.setValue(alarm.getRepeating() ? 1 : 0);
+	}
+
+	Date date = alarm.getDate();
+	mTimeEditor.setDate(date);
+	mDateEditor.setDate(date);
+	mDatePicker.setDate(date);
+
+	mInButton.setValue(usesPeriod ? 1 : 0);
+	mAtButton.setValue(usesPeriod ? 0 : 1);
+    }
+
+    public void editAlarm(Alarm alarm, boolean asNew) {
+	mEditingAlarm = alarm;
+	mAlarmIsNew = asNew;
+	alarm.beginEditing();
+	if (asNew) {
+	    mDiscardAlarmButton.hide();
+	    // XXX default to now?
+	} else {
+	    mMessageField.setText(alarm.getMessage());
+	    mDiscardAlarmButton.show();
+	    editDateOrPeriod(alarm);
+	    mAlertPicker.setRingTone(mEditingAlarm.getAlert());
+	}
+	validate();
+	show();
+	if (!haveShownWindow) {
+	    Rect alarmRect = mSetAlarmButton.getFrame();
+	    Rect contentRect = getContentRect();
+	    mStatusBox.setPosition(contentRect.left + 8, alarmRect.top + 2);
+	    mStatusBox.setSize(alarmRect.left - 8, 11);
+	    mStatusBox.show();
+	    addChild(mStatusBox);
+	    haveShownWindow = true;
+	    setAutoSize(false); // don't resize to contain it next time
+	}
+    }
+
+    protected boolean isIn() {
+	return (mInButton.getValue() == 1);
+    }
+
+    protected int editingInterval() {
+	int itemIndex = mPeriodUnitsPopup.getValue();
+	MenuItem itemAtIndex = mPeriodUnitsPopup.getItem(itemIndex);
+	Event event = itemAtIndex.getEvent();
+	return mPeriodField.getValue() * event.data;
+    }
+
+    protected Date editingDate() {
+	Date date;
+	if (isIn()) {
+	    date = new Date();
+	    date.addSeconds(editingInterval());
+	} else {
+	    date = mDateEditor.getDate();
+	    date.setTime(mTimeEditor.getDate().getTime());
+	    date.setSeconds(0);
+	}
+	return date;
+    }
+
+    protected boolean isValid() {
+	// doesn't redraw properly otherwise
+	invalidate(mStatusBox.getBounds());
+	if (mMessageField.length() == 0) {
+	    mStatusBox.setText("Specify a message.");
+	    return false;
+	}
+	Date now = new Date();
+	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()
+	return true;
+    }
+
+    protected void validate() {
+	mSetAlarmButton.setEnabled(isValid());
+    }
+
+    public boolean receiveEvent(Event e) {
+	switch (e.type) {
+	case EVENT_SET_ALARM:
+	    if (!isValid())
+		return true;
+	    mEditingAlarm.setMessage(mMessageField.toString());
+	    if (isIn()) {
+		mEditingAlarm.setPeriod(editingInterval(),
+					mRepeatCheckBox.getValue() == 1);
+	    } else {
+		mEditingAlarm.setDate(editingDate());
+	    }
+	    mEditingAlarm.setAlert(mAlertPicker.getRingTone());
+	    // schedule before adding so the tooltip is correct
+	    mEditingAlarm.schedule();
+	    if (mAlarmIsNew) Alarms.addAlarm(mEditingAlarm);
+	    mEditingAlarm = null;
+	    return true;
+	case EVENT_CANCEL_ALARM:
+	    if (!mAlarmIsNew) mEditingAlarm.resume();
+	    mEditingAlarm = null;
+	    return true;
+	case EVENT_DISCARD_ALARM:
+	    // XXX confirmation
+	    Alarms.removeAlarm(mEditingAlarm);
+	    mEditingAlarm = null;
+	    return true;
+ 	case EVENT_VALIDATE: // something changed
+	    validate();
+	    return true;
+	case EVENT_IN:
+	    mAtButton.setValue(0);
+	    validate();
+	    return true;
+	case EVENT_AT:
+	    mInButton.setValue(0);
+	    validate();
+	    return true;
+	case EVENT_TIME_EDITOR:
+	    validate();
+	    return true;
+	case EVENT_DATE_EDITOR_TOOK_FOCUS:
+	    Date date = editingDate();
+	    if (date.compareTo(new Date()) <= 0) {
+		date.addDays(1);
+		mDatePicker.setDate(date);
+		mDateEditor.setDate(date);
+	    }
+	    validate();
+	    return true;
+        case EVENT_DATE_EDITOR:
+	    mDatePicker.setDate(mDateEditor.getDate());
+	    validate();
+	    return true;
+        case EVENT_DATE_PICKER:
+	    mDateEditor.setDate((Date)e.argument);
+	    validate();
+	    return true;
+	}
+	return super.receiveEvent(e);
+    }
+}
Index: trunk/hiptop/pester/net/sabi/pester/AlarmTimeEditor.java
===================================================================
--- trunk/hiptop/pester/net/sabi/pester/AlarmTimeEditor.java	(revision 250)
+++ 	(revision )
@@ -1,30 +1,0 @@
-package net.sabi.pester;
-
-import danger.app.Event;
-import danger.ui.DateTimeEditor;
-import danger.util.DEBUG;
-
-public class AlarmTimeEditor extends DateTimeEditor
-    implements Resources, Commands {
-
-    public void takeFocus(boolean inFromAbove) {
-	DEBUG.p("taking focus" + (inFromAbove ? " in from above" : ""));
-	super.takeFocus(inFromAbove);
-    }
-
-    // XXX this is not working because we gain/lose focus moving through fields
-    public void loseFocus() {
-	DEBUG.p("losing focus");
-	if (isFocused()) DEBUG.p("had focus");
-	DEBUG.p("parent leaf was " + getParentContainer().getFocusedLeaf());
-	DEBUG.p("my leaf was " + getFocusedLeaf());
-	super.loseFocus();
-	if (isFocused()) DEBUG.p("still have focus");
-	DEBUG.p("parent leaf is " + getParentContainer().getFocusedLeaf());
-	DEBUG.p("my leaf is " + getFocusedLeaf());
-	DEBUG.p("sibling is " + getSibling());
-	Event event = getEvent();
-	event.type = EVENT_TIME_EDITOR_LOST_FOCUS;
-	event.sendToTarget();
-    }
-}
