--- a/calendar/base/content/calendar-dialog-utils.js
+++ b/calendar/base/content/calendar-dialog-utils.js
@@ -611,45 +611,53 @@ var gLastAlarmSelection = 0;
/**
* Load an item's reminders into the dialog
*
* @param item The item to load.
*/
function loadReminder(item) {
// select 'no reminder' by default
- var reminderPopup = document.getElementById("item-alarm");
+ let reminderPopup = document.getElementById("item-alarm");
reminderPopup.selectedIndex = 0;
gLastAlarmSelection = 0;
- if (!item.alarmOffset) {
+
+ // 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) {
return;
}
+ // END TODO ALARMSUPPORT
+
// try to match the reminder setting with the available popup items
- var origin = "1";
- if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
+ let origin = "1";
+ if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_END) {
origin = "-1";
}
- var duration = item.alarmOffset.clone();
- var relation = "END";
+ let duration = alarm.offset.clone();
+ let relation = "END";
if (duration.isNegative) {
duration.isNegative = false;
duration.normalize();
relation = "START";
}
- var matchingItem = null;
- var menuItems = reminderPopup.getElementsByTagName("menuitem");
- var numItems = menuItems.length;
- for (var i=0; i<numItems; i++) {
- var menuitem = menuItems[i];
+ 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 &&
menuitem.getAttribute("relation") == relation) {
- var unit = menuitem.getAttribute("unit");
- var length = menuitem.getAttribute("length");
+ 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;
@@ -671,31 +679,31 @@ function loadReminder(item) {
break;
}
}
} else {
reminderPopup.value = 'custom';
var customReminder =
document.getElementById("reminder-custom-menuitem");
var reminder = {};
- if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
+ if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
reminder.origin = "1";
} else {
reminder.origin = "-1";
}
- var offset = item.alarmOffset.clone();
- var relation = "END";
+ let offset = alarm.offset.clone();
+ relation = "END";
if (offset.isNegative) {
offset.isNegative = false;
offset.normalize();
relation = "START";
}
reminder.relation = relation;
if (offset.minutes) {
- var minutes = 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';
@@ -713,21 +721,20 @@ function loadReminder(item) {
}
/**
* Save the selected reminder into the passed item.
*
* @param item The item save the reminder into.
*/
function saveReminder(item) {
+ item.clearAlarms();
var reminderPopup = document.getElementById("item-alarm");
if (reminderPopup.value == 'none') {
- item.alarmOffset = null;
item.alarmLastAck = null;
- item.alarmRelated = null;
} else {
var menuitem = reminderPopup.selectedItem;
// 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) {
@@ -741,23 +748,26 @@ function saveReminder(item) {
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();
- item.alarmOffset = duration;
+ let alarm = cal.createAlarm();
if (Number(reminder.origin) >= 0) {
- item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+ alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
} else {
- item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
+ alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
}
+
+ alarm.offset = duration;
+ item.addAlarm(alarm);
}
}
/**
* Common update functions for both event dialogs. Called when a reminder has
* been selected from the menulist.
*/
function commonUpdateReminder() {
--- a/calendar/base/content/calendar-dnd-listener.js
+++ b/calendar/base/content/calendar-dnd-listener.js
@@ -32,33 +32,35 @@
* 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");
+
var itemConversion = {
/**
* Converts an email message to a calendar item.
*
* XXX Currently, only the title is taken from the passed message. Aside
* from that, the currently visible message in the preview pane is used.
*
* @param aItem The target calIItemBase.
* @param aMessage The message to convert from
*/
calendarItemFromMessage: function iC_calendarItemFromMessage(aItem, aMessage) {
aItem.calendar = getSelectedCalendar();
aItem.title = aMessage.mime2DecodedSubject;
setDefaultStartEndHour(aItem);
- setDefaultAlarmValues(aItem);
+ cal.alarms.setDefaultValues(aItem);
// XXX It would be great if nsPlainTextParser could take care of this.
function htmlToPlainText(html) {
var texts = html.split(/(<\/?[^>]+>)/);
var text = texts.map(function hTPT_map(string) {
if (string.length > 0 && string[0] == '<') {
var regExpRes = string.match(/^<img.*?alt\s*=\s*['"](.*)["']/i)
if (regExpRes) {
@@ -151,20 +153,19 @@ var itemConversion = {
// Dates and alarms
if (!aEvent.startDate.isDate && !aEvent.endDate.isDate) {
// Dates
item.entryDate = aEvent.startDate.clone();
item.dueDate = aEvent.endDate.clone();
// Alarms
- item.alarmOffset = (aEvent.alarmOffset ?
- aEvent.alarmOffset.clone() :
- null);
- item.alarmRelated = aEvent.alarmRelated;
+ for each (let alarm in aEvent.getAlarms({})) {
+ item.addAlarm(alarm.clone());
+ }
item.alarmLastAck = (aEvent.alarmLastAck ?
aEvent.alarmLastAck.clone() :
null);
}
return item;
},
/**
@@ -190,20 +191,19 @@ var itemConversion = {
if (!item.endDate) {
// Make the event be the default event length if no due date was
// specified.
item.endDate = item.startDate.clone();
item.endDate.minute += getPrefSafe("calendar.event.defaultlength", 60);
}
// Alarms
- item.alarmOffset = (aTask.alarmOffset ?
- aTask.alarmOffset.clone() :
- null);
- item.alarmRelated = aTask.alarmRelated;
+ for each (let alarm in aEvent.getAlarms({})) {
+ item.addAlarm(alarm.clone());
+ }
item.alarmLastAck = (aTask.alarmLastAck ?
aTask.alarmLastAck.clone() :
null);
return item;
}
};
/**
--- a/calendar/base/content/calendar-item-editing.js
+++ b/calendar/base/content/calendar-item-editing.js
@@ -32,16 +32,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");
+
/**
* Creates an event with the calendar event dialog.
*
* @param calendar (optional) The calendar to create the event in
* @param startDate (optional) The event's start date.
* @param endDate (optional) The event's end date.
* @param summary (optional) The event's title.
* @param event (optional) A template event to show in the dialog
@@ -129,17 +131,17 @@ function createEventWithDialog(calendar,
}
event.calendar = calendar || getSelectedCalendar();
if (summary) {
event.title = summary;
}
- setDefaultAlarmValues(event);
+ cal.alarms.setDefaultValues(event);
}
openEventDialog(event, calendar, "new", onNewEvent, null);
}
/**
* Creates a task with the calendar event dialog.
*
* @param calendar (optional) The calendar to create the task in
@@ -178,17 +180,17 @@ function createTodoWithDialog(calendar,
todo.calendar = calendar || getSelectedCalendar();
if (summary)
todo.title = summary;
if (dueDate)
todo.dueDate = dueDate;
- setDefaultAlarmValues(todo);
+ cal.alarms.setDefaultValues(todo);
}
openEventDialog(todo, calendar, "new", onNewItem, null);
}
/**
--- a/calendar/base/content/calendar-summary-dialog.js
+++ b/calendar/base/content/calendar-summary-dialog.js
@@ -33,16 +33,17 @@
* 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/calUtils.jsm");
Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
/**
* Sets up the summary dialog, setting all needed fields on the dialog from the
* item received in the window arguments.
*/
function onLoad() {
var args = window.arguments[0];
var item = args.calendarEvent;
@@ -76,18 +77,19 @@ function onLoad() {
}
}
window.readOnly = calendar.readOnly;
if (!window.readOnly && calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
var attendee = calendar.getInvitedAttendee(item);
if (attendee) {
// if this is an unresponded invitation, preset our default alarm values:
- if (!item.alarmOffset && (attendee.participationStatus == "NEEDS-ACTION")) {
- cal.setDefaultAlarmValues(item);
+ if (!item.getAlarms({}).length &&
+ (attendee.participationStatus == "NEEDS-ACTION")) {
+ cal.alarms.setDefaultValues(item);
}
window.attendee = attendee.clone();
// Since we don't have API to update an attendee in place, remove
// and add again. Also, this is needed if the attendee doesn't exist
// (i.e REPLY on a mailing list)
item.removeAttendee(attendee);
item.addAttendee(window.attendee);
--- a/calendar/base/content/calendar-task-editing.js
+++ b/calendar/base/content/calendar-task-editing.js
@@ -30,16 +30,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");
+
/**
* Used by the "quick add" feature for tasks, for example in the task view or
* the uniinder-todo.
*
* NOTE: many of the following methods are called without taskEdit being the
* |this| object.
*/
@@ -156,17 +158,17 @@ var taskEdit = {
onKeyPress: function tE_onKeyPress(aEvent) {
if (aEvent.keyCode == Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN) {
var edit = aEvent.target;
if (edit.value && edit.value.length > 0) {
var item = createTodo();
item.calendar = getSelectedCalendar();
item.title = edit.value;
edit.value = "";
- setDefaultAlarmValues(item);
+ cal.alarms.setDefaultValues(item);
doTransaction('add', item, item.calendar, null, null);
}
}
},
/**
* Window load function to set up all quick-add textboxes. The texbox must
* have the class "task-edit-field".
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -487,17 +487,17 @@
aProps.AppendElement(getAtomFromService(calendarIdAtom));
// Add item status atom
if (item.status) {
aProps.AppendElement(getAtomFromService("status-" + item.status.toLowerCase()));
}
// Alarm status atom
- if (item.alarmOffset) {
+ if (item.getAlarms({}).length) {
aProps.AppendElement(getAtomFromService("alarm"));
}
// Task categories
var categories = item.getCategories({});
categories.map(formatStringForCSSRule)
.map(getAtomFromService)
.forEach(aProps.AppendElement, aProps);
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -699,17 +699,17 @@ var unifinderTreeView = {
aProps.AppendElement(getAtomFromService(calendarAtom));
// Add item status atom
if (item.status) {
aProps.AppendElement(getAtomFromService("status-" + item.status.toLowerCase()));
}
// Alarm status atom
- if (item.alarmOffset) {
+ if (item.getAlarms({}).length) {
aProps.AppendElement(getAtomFromService("alarm"));
}
// Task categories
item.getCategories({}).map(formatStringForCSSRule)
.map(getAtomFromService)
.forEach(aProps.AppendElement, aProps);
},
--- a/calendar/base/content/calendar-view-core.xml
+++ b/calendar/base/content/calendar-view-core.xml
@@ -186,17 +186,17 @@
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.alarmOffset && getPrefSafe("calendar.alarms.indicator.show", true)) {
+ 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");
@@ -218,17 +218,17 @@
this.setAttribute("progress", getProgressAtom(item));
}
if (this.calendarView &&
item.hashId in this.calendarView.mFlashingEvents) {
this.setAttribute("flashing", "true");
}
- if (item.alarmOffset) {
+ if (item.getAlarms({}).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/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -37,16 +37,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");
+
/**
* Controller for the views
* @see calIcalendarViewController
*/
var calendarViewController = {
QueryInterface: function(aIID) {
if (!aIID.equals(Components.interfaces.calICalendarViewController) &&
!aIID.equals(Components.interfaces.nsISupports)) {
@@ -67,17 +69,17 @@ var calendarViewController = {
if (aStartTime && aEndTime && !aStartTime.isDate && !aEndTime.isDate) {
var event = createEvent();
event.startDate = aStartTime;
event.endDate = aEndTime;
var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
var props = sbs.createBundle("chrome://calendar/locale/calendar.properties");
event.title = props.GetStringFromName("newEvent");
- setDefaultAlarmValues(event);
+ cal.alarms.setDefaultValues(event);
doTransaction('add', event, aCalendar, null, null);
} else {
createEventWithDialog(aCalendar, aStartTime, null, null, null, aForceAllday);
}
},
pendingJobs: [],
--- a/calendar/base/content/dialogs/calendar-invitations-dialog.js
+++ b/calendar/base/content/dialogs/calendar-invitations-dialog.js
@@ -15,32 +15,34 @@
*
* 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):
* Thomas Benisch <thomas.benisch@sun.com>
* Daniel Boelzle <daniel.boelzle@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 ***** */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
/**
* Sets up the invitations dialog from the window arguments, retrieves the
* invitations from the invitations manager.
*/
function onLoad() {
var operationListener = {
onOperationComplete: function oL_onOperationComplete(aCalendar,
@@ -132,18 +134,20 @@ function fillJobQueue(queue) {
var newStatus = richListItem.participationStatus;
var oldStatus = richListItem.initialParticipationStatus;
if (newStatus != oldStatus) {
var actionString = "modify";
var oldCalendarItem = richListItem.calendarItem;
var newCalendarItem = oldCalendarItem.clone();
// set default alarm on unresponded items that have not been declined:
- if (!newCalendarItem.alarmOffset && (oldStatus == "NEEDS-ACTION") && (newStatus != "DECLINED")) {
- cal.setDefaultAlarmValues(newCalendarItem);
+ if (!newCalendarItem.getAlarms({}).length &&
+ (oldStatus == "NEEDS-ACTION") &&
+ (newStatus != "DECLINED")) {
+ cal.alarms.setDefaultValues(newCalendarItem);
}
richListItem.setCalendarItemParticipationStatus(newCalendarItem,
newStatus);
var job = {
action: actionString,
oldItem: oldCalendarItem,
newItem: newCalendarItem
--- a/calendar/base/modules/Makefile.in
+++ b/calendar/base/modules/Makefile.in
@@ -15,16 +15,17 @@
#
# The Initial Developer of the Original Code is
# Sun Microsystems, Inc.
# Portions created by the Initial Developer are Copyright (C) 2008
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Daniel Boelzle <daniel.boelzle@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
@@ -40,16 +41,17 @@ topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
MODULE = calbase
EXTRA_JS_MODULES = \
+ calAlarmUtils.jsm \
calUtils.jsm \
calIteratorUtils.jsm \
calItipUtils.jsm \
calProviderUtils.jsm \
calAuthUtils.jsm \
$(NULL)
include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calAlarmUtils.jsm
@@ -0,0 +1,122 @@
+/* ***** 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 ***** */
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
+EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
+cal.alarms = {
+ /**
+ * Read default alarm settings from user preferences and apply them to the
+ * event/todo passed in.
+ *
+ * @param aItem The item to apply the default alarm values to.
+ */
+ setDefaultValues: function cal_alarm_setDefaultValues(aItem) {
+ let type = cal.isEvent(aItem) ? "event" : "todo";
+ if (cal.getPrefSafe("calendar.alarms.onfor" + type + "s", 0) == 1) {
+ let alarmOffset = cal.createDuration();
+ let alarm = cal.createAlarm();
+ let units = cal.getPrefSafe("calendar.alarms." + type + "alarmunit", "minute");
+ alarmOffset[units] = cal.getPrefSafe("calendar.alarms." + type + "alarmlen", 0);
+ alarmOffset.normalize();
+ alarmOffset.isNegative = true;
+ if (type == "todo" && !aItem.entryDate) {
+ // You can't have an alarm if the entryDate doesn't exist.
+ aItem.entryDate = cal.now();
+ }
+ alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
+ alarm.offset = alarmOffset;
+ aItem.addAlarm(alarm);
+ }
+ },
+
+ /**
+ * Calculate the alarm date for a calIAlarm.
+ *
+ * @param aItem The item used to calculate the alarm date.
+ * @param aAlarm The alarm to calculate the date for.
+ * @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();
+ } else if (aAlarm.related = aAlarm.ALARM_RELATED_END) {
+ let endDate = aItem[cal.calGetEndDateProp(aItem)];
+ returnDate = endDate && endDate.clone();
+ }
+
+ if (returnDate && aAlarm.offset) {
+ returnDate.addDuration(aAlarm.offset);
+ return returnDate;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Calculate the alarm offset for a calIAlarm. The resulting offset is
+ * related to either start or end of the event, depending on the aRelated
+ * parameter.
+ *
+ * @param aItem The item to calculate the offset for.
+ * @param aAlarm The alarm to calculate the offset for.
+ * @param aRelated (optional) A relation constant from calIAlarm. If not
+ * passed, ALARM_RELATED_START will be assumed.
+ * @return The alarm offset.
+ */
+ calculateAlarmOffset: function cal_alarms_calculateAlarmOffset(aItem, aAlarm, aRelated) {
+ if (aAlarm.related == aAlarm.ALARM_RELATED_ABSOLUTE) {
+ let returnDate;
+ if (aRelated === undefined || aRelated == aAlarm.ALARM_RELATED_START) {
+ returnDate = aItem[cal.calGetStartDateProp(aItem)];
+ } else if (aRelated == aAlarm.ALARM_RELATED_END) {
+ returnDate = aItem[cal.calGetEndDateProp(aItem)];
+ }
+ if (returnDate && aAlarm.alarmDate) {
+ return returnDate.subtractDate(aAlarm.alarmDate);
+ }
+
+ return offset;
+ } else {
+ return aAlarm.offset;
+ }
+ }
+};
--- a/calendar/base/modules/calItipUtils.jsm
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -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 ***** */
Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
Components.utils.import("resource://calendar/modules/calIteratorUtils.jsm");
/*
* Scheduling and iTIP helper code;
* don't use deliberately, because it'll be moved into interfaces/components.
*
* May replace the current calItipProcessor.js code soon.
*/
@@ -429,17 +430,17 @@ function setReceivedInfo(item, itipItemI
/**
* Strips user specific data, e.g. categories and alarm settings and returns the stripped item.
*/
function stripUserData(item_) {
let item = item_.clone();
let stamp = item.stampTime;
let lastModified = item.lastModifiedTime;
- item.alarmOffset = null;
+ item.clearAlarms();
item.alarmLastAck = null;
item.setCategories(0, []);
item.deleteProperty("RECEIVED-SEQUENCE");
item.deleteProperty("RECEIVED-DTSTAMP");
let propEnum = item.propertyEnumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
let pname = prop.name;
@@ -462,18 +463,20 @@ function stripUserData(item_) {
*
* @param item the stored calendar item to update
* @param itipItemItem the received item
*/
function updateItem(item, itipItemItem) {
function updateUserData(newItem, item) {
// preserve user settings:
newItem.generation = item.generation;
- newItem.alarmOffset = item.alarmOffset;
- newItem.alarmRelated = item.alarmRelated;
+ newItem.clearAlarms();
+ for each (let alarm in item.getAlarms({})) {
+ newItem.addAlarm(alarm);
+ }
newItem.alarmLastAck = item.alarmLastAck;
let cats = item.getCategories({});
newItem.setCategories(cats.length, cats);
}
let newItem = item.clone();
newItem.icalComponent = itipItemItem.icalComponent;
setReceivedInfo(newItem, itipItemItem);
@@ -790,17 +793,17 @@ ItipFindItemListener.prototype = {
case "PUBLISH": {
let this_ = this;
let action = function(opListener, partStat) {
let newItem = itipItemItem.clone();
setReceivedInfo(newItem, itipItemItem);
newItem.parentItem.calendar = this_.mItipItem.targetCalendar;
if (partStat) {
if (partStat != "DECLINED") {
- cal.setDefaultAlarmValues(newItem);
+ cal.alarms.setDefaultValues(newItem);
}
let att = cal.getInvitedAttendee(newItem);
if (!att) { // fall back to using configured organizer
att = createOrganizer(newItem.calendar);
if (att) {
att.isOrganizer = false;
newItem.addAttendee(att);
}
--- a/calendar/base/public/calIItemBase.idl
+++ b/calendar/base/public/calIItemBase.idl
@@ -39,38 +39,33 @@
#include "nsISupports.idl"
interface nsISimpleEnumerator;
interface nsIVariant;
interface nsIPropertyBag;
+interface calIAlarm;
+interface calIAttachment;
+interface calIAttendee;
interface calICalendar;
-
interface calIDateTime;
-
interface calIDuration;
-
+interface calIIcalComponent;
interface calIRecurrenceInfo;
+interface calIRelation;
-interface calIAttendee;
-
-interface calIAttachment;
-
-interface calIIcalComponent;
-
-interface calIRelation;
//
// calIItemBase
//
// Base for Events, Todos, Journals, etc.
//
-[scriptable, uuid(c2a21f8c-9de6-47d5-912b-d1c423ea5f2e)]
+[scriptable, uuid(9c988b8d-af45-4046-b05e-34417bba9058)]
interface calIItemBase : nsISupports
{
// returns true if this thing is able to be modified;
// if the item is not mutable, attempts to modify
// any data will throw CAL_ERROR_ITEM_IS_IMMUTABLE
readonly attribute boolean isMutable;
// makes this item immutable
@@ -139,37 +134,41 @@ interface calIItemBase : nsISupports
// item will not be reflected in the other.
attribute calIIcalComponent icalComponent;
//
// alarms
//
/**
- * The amount of time from the date (specified by alarmRelated) to offset
- * the alarm's firing time by
+ * Get all alarms assigned to this item
+ *
+ * @param count The number of alarms
+ * @param aAlarms The array of calIAlarms
*/
- attribute calIDuration alarmOffset;
+ void getAlarms(out PRUint32 count, [array, size_is(count), retval] out calIAlarm aAlarms);
- /**
- * One of the ALARM_RELATED constants below.
+ /**
+ * Add an alarm to the item
+ *
+ * @param aAlarm The calIAlarm to add
*/
- attribute unsigned long alarmRelated;
+ void addAlarm(in calIAlarm aAlarm);
/**
- * Corresponds to an alarmOffset that should be based off of the startDate or
- * entryDate (for events and tasks, respectively)
+ * Delete an alarm from the item
+ *
+ * @param aAlarm The calIAlarm to delete
*/
- const unsigned long ALARM_RELATED_START = 0;
+ void deleteAlarm(in calIAlarm aAlarm);
/**
- * Corresponds to an alarmOffset that should be based off of the endDate or
- * dueDate (for events and tasks, respectively)
+ * Clear all alarms from the item
*/
- const unsigned long ALARM_RELATED_END = 1;
+ void clearAlarms();
// The last time this alarm was fired and acknowledged by the user; coerced to UTC.
attribute calIDateTime alarmLastAck;
//
// recurrence
//
attribute calIRecurrenceInfo recurrenceInfo;
--- a/calendar/base/src/calAlarmService.js
+++ b/calendar/base/src/calAlarmService.js
@@ -370,39 +370,49 @@ calAlarmService.prototype = {
unobserveCalendar: function cas_unobserveCalendar(calendar) {
calendar.removeObserver(this.calendarObserver);
this.disposeCalendarTimers([calendar]);
this.notifyObservers("onRemoveAlarmsByCalendar", [calendar]);
},
getAlarmDate: function cas_getAlarmTime(aItem) {
var alarmDate = null;
- if (aItem.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
+ // TODO ALARMSUPPORT This will change as soon as we support multiple
+ // alarms.
+ let alarms = aItem.getAlarms({}).filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+ let alarm = alarms[0]
+ if (!alarm) {
+ // No relative alarm available.
+ return null;
+ }
+ // END TODO ALARMSUPPORT
+
+ if (alarm.related == Components.interfaces.calIAlarm.ALARM_RELATED_START) {
alarmDate = aItem.startDate || aItem.entryDate || aItem.dueDate;
} else {
alarmDate = aItem.endDate || aItem.dueDate || aItem.entryDate;
}
- if (!aItem.alarmOffset || !alarmDate) {
+ if (!alarmDate) {
// If there is no alarm offset, or no date the alarm offset could be
// relative to, then there is no valid alarm.
return null;
}
alarmDate = alarmDate.clone();
// 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 (alarmDate.isDate) {
alarmDate = alarmDate.getInTimezone(this.timezone);
alarmDate.isDate = false;
}
- var offset = aItem.alarmOffset;
+ let offset = alarm.offset;
alarmDate.addDuration(offset);
alarmDate = alarmDate.getInTimezone(UTC());
return alarmDate;
},
addAlarm: function cas_addAlarm(aItem) {
--- a/calendar/base/src/calItemBase.js
+++ b/calendar/base/src/calItemBase.js
@@ -49,23 +49,25 @@ Components.utils.import("resource://cale
function calItemBase() {
cal.ASSERT(false, "Inheriting objects call initItemBase()!");
}
calItemBase.prototype = {
mPropertyParams: null,
mIsProxy: false,
+ mAlarms: null,
// initialize this class's members
initItemBase: function cib_initItemBase() {
this.wrappedJSObject = this;
this.mProperties = new calPropertyBag();
this.mPropertyParams = {};
this.mProperties.setProperty("CREATED", jsDateToDateTime(new Date()));
+ this.mAlarms = [];
},
QueryInterface: function (aIID) {
return doQueryInterface(this, calItemBase.prototype, aIID,
[Components.interfaces.calIItemBase]);
},
mHashId: null,
@@ -166,19 +168,20 @@ calItemBase.prototype = {
for each (let [propKey, propValue] in this.mProperties) {
if (propValue instanceof Components.interfaces.calIDateTime &&
propValue.isMutable) {
propValue.makeImmutable();
}
}
- if (this.mAlarmOffset) {
- this.mAlarmOffset.makeImmutable();
+ for (let i = 0; i < this.mAlarms.length; i++) {
+ this.mAlarms[i].makeImmutable();
}
+
if (this.mAlarmLastAck) {
this.mAlarmLastAck.makeImmutable();
}
this.ensureNotDirty();
this.mImmutable = true;
},
@@ -238,55 +241,39 @@ calItemBase.prototype = {
// xxx todo: the below two need proper cloning,
// calIAttachment::item, calIRelation::item are wrong
// bug 466439
m.mAttachments = this.getAttachments({});
m.mRelations = this.getRelations({});
m.mCategories = this.getCategories({});
- // Clone any alarm info that exists, set it to null if it doesn't
- let alarmOffset = this.alarmOffset;
- if (alarmOffset) {
- alarmOffset = alarmOffset.clone();
+ for each (let alarm in this.mAlarms) {
+ // Clone alarms into new item, assume the alarms from the old item
+ // are valid and don't need validation.
+ m.addAlarm(alarm.clone(), true);
}
- m.mAlarmOffset = alarmOffset;
let alarmLastAck = this.alarmLastAck;
if (alarmLastAck) {
alarmLastAck = alarmLastAck.clone();
}
m.mAlarmLastAck = alarmLastAck;
- m.alarmRelated = this.alarmRelated;
-
m.mDirty = this.mDirty;
return m;
},
- get alarmOffset() {
- if (this.mIsProxy && (this.mAlarmOffset === undefined)) {
- return this.parentItem.alarmOffset;
- } else {
- return this.mAlarmOffset;
- }
- },
-
- set alarmOffset(aValue) {
- this.modify();
- return (this.mAlarmOffset = aValue);
- },
-
mAlarmLastAck: null,
- get alarmLastAck cib_get_alarmLastAck() {
+ get alarmLastAck cIB_get_alarmLastAck() {
return this.mAlarmLastAck;
},
- set alarmLastAck cib_set_alarmLastAck(aValue) {
+ set alarmLastAck cIB_set_alarmLastAck(aValue) {
this.modify();
if (aValue && !aValue.timezone.isUTC) {
aValue = aValue.getInTimezone(UTC());
}
return (this.mAlarmLastAck = aValue);
},
get lastModifiedTime() {
@@ -693,45 +680,40 @@ calItemBase.prototype = {
rec = new calRecurrenceInfo();
rec.item = this;
}
rec.appendRecurrenceItem(ritem);
}
}
this.mRecurrenceInfo = rec;
- this.mAlarmOffset = null;
- this.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
- this.mAlarmLastAck = null;
-
- let alarmComp = icalcomp.getFirstSubcomponent("VALARM");
- if (alarmComp) {
- let triggerProp = alarmComp.getFirstProperty("TRIGGER");
- if (triggerProp) {
- this.mAlarmOffset = cal.createDuration(triggerProp.valueAsIcalString);
-
- let related = triggerProp.getParameter("RELATED");
- if (related && related == "END") {
- this.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
+ for (let alarmComp in cal.ical.subcomponentIterator(icalcomp, "VALARM")) {
+ let alarm = cal.createAlarm();
+ try {
+ alarm.icalComponent = alarmComp;
+ if (alarm.related != Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE) {
+ // TODO ALARMSUPPORT unconditionally add alarm when we
+ // support multiple alarms.
+ this.addAlarm(alarm, true);
}
-
- let email = alarmComp.getFirstProperty("X-EMAILADDRESS");
- if (email) {
- this.setProperty("alarmEmailAddress", email.value);
- }
- } else {
- // Really, really old Sunbird/Calendar versions didn't give us a
- // trigger.
- cal.ERROR("No trigger property for alarm on item: " + this.id);
+ } catch (e) {
+ Components.utils.reportError("Invalid alarm for item: " +
+ this.id + " (" +
+ alarmComp.serializeToICS() + ")");
}
- let lastAck = icalcomp.getFirstProperty("X-MOZ-LASTACK");
- if (lastAck) {
- this.mAlarmLastAck = cal.createDateTime(lastAck.value);
- }
+ // TODO ALARMSUPPORT remove break when we fully support multiple
+ // alarms
+ break;
+ }
+
+ let lastAck = icalcomp.getFirstProperty("X-MOZ-LASTACK");
+ this.mAlarmLastAck = null;
+ if (lastAck) {
+ this.mAlarmLastAck = cal.createDateTime(lastAck.value);
}
this.mDirty = false;
},
importUnpromotedProperties: function (icalcomp, promoted) {
for (let prop in cal.ical.propertyIterator(icalcomp)) {
let propName = prop.propertyName;
@@ -761,16 +743,17 @@ calItemBase.prototype = {
return (gen ? parseInt(gen, 10) : 0);
},
set generation(aValue) {
return this.setProperty("X-MOZ-GENERATION", String(aValue));
},
fillIcalComponentFromBase: function (icalcomp) {
this.ensureNotDirty();
+ let icssvc = cal.getIcsService();
this.mapPropsToICS(icalcomp, this.icsBasePropMap);
let org = this.organizer;
if (org) {
icalcomp.addProperty(org.icalProperty);
}
@@ -788,65 +771,79 @@ calItemBase.prototype = {
if (this.mRecurrenceInfo) {
for each (let ritem in this.mRecurrenceInfo.getRecurrenceItems({})) {
icalcomp.addProperty(ritem.icalProperty);
}
}
for each (let cat in this.getCategories({})) {
- let catprop = getIcsService().createIcalProperty("CATEGORIES");
+ let catprop = icssvc.createIcalProperty("CATEGORIES");
catprop.value = cat;
icalcomp.addProperty(catprop);
}
- let alarmOffset = this.alarmOffset;
- if (alarmOffset) {
- let icssvc = cal.getIcsService();
- let alarmComp = icssvc.createIcalComponent("VALARM");
-
- let triggerProp = icssvc.createIcalProperty("TRIGGER");
- triggerProp.valueAsIcalString = alarmOffset.icalString;
-
- if (this.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
- triggerProp.setParameter("RELATED", "END");
- }
-
- alarmComp.addProperty(triggerProp);
-
- // We don't use this, but the ics-spec requires it
- let descProp = icssvc.createIcalProperty("DESCRIPTION");
- descProp.value = "Mozilla Alarm: "+ this.title;
- alarmComp.addProperty(descProp);
-
- let actionProp = icssvc.createIcalProperty("ACTION");
- actionProp.value = "DISPLAY";
-
- let alarmEmailAddress = this.getProperty("alarmEmailAddress");
- if (alarmEmailAddress) {
- let emailProp = icssvc.createIcalProperty("X-EMAILADDRESS");
- emailProp.value = alarmEmailAddress;
- actionProp.value = "EMAIL";
- alarmComp.addProperty(emailProp);
- }
-
- alarmComp.addProperty(actionProp);
-
- icalcomp.addSubcomponent(alarmComp);
+ for each (let alarm in this.mAlarms) {
+ icalcomp.addSubcomponent(alarm.icalComponent);
}
let alarmLastAck = this.alarmLastAck;
if (alarmLastAck) {
let lastAck = cal.getIcsService().createIcalProperty("X-MOZ-LASTACK");
// - should we further ensure that those are UTC or rely on calAlarmService doing so?
lastAck.value = alarmLastAck.icalString;
icalcomp.addProperty(lastAck);
}
},
+ getAlarms: function cIB_getAlarms(aCount) {
+ if (typeof aCount != "object") {
+ throw Components.results.NS_ERROR_XPC_NEED_OUT_OBJECT;
+ }
+
+ aCount.value = this.mAlarms.length;
+ return this.mAlarms;
+ },
+
+ /**
+ * Adds an alarm. The second parameter is for internal use only, i.e not
+ * provided on the interface.
+ *
+ * @parm aDoNotValidate Don't serialize the component to check for
+ * errors.
+ */
+ addAlarm: function cIB_addAlarm(aAlarm, aDoNotValidate) {
+ if (!aDoNotValidate) {
+ try {
+ // Trigger the icalComponent getter to make sure the alarm is valid.
+ aAlarm.icalComponent;
+ } catch (e) {
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ this.modify();
+ this.mAlarms.push(aAlarm);
+ },
+
+ deleteAlarm: function cIB_deleteAlarm(aAlarm) {
+ this.modify();
+ for (let i = 0; i < this.mAlarms.length; i++) {
+ if (compareObjects(this.mAlarms[i], aAlarm, Components.interfaces.calIAlarm)) {
+ this.mAlarms.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ clearAlarms: function cIB_clearAlarms() {
+ this.modify();
+ this.mAlarms = [];
+ },
+
getOccurrencesBetween: function cIB_getOccurrencesBetween(aStartDate, aEndDate, aCount) {
if (this.recurrenceInfo) {
return this.recurrenceInfo.getOccurrences(aStartDate, aEndDate, 0, aCount);
}
if (checkIfInRange(this, aStartDate, aEndDate)) {
aCount.value = 1;
return [this];
--- a/calendar/base/src/calItipItem.js
+++ b/calendar/base/src/calItipItem.js
@@ -146,17 +146,17 @@ calItipItem.prototype = {
// should not be sent out and should not be relevant for incoming messages
// - faked master items
// so clean them out:
function cleanItem(item) {
// the following changes will bump LAST-MODIFIED/DTSTAMP, we want to preserve the originals:
let stamp = item.stampTime;
let lastModified = item.lastModifiedTime;
- item.alarmOffset = null;
+ item.clearAlarms();
item.alarmLastAck = null;
item.deleteProperty("RECEIVED-SEQUENCE");
item.deleteProperty("RECEIVED-DTSTAMP");
let propEnum = item.propertyEnumerator;
while (propEnum.hasMoreElements()) {
let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
let pname = prop.name;
if (pname != "X-MOZ-FAKED-MASTER" && pname.substr(0, "X-MOZ-".length) == "X-MOZ-") {
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -1832,64 +1832,16 @@ function binaryInsert(itemArray, item, c
comptor(itemArray[Math.min(newIndex, itemArray.length - 1)], item) != 0) {
// Only add the item if duplicates should not be discarded, or if
// they should and itemArray[newIndex] == item.
itemArray.splice(newIndex, 0, item);
}
return newIndex;
}
-/**
- * Read default alarm settings from user preferences and apply them to
- * the event/todo passed in.
- *
- * @param aItem The event or todo the settings should be applied to.
- */
-function setDefaultAlarmValues(aItem)
-{
- var prefService = Components.classes["@mozilla.org/preferences-service;1"]
- .getService(Components.interfaces.nsIPrefService);
- var alarmsBranch = prefService.getBranch("calendar.alarms.");
-
- if (isEvent(aItem)) {
- try {
- if (alarmsBranch.getIntPref("onforevents") == 1) {
- let alarmOffset = createDuration();
- let units = alarmsBranch.getCharPref("eventalarmunit");
- alarmOffset[units] = alarmsBranch.getIntPref("eventalarmlen");
- alarmOffset.isNegative = true;
- aItem.alarmOffset = alarmOffset;
- aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
- }
- } catch (ex) {
- Components.utils.reportError(
- "Failed to apply default alarm settings to event: " + ex);
- }
- } else if (isToDo(aItem)) {
- try {
- if (alarmsBranch.getIntPref("onfortodos") == 1) {
- // You can't have an alarm if the entryDate doesn't exist.
- if (!aItem.entryDate) {
- aItem.entryDate = getSelectedDay() &&
- getSelectedDay().clone() || now();
- }
- let alarmOffset = createDuration();
- let units = alarmsBranch.getCharPref("todoalarmunit");
- alarmOffset[units] = alarmsBranch.getIntPref("todoalarmlen");
- alarmOffset.isNegative = true;
- aItem.alarmOffset = alarmOffset;
- aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
- }
- } catch (ex) {
- Components.utils.reportError(
- "Failed to apply default alarm settings to task: " + ex);
- }
- }
-}
-
function getCompositeCalendar() {
if (getCompositeCalendar.mObject === undefined) {
getCompositeCalendar.mObject = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
.createInstance(Components.interfaces.calICompositeCalendar);
getCompositeCalendar.mObject.prefPrefix = 'calendar-main';
try {
if (gCalendarStatusFeedback) {
--- a/calendar/import-export/calOutlookCSVImportExport.js
+++ b/calendar/import-export/calOutlookCSVImportExport.js
@@ -17,16 +17,17 @@
* The Initial Developer of the Original Code is
* Jussi Kukkonen <jussi.kukkonen@welho.com>.
* Portions created by the Initial Developer are Copyright (C) 2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Michiel van Leeuwen <mvl@exedo.nl>
* Ernst Herbst <hb@calen.de>
+ * 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
@@ -330,37 +331,30 @@ function csv_importFromStream(aStream, a
}
event.startDate = sDate;
event.endDate = eDate;
// Exists an alarm true/false column?
if ("alarmIndex" in args) {
// Is an alarm wanted for this event?
if (locale.valueTrue == eventFields[args.alarmIndex]) {
- var alarmDate =
+ let alarmDate =
parseDateTime(eventFields[args.alarmDateIndex],
eventFields[args.alarmTimeIndex],
locale);
- // Set to default if non valid alarmDate was achieved
+ // Only set the alarm if a date was parsed
if (alarmDate) {
- event.alarmOffset = alarmDate.subtractDate(sDate);
+ let alarm = cal.createAlarm();
+ alarm.related = alarm.ALARM_RELATED_ABSOLUTE;
+ alarm.alarmDate = alarmDate;
+ event.addAlarm(alarm);
} else {
- var alarmOffset = Components
- .classes["@mozilla.org/calendar/duration;1"]
- .createInstance(Components
- .interfaces.calIDuration);
- var units = getPrefSafe("calendar.alarms.eventalarmunit",
- "minutes");
- alarmOffset[units] = getPrefSafe("calendar.alarms.eventalarmlen",
- 15);
- alarmOffset.isNegative = true;
- event.alarmOffset = alarmOffset;
+ // XXX Is this really wanted here?
+ cal.alarms.setDefaultValues(event);
}
- event.alarmRelated = Components.interfaces.calIItemBase
- .ALARM_RELATED_START;
}
}
// Using the "Private" field only for getting privacy status.
// "Sensitivity" is neglected for now.
if ("privateIndex" in args) {
if (locale.valueTrue == eventFields[args.privateIndex]) {
event.privacy = "PRIVATE";
@@ -508,31 +502,28 @@ function csv_exportToStream(aStream, aCo
}
var line = [];
line.push(item.title);
line.push(dateString(item.startDate));
line.push(timeString(item.startDate));
line.push(dateString(item.endDate));
line.push(timeString(item.endDate));
line.push(item.startDate.isDate ? localeEn.valueTrue : localeEn.valueFalse);
- if (item.alarmOffset) {
- line.push(localeEn.valueTrue);
- var fireTime;
- if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
- fireTime = item.startDate.clone();
+ let alarms = item.getAlarms({});
+ if (alarms.length) {
+ let alarmDate = cal.alarms.calculateAlarmDate(item, alarms[0]);
+ if (alarmDate) {
+ line.push(localeEn.valueTrue);
+ line.push(dateString(alarmDate));
+ line.push(timeString(alarmDate));
} else {
- fireTime = item.endDate.clone();
+ line.push(localeEn.valueFalse);
+ line.push("");
+ line.push("");
}
- fireTime.addDuration(item.alarmOffset);
- line.push(dateString(fireTime));
- line.push(timeString(fireTime));
- } else {
- line.push(localeEn.valueFalse);
- line.push("");
- line.push("");
}
line.push(txtString(categoriesArrayToString(item.getCategories({})))); // xxx todo: what's the correct way to encode ',' in csv?, how are multi-values expressed?
line.push(txtString(item.getProperty("DESCRIPTION")));
line.push(txtString(item.getProperty("LOCATION")));
line.push((item.privacy=="PRIVATE") ? localeEn.valueTrue : localeEn.valueFalse);
line = line.map(function(v) {
v = String(v).replace(/"/g,'""');
--- a/calendar/providers/gdata/components/calGoogleUtils.js
+++ b/calendar/providers/gdata/components/calGoogleUtils.js
@@ -493,44 +493,55 @@ function ItemToXMLEntry(aItem, aAuthorEm
entry.gCal::sendEventNotifications.@value = (notify ? "true" : "false");
// gd:when
var duration = aItem.endDate.subtractDate(aItem.startDate);
entry.gd::when.@startTime = toRFC3339(aItem.startDate);
entry.gd::when.@endTime = toRFC3339(aItem.endDate);
// gd:reminder
- if (aItem.alarmOffset && selfIsOrganizer) {
- var gdReminder = <gd:reminder xmlns:gd={gd}/>;
- var alarmOffset = aItem.alarmOffset.clone();
-
- if (aItem.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
- // Google always uses an alarm offset related to the start time
- alarmOffset.addDuration(duration);
- }
-
- gdReminder.@minutes = -aItem.alarmOffset.inSeconds / 60;
- gdReminder.@method = "alert";
+ let alarms = aItem.getAlarms({});
+ let actionMap = {
+ DISPLAY: "alert",
+ EMAIL: "email",
+ SMS: "sms"
+ };
+ if (selfIsOrganizer) {
+ for (let i = 0; i < 5 && i < alarms.length; i++) {
+ let alarm = alarms[i];
+ let gdReminder = <gd:reminder xmlns:gd={gd}/>;
+ if (alarm.related == alarm.ALARM_RELATED_ABSOLUTE) {
+ // Setting an absolute date can be done directly. Google will take
+ // care of calculating the offset.
+ gdReminder.@absoluteTime = toRFC3339(alarm.alarmDate);
+ } else {
+ let alarmOffset = alarm.offset;
+ if (alarm.related == alarm.ALARM_RELATED_END) {
+ // Google always uses an alarm offset related to the start time
+ // for relative alarms.
+ alarmOffset = alarmOffset.clone();
+ alarmOffset.addDuration(duration);
+ }
- if (aItem.recurrenceInfo) {
- // On recurring items, set the reminder directly in the <entry> tag.
- entry.gd::reminder += gdReminder;
- } else {
- // Otherwise, its a child of the gd:when element
- entry.gd::when.gd::reminder += gdReminder;
+ gdReminder.@minutes = -alarmOffset.inSeconds / 60;
+ gdReminder.@method = actionMap[alarm.action] || "alert";
+ }
+
+
+ if (aItem.recurrenceInfo) {
+ // On recurring items, set the reminder directly in the <entry> tag.
+ entry.gd::reminder += gdReminder;
+ } else {
+ // Otherwise, its a child of the gd:when element
+ entry.gd::when.gd::reminder += gdReminder;
+ }
}
- } else if (aItem.alarmOffset) {
+ } else if (alarms.length) {
// We need to reset this so the item gets returned correctly.
- aItem.alarmOffset = null;
- }
-
- // saved alarms
- var otherAlarms = aItem.getProperty("X-GOOGLE-OTHERALARMS");
- for each (var alarm in otherAlarms) {
- entry.gd::when.gd::reminder += new XML(alarm);
+ aItem.clearAlarms();
}
// gd:extendedProperty (alarmLastAck)
addExtendedProperty("X-MOZ-LASTACK", toRFC3339(aItem.alarmLastAck));
// XXX While Google now supports multiple alarms and alarm values, we still
// need to fix bug 353492 first so we can better take care of finding out
// what alarm is used for snoozing.
@@ -646,93 +657,114 @@ function relevantFieldsMatch(a, b) {
if (a.id != b.id ||
a.title != b.title ||
a.status != b.status ||
a.privacy != b.privacy) {
return false;
}
function compareNotNull(prop) {
- var ap = a[prop];
- var bp = b[prop];
+ let ap = a[prop];
+ let bp = b[prop];
return (ap && !bp || !ap && bp ||
(typeof(ap) == 'object' && ap && bp &&
ap.compare && ap.compare(bp)));
}
// Object flat values
- if (compareNotNull("alarmOffset") ||
- compareNotNull("alarmLastAck") ||
- compareNotNull("recurrenceInfo") ||
+ if (compareNotNull("recurrenceInfo") ||
/* Compare startDate and endDate */
compareNotNull("startDate") ||
compareNotNull("endDate") ||
(a.startDate.isDate != b.startDate.isDate) ||
(a.endDate.isDate != b.endDate.isDate)) {
return false;
}
// Properties
const kPROPERTIES = ["DESCRIPTION", "TRANSP", "X-GOOGLE-EDITURL",
"LOCATION", "X-MOZ-SNOOZE-TIME"];
- for each (var p in kPROPERTIES) {
+ for each (let p in kPROPERTIES) {
// null and an empty string should be handled as non-relevant
if ((a.getProperty(p) || "") != (b.getProperty(p) || "")) {
return false;
}
}
// categories
- var aCat = a.getCategories({});
- var bCat = b.getCategories({});
+ let aCat = a.getCategories({});
+ let bCat = b.getCategories({});
if ((aCat.length != bCat.length) ||
aCat.some(function notIn(cat) { return (bCat.indexOf(cat) == -1); })) {
return false;
}
// attendees and organzier
- var aa = a.getAttendees({});
- var ab = b.getAttendees({});
+ let aa = a.getAttendees({});
+ let ab = b.getAttendees({});
if (aa.length != ab.length) {
return false;
}
if ((a.organizer && !b.organizer) ||
(!a.organizer && b.organizer) ||
(a.organizer && b.organizer && a.organizer.id != b.organizer.id)) {
return false;
}
// go through attendees in a, check if its id is in b
- for each (var attendee in aa) {
- var ba = b.getAttendeeById(attendee.id);
+ for each (let attendee in aa) {
+ let ba = b.getAttendeeById(attendee.id);
if (!ba ||
ba.participationStatus != attendee.participationStatus ||
ba.commonName != attendee.commonName ||
ba.isOrganizer != attendee.isOrganizer ||
ba.role != attendee.role) {
return false;
}
}
+ // Alarms
+ aa = a.getAlarms({});
+ ab = b.getAlarms({});
+
+ if (aa.length != ab.length) {
+ return false;
+ }
+
+ let alarmMap = {};
+ for each (let alarm in aa) {
+ alarmMap[alarm.icalString] = true;
+ }
+ let found = 0;
+ for each (let alarm in ab) {
+ if (alarm.icalString in alarmMap) {
+ found++;
+ }
+ }
+
+ if (found != ab.length) {
+ return false;
+ }
+
// Recurrence Items
if (a.recurrenceInfo) {
- var ra = a.recurrenceInfo.getRecurrenceItems({});
- var rb = b.recurrenceInfo.getRecurrenceItems({});
+ let ra = a.recurrenceInfo.getRecurrenceItems({});
+ let rb = b.recurrenceInfo.getRecurrenceItems({});
// If we have more or less, it definitly changed.
if (ra.length != rb.length) {
return false;
}
// I assume that if the recurrence pattern has not changed, the order
// of the recurrence items should not change. Anything more will be
// very expensive.
- for (var i=0; i < ra.length; i++) {
+ for (let i = 0; i < ra.length; i++) {
if (ra[i].icalProperty.icalString !=
rb[i].icalProperty.icalString) {
return false;
}
}
}
return true;
@@ -833,86 +865,64 @@ function XMLEntryToItem(aXMLEntry, aTime
aXMLEntry.gd::transparency.@value.toString()
.substring(39).toUpperCase());
// gd:eventStatus
item.status = aXMLEntry.gd::eventStatus.@value.toString()
.substring(39).toUpperCase();
// gd:reminder (preparation)
- // Google's alarms are always related to the start
- item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+ // If a reference item was passed, it may already contain alarms. Since
+ // we have no alarm id or such and the alarms are contained in every
+ // feed, we can go ahead and clear the alarms here.
+ item.clearAlarms();
/**
- * Helper function to parse all reminders in a tagset. This sets the
- * item's alarm, and also saves all other alarms using the
- * X-GOOGLE-OTHERALARMS property.
+ * Helper function to parse all reminders in a tagset.
*
* @param reminderTags The tagset to parse.
*/
function parseReminders(reminderTags) {
if (aXMLEntry.gd::who.(@rel.substring(33) == "event.organizer")
.@email.toString() != aCalendar.googleCalendarName) {
// We are not the organizer, so its not smart to set alarms on
// this event.
return;
}
- var lastAlarm;
- var otherAlarms = [];
- // Go through all reminder tags given and pick the best alarm.
- for each (var reminder in reminderTags) {
- // We are only intrested in "alert" reminders. Other types
- // include sms and email alerts, but thats not the point here.
- if (reminder.@method == "alert") {
+ const actionMap = {
+ email: "EMAIL",
+ alert: "DISPLAY",
+ sms: "SMS"
+ };
+ for each (let reminderTag in reminderTags) {
+ let alarm = cal.createAlarm();
+ alarm.action = actionMap[reminderTag.@method] || "DISPLAY";
+ if (reminderTag.@absoluteTime.toString()) {
+ alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_ABSOLUTE;
+ let absolute = fromRFC3339(reminderTag.@absoluteTime,
+ aTimezone);
+ alarm.alarmDate = absolute;
+ } else {
+ alarm.related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
let alarmOffset = cal.createDuration();
-
- if (reminder.@absoluteTime.toString()) {
- var absolute = fromRFC3339(reminder.@absoluteTime,
- aTimezone);
- alarmOffset = startDate.subtractDate(absolute);
- } else if (reminder.@days.toString()) {
- alarmOffset.days = -reminder.@days;
- } else if (reminder.@hours.toString()) {
- alarmOffset.hours = -reminder.@hours;
- } else if (reminder.@minutes.toString()) {
- alarmOffset.minutes = -reminder.@minutes;
+ if (reminderTag.@days.toString()) {
+ alarmOffset.days = -reminderTag.@days;
+ } else if (reminderTag.@hours.toString()) {
+ alarmOffset.hours = -reminderTag.@hours;
+ } else if (reminderTag.@minutes.toString()) {
+ alarmOffset.minutes = -reminderTag.@minutes;
} else {
// Invalid alarm, skip it
continue;
}
alarmOffset.normalize();
-
- // If there is more than one alarm, we could either take the
- // alarm closest to the event or the alarm furthest to the
- // event. Let the user decide (use a property)
- var useClosest = getPrefSafe("calendar.google.alarmClosest",
- true);
- if (!item.alarmOffset ||
- (useClosest &&
- alarmOffset.compare(item.alarmOffset) > 0) ||
- (!useClosest &&
- alarmOffset.compare(item.alarmOffset) < 0)) {
-
- item.alarmOffset = alarmOffset;
- if (lastAlarm) {
- // If there was already an alarm, then it is now one
- // of the other alarms.
- otherAlarms.push(lastAlarm.toXMLString());
- }
- lastAlarm = reminder;
- // Don't push the reminder below, since we might be
- // keeping this one as our item's alarmOffset.
- continue;
- }
+ alarm.offset = alarmOffset;
}
- otherAlarms.push(reminder.toXMLString());
+ item.addAlarm(alarm);
}
-
- // Save other alarms that were set so we don't loose them
- item.setProperty("X-GOOGLE-OTHERALARMS", otherAlarms);
}
// gd:when
var recurrenceInfo = aXMLEntry.gd::recurrence.toString();
if (recurrenceInfo.length == 0) {
// If no recurrence information is given, then there will only be
// one gd:when tag. Otherwise, we will be parsing the startDate from
// the recurrence information.
@@ -1207,48 +1217,79 @@ function LOGitem(item) {
rstr += "\tExceptions:\n";
var exids = item.recurrenceInfo.getExceptionIds({});
for each (var exc in exids) {
rstr += "\t\t" + exc + "\n";
}
}
+ let astr = "\n";
+ let alarms = item.getAlarms({});
+ for each (let alarm in alarms) {
+ astr += "\t\t" + LOGalarm(alarm) + "\n";
+ }
+
+
LOG("Logging calIEvent:" +
"\n\tid:" + item.id +
"\n\tediturl:" + item.getProperty("X-GOOGLE-EDITURL") +
"\n\tcreated:" + item.getProperty("CREATED") +
"\n\tupdated:" + item.getProperty("LAST-MODIFIED") +
"\n\ttitle:" + item.title +
"\n\tcontent:" + item.getProperty("DESCRIPTION") +
"\n\ttransparency:" + item.getProperty("TRANSP") +
"\n\tstatus:" + item.status +
"\n\tstartTime:" + item.startDate.toString() +
"\n\tendTime:" + item.endDate.toString() +
"\n\tlocation:" + item.getProperty("LOCATION") +
"\n\tprivacy:" + item.privacy +
- "\n\talarmOffset:" + item.alarmOffset +
"\n\talarmLastAck:" + item.alarmLastAck +
"\n\tsnoozeTime:" + item.getProperty("X-MOZ-SNOOZE-TIME") +
"\n\tisOccurrence: " + (item.recurrenceId != null) +
"\n\tOrganizer: " + LOGattendee(item.organizer) +
"\n\tAttendees: " + attendeeString +
- "\n\trecurrence: " + (rstr.length > 1 ? "yes: " + rstr : "no"));
+ "\n\trecurrence: " + (rstr.length > 1 ? "yes: " + rstr : "no") +
+ "\n\talarms: " + (astr.length > 1 ? "yes: " + astr : "no"));
}
function LOGattendee(aAttendee, asString) {
return aAttendee &&
("\n\t\tID: " + aAttendee.id +
"\n\t\t\tName: " + aAttendee.commonName +
"\n\t\t\tRsvp: " + aAttendee.rsvp +
"\n\t\t\tIs Organizer: " + (aAttendee.isOrganizer ? "yes" : "no") +
"\n\t\t\tRole: " + aAttendee.role +
"\n\t\t\tStatus: " + aAttendee.participationStatus);
}
+function LOGalarm(aAlarm) {
+ if (!aAlarm) {
+ return "";
+ }
+
+ let enumerator = aAlarm.propertyEnumerator;
+ let xpropstr = "";
+ while (enumerator.hasMoreElements()) {
+ let el = enumerator.getNext();
+ xpropstr += "\n\t\t\t" + el.key + ":" + el.value;
+ }
+
+ return ("\n\t\tAction: " + aAlarm.action +
+ "\n\t\tOffset: " + (aAlarm.offset && aAlarm.offset.toString()) +
+ "\n\t\talarmDate: " + (aAlarm.alarmDate && aAlarm.alarmDate.toString()) +
+ "\n\t\trelated: " + aAlarm.related +
+ "\n\t\trepeat: " + aAlarm.repeat +
+ "\n\t\trepeatOffset: " + (aAlarm.repeatOffset && aAlarm.repeatOffset.toString()) +
+ "\n\t\trepeatDate: " + (aAlarm.repeatDate && aAlarm.repeatDate.toString()) +
+ "\n\t\tdescription: " + aAlarm.description +
+ "\n\t\tsummary: " + aAlarm.summary +
+ "\n\t\tproperties: " + (xpropstr.length > 0 ? "yes:" + xpropstr : "no"));
+}
+
function LOGinterval(aInterval) {
const fbtypes = Components.interfaces.calIFreeBusyInterval;
if (aInterval.freeBusyType == fbtypes.FREE) {
type = "FREE";
} else if (aInterval.freeBusyType == fbtypes.BUSY) {
type = "BUSY";
} else {
type = aInterval.freeBusyType + "(UNKNOWN)";
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -40,16 +40,18 @@
* 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/calProviderUtils.jsm");
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
const kStorageServiceContractID = "@mozilla.org/storage/service;1";
const kStorageServiceIID = Components.interfaces.mozIStorageService;
const kCalICalendar = Components.interfaces.calICalendar;
const kCalAttendeeContractID = "@mozilla.org/calendar/attendee;1";
const kCalIAttendee = Components.interfaces.calIAttendee;
var CalAttendee;
@@ -1728,48 +1730,56 @@ calStorageCalendar.prototype = {
item.status = row.ical_status;
if (row.alarm_time) {
// Old (schema version 4) data, need to convert this nicely to the
// new alarm interface. Eventually, we're going to want to be able
// to deal with both types of data in a calIAlarm interface, but
// not yet. Leaving this column around though may help ease that
// transition in the future.
- var alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
- var time;
- var related = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+ let alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
+ let time;
+ let related = Components.interfaces.calIAlarm.ALARM_RELATED_START;
if (isEvent(item)) {
time = newDateTime(row.event_start, row.event_start_tz);
} else { //tasks
if (row.todo_entry) {
time = newDateTime(row.todo_entry, row.todo_entry_tz);
} else if (row.todo_due) {
- related = Components.interfaces.calIItemBase.ALARM_RELATED_END;
+ related = Components.interfaces.calIAlarm.ALARM_RELATED_END;
time = newDateTime(row.todo_due, row.todo_due_tz);
}
}
if (time) {
- var duration = alarmTime.subtractDate(time);
- item.alarmOffset = duration;
- item.alarmRelated = related;
+ // TODO ALARMSUPPORT for now just convert this to a relative
+ // alarm. Will change when supporting multiple alarms.
+ item.clearAlarms();
+ let alarm = cal.createAlarm();
+ let duration = alarmTime.subtractDate(time);
+ alarm.related = related;
+ alarm.offset = duration;
+ item.addAlarm(alarm);
} else {
Components.utils.reportError("WARNING! Couldn't do alarm conversion for item:"+
item.title+','+item.id+"!\n");
}
}
// Alarm offset could be 0, but this is ok, so compare with null
if (row.alarm_offset != null) {
- var duration = Components.classes["@mozilla.org/calendar/duration;1"]
- .createInstance(Components.interfaces.calIDuration);
+ let duration = cal.createDuration();
duration.inSeconds = row.alarm_offset;
duration.normalize();
- item.alarmOffset = duration;
- item.alarmRelated = row.alarm_related;
+ // TODO ALARMSUPPORT for now just use the one relative alarm. Will
+ // change when supporting multiple alarms.
+ item.clearAlarms();
+ let alarm = cal.createAlarm();
+ alarm.related = row.alarm_related + 1;
+ alarm.offset = duration;
}
if (row.alarm_last_ack) {
// alarm acks are always in utc
item.alarmLastAck = newDateTime(row.alarm_last_ack, "UTC");
}
if (row.recurrence_id)
item.recurrenceId = newDateTime(row.recurrence_id, row.recurrence_id_tz);
@@ -2369,19 +2379,23 @@ calStorageCalendar.prototype = {
if ((tmp = item.getProperty("LAST-MODIFIED")))
ip.last_modified = tmp.nativeTime;
ip.title = item.getProperty("SUMMARY");
ip.priority = item.getProperty("PRIORITY");
ip.privacy = item.getProperty("CLASS");
ip.ical_status = item.getProperty("STATUS");
- if (item.alarmOffset) {
- ip.alarm_offset = item.alarmOffset.inSeconds;
- ip.alarm_related = item.alarmRelated;
+ // TODO ALARMSUPPORT for now, just use the first relative alarm. This
+ // will change when supporting multiple alarms.
+ let alarms = item.getAlarms({})
+ .filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+ if (alarms.length) {
+ ip.alarm_offset = alarms[0].offset.inSeconds;
+ ip.alarm_related = alarms[0].related - 1;
}
if (item.alarmLastAck) {
ip.alarm_last_ack = item.alarmLastAck.nativeTime;
}
},
writeAttendees: function (item, olditem) {
var attendees = item.getAttendees({});
--- a/calendar/providers/wcap/calWcapCalendarItems.js
+++ b/calendar/providers/wcap/calWcapCalendarItems.js
@@ -175,30 +175,37 @@ calWcapCalendar.prototype.encodeRecurren
// rchange=1: expand recurrences,
// or whether to replace the rrule, ambiguous documentation!!!
// check with store(with no uid) upon adoptItem() which behaves strange
// if rchange=0 is set!
};
calWcapCalendar.prototype.getAlarmParams =
function calWcapCalendar_getAlarmParams(item) {
- var params = null;
- var alarmStart = item.alarmOffset;
- if (alarmStart) {
- if (item.alarmRelated == calIItemBase.ALARM_RELATED_END) {
+ let params = null;
+ // TODO ALARMSUPPORT This will change as soon as email alarms are supported
+ // by the UI.
+ let alarms = item.getAlarms({})
+ .filter(function(x) x.related != x.ALARM_RELATED_ABSOLUTE);
+ let alarm = alarms[0];
+ // END TODO ALARMSUPPORT
+
+ if (alarm && alarm.offset) {
+ let alarmStart = alarm.offset;
+ if (alarm.related == alarm.ALARM_RELATED_END) {
// cs does not support explicit RELATED=END when
// both start|entry and end|due are written
- var dur = item.duration;
+ let dur = item.duration;
if (dur) { // both given
alarmStart = alarmStart.clone();
alarmStart.addDuration(dur);
} // else only end|due is set, alarm makes little sense though
}
- var emails = "";
+ let emails = "";
if (item.hasProperty("alarmEmailAddress")) {
emails = encodeURIComponent(item.getProperty("alarmEmailAddress"));
} else {
emails = this.session.getDefaultAlarmEmails({}).map(encodeURIComponent).join(";");
}
if (emails.length > 0) {
params = ("&alarmStart=" + alarmStart.icalString);
params += ("&alarmEmails=" + emails);
@@ -365,17 +372,17 @@ function calWcapCalendar_storeItem(bAddI
}
} else { // calITodo
// xxx todo: dtstart is mandatory for cs, so if this is
// undefined, assume an allDay todo???
var dtstart = item.entryDate;
var dtend = item.dueDate;
// cs bug: enforce DUE (set to DTSTART) if alarm is set
- if (!dtend && item.alarmOffset) {
+ if (!dtend && item.getAlarms({}).length) {
dtend = dtstart;
}
bIsAllDay = (dtstart && dtstart.isDate);
if (!oldItem || !identicalDatetimes(dtstart, oldItem.entryDate)
|| !identicalDatetimes(dtend, oldItem.dueDate)) {
params += ("&dtstart=" + getIcalUTC(dtstart)); // timezone will be set with tzid param
params += ("&due=" + getIcalUTC(dtend)); // timezone will be set with tzid param
@@ -639,27 +646,27 @@ function calWcapCalendar_storeItem(bAddI
calWcapCalendar.prototype.tunnelXProps =
function calWcapCalendar_tunnelXProps(destItem, srcItem) {
// xxx todo: temp workaround for bug in calItemBase.js
if (!isParent(srcItem)) {
return;
}
// tunnel alarm X-MOZ-SNOOZE only if alarm is still set:
- var alarmOffset = destItem.alarmOffset;
- var enumerator = srcItem.propertyEnumerator;
+ let hasAlarms = destItem.getAlarms({}).length;
+ let enumerator = srcItem.propertyEnumerator;
while (enumerator.hasMoreElements()) {
try {
var prop = enumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
var name = prop.name;
if (name.indexOf("X-MOZ-") == 0) {
switch (name) {
// keep snooze stamps for occurrences only and if alarm is still set:
case "X-MOZ-SNOOZE-TIME":
- if (!alarmOffset) {
+ if (!hasAlarms) {
break; // alarm has been reset
}
// fallthru intended
default:
if (LOG_LEVEL > 1) {
log("tunneling " + name + "=" + prop.value, this);
}
destItem.setProperty(name, prop.value);
--- a/calendar/test/unit/head_consts.js
+++ b/calendar/test/unit/head_consts.js
@@ -132,17 +132,16 @@ function getStorageCal() {
/**
* Return an item property as string.
* @param aItem
* @param string aProp possible item properties: start, end, duration,
* generation, title,
* id, calendar, creationDate, lastModifiedTime,
* stampTime, priority, privacy, status,
- * alarmOffset, alarmRelated,
* alarmLastAck, recurrenceStartDate
* and any property that can be obtained using getProperty()
*/
function getProps(aItem, aProp) {
var value = null;
switch (aProp) {
case "start":
value = aItem.startDate || aItem.entryDate || null;
@@ -178,22 +177,16 @@ function getProps(aItem, aProp) {
value = aItem.priority;
break;
case "privacy":
value = aItem.privacy;
break;
case "status":
value = aItem.status;
break;
- case "alarmOffset":
- value = aItem.alarmOffset;
- break;
- case "alarmRelated":
- value = aItem.alarmRelated;
- break;
case "alarmLastAck":
value = aItem.alarmLastAck;
break;
case "recurrenceStartDate":
value = aItem.recurrenceStartDate;
break;
default:
value = aItem.getProperty(aProp);
@@ -206,17 +199,17 @@ function getProps(aItem, aProp) {
}
function compareItemsSpecific(aLeftItem, aRightItem, aPropArray) {
if (!aPropArray) {
// left out: "id", "calendar", "lastModifiedTime", "generation",
// "stampTime" as these are expected to change
aPropArray = ["start", "end", "duration",
"title", "priority", "privacy", "creationDate",
- "status", "alarmOffset", "alarmRelated", "alarmLastAck",
+ "status", "alarmLastAck",
"recurrenceStartDate"];
}
for (var i = 0; i < aPropArray.length; i++) {
do_check_eq(getProps(aLeftItem, aPropArray[i]),
getProps(aRightItem,
aPropArray[i]));
}
}
--- a/calendar/test/unit/test_alarm.js
+++ b/calendar/test/unit/test_alarm.js
@@ -47,34 +47,34 @@ function run_test() {
test_dates();
test_clone();
test_immutable();
}
function test_initial_creation() {
dump("Testing initial creation...");
- alarm = createAlarm();
+ alarm = cal.createAlarm();
let passed;
try {
alarm.icalString;
passed = false;
} catch (e) {
passed = true;
}
if (!passed) {
do_throw("Fresh calIAlarm should not produce a valid icalString");
}
dump("Done\n");
}
function test_display_alarm() {
dump("Testing DISPLAY alarms...");
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
// Set ACTION to DISPLAY, make sure this was not rejected
alarm.action = "DISPLAY";
do_check_eq(alarm.action, "DISPLAY");
// Set a Description, REQUIRED for ACTION:DISPLAY
alarm.description = "test";
do_check_eq(alarm.description, "test");
@@ -83,17 +83,17 @@ function test_display_alarm() {
do_check_eq(alarm.summary, null);
// TODO No attendees
dump("Done\n");
}
function test_email_alarm() {
dump("Testing EMAIL alarms...");
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
// Set ACTION to DISPLAY, make sure this was not rejected
alarm.action = "EMAIL";
do_check_eq(alarm.action, "EMAIL");
// Set a Description, REQUIRED for ACTION:EMAIL
alarm.description = "description";
do_check_eq(alarm.description, "description");
@@ -104,17 +104,17 @@ function test_email_alarm() {
// TODO check for at least one attendee
// TODO test attachments
dump("Done\n");
}
function test_audio_alarm() {
dump("Testing AUDIO alarms...");
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
// Set ACTION to AUDIO, make sure this was not rejected
alarm.action = "AUDIO";
do_check_eq(alarm.action, "AUDIO");
// No Description for ACTION:AUDIO
alarm.description = "description";
do_check_eq(alarm.description, null);
@@ -124,17 +124,17 @@ function test_audio_alarm() {
// TODO No attendees
// TODO test for one attachment
dump("Done\n");
}
function test_custom_alarm() {
dump("Testing X-SMS (custom) alarms...");
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
// Set ACTION to a custom value, make sure this was not rejected
alarm.action = "X-SMS"
do_check_eq(alarm.action, "X-SMS");
// There is no restriction on DESCRIPTION for custom alarms
alarm.description = "description";
do_check_eq(alarm.description, "description");
@@ -146,17 +146,17 @@ function test_custom_alarm() {
// TODO test for attachments
dump("Done\n");
}
// Check if any combination of REPEAT and DURATION work as expected.
function test_repeat() {
dump("Testing REPEAT and DURATION properties...");
let message;
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
// Check initial value
do_check_eq(alarm.repeat, 0);
do_check_eq(alarm.repeatOffset, null);
do_check_eq(alarm.repeatDate, null);
// Should not be able to get REPEAT when DURATION is not set
alarm.repeat = 1;
@@ -181,31 +181,31 @@ function test_repeat() {
// Check final value
do_check_eq(alarm.repeat, 0);
do_check_eq(alarm.repeatOffset, null);
dump("Done\n");
}
function test_xprop() {
dump("Testing X-Props...");
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
alarm.setProperty("X-PROP", "X-VALUE");
do_check_true(alarm.hasProperty("X-PROP"));
do_check_eq(alarm.getProperty("X-PROP"), "X-VALUE");
alarm.deleteProperty("X-PROP");
do_check_false(alarm.hasProperty("X-PROP"));
do_check_eq(alarm.getProperty("X-PROP"), null);
dump("Done\n");
}
function test_dates() {
dump("Testing alarm dates...");
let passed;
// Initial value
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
do_check_eq(alarm.alarmDate, null);
do_check_eq(alarm.offset, null);
// Set an offset and check it
alarm.related = Ci.calIAlarm.ALARM_RELATED_START
let offset = createDuration("-PT5M");
alarm.offset = offset;
do_check_eq(alarm.alarmDate, null);
@@ -297,17 +297,17 @@ function test_immutable() {
if (!passed) {
do_throw("setProperty succeeded while item was immutable");
}
dump("Done\n");
}
function test_clone() {
dump("Testing cloning alarms...");
- let alarm = createAlarm();
+ let alarm = cal.createAlarm();
// Set up each attribute
for (let prop in propMap) {
alarm[prop] = propMap[prop];
}
// Make a copy
let newAlarm = alarm.clone();
newAlarm.makeImmutable();
newAlarm = newAlarm.clone();