Fix (part of) bug 353492 - support multiple alarms per events/task, support absolute alarms with fixed date/time - Frontend patch, r=berend
authorPhilipp Kewisch <mozilla@kewis.ch>
Fri, 30 Jan 2009 17:01:19 +0100
changeset 1802 125c3a73ab463b369716dcdec58df67ef420fe0d
parent 1801 9d574eae525edfac3a5068d5453ac5b4c65aa31b
child 1803 f651a179b304c1d5a82ed657b174be90fcc1036f
push idunknown
push userunknown
push dateunknown
reviewersberend
bugs353492
Fix (part of) bug 353492 - support multiple alarms per events/task, support absolute alarms with fixed date/time - Frontend patch, r=berend
calendar/base/content/calendar-base-view.xml
calendar/base/content/calendar-dialog-utils.js
calendar/base/content/calendar-month-view.xml
calendar/base/content/calendar-multiday-view.xml
calendar/base/content/calendar-summary-dialog.js
calendar/base/content/calendar-view-core.xml
calendar/base/content/dialogs/calendar-event-dialog-reminder.js
calendar/base/content/dialogs/calendar-event-dialog-reminder.xul
calendar/base/content/dialogs/calendar-event-dialog.js
calendar/base/content/dialogs/calendar-event-dialog.xul
calendar/base/jar.mn
calendar/base/modules/calAlarmUtils.jsm
calendar/base/modules/calIteratorUtils.jsm
calendar/base/src/calAlarmService.js
calendar/base/themes/common/alarm-icons.png
calendar/base/themes/common/alarm-suppressed.png
calendar/base/themes/common/alarm.png
calendar/base/themes/pinstripe/calendar-alarms.css
calendar/base/themes/pinstripe/calendar-views.css
calendar/base/themes/winstripe/calendar-alarms.css
calendar/base/themes/winstripe/calendar-views.css
calendar/import-export/calOutlookCSVImportExport.js
calendar/locales/en-US/chrome/calendar/calendar-alarms.properties
calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
calendar/locales/en-US/chrome/calendar/dialogs/calendar-event-dialog-reminder.dtd
calendar/locales/jar.mn
calendar/providers/gdata/components/calGoogleCalendar.js
calendar/providers/gdata/content/gdata-calendar-event-dialog.xul
calendar/providers/gdata/content/gdata-event-dialog-reminder.css
calendar/providers/gdata/content/gdata-event-dialog-reminder.xul
calendar/providers/gdata/content/reminder-action-sms.png
calendar/providers/gdata/install.rdf
calendar/providers/gdata/jar.mn
--- a/calendar/base/content/calendar-base-view.xml
+++ b/calendar/base/content/calendar-base-view.xml
@@ -39,16 +39,19 @@
 
 <bindings id="calendar-multiday-view-bindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="calendar-base-view">
+    <resources>
+      <stylesheet src="chrome://calendar/skin/calendar-alarms.css"/>
+    </resources>
     <implementation>
       <field name="mWeekStartOffset">0</field>
       <field name="mRangeStartDate">null</field>;
       <field name="mRangeEndDate">null</field>;
       <field name="mWorkdaysOnly">false</field>
       <field name="mRefreshQueue">[]</field>
       <field name="mRefreshPending">null</field>
       <field name="mCalendar">null</field>
--- a/calendar/base/content/calendar-dialog-utils.js
+++ b/calendar/base/content/calendar-dialog-utils.js
@@ -35,16 +35,19 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/PluralForm.jsm");
 
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
+Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
+
 /**
  * This function takes the recurrence info passed as argument and creates a
  * literal string representing the repeat pattern in natural language.
  *
  * @param recurrenceInfo    An item's recurrence info to parse.
  * @param startDate         The start date to base rules on.
  * @param endDate           The end date to base rules on.
  * @param allDay            If true, the pattern should assume an allday item.
@@ -338,27 +341,41 @@ function checkRecurrenceRule(aRule, aArr
 function dispose() {
     var args = window.arguments[0];
     if (args.job && args.job.dispose) {
         args.job.dispose();
     }
 }
 
 /**
- * Opens the edit reminder dialog modally, setting the custom menuitem's
- * 'reminder' property when done.
+ * This function opens the needed dialogs to edit the reminder. Note however
+ * that calling this function from an extension is not recommended. To allow an
+ * extension to open the reminder dialog, set the menulist "item-alarm" to the
+ * custom menuitem and call updateReminder().
  */
 function editReminder() {
-    var customReminder =
-        document.getElementById("reminder-custom-menuitem");
-    var args = new Object();
-    args.reminder = customReminder.reminder;
-    var savedWindow = window;
-    args.onOk = function(reminder) {
-        customReminder.reminder = reminder;
+    let customItem =  document.getElementById("reminder-custom-menuitem");
+    let args = {};
+    args.reminders = customItem.reminders;
+    args.item = window.calendarItem;
+    args.timezone = gStartTimezone || gEndTimezone;
+    
+    let calendarNode = document.getElementById("item-calendar");
+    args.calendar = (calendarNode && calendarNode.selectedItem ?
+                        calendarNode.selectedItem.calendar :
+                        window.calendarItem.calendar);
+    let savedWindow = window;
+
+    // While these are "just" callbacks, the dialog is opened modally, so aside
+    // from whats needed to set up the reminders, nothing else needs to be done.
+    args.onOk = function(reminders) {
+        customItem.reminders = reminders;
+    };
+    args.onCancel = function() {
+        document.getElementById("item-alarm").selectedIndex = gLastAlarmSelection;
     };
 
     window.setCursor("wait");
 
     // open the dialog modally
     openDialog(
         "chrome://calendar/content/calendar-event-dialog-reminder.xul",
         "_blank",
@@ -368,309 +385,222 @@ function editReminder() {
 
 /**
  * Update the reminder details from the selected alarm. This shows a string
  * describing the reminder set, or nothing in case a preselected reminder was
  * chosen.
  */
 function updateReminderDetails() {
     // find relevant elements in the document
-    var reminderPopup = document.getElementById("item-alarm");
-    var reminderDetails = document.getElementById("reminder-details");
-    var reminder = document.getElementById("reminder-custom-menuitem").reminder;
-
-    // first of all collapse the details text. if we fail to
-    // create a details string, we simply don't show anything.
-    reminderDetails.setAttribute("collapsed", "true");
+    let reminderList = document.getElementById("item-alarm");
+    let reminderDetails = document.getElementById("reminder-details");
+    let reminderMultipleLabel = document.getElementById("reminder-multiple-alarms-label");
 
-    // don't try to show the details text
-    // for anything but a custom recurrence rule.
-    if (reminderPopup.value == "custom" && reminder) {
-        var unitString;
-        switch (reminder.unit) {
-            case 'minutes':
-                unitString = Number(reminder.length) <= 1 ?
-                    calGetString(
-                        "calendar-event-dialog",
-                        "reminderCustomUnitMinute") :
-                    calGetString(
-                        "calendar-event-dialog",
-                        "reminderCustomUnitMinutes");
-                break;
-            case 'hours':
-                unitString = Number(reminder.length) <= 1 ?
-                    calGetString(
-                        "calendar-event-dialog",
-                        "reminderCustomUnitHour") :
-                    calGetString(
-                        "calendar-event-dialog",
-                        "reminderCustomUnitHours");
-                break;
-            case 'days':
-                unitString = Number(reminder.length) <= 1 ?
-                    calGetString(
-                        "calendar-event-dialog",
-                        "reminderCustomUnitDay") :
-                    calGetString(
-                        "calendar-event-dialog",
-                        "reminderCustomUnitDays");
-                break;
-        }
+    let reminderSingleLabel = document.getElementById("reminder-single-alarms-label");
+    let reminders = document.getElementById("reminder-custom-menuitem").reminders || [];
+    let calendar = document.getElementById("item-calendar")
+                           .selectedItem.calendar;
+    let actionValues = calendar.getProperty("capabilities.alarms.actionValues") || ["DISPLAY"];
+    let actionMap = {};
+    for each (var action in actionValues) {
+        actionMap[action] = true;
+    }
+
+    // Filter out any unsupported action types.
+    reminders = reminders.filter(function(x) x.action in actionMap);
 
-        var relationString;
-        switch (reminder.relation) {
-            case 'START':
-                relationString = calGetString(
-                    "calendar-event-dialog",
-                    "reminderCustomRelationBefore");
-                break;
-            case 'END':
-                relationString = calGetString(
-                    "calendar-event-dialog",
-                    "reminderCustomRelationAfter");
-                break;
-        }
+    if (reminderList.value != "custom" || !reminders.length) {
+        // Don't try to show the details text for anything but a custom
+        // recurrence rule.
+        hideElement(reminderDetails);
+        return;
+    } else {
+        showElement(reminderDetails);
+    }
 
-        var originString;
-        if (reminder.origin && reminder.origin < 0) {
-            originString = calGetString(
-                "calendar-event-dialog",
-                "reminderCustomOriginEndEvent");
-        } else {
-            originString = calGetString(
-                "calendar-event-dialog",
-                "reminderCustomOriginBeginEvent");
-        }
+    // Depending on how many alarms we have, show either the "Multiple Alarms"
+    // label or the single reminder label.
+    setElementValue(reminderMultipleLabel,
+                    reminders.length < 2 && "true",
+                    "hidden");
+    setElementValue(reminderSingleLabel,
+                    reminders.length > 1 && "true",
+                    "hidden");
 
-        var detailsString = calGetString(
-          "calendar-event-dialog",
-          "reminderCustomTitle",
-          [ reminder.length,
-            unitString,
-            relationString,
-            originString]);
+    cal.alarms.addReminderImages(document.getElementById("reminder-icon-box"),
+                                 reminders);
 
-        var lines = detailsString.split("\n");
-        reminderDetails.removeAttribute("collapsed");
-        while (reminderDetails.childNodes.length > lines.length) {
-            reminderDetails.removeChild(reminderDetails.lastChild);
-        }
-        var numChilds = reminderDetails.childNodes.length;
-        for (var i = 0; i < lines.length; i++) {
-            if (i >= numChilds) {
-                var newNode = reminderDetails.childNodes[0].cloneNode(true);
-                reminderDetails.appendChild(newNode);
-            }
-            var node = reminderDetails.childNodes[i];
-            node.setAttribute('value', lines[i]);
-        }
+    // If there is only one reminder, display the reminder string
+    if (reminders.length == 1) {
+        setElementValue(reminderSingleLabel,
+                        reminders[0].toString(window.calendarItem));
     }
 }
 
 var gLastAlarmSelection = 0;
 
 /**
  * Load an item's reminders into the dialog
  *
- * @param item      The item to load.
+ * @param reminders     An array of calIAlarms to load. 
  */
-function loadReminder(item) {
+function loadReminders(reminders) {
     // select 'no reminder' by default
-    let reminderPopup = document.getElementById("item-alarm");
-    reminderPopup.selectedIndex = 0;
+    let reminderList = document.getElementById("item-alarm");
+    let reminderPopup = reminderList.firstChild;
+    let customItem = document.getElementById("reminder-custom-menuitem");
+
+    reminderList.selectedIndex = 0;
     gLastAlarmSelection = 0;
 
-    // TODO ALARMSUPPORT right now, just consider the first relative alarm. This
-    // should change as soon as the UI supports multiple alarms.
-    let alarms = item.getAlarms({})
-                     .filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
-    let alarm = alarms[0];
-    if (!alarm) {
+    if (!reminders || !reminders.length) {
+        // No reminders selected, we are done
         return;
     }
-    // END TODO ALARMSUPPORT
-        
+
+    if (reminders.length == 1 &&
+        reminders[0].related != Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE &&
+        reminders[0].offset &&
+        reminders[0].action == "DISPLAY") {
+        // Exactly one reminder thats not absolute, we may be able to match up
+        // popup items. The reminder should also be a DISPLAY alarm
+        let reminder = reminders[0];
+
+        let relation = (reminder.related == reminder.ALARM_RELATED_START ? "START" : "END");
+        let origin = (reminder.offset.isNegative ? "before" : "after");
 
-    // try to match the reminder setting with the available popup items
-    let origin = "1";
-    if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_END) {
-        origin = "-1";
-    }
-    let duration = alarm.offset.clone();
-    let relation = "END";
-    if (duration.isNegative) {
-        duration.isNegative = false;
-        duration.normalize();
-        relation = "START";
-    }
-    let matchingItem = null;
-    let menuItems = reminderPopup.getElementsByTagName("menuitem");
-    let numItems = menuItems.length;
-    for (let i = 0; i < numItems; i++) {
-        let menuitem = menuItems[i];
-        if (menuitem.hasAttribute("length")) {
-            if (menuitem.getAttribute("origin") == origin &&
+        let unitMap = {
+          days: 86400,
+          hours: 3600,
+          minutes: 60
+        };
+
+        for each (let menuitem in Array.slice(reminderPopup.childNodes)) {
+            if (menuitem.localName == "menuitem" &&
+                menuitem.hasAttribute("length") &&
+                menuitem.getAttribute("origin") == origin &&
                 menuitem.getAttribute("relation") == relation) {
-                let unit = menuitem.getAttribute("unit");
-                let length = menuitem.getAttribute("length");
-                if (unit == "days") {
-                    length = length * 60 * 60 * 24;
-                } else if (unit == "hours") {
-                    length = length * 60 * 60;
-                } else if (unit == "minutes") {
-                    length = length * 60;
-                } else {
-                    continue;
-                }
-                if (duration.inSeconds == length) {
-                    matchingItem = menuitem;
-                    break;
+                let unitMult = unitMap[menuitem.getAttribute("unit")] || 1;
+                let length = menuitem.getAttribute("length") * unitMult;
+
+                if (Math.abs(reminder.offset.inSeconds) == length) {
+                    reminderList.selectedItem = menuitem;
+                    // We've selected an item, so we are done here.
+                    return;
                 }
             }
         }
     }
 
-    if (matchingItem) {
-        var numChilds = reminderPopup.childNodes[0].childNodes.length;
-        for (var i = 0; i < numChilds; i++) {
-            var node = reminderPopup.childNodes[0].childNodes[i];
-            if (node == matchingItem) {
-                reminderPopup.selectedIndex = i;
-                break;
-            }
-        }
-    } else {
-        reminderPopup.value = 'custom';
-        var customReminder =
-            document.getElementById("reminder-custom-menuitem");
-        var reminder = {};
-        if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
-            reminder.origin = "1";
-        } else {
-            reminder.origin = "-1";
-        }
-        let offset = alarm.offset.clone();
-        relation = "END";
-        if (offset.isNegative) {
-            offset.isNegative = false;
-            offset.normalize();
-            relation = "START";
-        }
-        reminder.relation = relation;
-        if (offset.minutes) {
-            let minutes = offset.minutes +
-                          offset.hours * 60 +
-                          offset.days * 24 * 60 +
-                          offset.weeks * 60 * 24 * 7;
-            reminder.unit = 'minutes';
-            reminder.length = minutes;
-        } else if (offset.hours) {
-            var hours = offset.hours + offset.days * 24 + offset.weeks * 24 * 7;
-            reminder.unit = 'hours';
-            reminder.length = hours;
-        } else {
-            var days = offset.days + offset.weeks * 7;
-            reminder.unit = 'days';
-            reminder.length = days;
-        }
-        customReminder.reminder = reminder;
+    // If more than one alarm is selected, or we didn't find a matching item
+    // above, then select the "custom" item and attach the item's reminders to
+    // it.
+    if (reminderList.selectedIndex < 1) {
+        reminderList.value = 'custom';
+        customItem.reminders = reminders;
     }
 
     // remember the selected index
-    gLastAlarmSelection = reminderPopup.selectedIndex;
+    gLastAlarmSelection = reminderList.selectedIndex;
 }
 
 /**
  * Save the selected reminder into the passed item.
  *
  * @param item      The item save the reminder into.
  */
 function saveReminder(item) {
+    // Clear alarms, we'll need to remove alarms later  anyway.
     item.clearAlarms();
-    var reminderPopup = document.getElementById("item-alarm");
-    if (reminderPopup.value == 'none') {
-        item.alarmLastAck = null;
-    } else {
-        var menuitem = reminderPopup.selectedItem;
+
+    let reminderList = document.getElementById("item-alarm");
+    if (reminderList.value != 'none') {
+        let menuitem = reminderList.selectedItem;
+        let reminders;
+
+        if (menuitem.reminders) {
+            // Custom reminder entries carry their own reminder object with
+            // them. Make sure to clone in case these are the original item's
+            // reminders.
 
-        // custom reminder entries carry their own reminder object
-        // with them, pre-defined entries specify the necessary information
-        // as attributes attached to the menuitem elements.
-        var reminder = menuitem.reminder;
-        if (!reminder) {
-            reminder = {};
-            reminder.length = menuitem.getAttribute('length');
-            reminder.unit = menuitem.getAttribute('unit');
-            reminder.relation = menuitem.getAttribute('relation');
-            reminder.origin = menuitem.getAttribute('origin');
+            // XXX do we need to clone here?
+            reminders = menuitem.reminders.map(function(x) x.clone());
+        } else {
+            // Pre-defined entries specify the necessary information
+            // as attributes attached to the menuitem elements.
+            let reminder = cal.createAlarm();
+            let offset = cal.createDuration();
+            offset[menuitem.getAttribute("unit")] = menuitem.getAttribute("length");
+            offset.normalize();
+            offset.isNegative = (menuitem.getAttribute("origin") == "before");
+            reminder.related = (menuitem.getAttribute("relation") == "START" ?
+                                reminder.ALARM_RELATED_START : reminder.ALARM_RELATED_END);
+            reminder.offset = offset;
+            reminders = [reminder];
         }
 
-        var duration = Components.classes["@mozilla.org/calendar/duration;1"]
-                       .createInstance(Components.interfaces.calIDuration);
-
-        duration[reminder.unit] = Number(reminder.length);
-        if (reminder.relation != "END") {
-            duration.isNegative = true;
-        }
-        duration.normalize();
-        let alarm = cal.createAlarm();
-
-        if (Number(reminder.origin) >= 0) {
-            alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
-        } else {
-            alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
+        let alarmCaps = item.calendar.getProperty("capabilities.alarms.actionValues") ||
+                        ["DISPLAY"];
+        let alarmActions = {};
+        for each (let action in alarmCaps) {
+            alarmActions[action] = true;
         }
 
-        alarm.offset = duration;
-        item.addAlarm(alarm);
+        // Make sure only alarms are saved that work in the given calendar.
+        reminders.filter(function(x) x.action in alarmActions)
+                 .forEach(item.addAlarm, item);
     }
 }
 
 /**
  * Common update functions for both event dialogs. Called when a reminder has
  * been selected from the menulist.
  */
 function commonUpdateReminder() {
     // if a custom reminder has been selected, we show the appropriate
     // dialog in order to allow the user to specify the details.
     // the result will be placed in the 'reminder-custom-menuitem' tag.
-    var reminderPopup = document.getElementById("item-alarm");
-    if (reminderPopup.value == 'custom') {
-        // show the dialog.
-        // don't pop up the dialog if this happens during
-        // initialization of the dialog.
-        if (reminderPopup.hasAttribute("last-value")) {
+    let reminderList = document.getElementById("item-alarm");
+    if (reminderList.value == 'custom') {
+        // show the dialog. This call blocks until the dialog is closed. Don't
+        // pop up the dialog if this happens during initialization of the dialog
+        if (reminderList.hasAttribute("last-value")) {
             editReminder();
         }
 
-        // Now check if the resulting custom reminder is valid.
-        // possibly we receive an invalid reminder if the user cancels the
-        // dialog. In that case we revert to the previous selection of the
-        // reminder drop down.
-        if (!document.getElementById("reminder-custom-menuitem").reminder) {
-            reminderPopup.selectedIndex = gLastAlarmSelection;
+        // If one or no reminders were selected, we have a chance of mapping
+        // them to the existing elements in the dropdown.
+        let customItem = reminderList.selectedItem;
+        if (customItem.reminders.length == 0) {
+            // No reminder was selected
+            reminderList.value = "none";
+        } else if (customItem.reminders.length == 1 &&
+                   customItem.reminders[0].action == "DISPLAY") {
+            // TODO This can be taken care of in a different bug. What needs to
+            // be done is to go through the menuitems in item-alarm and check if
+            // customItem.reminders[0] matches with that.
         }
     }
 
     // remember the current reminder drop down selection index.
-    gLastAlarmSelection = reminderPopup.selectedIndex;
-    reminderPopup.setAttribute("last-value", reminderPopup.value);
+    gLastAlarmSelection = reminderList.selectedIndex;
+    reminderList.setAttribute("last-value", reminderList.value);
 
     // possibly the selected reminder conflicts with the item.
     // for example an end-relation combined with a task without duedate
     // is an invalid state we need to take care of. we take the same
     // approach as with recurring tasks. in case the reminder is related
     // to the entry date we check the entry date automatically and disable
     // the checkbox. the same goes for end related reminder and the due date.
     if (isToDo(window.calendarItem)) {
 
         // custom reminder entries carry their own reminder object
         // with them, pre-defined entries specify the necessary information
         // as attributes attached to the menuitem elements.
-        var menuitem = reminderPopup.selectedItem;
+        let menuitem = reminderList.selectedItem;
         if (menuitem.value == 'none') {
             enableElementWithLock("todo-has-entrydate", "reminder-lock");
             enableElementWithLock("todo-has-duedate", "reminder-lock");
         } else {
             var reminder = menuitem.reminder;
             if (!reminder) {
                 reminder = {};
                 reminder.length = menuitem.getAttribute('length');
--- a/calendar/base/content/calendar-month-view.xml
+++ b/calendar/base/content/calendar-month-view.xml
@@ -81,22 +81,20 @@
                                style="margin: 0;"/>
                     <xul:textbox anonid="event-name-textbox"
                                  class="plain calendar-event-name-textbox"
                                  crop="end"
                                  hidden="true"
                                  wrap="true"/>
                     <xul:spacer flex="1"/>
                   </xul:vbox>
-                  <xul:vbox pack="center">
-                    <xul:image anonid="alarm-image"
-                               class="alarm-image"
-                               xbl:inherits="flashing"
-                               hidden="true"/>
-                  </xul:vbox>
+                  <xul:hbox anonid="alarm-icons-box"
+                            class="alarm-icons-box"
+                            align="center"
+                            xbl:inherits="flashing"/>
                 </xul:hbox>
                 <xul:image anonid="gradient"
                            class="calendar-event-box-gradient"
                            height="1px"/>
               </xul:stack>
             </xul:box>
             <xul:calendar-category-box anonid="category-box" xbl:inherits="categories"/>
           </xul:box>
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -1906,24 +1906,23 @@
                   <xul:description anonid="event-name" class="calendar-event-details-core" flex="1"/>
                   <xul:textbox anonid="event-name-textbox"
                                class="plain calendar-event-details-core calendar-event-name-textbox"
                                flex="1"
                                hidden="true"
                                wrap="true"/>
                 </xul:vbox>
                 <xul:hbox pack="end">
-                  <xul:hbox align="start">
-                    <xul:image anonid="alarm-image"
-                               class="alarm-image"
-                               xbl:inherits="flashing"
-                               hidden="true"/>
-                   </xul:hbox>
+                   <xul:hbox anonid="alarm-icons-box"
+                             class="alarm-icons-box"
+                             pack="end"
+                             align="top"
+                             xbl:inherits="flashing"/>
                    <xul:calendar-category-box anonid="category-box" xbl:inherits="categories" pack="end"/>
-                 </xul:hbox>
+                </xul:hbox>
                 <xul:box xbl:inherits="orient">
                   <xul:calendar-event-gripbar anonid="gripbar1"
                                               class="calendar-event-box-grippy-top"
                                               whichside="start"
                                               xbl:inherits="parentorient=orient"/>
                   <xul:spacer flex="1"/>
                   <xul:calendar-event-gripbar anonid="gripbar2"
                                               class="calendar-event-box-grippy-bottom"
--- a/calendar/base/content/calendar-summary-dialog.js
+++ b/calendar/base/content/calendar-summary-dialog.js
@@ -105,17 +105,17 @@ function onLoad() {
 
     // show reminder if this item is *not* readonly.
     // this case happens for example if this is an invitation.
     var calendar = window.arguments[0].calendarEvent.calendar;
     var supportsReminders =
         (calendar.getProperty("capabilities.alarms.oninvitations.supported") !== false);
     if (!window.readOnly && supportsReminders) {
         document.getElementById("reminder-row").removeAttribute("hidden");
-        loadReminder(window.item);
+        loadReminders(window.item.getAlarms({}));
         updateReminderDetails();
     }
 
     updateRepeatDetails();
     updateAttendees();
     updateLink();
 
     var location = item.getProperty("LOCATION");
--- a/calendar/base/content/calendar-view-core.xml
+++ b/calendar/base/content/calendar-view-core.xml
@@ -71,36 +71,36 @@
                     <xul:textbox anonid="event-name-textbox"
                                  class="plain"
                                  crop="end"
                                  hidden="true"
                                  style="background: transparent !important;"
                                  wrap="true"/>
                     <xul:spacer flex="1"/>
                   </xul:vbox>
-                  <xul:vbox pack="center">
-                    <xul:image anonid="alarm-image"
-                               class="alarm-image"
-                               xbl:inherits="flashing"
-                               hidden="true"/>
-                  </xul:vbox>
+                  <xul:hbox anonid="alarm-icons-box"
+                            class="alarm-icons-box"
+                            align="center"
+                            xbl:inherits="flashing"/>
                 </xul:hbox>
                 <xul:image anonid="gradient"
                            class="calendar-event-box-gradient"
                            height="1px"/>
               </xul:stack>
             </xul:box>
             <xul:calendar-category-box anonid="category-box" xbl:inherits="categories"/>
           </xul:box>
         </xul:hbox>
       </xul:vbox>
     </content>
 
     <implementation>
       <constructor><![CDATA[
+         Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
+
          var self = this;
          this.eventNameInput.onblur = function onBlur() { self.stopEditing(true); };
          this.eventNameInput.onkeypress = function onKeyPress(event) {
              // save on enter
              if (event.keyCode == 13)
                  self.stopEditing(true);
              // abort on escape
              else if (event.keyCode == 27)
@@ -186,28 +186,25 @@
           var item = this.mOccurrence;          
           this.setAttribute("item-calendar", item.calendar.uri.spec);
           var categoriesArray = item.getCategories({});
           if (categoriesArray.length > 0) {
             var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
             this.setAttribute("categories", cssClassesArray.join(" "));
           }
 
-          if (item.getAlarms({}).length && getPrefSafe("calendar.alarms.indicator.show", true)) {
-              var alarmImage = document.getAnonymousElementByAttribute(this, "anonid", "alarm-image");
-              if (item.calendar.getProperty("suppressAlarms") &&
-                  item.calendar.getProperty("capabilities.alarms.popup.supported") !== false) {
-                  // Only show the suppressed icon if alarms are suppressed and
-                  // popup alarms are supported. This keeps us from making the
-                  // alarm look disabled when the server supports only email
-                  // alarms (e.g. currently WCAP).
-                  alarmImage.setAttribute("suppressed", "true");
-              }
-              alarmImage.removeAttribute("hidden");
-          }
+          // Add alarm icons as needed. Also add parent item alarms in case this is an occurrence.
+          let alarms = item.getAlarms({}).concat(item.parentItem.getAlarms({}));
+          let iconsBox = document.getAnonymousElementByAttribute(this, "anonid", "alarm-icons-box");
+          cal.alarms.addReminderImages(iconsBox, alarms);
+
+          // Set suppressed status on the icons box
+          setElementValue(iconsBox,
+                          item.calendar.getProperty("suppressAlarms") || false,
+                          "suppressed");
 
           // Set up event box attributes for use in css selectors. Note if
           // something is added here, it should also be xbl:inherited correctly
           // in the <content> section of this binding, and all that inherit it.
 
           // Event type specific properties
           if (isEvent(item)) {
               if (item.startDate.isDate) {
@@ -218,17 +215,17 @@
               this.setAttribute("progress", getProgressAtom(item));
           }
 
           if (this.calendarView &&
               item.hashId in this.calendarView.mFlashingEvents) {
               this.setAttribute("flashing", "true");
           }
 
-          if (item.getAlarms({}).length) {
+          if (alarms.length) {
               this.setAttribute("alarm", "true")
           }
 
           // priority
           if (item.priority > 0 && item.priority < 5) {
               this.setAttribute("priority", "high");
           } else if (item.priority > 5 && item.priority < 10) {
               this.setAttribute("priority", "low");
--- a/calendar/base/content/dialogs/calendar-event-dialog-reminder.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-reminder.js
@@ -29,281 +29,432 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-function onLoad() {
-    var args = window.arguments[0];
-    window.onAcceptCallback = args.onOk;
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+let allowedActionsMap = {}; 
 
-    // set the current date/time for the duedate control.
-    // this is just a placeholder until we support absolute reminders.
-    var duedate = document.getElementById("reminder-duetime");
-    duedate.value = new Date();
-
-    // load the reminders from preferences
-    var reminders = loadReminders();
+/**
+ * Sets up the reminder dialog.
+ */
+function onLoad() {
+    let calendar = window.arguments[0].calendar;
 
-    // check if a possibly incoming reminder object matches
-    // the ones we already know about...
-    var incoming = args.reminder;
-    var selectedIndex = -1;
-    if (incoming) {
-        for (var i = 0; i < reminders.length; i++) {
-            var reminder = reminders[i];
-            if (reminder.relation == incoming.relation &&
-                reminder.length == incoming.length &&
-                reminder.unit == incoming.unit) {
-                selectedIndex = i;
-                break;
-            }
-        }
+    // Make sure the origin menulist uses the right labels, depending on if the
+    // dialog is showing an event or task.
+    document.getElementById("reminder-origin-start-menuitem")
+            .setAttribute("label", calGetString("calendar-alarms",
+                                                getItemBundleStringName("reminderDialogOriginBegin")));
+    document.getElementById("reminder-origin-end-menuitem")
+            .setAttribute("label", calGetString("calendar-alarms",
+                                                getItemBundleStringName("reminderDialogOriginEnd")));
 
-        // if we didn't find a match, we append the unknown
-        // reminder to the array of custom reminders.
-        if (selectedIndex < 0) {
-            reminders.push(incoming);
-            selectedIndex = reminders.length - 1;
+    // Set up the action map
+    let supportedActions = calendar.getProperty("capabilities.alarms.actionValues") ||
+                           ["DISPLAY" /* TODO email support, "EMAIL" */];
+    for each (let action in supportedActions) {
+        allowedActionsMap[action] = true;
+    }
+
+    // Hide all actions that are not supported by this provider
+    let firstAvailableItem;
+    let actionNodes = document.getElementById("reminder-actions-menupopup").childNodes;
+    for each (let actionNode in Array.slice(actionNodes)) {
+        let shouldHide = (!(actionNode.value in allowedActionsMap) ||
+                          (actionNode.hasAttribute("provider") &&
+                           actionNode.getAttribute("provider") != calendar.type));
+        setElementValue(actionNode, shouldHide && "true", "hidden");
+        if (!firstAvailableItem && !shouldHide) {
+            firstAvailableItem = actionNode;
         }
     }
 
-    var listbox = document.getElementById("reminder-listbox");
-    while (listbox.childNodes.length > reminders.length) {
-        listbox.removeChild(listbox.lastChild);
-    }
-    var numChilds = listbox.childNodes.length;
-    for (var i = 0; i < reminders.length; i++) {
-        if (i >= numChilds) {
-            var newNode = listbox.childNodes[0].cloneNode(true);
-            listbox.appendChild(newNode);
-        }
-        var node = listbox.childNodes[i];
-        var reminder = reminders[i];
-        var details = stringFromReminderObject(reminder);
-        node.setAttribute('label', details);
-        node.reminder = reminder;
+    // Correct the selected item on the supported actions list. This will be
+    // changed when reminders are loaded, but in case there are none we need to
+    // provide a sensible default.
+    if (firstAvailableItem) {
+        document.getElementById("reminder-actions-menulist").selectedItem = firstAvailableItem;
     }
 
-    if (selectedIndex < 0) {
-        selectedIndex = 0;
-    }
-    listbox.selectedIndex = selectedIndex;
-
+    loadReminders();
     opener.setCursor("auto");
 }
 
-function stringFromReminderObject(reminder) {
-    var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
-              .getService(Components.interfaces.nsIStringBundleService);
-
-    var props =
-        sbs.createBundle(
-            "chrome://calendar/locale/calendar-event-dialog.properties");
-
-    var unitString;
-    switch (reminder.unit) {
-        case 'minutes':
-            unitString = Number(reminder.length) <= 1 ?
-                props.GetStringFromName('reminderCustomUnitMinute') :
-                props.GetStringFromName('reminderCustomUnitMinutes');
-            break;
-        case 'hours':
-            unitString = Number(reminder.length) <= 1 ?
-                props.GetStringFromName('reminderCustomUnitHour') :
-                props.GetStringFromName('reminderCustomUnitHours');
-            break;
-        case 'days':
-            unitString = Number(reminder.length) <= 1 ?
-                props.GetStringFromName('reminderCustomUnitDay') :
-                props.GetStringFromName('reminderCustomUnitDays');
-            break;
-    }
-
-    var relationString;
-    switch (reminder.relation) {
-        case 'START':
-            relationString = props.GetStringFromName('reminderCustomRelationBefore');
-            break;
-        case 'END':
-            relationString = props.GetStringFromName('reminderCustomRelationAfter');
-            break;
-    }
+/**
+ * Load Reminders from the window's arguments and set up dialog controls to
+ * their initial values.
+ */
+function loadReminders() {
+    let args = window.arguments[0];
+    let listbox = document.getElementById("reminder-listbox");
+    let reminders = args.reminders || args.item.getAlarms({});
 
-    var originString;
-    if (reminder.origin && reminder.origin < 0) {
-        originString = props.GetStringFromName('reminderCustomOriginEndEvent');
-    } else {
-        originString = props.GetStringFromName('reminderCustomOriginBeginEvent');
-    }
-
-    return props.formatStringFromName('reminderCustomTitle',
-                                      [reminder.length,
-                                       unitString,
-                                       relationString,
-                                       originString],
-                                      4);
-}
-
-function loadReminders() {
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                      .getService(Components.interfaces.nsIPrefService);
-    var prefBranch = prefService.getBranch("calendar.reminder.");
-
-    var pref = "length=15;unit=minutes;relation=START;origin=1,length=3;" +
-               "unit=hours;relation=START;origin=1";
-    try {
-        var newPref = prefBranch.getCharPref("custom");
-        if (newPref && newPref != "") {
-            pref = newPref;
-        }
-    } catch (ex) {
-    }
-
-    var reminderArray = [];
-    var reminders = pref.split(',');
-    for each (var reminder in reminders) {
-        if (reminder.indexOf(';') >= 0) {
-            var object = {};
-            var attributes = reminder.split(';');
-            for each (var attribute in attributes) {
-                var index = attribute.indexOf('=');
-                if (index > 0) {
-                    var key = attribute.substring(0, index);
-                    var value = attribute.substring(index + 1,
-                                                    attribute.length);
-                    object[key] = value;
-                }
-            }
-            reminderArray.push(object);
+    // This dialog should not be shown if the calendar doesn't support alarms at
+    // all, so the case of maxCount = 0 breaking this logic doesn't apply.
+    let maxReminders = args.calendar.getProperty("capabilities.alarms.maxCount");
+    let count = Math.min(reminders.length, maxReminders || reminders.length);
+    for (let i = 0; i < count; i++) {
+        if (reminders[i].action in allowedActionsMap) {
+            // Set up the listitem and add it to the listbox, but only if the
+            // action is actually supported by the calendar.
+            listbox.appendChild(setupListItem(null, reminders[i].clone(), args.item));
         }
     }
 
-    return reminderArray;
+    // Set up a default absolute date. This will be overridden if the selected
+    // alarm is absolute.
+    let absDate = document.getElementById("reminder-absolute-date");
+    absDate.value = getDefaultStartDate().jsDate;
+
+    if (listbox.childNodes.length) {
+        // We have reminders, select the first by default. For some reason,
+        // setting the selected index in a load handler makes the selection
+        // break for the set item, therefore we need a setTimeout.
+        setupMaxReminders();
+        setTimeout(function() { listbox.selectedIndex = 0; }, 0);
+    } else {
+        // Make sure the fields are disabled if we have no alarms
+        setupRadioEnabledState(true);
+    }
 }
 
-function saveReminders(reminderArray) {
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                      .getService(Components.interfaces.nsIPrefService);
-    var prefBranch = prefService.getBranch("calendar.reminder.");
+/**
+ * Sets up the enabled state of the reminder details controls. Used when
+ * switching between absolute and relative alarms to disable and enable the
+ * needed controls.
+ *
+ * @param aDisableAll       Disable all relation controls. Used when no alarms
+ *                            are added yet.
+ */
+function setupRadioEnabledState(aDisableAll) {
+    let relationItem = document.getElementById("reminder-relation-radiogroup").selectedItem;
+    let relativeDisabled, absoluteDisabled;
 
-    var result = "";
-    for each (var reminder in reminderArray) {
-        for (var key in reminder) {
-            result += key + '=' + reminder[key] + ';';
-        }
-        if (result.length && result[result.length - 1] == ';') {
-            result = result.substr(0, result.length - 1);
-        }
-        result += ',';
-    }
-    if (result.length && result[result.length - 1] == ',') {
-        result = result.substr(0, result.length - 1);
+    // Note that the mix of string/boolean here is not a mistake.
+    // setElementValue removes the attribute from the node if the second
+    // parameter is === false, otherwise sets the attribute value to the given
+    // string (i.e "true").
+    if (aDisableAll) {
+        relativeDisabled = "true";
+        absoluteDisabled = "true";
+    } else if (relationItem) {
+        // This is not a mistake, when this function is called from onselect,
+        // the value has not been set.
+        relativeDisabled = (relationItem.value == "absolute") && "true";
+        absoluteDisabled = (relationItem.value == "relative") && "true";
+    } else {
+        relativeDisabled = false;
+        absoluteDisabled = false;
     }
 
-    prefBranch.setCharPref("custom", result);
+    setElementValue("reminder-length", relativeDisabled, "disabled");
+    setElementValue("reminder-unit", relativeDisabled, "disabled");
+    setElementValue("reminder-relation", relativeDisabled, "disabled");
+    setElementValue("reminder-origin", relativeDisabled, "disabled");
+
+    setElementValue("reminder-absolute-date", absoluteDisabled, "disabled");
+
+    let disableAll = (aDisableAll ? "true" : false);
+    setElementValue("reminder-relative-radio", disableAll, "disabled");
+    setElementValue("reminder-absolute-radio", disableAll, "disabled");
+    setElementValue("reminder-actions-menulist", disableAll, "disabled");
 }
 
-function onReminderSelected() {
-    var length = document.getElementById("reminder-length");
-    var unit = document.getElementById("reminder-unit");
-    var relation = document.getElementById("reminder-relation");
-    var origin = document.getElementById("reminder-origin");
+/**
+ * Sets up the max reminders notification. Shows or hides the notification
+ * depending on if the max reminders limit has been hit or not.
+ */
+function setupMaxReminders() {
+    let args = window.arguments[0];
+    let listbox = document.getElementById("reminder-listbox");
+    let notificationbox = document.getElementById("reminder-notifications");
+    // TODO ALARMSUPPORT right now some of our backend still only supports one
+    // alarm. Limit this in the UI to not confuse users too much.
+    // let maxReminders = args.calendar.getProperty("capabilities.alarms.maxCount");
+    let maxReminders = 1;
+
+    // != null is needed here to ensure cond to be true/false, instead of
+    // true/null. The former is needed for setElementValue.
+    let cond = (maxReminders != null && listbox.childNodes.length >= maxReminders);
 
-    var listbox = document.getElementById("reminder-listbox");
-    var listitem = listbox.selectedItem;
+    // If we hit the maximum number of reminders, show the error box and
+    // disable the new button.
+    setElementValue("reminder-new-button", cond && "true", "disabled");
+    
+    if (!setupMaxReminders.notification) {
+        let notification = createXULElement("notification");
+        let localeErrorString = 
+            calGetString("calendar-alarms",
+                         getItemBundleStringName("reminderErrorMaxCountReached"),
+                         [maxReminders]);
+        let pluralErrorLabel = PluralForm.get(maxReminders, localeErrorString)
+                                         .replace("#1", maxReminders);
 
-    if (listitem) {
-        var reminder = listitem.reminder;
-        length.value = reminder.length;
-        unit.value = reminder.unit;
-        relation.value = reminder.relation;
-        origin.value = reminder.origin;
+        notification.setAttribute("label", pluralErrorLabel);
+        notification.setAttribute("type", "warning");
+        notification.setAttribute("hideclose", "true");
+        setupMaxReminders.notification = notification;
+    }
+
+    if (cond) {
+        notificationbox.appendChild(setupMaxReminders.notification);
+    } else {
+        notificationbox.removeNotification(setupMaxReminders.notification);
     }
 }
 
-function updateReminderLength(event) {
-    validateIntegers(event);
-    updateReminder();
+/**
+ * Sets up a reminder listitem for the list of reminders applied to this item.
+ *
+ * @param aListItem     (optional) A reference listitem to set up. If not
+ *                                   passed, a new listitem will be created.
+ * @param aReminder     The calIAlarm to display in this listitem
+ * @param aItem         The item the alarm is set up on.
+ * @return              The  XUL listitem node showing the passed reminder.
+ */
+function setupListItem(aListItem, aReminder, aItem) {
+    let listitem = aListItem || createXULElement("listitem");
+
+    // Create a random id to be used for accessibility
+    let reminderId = cal.getUUID();
+    let ariaLabel = "reminder-action-" + aReminder.action + " " + reminderId;
+
+    listitem.reminder = aReminder;
+    listitem.setAttribute("id", reminderId);
+    listitem.setAttribute("label", aReminder.toString(aItem));
+    listitem.setAttribute("aria-labelledby", ariaLabel);
+    listitem.setAttribute("class", "reminder-icon listitem-iconic");
+    listitem.setAttribute("value", aReminder.action);
+    return listitem;
 }
 
-function updateReminder() {
-    var length = document.getElementById("reminder-length");
-    var unit = document.getElementById("reminder-unit");
-    var relation = document.getElementById("reminder-relation");
-    var origin = document.getElementById("reminder-origin");
+/**
+ * Handler function to be called when a reminder is selected in the listbox.
+ * Sets up remaining controls to show the selected alarm.
+ */
+function onReminderSelected() {
+    let length = document.getElementById("reminder-length");
+    let unit = document.getElementById("reminder-unit");
+    let relation = document.getElementById("reminder-relation");
+    let origin = document.getElementById("reminder-origin");
+    let absDate = document.getElementById("reminder-absolute-date");
+    let actionType = document.getElementById("reminder-actions-menulist");
+    let relationType = document.getElementById("reminder-relation-radiogroup");
 
-    var listbox = document.getElementById("reminder-listbox");
-    var listitem = listbox.selectedItem;
-    var reminder = listitem.reminder;
+    let listbox = document.getElementById("reminder-listbox");
+    let listitem = listbox.selectedItem;
+
+    if (listitem) {
+        let reminder = listitem.reminder;
+
+        // Action
+        actionType.value = reminder.action;
 
-    reminder.length = length.value;
-    reminder.unit = unit.value;
-    reminder.relation = relation.value;
-    reminder.origin = origin.value;
+        // Absolute/relative things
+        if (reminder.related == Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE) {
+            relationType.value = "absolute";
 
-    var details = stringFromReminderObject(reminder);
-    listitem.setAttribute('label', details);
-}
+            // Date
+            absDate.value = (reminder.alarmDate || getDefaultStartDate()).jsDate;
+        } else {
+            relationType.value =  "relative";
 
-function onNewReminder() {
-    var listbox = document.getElementById("reminder-listbox");
-    var listitem = listbox.selectedItem;
-    var reminder = listitem.reminder;
-    listbox.clearSelection();
-    var newNode = listitem.cloneNode(true);
-    var newReminder = {};
-    newReminder.length = reminder.length;
-    newReminder.unit = reminder.unit;
-    newReminder.relation = reminder.relation;
-    newReminder.origin = reminder.origin;
-    newNode.reminder = newReminder;
-    listbox.appendChild(newNode);
-    listbox.selectItem(newNode);
+            // Unit and length
+            let alarmlen = Math.abs(reminder.offset.inSeconds / 60);
+            if (alarmlen % 1440 == 0) {
+                unit.value = "days";
+                length.value = alarmlen / 1440;
+            } else if (alarmlen % 60 == 0) {
+                unit.value = "hours";
+                length.value = alarmlen / 60;
+            } else {
+                unit.value = "minutes";
+                length.value = alarmlen;
+            }
 
-    var button = document.getElementById("reminder-delete");
-    if (listbox.childNodes.length > 1) {
-        button.removeAttribute('disabled');
+            // Relation
+            if (reminder.offset.isNegative) {
+                relation.value =  "before";
+            } else {
+                relation.value = "after";
+            }
+
+            // Origin
+            if (reminder.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
+                origin.value = "START";
+            } else if (reminder.related == Components.interfaces.calIAlarm.ALARM_RELATED_END) {
+                origin.value = "END";
+            }
+        }
     } else {
-        button.setAttribute('disabled', 'true');
+        // no list item is selected, disable elements
+        setupRadioEnabledState(true);
     }
 }
 
-function onDeleteReminder() {
-    var listbox = document.getElementById("reminder-listbox");
-    var listitem = listbox.selectedItem;
-    var selectitem = listitem.nextSibling;
-    if (!selectitem) {
-        selectitem = listitem.previousSibling;
+/**
+ * Handler function to be called when an aspect of the alarm has been changed
+ * using the dialog controls.
+ *
+ * @param event         The DOM event caused by the change.
+ */
+function updateReminder(event) {
+    if (event.explicitOriginalTarget.localName == "listitem" ||
+        !document.commandDispatcher.focusedElement) {
+        // Do not set things if the select came from selecting an alarm from the
+        // list, or from setting when the dialog initially loaded.
+        // XXX Quite fragile hack since radio/radiogroup doesn't have the
+        // supressOnSelect stuff.
+        return;
+    }
+    let listbox = document.getElementById("reminder-listbox");
+    let relationItem = document.getElementById("reminder-relation-radiogroup").selectedItem;
+    let listitem = listbox.selectedItem;
+    if (!listitem || !relationItem) {
+        return;
     }
-    listbox.clearSelection();
-    listbox.removeChild(listitem);
-    listbox.selectItem(selectitem);
+    let reminder = listitem.reminder;
+    let length = document.getElementById("reminder-length");
+    let unit = document.getElementById("reminder-unit");
+    let relation = document.getElementById("reminder-relation");
+    let origin = document.getElementById("reminder-origin");
+    let absDate = document.getElementById("reminder-absolute-date");
+    let action = document.getElementById("reminder-actions-menulist").selectedItem.value;
+
+    // Action
+    reminder.action = action;
+
+    let relationType;
+    if (relationItem.value == "relative") {
+        if (origin.value == "START") {
+            reminder.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
+        } else if (origin.value == "END") {
+            reminder.related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
+        }
 
-    var button = document.getElementById("reminder-delete");
-    if (listbox.childNodes.length > 1) {
-        button.removeAttribute('disabled');
+        // Set up offset, taking units and before/after into account
+        let offset = cal.createDuration();
+        offset[unit.value] = length.value;
+        offset.normalize();
+        offset.isNegative = (relation.value == "before");
+        reminder.offset = offset;
+    } else if (relationItem.value == "absolute") {
+        reminder.related = Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE;
+
+        if (absDate.value) {
+            reminder.alarmDate = jsDateToDateTime(absDate.value,
+                                                  window.arguments[0].timezone);
+        } else {
+            reminder.alarmDate = null;
+        }
+    }
+
+    setupListItem(listitem, reminder, window.arguments[0].item);
+}
+
+/**
+ * Gets the locale stringname that is dependant on the item type. This function
+ * appends the item type, i.e |aPrefix + "Event"|.
+ *
+ * @param aPrefix       The prefix to prepend to the item type
+ * @return              The full string name.
+ */
+function getItemBundleStringName(aPrefix) {
+    if (isEvent(window.arguments[0].item)) {
+        return aPrefix + "Event";
     } else {
-        button.setAttribute('disabled', 'true');
+        return aPrefix + "Task";
     }
 }
 
+/**
+ * Handler function to be called when the "new" button is pressed, to create a
+ * new reminder item.
+ */
+function onNewReminder() {
+    let itemType = (isEvent(window.arguments[0].item) ? "event" : "todo");
+    let listbox = document.getElementById("reminder-listbox");
+
+    let reminder = cal.createAlarm();
+    let alarmlen = getPrefSafe("calendar.alarms." + itemType + "alarmlen", 15);
+
+    // Default is a relative DISPLAY alarm, |alarmlen| minutes before the event.
+    // If DISPLAY is not supported by the provider, then pick the provider's
+    // first alarm type.
+    let offset = cal.createDuration();
+    offset.minutes = alarmlen;
+    offset.normalize();
+    offset.isNegative = true;
+    reminder.related = reminder.ALARM_RELATED_START;
+    reminder.offset = offset;
+    if ("DISPLAY" in allowedActionsMap) {
+        reminder.action = "DISPLAY";
+    } else {
+        let calendar = window.arguments[0].calendar
+        let actions = calendar.getProperty("capabilities.alarms.actionValues") || [];
+        reminder.action = actions[0];
+    }
+
+    // Set up the listbox
+    let listitem = setupListItem(null, reminder, window.arguments[0].item);
+    listbox.appendChild(listitem);
+    listbox.selectItem(listitem);
+
+    // Since we've added an item, its safe to always enable the button
+    enableElement("reminder-remove-button");
+
+    // Set up the enabled state and max reminders
+    setupRadioEnabledState();
+    setupMaxReminders();
+}
+
+/**
+ * Handler function to be called when the "remove" button is pressed to remove
+ * the selected reminder item and advance the selection.
+ */
+function onRemoveReminder() {
+    let listbox = document.getElementById("reminder-listbox");
+    let listitem = listbox.selectedItem;
+    let newSelection = (listitem ? listitem.nextSibling ||
+                                   listitem.previousSibling : null);
+
+    listbox.clearSelection();
+    listbox.removeChild(listitem);
+    listbox.selectItem(newSelection);
+
+    setElementValue("reminder-remove-button",
+                    listbox.childNodes.length < 1 && "true",
+                    "disabled");
+    setupMaxReminders();
+}
+
+/**
+ * Handler function to be called when the accept button is pressed.
+ *
+ * @return      Returns true if the window should be closed
+ */
 function onAccept() {
-    var array = [];
-    var listbox = document.getElementById("reminder-listbox");
-    var numChilds = listbox.childNodes.length;
-    for (var i = 0; i < numChilds; i++) {
-        var item = listbox.childNodes[i];
-        array.push(item.reminder);
+    let listbox = document.getElementById("reminder-listbox");
+    let reminders = [ node.reminder 
+                      for each (node in Array.slice(listbox.childNodes)) ];
+    if (window.arguments[0].onOk) {
+        window.arguments[0].onOk(reminders);
     }
-    saveReminders(array);
-
-    var listitem = listbox.selectedItem;
-    window.onAcceptCallback(listitem.reminder);
 
     return true;
 }
 
+/**
+ * Handler function to be called when the cancel button is pressed.
+ */
 function onCancel() {
+    if (window.arguments[0].onCancel) {
+        window.arguments[0].onCancel();
+    }
 }
--- a/calendar/base/content/dialogs/calendar-event-dialog-reminder.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog-reminder.xul
@@ -15,124 +15,140 @@
    - The Original Code is Sun Microsystems code.
    -
    - The Initial Developer of the Original Code is Sun Microsystems.
    - Portions created by the Initial Developer are Copyright (C) 2006
    - the Initial Developer. All Rights Reserved.
    -
    - Contributor(s):
    -   Michael Buettner <michael.buettner@sun.com>
+   -   Philipp Kewisch <mozilla@kewis.ch>
    -
    - Alternatively, the contents of this file may be used under the terms of
    - either the GNU General Public License Version 2 or later (the "GPL"), or
    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    - in which case the provisions of the GPL or the LGPL are applicable instead
    - of those above. If you wish to allow use of your version of this file only
    - under the terms of either the GPL or the LGPL, and not to allow others to
    - use your version of this file under the terms of the MPL, indicate your
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the GPL or the LGPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?>
+<?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-alarms.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-event-dialog.css"?>
+<?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-event-dialog-reminder.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/datetimepickers/datetimepickers.css"?>
+<?xml-stylesheet type="text/css" href="chrome://calendar/content/calendar-bindings.css"?>
 
-<!DOCTYPE dialog [
-  <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
-  <!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
-  <!ENTITY % dtd3 SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd" > %dtd3;
-]>
+<!DOCTYPE dialog SYSTEM "chrome://calendar/locale/dialogs/calendar-event-dialog-reminder.dtd" >
 
 <dialog id="calendar-event-dialog-reminder"
-        title="&reminder.title.label;"
+        title="&reminderdialog.title;"
         windowtype="Calendar:EventDialog:Reminder"
         onload="onLoad()"
         ondialogaccept="return onAccept();"
         ondialogcancel="return onCancel();"
         persist="screenX screenY width height"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <!-- Javascript includes -->
   <script type="application/javascript" src="chrome://calendar/content/calendar-event-dialog-reminder.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calUtils.js"/>
 
+  <notificationbox id="reminder-notifications"/>
+
   <!-- Listbox with custom reminders -->
-  <grid flex="1">
-    <rows>
-      <row/>
-      <row/>
-      <row flex="1"/>
-      <row/>
-    </rows>
-    <columns>
-      <listbox id="reminder-listbox"
-               seltype="single"
-               class="event-dialog-listbox"
-               onselect="onReminderSelected()"
-               flex="1">
-        <listitem/>
-      </listbox>
-      <column>
-        <button id="reminder-new" label="&reminder.new.label;" oncommand="onNewReminder()"/>
-        <button id="reminder-delete" label="&reminder.delete.label;" oncommand="onDeleteReminder()"/>
-      </column>
-    </columns>
-  </grid>
+  <vbox flex="1">
+    <listbox id="reminder-listbox"
+             seltype="single"
+             class="event-dialog-listbox"
+             onselect="onReminderSelected()"
+             flex="1"/>
+    <hbox id="reminder-action-buttons-box" pack="end">
+      <button id="reminder-new-button"
+              label="&reminder.add.label;"
+              accesskey="&reminder.add.accesskey;"
+              oncommand="onNewReminder()"/>
+      <button id="reminder-remove-button"
+              label="&reminder.remove.label;"
+              accesskey="&reminder.remove.accesskey;"
+              oncommand="onRemoveReminder()"/>
+    </hbox>
+  </vbox>
 
   <!-- Custom reminder details -->
-  <groupbox>
-    <caption label="&reminder.reminderDetails.label;"/>
-    <radiogroup>
-      <grid flex="1">
-        <columns>
-          <column/>
-          <column flex="1"/>
-        </columns>
-        <rows>
-          <row align="top">
-            <radio selected="true"/>
-            <vbox>
-              <hbox>
-                <textbox id="reminder-length" size="5" type="number" max="32767"
-                         oninput="updateReminderLength(event)"/>
-                <menulist id="reminder-unit" oncommand="updateReminder()" flex="1">
-                  <menupopup>
-                    <menuitem label="&alarm.units.minutes;" value="minutes" selected="true"/>
-                    <menuitem label="&alarm.units.hours;" value="hours"/>
-                    <menuitem label="&alarm.units.days;" value="days"/>
-                  </menupopup>
-                </menulist>
-                <menulist id="reminder-relation" oncommand="updateReminder()" flex="1">
-                  <menupopup>
-                    <menuitem label="&newevent.before.label;" value="START" selected="true"/>
-                    <menuitem label="&newevent.after.label;" value="END"/>
-                  </menupopup>
-                </menulist>
-              </hbox>
-              <menulist id="reminder-origin" oncommand="updateReminder()">
-                <menupopup>
-                  <menuitem label="&reminder.relation.start.label;" value="1" selected="true"/>
-                  <menuitem label="&reminder.relation.end.label;" value="-1"/>
-                </menupopup>
-              </menulist>
-            </vbox>
-          </row>
-          <row>
-            <radio disabled="true"/>
-            <datetimepicker id="reminder-duetime" disabled="true"/>
-          </row>
-        </rows>
-      </grid>
-    </radiogroup>
-  </groupbox>
+  <calendar-caption id="reminder-details-caption" label="&reminder.reminderDetails.label;"/>
+  <radiogroup id="reminder-relation-radiogroup"
+                onselect="setupRadioEnabledState(); updateReminder(event)">
+    <hbox id="reminder-relative-box" align="top" flex="1">
+      <radio id="reminder-relative-radio"
+             value="relative"
+             aria-labeledby="reminder-length reminder-unit reminder-relation reminder-origin"/>
+      <vbox id="reminder-relative-box" flex="1">
+        <hbox id="reminder-relative-length-unit-relation" flex="1">
+          <textbox id="reminder-length"
+                   size="1"
+                   oninput="validateIntegers(event); updateReminder(event)"/>
+          <menulist id="reminder-unit" oncommand="updateReminder(event)" flex="1">
+            <menupopup id="reminder-unit-menupopup">
+              <menuitem id="reminder-minutes-menuitem"
+                        label="&alarm.units.minutes;"
+                        value="minutes"/>
+              <menuitem id="reminder-hours-menuitem"
+                        label="&alarm.units.hours;"
+                        value="hours"/>
+              <menuitem id="reminder-days-menuitem"
+                        label="&alarm.units.days;"
+                        value="days"/>
+            </menupopup>
+          </menulist>
+          <menulist id="reminder-relation" oncommand="updateReminder(event)" flex="1">
+            <menupopup>
+              <menuitem label="&reminder.relation.before.label;" value="before"/>
+              <menuitem label="&reminder.relation.after.label;" value="after"/>
+            </menupopup>
+          </menulist>
+        </hbox>
+        <menulist id="reminder-origin" oncommand="updateReminder(event)">
+          <menupopup id="reminder-origin-menupopup">
+            <menuitem id="reminder-origin-start-menuitem"
+                      value="START"/>
+            <menuitem id="reminder-origin-end-menuitem"
+                      value="END"/>
+          </menupopup>
+        </menulist>
+      </vbox>
+    </hbox>
+    <hbox id="reminder-absolute-box" flex="1">
+      <radio id="reminder-absolute-radio"
+             control="reminder-absolute-date"
+             value="absolute"/>
+      <datetimepicker id="reminder-absolute-date"/>
+    </hbox>
+  </radiogroup>
 
   <!-- Custom reminder action -->
-  <groupbox>
-    <caption label="&reminder.action.label;"/>
-    <checkbox label="&reminder.action.alert.label;" checked="true" disabled="true"/>
-    <checkbox label="&reminder.action.email.label;" disabled="true"/>
-  </groupbox>
+  <calendar-caption id="reminder-actions-caption"
+                    control="reminder-actions-menulist"
+                    label="&reminder.action.label;"/>
+  <menulist id="reminder-actions-menulist"
+            oncommand="updateReminder(event)"
+            class="reminder-icon">
+    <!-- Make sure the id is formatted "reminder-action-<VALUE>", for accessibility -->
+    <!-- TODO provider specific -->
+    <menupopup id="reminder-actions-menupopup">
+      <menuitem id="reminder-action-DISPLAY"
+                class="reminder-icon menuitem-iconic"
+                value="DISPLAY"
+                label="&reminder.action.alert.label;"/>
+      <menuitem id="reminder-action-EMAIL"
+                class="reminder-icon menuitem-iconic"
+                value="EMAIL"
+                label="&reminder.action.email.label;"/>
+    </menupopup>
+  </menulist>
 </dialog>
--- a/calendar/base/content/dialogs/calendar-event-dialog.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog.js
@@ -385,17 +385,17 @@ function loadDialog(item) {
     // Privacy
     gPrivacy = item.privacy;
     updatePrivacy();
 
     // load repeat details
     loadRepeat(item);
 
     // load reminder details
-    loadReminder(item);
+    loadReminders(item.getAlarms({}));
 
     // hide rows based on if this is an event or todo
     updateStyle();
 
     updateDateTime();
 
     updateCalendar();
 
@@ -2839,16 +2839,17 @@ function sendMailToAttendees(aAttendees)
 
 /**
  * Make sure all fields that may have calendar specific capabilities are updated
  */
 function updateCapabilities() {
     updateAttachment();
     updatePriority();
     updatePrivacy();
+    updateReminderDetails();
 }
 
 /**
  * Test if a specific capability is supported
  *
  * @param aCap      The capability from "capabilities.<aCap>.supported"
  */
 function capSupported(aCap) {
--- a/calendar/base/content/dialogs/calendar-event-dialog.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog.xul
@@ -34,16 +34,17 @@
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the GPL or the LGPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?>
+<?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-alarms.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-event-dialog.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/calendar-event-dialog.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/datetimepickers/datetimepickers.css"?>
 
 <!DOCTYPE dialog [
     <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
     <!ENTITY % globalDTD SYSTEM "chrome://calendar/locale/global.dtd">
     <!ENTITY % calendarDTD SYSTEM "chrome://calendar/locale/calendar.dtd">
@@ -909,66 +910,66 @@
                         <menupopup id="item-alarm-menupopup">
                             <menuitem id="reminder-none-menuitem"
                                       label="&event.reminder.none.label;"
                                       selected="true"
                                       value="none"/>
                             <menuitem id="reminder-5minutes-menuitem"
                                       label="&event.reminder.5minutes.before.label;"
                                       length="5"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="minutes"/>
                             <menuitem id="reminder-10minutes-menuitem"
                                       label="&event.reminder.10minutes.before.label;"
                                       length="10"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="minutes"/>
                             <menuitem id="reminder-15minutes-menuitem"
                                       label="&event.reminder.15minutes.before.label;"
                                       length="15"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="minutes"/>
                             <menuitem id="reminder-30minutes-menuitem"
                                       label="&event.reminder.30minutes.before.label;"
                                       length="30"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="minutes"/>
                             <menuitem id="reminder-45minutes-menuitem"
                                       label="&event.reminder.45minutes.before.label;"
                                       length="45"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="minutes"/>
                             <menuseparator id="reminder-minutes-separator"/>
                             <menuitem id="reminder-1hour-menuitem"
                                       label="&event.reminder.1hour.before.label;"
                                       length="1"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="hours"/>
                             <menuitem id="reminder-2hours-menuitem"
                                       label="&event.reminder.2hours.before.label;"
                                       length="2"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="hours"/>
                             <menuitem id="reminder-5hours-menuitem"
                                       label="&event.reminder.5hours.before.label;"
                                       length="5"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="hours"/>
                             <menuitem id="reminder-15hours-menuitem"
                                       label="&event.reminder.15hours.before.label;"
                                       length="15"
-                                      origin="1"
+                                      origin="before"
                                       relation="START"
                                       unit="hours"/>
                             <menuseparator id="reminder-hours-separator"/>
                             <menuitem id="reminder-1day-menuitem"
                                       label="&event.reminder.1day.before.label;"
                                       length="1"
                                       origin="1"
                                       relation="START"
@@ -986,24 +987,38 @@
                                       relation="START"
                                       unit="days"/>
                             <menuseparator id="reminder-custom-separator"/>
                             <menuitem id="reminder-custom-menuitem"
                                       label="&event.reminder.custom.label;"
                                       value="custom"/>
                         </menupopup>
                     </menulist>
-                    <vbox id="reminder-details">
-                        <label class="text-link"
-                               disable-on-readonly="true"
-                               flex="1"
-                               hyperlink="true"
-                               onclick="updateReminder()"/>
-                    </vbox>
-                </hbox>
+                    <hbox id="reminder-details">
+                      <hbox id="reminder-icon-box"
+                            class="alarm-icons-box"
+                            align="center"/>
+                      <!-- TODO oncommand? onkeypress? -->
+                      <label id="reminder-multiple-alarms-label"
+                             hidden="true"
+                             value="&event.reminder.multiple.label;"
+                             class="text-link"
+                             disable-on-readonly="true"
+                             flex="1"
+                             hyperlink="true"
+                             onclick="updateReminder()"/>
+                      <label id="reminder-single-alarms-label"
+                             hidden="true"
+                             class="text-link"
+                             disable-on-readonly="true"
+                             flex="1"
+                             hyperlink="true"
+                             onclick="updateReminder()"/>
+                    </hbox>
+               </hbox>
             </row>
 
             <separator id="event-grid-description-separator"
                        class="groove"/>
 
             <!-- Description -->
             <row id="description-row" flex="1">
                 <label value="&event.description.label;"
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -93,19 +93,18 @@ calendar.jar:
     content/calendar/widgets/calendar-widget-bindings.css  (content/widgets/calendar-widget-bindings.css)
 *   content/calendar/calApplicationUtils.js                (src/calApplicationUtils.js)
     content/calendar/calUtils.js                           (src/calUtils.js)
     content/calendar/calFilter.js                          (src/calFilter.js)
     content/calendar/Windows98ToZoneInfoTZId.properties    (src/Windows98ToZoneInfoTZId.properties)
     content/calendar/WindowsNTToZoneInfoTZId.properties    (src/WindowsNTToZoneInfoTZId.properties)
 % skin calendar classic/1.0 %skin/calendar/
     skin/calendar/abcard.png                               (themes/common/abcard.png)
-    skin/calendar/alarm-suppressed.png                     (themes/common/alarm-suppressed.png)
     skin/calendar/alarm-flashing.png                       (themes/common/alarm-flashing.png)
-    skin/calendar/alarm.png                                (themes/common/alarm.png)
+    skin/calendar/alarm-icons.png                          (themes/common/alarm-icons.png)
     skin/calendar/calendar-day-label-back.png              (themes/common/calendar-day-label-back.png)
     skin/calendar/calendar-overlay.png                     (themes/common/calendar-overlay.png)
     skin/calendar/category-overlay.png                     (themes/common/category-overlay.png)
     skin/calendar/calendar-status.png                      (themes/common/calendar-status.png)
     skin/calendar/daypicker-background.png                 (themes/common/daypicker-background.png)
     skin/calendar/day-box-item-image.png                   (themes/common/day-box-item-image.png)
     skin/calendar/event-grippy-bottom.png                  (themes/common/event-grippy-bottom.png)
     skin/calendar/event-grippy-left.png                    (themes/common/event-grippy-left.png)
@@ -113,16 +112,17 @@ calendar.jar:
     skin/calendar/event-grippy-top.png                     (themes/common/event-grippy-top.png)
     skin/calendar/gradient-overlay.png                     (themes/common/gradient-overlay.png)
     skin/calendar/mini-day-background.png                  (themes/common/mini-day-background.png)
     skin/calendar/unifinder-images.png                     (themes/common/unifinder-images.png)
     skin/calendar/widgets/nav-buttons.png                  (themes/common/widgets/nav-buttons.png)
     skin/calendar/widgets/view-navigation.png              (themes/common/widgets/view-navigation.png)
     skin/calendar/cal-icon32.png                           (themes/@THEME@/cal-icon32.png)
     skin/calendar/calendar-alarm-dialog.css                (themes/@THEME@/calendar-alarm-dialog.css)
+    skin/calendar/calendar-alarms.css                      (themes/@THEME@/calendar-alarms.css)
     skin/calendar/calendar-creation-wizard.css             (themes/@THEME@/calendar-creation-wizard.css)
     skin/calendar/calendar-daypicker.css                   (themes/@THEME@/calendar-daypicker.css)
     skin/calendar/calendar-event-dialog.css                (themes/@THEME@/calendar-event-dialog.css)
     skin/calendar/calendar-event-dialog-attendees.png      (themes/@THEME@/dialogs/calendar-event-dialog-attendees.png)
     skin/calendar/calendar-event-dialog-toolbar.png        (themes/@THEME@/dialogs/calendar-event-dialog-toolbar.png)
     skin/calendar/calendar-event-dialog.png                (themes/@THEME@/dialogs/calendar-event-dialog.png)
     skin/calendar/calendar-invitations-dialog.css          (themes/@THEME@/dialogs/calendar-invitations-dialog.css)
     skin/calendar/calendar-invitations-dialog-button-images.png  (themes/@THEME@/dialogs/calendar-invitations-dialog-button-images.png)
--- a/calendar/base/modules/calAlarmUtils.jsm
+++ b/calendar/base/modules/calAlarmUtils.jsm
@@ -71,24 +71,35 @@ cal.alarms = {
      * @return          The alarm offset.
      */
     calculateAlarmDate: function cal_alarm_calculateAlarmDate(aItem, aAlarm) {
         if (aAlarm.related == aAlarm.ALARM_RELATED_ABSOLUTE) {
             return aAlarm.alarmDate;
         } else {
             let returnDate;
             if (aAlarm.related == aAlarm.ALARM_RELATED_START) {
-                let startDate = aItem[cal.calGetStartDateProp(aItem)];
-                returnDate = startDate && startDate.clone();
+                returnDate = aItem[cal.calGetStartDateProp(aItem)];
             } else if (aAlarm.related = aAlarm.ALARM_RELATED_END) {
-                let endDate = aItem[cal.calGetEndDateProp(aItem)];
-                returnDate = endDate && endDate.clone();
+                returnDate = aItem[cal.calGetEndDateProp(aItem)];
             }
 
             if (returnDate && aAlarm.offset) {
+                // Handle all day events.  This is kinda weird, because they don't
+                // have a well defined startTime.  We just consider the start/end
+                // to be midnight in the user's timezone.
+                if (returnDate.isDate) {
+                    let tz = cal.calendarDefaultTimezone();
+                    // This returns a copy, so no extra cloning needed.
+                    returnDate = alarmDate.getInTimezone(tz);
+                    returnDate.isDate = false;
+                } else {
+                    // Clone the date to correctly add the duration.
+                    returnDate = returnDate.clone();
+                }
+
                 returnDate.addDuration(aAlarm.offset);
                 return returnDate;
             }
         }
         return null;
     },
 
     /**
@@ -113,10 +124,59 @@ cal.alarms = {
             if (returnDate && aAlarm.alarmDate) {
                 return returnDate.subtractDate(aAlarm.alarmDate);
             }
                 
             return offset;
         } else {
             return aAlarm.offset;
         }
+    },
+
+    /**
+     * Adds reminder images to a given node, making sure only one icon per alarm
+     * action is added.
+     *
+     * @param aElement    The element to add the images to.
+     * @param aReminders  The set of reminders to add images for.
+     */
+    addReminderImages: function cal_alarms_addReminderImages(aElement, aReminders) {
+        function createOwnedXULNode(el) {
+            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+            return aElement.ownerDocument.createElementNS(XUL_NS, el);
+        }
+
+        function setupActionImage(node, reminder) {
+            let image = node || createOwnedXULNode("image");
+            image.setAttribute("class", "reminder-icon");
+            image.setAttribute("value", reminder.action);
+            return image;
+        }
+
+        // Fill up the icon box with the alarm icons, show max one icon per
+        // alarm type.
+        let countIconChildren = aElement.childNodes.length;
+        let actionMap = {};
+        let i, offset;
+        for (i = 0, offset = 0; i < aReminders.length; i++) {
+            var reminder = aReminders[i];
+            if (reminder.action in actionMap) {
+                // Only show one icon of each type;
+                offset++;
+                continue;
+            }
+            actionMap[reminder.action] = true;
+
+            if (i - offset >= countIconChildren) {
+                // Not enough nodes, append it.
+                aElement.appendChild(setupActionImage(null, reminder));
+            } else {
+                // There is already a node there, change its properties
+                setupActionImage(aElement.childNodes[i - offset], reminder);
+            }
+        }
+
+        // Remove unused image nodes
+        for (i -= offset; i < countIconChildren; i++) {
+            aElement.removeChild(aElement.childNodes[i]);
+        }
     }
 };
--- a/calendar/base/modules/calIteratorUtils.jsm
+++ b/calendar/base/modules/calIteratorUtils.jsm
@@ -57,33 +57,16 @@ cal.itemIterator = function cal_itemIter
                     }
                 }
             }
         }
     };
 };
 
 /**
- * Iterator for a DOM NodeList where for each() cannot be used directly.
- *
- * @param nodeList          The NodeList to iterate (i.e el.childNodes)
- */
-cal.nodeIterator = function cal_nodeIterator(nodeList) {
-    return {
-        __iterator__: function nodeIterator(aWantKeys) {
-            cal.ASSERT(!aWantKeys, "Please use for each() on the node iterator");
-            let nodeListLength = nodeList.length;
-            for (let i = 0; i < nodeListLength; i++) {
-                yield nodeList[i];
-            }
-        }
-    };
-};
-
-/**
  * "ical" namespace. Used for all iterators (and possibly other functions) that
  * are related to libical.
  */
 cal.ical = {
     /**
      *  Yields all subcomponents in all calendars in the passed component.
      *  - If the passed component is an XROOT (contains multiple calendars),
      *    then go through all VCALENDARs in it and get their subcomponents.
--- a/calendar/base/src/calAlarmService.js
+++ b/calendar/base/src/calAlarmService.js
@@ -33,16 +33,18 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
+
 const kHoursBetweenUpdates = 6;
 
 function nowUTC() {
     return jsDateToDateTime(new Date()).getInTimezone(UTC());
 }
 
 function newTimerWithCallback(callback, delay, repeating)
 {
@@ -371,18 +373,18 @@ calAlarmService.prototype = {
         calendar.removeObserver(this.calendarObserver);
         this.disposeCalendarTimers([calendar]);
         this.notifyObservers("onRemoveAlarmsByCalendar", [calendar]);
     },
 
     getAlarmDate: function cas_getAlarmTime(aItem) {
         var alarmDate = null;
         // TODO ALARMSUPPORT This will change as soon as we support multiple
-        // alarms. Get the first alarm for now.
-        let alarms = aItem.getAlarms({});
+        // alarms. Get the first DISPLAY alarm for now.
+        let alarms = aItem.getAlarms({}).filter(function(x) x.action == "DISPLAY");
         let alarm = alarms[0];
         if (!alarm) {
             // No relative alarm available.
             return null;
         }
         // END TODO ALARMSUPPORT
         
         let alarmDate = cal.alarms.calculateAlarmDate(aItem, alarm)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..554f823a0a9e38c81f0366674378fcb0f639ddfd
GIT binary patch
literal 1013
zc$@+C0}A|!P)<h;3K|Lk000e1NJLTq001li000XJ1^@s61TAlm00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01Cta01CtbW^IDh00007bV*G`2iOJ_
z4GuA-2fu9q00VVNL_t(I%bk@?XdP7)$A9<E%=^ffY0SbdVjmGJFN&syYE>{$i!r81
zq1oA@3#}+9QR%`;L=1KzRntX{iV8v&LKiK9U)^ZBuz8lWA{BLENn51Gq&DWmm-)Eo
zxOgv*yp|W6-r;a&4*c%@&;Q)HV<ry2qb?4)p5dj^boIYvR@TN3aeKPKAGLKG?$mgS
zcz)q0=h(I7IeuPku50EBqx-dbNGZgrN}?)}s)VYpsOlC~-GWLor;^NSVf0O{TJ$}F
zlOHp&bn#Qw;&FAq->-`&(9@qVF+M_2Q|uZz>zo4=R6t!hV$i4(sv)WoDiN=}9W!{q
z5r+{jaa9Ye(C620*pyy(EAiwDB!7QTx*jlWLg}j|crBcjq%4qSk!5ijp%Nl4LXrp<
zha_=`h$0Fu4slMZ7S3eZnQCk|`tNUOcor;(fFEEm?5rhtEu3}ERoqjJ5Q&f^M3N8}
zmxQ_~tmIdoo@3urm(e6dRybT3p+yW``K^bL`EiKmz?2%du>-87tMEQ8SBhPYyRED9
zD$bY0p)Nv2P?d7l$}=_Ui3p|b1cec9ROJyNotOFVklD|`TkbcQ#%_!^mY0a$!U$jA
z`z)9Km-8NC`d||+r)KlOrE~qc^UaU%>G7R&BvA+|96tIjB63yBcJ$l>swLs7fWa73
zaae`dQ)#wtd|)^7=c{P)53D#lOfp6Oz8(B>;^;mXvjup5HO^!^nLgM=@ziNrUmT>d
zub-LWp_^w%Km5u{;!6873K3PbWJ)<o*O702;K;F)C>HCb@PZ6pkgYfzWZ2%;PDeKU
z2qTxV9#~rr4|M5Q>arJUziV4ZQ0H%7evw)nP!T)x?huP($7mfGB)%}2^IhU_QHZMY
z*tWYtwWQV<jIm%W##)T=sxrn_9CitxpF2zUALqG4l~|Na)rVqOGPV#n#~GI}wp_2x
z{m2dEXzc69HZ<|q&|X>>=E|ppbGRf%oWo$THoyjHydaC0%HpT8m3uG9;sqIO*<mR=
ztjd6k+1&XgepsYudJ5X}>uJxgXL<_4B0Zf?;5Rq0S%rbhakdJ_EjKYUJVbr>!xg84
zw%nxo;;om}MN8N9r}NWU9^HGuTv;C*=0QK;1ArvvneG9OU(0+expkS|{q<iK--s_v
j<^Y1W++_2vJ-z<{mAHOz)Z6kg00000NkvXXu0mjf%<|jx
deleted file mode 100644
index 21bd46b9bb46a731e39c24c451ab9cc8f8f00794..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 81b44d927e0f7e40332a1616e08ffa6a277d4eca..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/calendar/base/themes/pinstripe/calendar-alarms.css
@@ -0,0 +1,98 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Sun Microsystems code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Philipp Kewisch <mozilla@kewis.ch>
+ * Portions created by the Initial Developer are Copyright (C) 2008-2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Reminder icons (used from the event dialog, reminder dialog, views, ...)
+ */
+.reminder-icon {
+    list-style-image: url(chrome://calendar/skin/alarm-icons.png);
+}
+
+.reminder-icon[value="DISPLAY"] {
+    -moz-image-region: rect(0px 30px 11px 17px);
+}
+
+.alarm-icons-box[suppressed="true"] > .reminder-icon[value="DISPLAY"] {
+    -moz-image-region: rect(0px 44px 11px 31px);
+}
+
+.reminder-icon[value="EMAIL"] {
+    -moz-image-region: rect(0px 16px 11px 0px);
+}
+
+.alarm-icons-box[flashing="true"] > .reminder-icon[value="DISPLAY"] {
+    list-style-image: url(chrome://calendar/skin/alarm-flashing.png);
+    -moz-image-region: auto;
+}
+
+/**
+ * Reminder dialog (i.e "custom" alarm in the event dialog)
+ * Please make sure rules added here are very specific and won't hurt other
+ * dialogs.
+ */
+#reminder-relative-radio > .radio-label-center-box > .radio-label-box,
+#reminder-absolute-radio > .radio-label-center-box > .radio-label-box {
+    display: none;
+}
+
+#reminder-actions-menulist > menupopup > menuitem > .menu-iconic-left {
+    display: -moz-box;
+}
+
+#reminder-notifications {
+    overflow-y: visible;
+}
+
+#reminder-notifications > notification {
+    background-color: transparent;
+}
+#reminder-notifications > notification > .notification-inner {
+    border: 0;
+}
+#reminder-notifications > notification[type="warning"] {
+    list-style-image: url(chrome://global/skin/icons/Warning.png);
+}
+
+#reminder-actions-caption,
+#reminder-details-caption,
+#calendar-event-dialog-reminder > .dialog-button-box {
+    padding-top: 20px;
+}
+
+.reminder-icon > .menu-iconic-left > .menu-iconic-icon {
+    width: auto;
+    height: auto;
+}
--- a/calendar/base/themes/pinstripe/calendar-views.css
+++ b/calendar/base/themes/pinstripe/calendar-views.css
@@ -535,30 +535,16 @@ calendar-event-box[orient="horizontal"] 
 }
 
 calendar-event-box[readonly="true"]:hover .calendar-event-box-grippy-top,
 calendar-event-box[readonly="true"]:hover .calendar-event-box-grippy-bottom {
     visibility: hidden;
     cursor: auto;
 }
 
-/* Alarm image */
-.alarm-image {
-    list-style-image: url(chrome://calendar/skin/alarm.png);
-    margin: 2px 2px 0px;
-}
-
-.alarm-image[flashing="true"] {
-    list-style-image: url(chrome://calendar/skin/alarm-flashing.png);
-}
-
-.alarm-image[suppressed="true"] {
-    list-style-image: url(chrome://calendar/skin/alarm-suppressed.png);
-}
-
 /* tooltips */
 vbox.tooltipBox {
     max-width: 40em;
 }
 
 column.tooltipValueColumn {
     max-width: 35em; /* tooltipBox max-width minus space for label */
 }
new file mode 100644
--- /dev/null
+++ b/calendar/base/themes/winstripe/calendar-alarms.css
@@ -0,0 +1,98 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Sun Microsystems code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Philipp Kewisch <mozilla@kewis.ch>
+ * Portions created by the Initial Developer are Copyright (C) 2008-2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Reminder icons (used from the event dialog, reminder dialog, views, ...)
+ */
+.reminder-icon {
+    list-style-image: url(chrome://calendar/skin/alarm-icons.png);
+}
+
+.reminder-icon[value="DISPLAY"] {
+    -moz-image-region: rect(0px 30px 11px 17px);
+}
+
+.alarm-icons-box[suppressed="true"] > .reminder-icon[value="DISPLAY"] {
+    -moz-image-region: rect(0px 44px 11px 31px);
+}
+
+.reminder-icon[value="EMAIL"] {
+    -moz-image-region: rect(0px 16px 11px 0px);
+}
+
+.alarm-icons-box[flashing="true"] > .reminder-icon[value="DISPLAY"] {
+    list-style-image: url(chrome://calendar/skin/alarm-flashing.png);
+    -moz-image-region: auto;
+}
+
+/**
+ * Reminder dialog (i.e "custom" alarm in the event dialog)
+ * Please make sure rules added here are very specific and won't hurt other
+ * dialogs.
+ */
+#reminder-relative-radio > .radio-label-center-box > .radio-label-box,
+#reminder-absolute-radio > .radio-label-center-box > .radio-label-box {
+    display: none;
+}
+
+#reminder-actions-menulist > menupopup > menuitem > .menu-iconic-left {
+    display: -moz-box;
+}
+
+#reminder-notifications {
+    overflow-y: visible;
+}
+
+#reminder-notifications > notification {
+    background-color: transparent;
+}
+#reminder-notifications > notification > .notification-inner {
+    border: 0;
+}
+#reminder-notifications > notification[type="warning"] {
+    list-style-image: url(chrome://global/skin/icons/Warning.png);
+}
+
+#reminder-actions-caption,
+#reminder-details-caption,
+#calendar-event-dialog-reminder > .dialog-button-box {
+    padding-top: 20px;
+}
+
+.reminder-icon > .menu-iconic-left > .menu-iconic-icon {
+    width: auto;
+    height: auto;
+}
--- a/calendar/base/themes/winstripe/calendar-views.css
+++ b/calendar/base/themes/winstripe/calendar-views.css
@@ -535,32 +535,16 @@ calendar-event-box[orient="horizontal"] 
 }
 
 calendar-event-box[readonly="true"]:hover .calendar-event-box-grippy-top,
 calendar-event-box[readonly="true"]:hover .calendar-event-box-grippy-bottom {
     visibility: hidden;
     cursor: auto;
 }
 
-/* Alarm image */
-.alarm-image {
-    list-style-image: url(chrome://calendar/skin/alarm.png);
-    margin: 2px 2px 0px;
-}
-
-.alarm-image[flashing="true"] {
-    list-style-image: url(chrome://calendar/skin/alarm-flashing.png);
-}
-
-.alarm-image[suppressed="true"] {
-    list-style-image: url(chrome://calendar/skin/alarm-suppressed.png);
-}
-
-
-
 /* tooltips */
 vbox.tooltipBox {
     max-width: 40em;
 }
 
 column.tooltipValueColumn {
     max-width: 35em; /* tooltipBox max-width minus space for label */
 }
--- a/calendar/import-export/calOutlookCSVImportExport.js
+++ b/calendar/import-export/calOutlookCSVImportExport.js
@@ -33,16 +33,18 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
+
 // Import
 
 function calOutlookCSVImporter() {
     this.wrappedJSObject = this;
 }
 
 calOutlookCSVImporter.prototype.QueryInterface =
 function QueryInterface(aIID) {
--- a/calendar/locales/en-US/chrome/calendar/calendar-alarms.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar-alarms.properties
@@ -51,10 +51,18 @@ reminderCustomOriginBeginBeforeEvent=bef
 reminderCustomOriginBeginAfterEvent=after the event starts
 reminderCustomOriginEndBeforeEvent=before the event ends
 reminderCustomOriginEndAfterEvent=after the event ends
 reminderCustomOriginBeginBeforeTask=before the task starts
 reminderCustomOriginBeginAfterTask=after the task starts
 reminderCustomOriginEndBeforeTask=before the task ends
 reminderCustomOriginEndAfterTask=after the task ends
 
-reminderErrorMaxCountReachedEventN=The selected calendar has a limitation of #1 reminder per event.;The selected calendar has a limitation of #1 reminders per event.
-reminderErrorMaxCountReachedTaskN=The selected calendar has a limitation of #1 reminder per task.;The selected calendar has a limitation of #1 reminders per task.
+# Sorry about this, but the reminder dialog possibly needs to support changing the
+# order of its elements for some locales. This needs to be done in a different
+# bug. Please comment in bug XXX what your needs are.
+reminderDialogOriginBeginEvent=the event starts
+reminderDialogOriginEndEvent=the event ends
+reminderDialogOriginBeginTask=the task starts
+reminderDialogOriginEndTask=the task ends
+
+reminderErrorMaxCountReachedEvent=The selected calendar has a limitation of #1 reminder per event.;The selected calendar has a limitation of #1 reminders per event.
+reminderErrorMaxCountReachedTask=The selected calendar has a limitation of #1 reminder per task.;The selected calendar has a limitation of #1 reminders per task.
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
@@ -39,18 +39,16 @@
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <!ENTITY event.title.label                  "Edit Item" >
 
-<!ENTITY newevent.before.label              "before" >
-<!ENTITY newevent.after.label               "after" >
 
 <!ENTITY newevent.from.label                "From" >
 <!ENTITY newevent.to.label                  "To" >
 <!ENTITY newevent.attendees.notify.label    "Notify attendees">
 
 <!ENTITY newevent.status.label              "Status" >
 <!ENTITY newevent.status.accesskey          "S" >
 <!ENTITY newevent.status.none.label         "Not specified" >
@@ -332,37 +330,16 @@
 
 <!ENTITY event.recurrence.range.label                   "Range of recurrence">
 <!ENTITY event.recurrence.forever.label                 "No end date" >
 <!ENTITY event.recurrence.repeat.for.label              "Create" >
 <!ENTITY event.recurrence.appointments.label            "Appointment(s)" >
 <!ENTITY event.repeat.until.label                       "Repeat until" >
 <!ENTITY event.recurrence.preview.label                 "Preview">
 
-<!-- Reminder dialog -->
-<!ENTITY alarm.units.minutes                "minutes" >
-<!ENTITY alarm.units.hours                  "hours" >
-<!ENTITY alarm.units.days                   "days" >
-
-<!ENTITY reminder.title.label                           "Custom Reminder">
-<!ENTITY reminder.dialogtitle.label                     "Set up reminders">
-<!ENTITY reminder.reminderDetails.label                 "Reminder Details">
-<!ENTITY reminder.action.label                          "Choose a Reminder Action">
-<!ENTITY reminder.action.alert.label                    "Show an Alert">
-<!ENTITY reminder.action.email.label                    "Send an E-mail">
-<!ENTITY reminder.new.label                             "New">
-<!ENTITY reminder.delete.label                          "Delete">
-<!ENTITY reminder.relation.start.label                  "the event starts">
-<!ENTITY reminder.relation.end.label                    "the event ends">
-<!ENTITY reminder.add.label                             "Add">
-<!ENTITY reminder.add.accesskey                         "A">
-<!ENTITY reminder.remove.label                          "Remove">
-<!ENTITY reminder.remove.accesskey                      "R">
-
-
 <!-- Attendees dialog -->
 <!ENTITY invite.title.label                     "Invite Attendees">
 <!ENTITY event.organizer.label                  "Organizer">
 <!ENTITY event.freebusy.suggest.slot            "Suggest time slot:">
 <!ENTITY event.freebusy.next.slot               "Next slot " >
 <!ENTITY event.freebusy.previous.slot           " Previous slot" >
 <!ENTITY event.freebusy.zoom                    "Zoom:">
 <!ENTITY event.freebusy.plus                    "Next hour" >
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
@@ -196,42 +196,16 @@ repeatDetailsInfinite=Occurs %1$S\neffec
 #  effective 1/1/2009"
 repeatDetailsInfiniteAllDay=Occurs %1$S\neffective %2$S.
 
 # LOCALIZATION NOTE (ruleTooComplex):
 # This string is shown in the reminder details area if our code can't handle the
 # complexity of the recurrence rule yet.
 ruleTooComplex=Click here for details
 
-# LOCALIZATION NOTE (reminderCustomTitle):
-# Edit reminder window -> Reminder details link on Event/Task dialog window
-# 1%$S - number (reminder length)
-# 2%$S - reminderCustomUnit... (minute/hour/day)
-# %3$S - reminderCustomRelation... (before/after)
-# %4$S - reminderCustomOrigin... (the event starts/ends)
-# e.g. "5 hours before the event starts"
-reminderCustomTitle=%1$S %2$S %3$S %4$S
-reminderTitleAtStartEvent=The moment the event starts
-reminderTitleAtStartTask=The moment the task starts
-reminderTitleAtEndEvent=The moment the event ends
-reminderTitleAtEndTask=The moment the task ends
-reminderCustomUnitMinute=minute
-reminderCustomUnitMinutes=minutes
-reminderCustomUnitHour=hour
-reminderCustomUnitHours=hours
-reminderCustomUnitDay=day
-reminderCustomUnitDays=days
-reminderCustomRelationBefore=before
-reminderCustomRelationAfter=after
-reminderCustomOriginBeginEvent=the event starts
-reminderCustomOriginEndEvent=the event ends
-reminderCustomOriginBeginTask=the task starts
-reminderCustomOriginEndTask=the task ends
-reminderErrorMaxCountReachedEvent=The selected calendar has a limitation of %1$S reminders per event.
-reminderErrorMaxCountReachedTask=The selected calendar has a limitation of %1$S reminders per task.
 newEvent=New Event
 newTask=New Task
 
 emailSubjectReply=Re: %1$S
 
 # Link Location Dialog
 specifyLinkLocation=Please specify the link location
 enterLinkLocation=Enter a web page, or document location.
new file mode 100644
--- /dev/null
+++ b/calendar/locales/en-US/chrome/calendar/dialogs/calendar-event-dialog-reminder.dtd
@@ -0,0 +1,54 @@
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is Sun Microsystems code.
+   -
+   - The Initial Developer of the Original Code is
+   -   Philipp Kewisch <mozilla@kewis.ch>
+   - Portions created by the Initial Developer are Copyright (C) 2008-2009
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the LGPL or the GPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!ENTITY reminderdialog.title                              "Set up Reminders">
+<!ENTITY reminder.add.label                                "Add">
+<!ENTITY reminder.add.accesskey                            "A">
+<!ENTITY reminder.remove.label                             "Remove">
+<!ENTITY reminder.remove.accesskey                         "R">
+
+<!ENTITY reminder.reminderDetails.label                    "Reminder Details">
+<!ENTITY reminder.action.label                             "Choose a Reminder Action">
+
+<!ENTITY reminder.relation.before.label                    "before" >
+<!ENTITY reminder.relation.after.label                     "after" >
+
+<!ENTITY reminder.action.alert.label                       "Show an Alert">
+<!ENTITY reminder.action.email.label                       "Send an E-mail">
+
+<!ENTITY alarm.units.minutes                               "minutes" >
+<!ENTITY alarm.units.hours                                 "hours" >
+<!ENTITY alarm.units.days                                  "days" >
--- a/calendar/locales/jar.mn
+++ b/calendar/locales/jar.mn
@@ -22,8 +22,9 @@ calendar-@AB_CD@.jar:
     locale/@AB_CD@/calendar/preferences/general.dtd        (%chrome/calendar/preferences/general.dtd)
     locale/@AB_CD@/calendar/preferences/preferences.dtd    (%chrome/calendar/preferences/preferences.dtd)
     locale/@AB_CD@/calendar/preferences/timezones.dtd      (%chrome/calendar/preferences/timezones.dtd)
     locale/@AB_CD@/calendar/preferences/views.dtd          (%chrome/calendar/preferences/views.dtd)
     locale/@AB_CD@/calendar/calendar-event-dialog.dtd      (%chrome/calendar/calendar-event-dialog.dtd)
     locale/@AB_CD@/calendar/calendar-event-dialog.properties  (%chrome/calendar/calendar-event-dialog.properties)
     locale/@AB_CD@/calendar/calendar-invitations-dialog.dtd  (%chrome/calendar/calendar-invitations-dialog.dtd)
     locale/@AB_CD@/calendar/calendar-subscriptions-dialog.dtd  (%chrome/calendar/calendar-subscriptions-dialog.dtd)
+    locale/@AB_CD@/calendar/dialogs/calendar-event-dialog-reminder.dtd  (%chrome/calendar/dialogs/calendar-event-dialog-reminder.dtd)
--- a/calendar/providers/gdata/components/calGoogleCalendar.js
+++ b/calendar/providers/gdata/components/calGoogleCalendar.js
@@ -254,16 +254,20 @@ calGoogleCalendar.prototype = {
             case "capabilities.timezones.floating.supported":
             case "capabilities.attachments.supported":
             case "capabilities.priority.supported":
             case "capabilities.tasks.supported":
             case "capabilities.alarms.oninvitations.supported":
                 return false;
             case "capabilities.privacy.values":
                 return ["DEFAULT", "PUBLIC", "PRIVATE"];
+            case "capabilities.alarms.maxCount":
+                return 5;
+            case "capabilities.alarms.actionValues":
+                return ["DISPLAY", "EMAIL", "SMS"];
             case "organizerId":
                 if (this.mSession) {
                     return "mailto:" + this.session.userName;
                 }
                 break;
             case "organizerCN":
                 if (this.mSession) {
                     return this.session.fullName;
--- a/calendar/providers/gdata/content/gdata-calendar-event-dialog.xul
+++ b/calendar/providers/gdata/content/gdata-calendar-event-dialog.xul
@@ -32,18 +32,16 @@
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <!DOCTYPE overlay SYSTEM "chrome://gdata-provider/locale/gdata.dtd">
 
-<?xml-stylesheet href="chrome://gdata-provider/skin/gdata-calendar-event-dialog.css" type="text/css"?>
-
 <overlay id="gdata-calendar-event-dialog"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <!-- Privacy items -->
   <menupopup id="options-privacy-menupopup">
     <menuitem id="gdata-options-privacy-default-menuitem"
               insertbefore="options-privacy-public-menuitem,options-privacy-private-menuitem"
               label="&gdata.privacy.default.label;"
new file mode 100644
--- /dev/null
+++ b/calendar/providers/gdata/content/gdata-event-dialog-reminder.css
@@ -0,0 +1,39 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Sun Microsystems code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Philipp Kewisch <mozilla@kewis.ch>
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.reminder-icon[value="SMS"] {
+  list-style-image: url(chrome://gdata-provider/skin/reminder-action-sms.png);
+}
new file mode 100644
--- /dev/null
+++ b/calendar/providers/gdata/content/gdata-event-dialog-reminder.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is Sun Microsystems code.
+   -
+   - The Initial Developer of the Original Code is
+   -   Philipp Kewisch <mozilla@kewis.ch>
+   - Portions created by the Initial Developer are Copyright (C) 2008
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the LGPL or the GPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<?xml-stylesheet type="text/css" href="chrome://gdata-provider/skin/gdata-event-dialog-reminder.css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://gdata-provider/locale/gdata.dtd">
+
+<overlay id="gdata-event-dialog-reminder-overlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <menupopup id="reminder-actions-menupopup">
+    <menuitem id="reminder-action-SMS"
+              class="reminder-icon menuitem-iconic"
+              value="SMS"
+              insertafter="reminder-action-EMAIL reminder-action-DISPLAY"
+              provider="gdata"
+              label="&gdata.reminder.action.sms.label;"/>
+  </menupopup>
+</overlay>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c39f162f854a7c412fab9b6ff38fffdc61754a58
GIT binary patch
literal 488
zc$@*~0T=#>P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzdPzh<R5;6x
zlfO&DKorNnm&7yyB@|s83iTgw6gS<vb#?U*aB^rj{{iXXQt03yI0^0O=;GE<oC>A1
zOEJ*J2%-6P-uFsN(314vc)81apZC3Y?}U^RyNqtPD}x|FyWJM~eFHm=EXyz)4*%_2
z`9-hS>nKH+LWpCvJW_K{ee2+Qy$;K=iUCm+-8LExR4SE?bDE}l5@MQWF@R2<=i#hh
zg==L9Gagssz>e{B{#CAk4$LM@iPnXHWk?Xw`LOUEW#s_FFc8NvgbK8&R^1S*OrR37
zn*ts~sNlPI6{FG%N)TkF<p36mfS1!xZP&%4^-OOoz}N=p2hVs-`WTHydVTot2sT{l
zEvx}e=W3PtWTn8rxkeQzSFo<Zcs$m2GMVViv)OEup!5g~VApZb5>%_zHVFy=yhaAh
z=X2P$4d3_SI1Uzzg$DC~VGUH@|Ab+<pI5-QK+FD6uTd5Prqk*DU@&MdmrEU(10zWi
exUT#57w`*0Xt}|<Lu*t30000<MNUMnLSTXhgxDSc
--- a/calendar/providers/gdata/install.rdf
+++ b/calendar/providers/gdata/install.rdf
@@ -70,12 +70,13 @@
         <em:minVersion>@CALENDAR_VERSION@</em:minVersion>
         <em:maxVersion>@CALENDAR_VERSION@</em:maxVersion>
       </Description>
     </em:requires>
 
     <em:name>Provider for Google Calendar</em:name>
     <em:description>Allows bidirectional access to Google Calendar</em:description>
     <em:creator>Philipp Kewisch</em:creator>
+    <em:contributor>Mark James (http://www.famfamfam.com/lab/icons/silk/)</em:contributor>
     <em:homepageURL>https://addons.mozilla.org/en-US/sunbird/addon/4631</em:homepageURL>
     <em:iconURL>chrome://gdata-provider/content/gcal.png</em:iconURL>
   </Description>
 </RDF>
--- a/calendar/providers/gdata/jar.mn
+++ b/calendar/providers/gdata/jar.mn
@@ -1,15 +1,22 @@
 #filter substitution
 
 gdata-provider.jar:
 % content gdata-provider %content/
-% skin gdata-provider classic/1.0 %skin/
 % overlay chrome://calendar/content/calendarCreation.xul chrome://gdata-provider/content/calendarCreation.xul
 % overlay chrome://sunbird/content/calendar.xul chrome://gdata-provider/content/gdata-migration-overlay.xul    application={718e30fb-e89b-41dd-9da7-e25a45638b28}
 % overlay chrome://messenger/content/messenger.xul chrome://gdata-provider/content/gdata-migration-overlay.xul  application={3550f703-e582-4d05-9a08-453d09bdfdc6}
 % overlay chrome://calendar/content/calendar-event-dialog.xul chrome://gdata-provider/content/gdata-calendar-event-dialog.xul
+% overlay chrome://calendar/content/sun-calendar-event-dialog-reminder.xul chrome://gdata-provider/content/gdata-event-dialog-reminder.xul
+% style chrome://calendar/content/sun-calendar-event-dialog.xul chrome://gdata-provider/skin/gdata-event-dialog-reminder.css
+% style chrome://calendar/content/calendar.xul chrome://gdata-provider/skin/gdata-event-dialog-reminder.css     application={718e30fb-e89b-41dd-9da7-e25a45638b28}
+% style chrome://messenger/content/messenger.xul chrome://gdata-provider/skin/gdata-event-dialog-reminder.css   application={3550f703-e582-4d05-9a08-453d09bdfdc6}
     content/calendarCreation.xul                 (content/calendarCreation.xul)
     content/gdata-calendar-event-dialog.xul      (content/gdata-calendar-event-dialog.xul)
+    content/gdata-event-dialog-reminder.xul      (content/gdata-event-dialog-reminder.xul)
     content/gdata-migration.js                   (content/gdata-migration.js)
     content/gdata-migration-overlay.xul          (content/gdata-migration-overlay.xul)
     content/gdata-migration-wizard.xul           (content/gdata-migration-wizard.xul)
     content/gcal.png                             (content/gcal.png)
+% skin gdata-provider classic/1.0 %skin/
+    skin/reminder-action-sms.png                 (content/reminder-action-sms.png)
+    skin/gdata-event-dialog-reminder.css         (content/gdata-event-dialog-reminder.css)