1 /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Oracle Corporation code.
16 *
17 * The Initial Developer of the Original Code is Oracle Corporation
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Stuart Parmenter <stuart.parmenter@oracle.com>
23 * Michael Buettner <michael.buettner@sun.com>
24 * Stefan Sitter <ssitter@gmail.com>
25 * Philipp Kewisch <mozilla@kewis.ch>
26 *
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
38 *
39 * ***** END LICENSE BLOCK ***** */
40
41 /* use with textfields oninput to only allow integers */
42 function validateIntegerRange(event, lowerBound, upperBound) {
43 validateIntegers(event);
44
45 var num = Number(event.target.value);
46
47 // Only modify the number if a value is entered, otherwise deleting the
48 // value (to maybe enter a new number) will cause the field to be set to the
49 // lower bound.
50 if (event.target.value != "" && (num < lowerBound || num > upperBound)) {
51 event.target.value = Math.min(Math.max(num, lowerBound), upperBound);
52 event.preventDefault();
53 }
54 }
55
56 function validateIntegers(event) {
57 if (isNaN(Number(event.target.value))) {
58 var newValue = parseInt(event.target.value);
59 event.target.value = isNaN(newValue) ? "" : newValue;
60 event.preventDefault();
61 }
62 }
63
64 function validateNaturalNums(event) {
65 validateIntegers(event);
66 var num = event.target.value;
67 if (num < 0) {
68 event.target.value = -1 * num;
69 event.preventDefault();
70 }
71 }
72
73 /**
74 * This function takes the recurrence info passed as argument and creates a
75 * literal string representing the repeat pattern in natural language.
76 */
77 function recurrenceRule2String(recurrenceInfo, startDate, endDate, allDay) {
78
79 // Retrieve a valid recurrence rule from the currently
80 // set recurrence info. Bail out if there's more
81 // than a single rule or something other than a rule.
82 recurrenceInfo = recurrenceInfo.clone();
83 var rrules = splitRecurrenceRules(recurrenceInfo);
84 if (rrules[0].length == 1) {
85 var rule = rrules[0][0];
86 // currently we don't allow for any BYxxx-rules.
87 if (rule instanceof Components.interfaces.calIRecurrenceRule &&
88 !checkRecurrenceRule(rule, ['BYSECOND',
89 'BYMINUTE',
90 //'BYDAY',
91 'BYHOUR',
92 //'BYMONTHDAY',
93 'BYYEARDAY',
94 'BYWEEKNO',
95 //'BYMONTH',
96 'BYSETPOS'])) {
97 function day_of_week(day) {
98 return Math.abs(day) % 8;
99 }
100 function day_position(day) {
101 var dow = day_of_week(day);
102 return (Math.abs(day) - dow) / 8 * (day < 0 ? -1 : 1);
103 }
104
105 var ruleString = "???";
106 if (rule.type == 'DAILY') {
107 if (checkRecurrenceRule(rule, ['BYDAY'])) {
108 var days = rule.getComponent("BYDAY", {});
109 var weekdays = [2, 3, 4, 5, 6];
110 if (weekdays.length == days.length) {
111 for (var i = 0; i < weekdays.length; i++) {
112 if (weekdays[i] != days[i]) {
113 break;
114 }
115 }
116 if (i == weekdays.length) {
117 ruleString = calGetString(
118 "sun-calendar-event-dialog",
119 "repeatDetailsRuleDaily4");
120 }
121 }
122 } else {
123 if (rule.interval == 1) {
124 ruleString = calGetString(
125 "sun-calendar-event-dialog",
126 "repeatDetailsRuleDaily1");
127 } else if (rule.interval == 2) {
128 ruleString = calGetString(
129 "sun-calendar-event-dialog",
130 "repeatDetailsRuleDaily2");
131 } else {
132 ruleString = calGetString(
133 "sun-calendar-event-dialog",
134 "repeatDetailsRuleDaily3",
135 [ rule.interval ], 1);
136 }
137 }
138 } else if (rule.type == 'WEEKLY') {
139 // weekly recurrence, currently we
140 // support a single 'BYDAY'-rule only.
141 if (checkRecurrenceRule(rule, ['BYDAY'])) {
142 // create a string like 'Monday, Tuesday and
143 // Wednesday'
144 var days = rule.getComponent("BYDAY", {});
145 var weekdays = "";
146 for (var i = 0; i < days.length; i++) {
147 weekdays += calGetString(
148 "sun-calendar-event-dialog",
149 "repeatDetailsDay" + days[i]);
150 if (days.length > 1 && i == (days.length - 2)) {
151 weekdays += ' ' + calGetString(
152 "sun-calendar-event-dialog",
153 "repeatDetailsAnd") + ' ';
154 } else if (i < days.length - 1) {
155 weekdays += ', ';
156 }
157 }
158
159 // now decorate this with 'every other week, etc'.
160 if (rule.interval == 1) {
161 ruleString = calGetString(
162 "sun-calendar-event-dialog",
163 "repeatDetailsRuleWeekly1", [ weekdays ], 1);
164 } else if (rule.interval == 2) {
165 ruleString = calGetString(
166 "sun-calendar-event-dialog",
167 "repeatDetailsRuleWeekly2", [ weekdays ], 1);
168 } else {
169 ruleString = calGetString(
170 "sun-calendar-event-dialog",
171 "repeatDetailsRuleWeekly3",
172 [ rule.interval, weekdays ],
173 2);
174 }
175 } else {
176 if (rule.interval == 1) {
177 ruleString = calGetString(
178 "sun-calendar-event-dialog",
179 "repeatDetailsRuleWeekly4");
180 } else if (rule.interval == 2) {
181 ruleString = calGetString(
182 "sun-calendar-event-dialog",
183 "repeatDetailsRuleWeekly5");
184 } else {
185 ruleString = calGetString(
186 "sun-calendar-event-dialog",
187 "repeatDetailsRuleWeekly6",
188 [ rule.interval ], 1);
189 }
190 }
191 } else if (rule.type == 'MONTHLY') {
192 if (checkRecurrenceRule(rule, ['BYDAY'])) {
193 var component = rule.getComponent("BYDAY", {});
194 var byday = component[0];
195 var ordinal_string =
196 calGetString(
197 "sun-calendar-event-dialog",
198 "repeatDetailsOrdinal" + day_position(byday));
199 var day_string =
200 calGetString(
201 "sun-calendar-event-dialog",
202 "repeatDetailsDay" + day_of_week(byday));
203
204 if (rule.interval == 1) {
205 ruleString = calGetString(
206 "sun-calendar-event-dialog",
207 "repeatDetailsRuleMonthly1",
208 [ ordinal_string, day_string ],
209 2);
210 } else if (rule.interval == 2) {
211 ruleString = calGetString(
212 "sun-calendar-event-dialog",
213 "repeatDetailsRuleMonthly2",
214 [ ordinal_string, day_string ],
215 2);
216 } else {
217 ruleString = calGetString(
218 "sun-calendar-event-dialog",
219 "repeatDetailsRuleMonthly3",
220 [ ordinal_string, day_string, rule.interval ],
221 3);
222 }
223 } else if (checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
224 var component = rule.getComponent("BYMONTHDAY", {});
225
226 var day_string = "";
227 for (var i = 0; i < component.length; i++) {
228 // TODO: we also need to handle BYMONTHDAY rules with
229 // negative array elements, but we're currently in string
230 // freeze for 0.7 so I can't add the necessary bits and
231 // pieces.
232 if (component[i] < 0) {
233 return null;
234 }
235 day_string += component[i];
236 if (component.length > 1 &&
237 i == (component.length - 2)) {
238 day_string += ' ' +calGetString(
239 "sun-calendar-event-dialog",
240 "repeatDetailsAnd") + ' ';
241 } else if (i < component.length-1) {
242 day_string += ', ';
243 }
244 }
245
246 if (rule.interval == 1) {
247 ruleString = calGetString(
248 "sun-calendar-event-dialog",
249 "repeatDetailsRuleMonthly4",
250 [ day_string ], 1);
251 } else if (rule.interval == 2) {
252 ruleString = calGetString(
253 "sun-calendar-event-dialog",
254 "repeatDetailsRuleMonthly5",
255 [ day_string ], 1);
256 } else {
257 ruleString = calGetString(
258 "sun-calendar-event-dialog",
259 "repeatDetailsRuleMonthly6",
260 [ day_string, rule.interval ],
261 2);
262 }
263 } else {
264 if (rule.interval == 1) {
265 ruleString = calGetString(
266 "sun-calendar-event-dialog",
267 "repeatDetailsRuleMonthly4",
268 [ startDate.day ], 1);
269 } else if (rule.interval == 2) {
270 ruleString = calGetString(
271 "sun-calendar-event-dialog",
272 "repeatDetailsRuleMonthly5",
273 [ startDate.day ], 1);
274 } else {
275 ruleString = calGetString(
276 "sun-calendar-event-dialog",
277 "repeatDetailsRuleMonthly6",
278 [ startDate.day, rule.interval ],
279 2);
280 }
281 }
282 } else if (rule.type == 'YEARLY') {
283 if (checkRecurrenceRule(rule, ['BYMONTH']) &&
284 checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
285 bymonth = rule.getComponent("BYMONTH", {});
286 bymonthday = rule.getComponent("BYMONTHDAY", {});
287
288 if (bymonth.length == 1 && bymonthday.length == 1) {
289 var month_string =
290 calGetString(
291 "sun-calendar-event-dialog",
292 "repeatDetailsMonth" + bymonth[0]);
293
294 if (rule.interval == 1) {
295 ruleString = calGetString(
296 "sun-calendar-event-dialog",
297 "repeatDetailsRuleYearly1",
298 [ month_string, bymonthday[0] ],
299 2);
300 } else if (rule.interval == 2) {
301 ruleString = calGetString(
302 "sun-calendar-event-dialog",
303 "repeatDetailsRuleYearly2",
304 [ month_string, bymonthday[0] ],
305 2);
306 } else {
307 ruleString = calGetString(
308 "sun-calendar-event-dialog",
309 "repeatDetailsRuleYearly3",
310 [ month_string,
311 bymonthday[0],
312 rule.interval ],
313 3);
314 }
315 }
316 } else if (checkRecurrenceRule(rule, ['BYMONTH']) &&
317 checkRecurrenceRule(rule, ['BYDAY'])) {
318 bymonth = rule.getComponent("BYMONTH", {});
319 byday = rule.getComponent("BYDAY", {});
320
321 if (bymonth.length == 1 && byday.length == 1) {
322 var month_string =
323 calGetString(
324 "sun-calendar-event-dialog",
325 "repeatDetailsMonth" + bymonth[0]);
326 var ordinal_string =
327 calGetString(
328 "sun-calendar-event-dialog",
329 "repeatDetailsOrdinal" +
330 day_position(byday[0]));
331 var day_string =
332 calGetString(
333 "sun-calendar-event-dialog",
334 "repeatDetailsDay" + day_of_week(byday[0]));
335
336 if (rule.interval == 1) {
337 ruleString = calGetString(
338 "sun-calendar-event-dialog",
339 "repeatDetailsRuleYearly4",
340 [ ordinal_string, day_string, month_string ],
341 3);
342 } else if (rule.interval == 2) {
343 ruleString = calGetString(
344 "sun-calendar-event-dialog",
345 "repeatDetailsRuleYearly5",
346 [ ordinal_string, day_string, month_string ],
347 3);
348 } else {
349 ruleString = calGetString(
350 "sun-calendar-event-dialog",
351 "repeatDetailsRuleYearly6",
352 [ ordinal_string,
353 day_string,
354 month_string,
355 rule.interval ],
356 4);
357 }
358 }
359 } else {
360 var month_string =
361 calGetString(
362 "sun-calendar-event-dialog",
363 "repeatDetailsMonth" + (startDate.month+1));
364 if (rule.interval == 1) {
365 ruleString = calGetString(
366 "sun-calendar-event-dialog",
367 "repeatDetailsRuleYearly1",
368 [ month_string, startDate.day ],
369 2);
370 } else if (rule.interval == 2) {
371 ruleString = calGetString(
372 "sun-calendar-event-dialog",
373 "repeatDetailsRuleYearly2",
374 [ month_string, startDate.day ],
375 2);
376 } else {
377 ruleString = calGetString(
378 "sun-calendar-event-dialog",
379 "repeatDetailsRuleYearly3",
380 [ month_string,
381 startDate.day,
382 rule.interval ],
383 3);
384 }
385 }
386 }
387
388 var kDefaultTimezone = calendarDefaultTimezone();
389
390 var dateFormatter =
391 Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
392 .getService(Components.interfaces.calIDateTimeFormatter);
393
394 var detailsString;
395 if (!endDate || allDay) {
396 if (rule.isFinite) {
397 if (rule.isByCount) {
398 detailsString = calGetString(
399 "sun-calendar-event-dialog",
400 "repeatDetailsCountAllDay",
401 [ ruleString,
402 dateFormatter.formatDateShort(startDate),
403 rule.count ], 3);
404 } else {
405 var untilDate = rule.endDate.getInTimezone(kDefaultTimezone);
406 detailsString = calGetString(
407 "sun-calendar-event-dialog",
408 "repeatDetailsUntilAllDay",
409 [ ruleString,
410 dateFormatter.formatDateShort(startDate),
411 dateFormatter.formatDateShort(untilDate) ],
412 3);
413 }
414 } else {
415 detailsString = calGetString(
416 "sun-calendar-event-dialog",
417 "repeatDetailsInfiniteAllDay",
418 [ ruleString,
419 dateFormatter.formatDateShort(startDate) ], 2);
420 }
421 } else {
422 if (rule.isFinite) {
423 if (rule.isByCount) {
424 detailsString = calGetString(
425 "sun-calendar-event-dialog",
426 "repeatDetailsCount",
427 [ ruleString,
428 dateFormatter.formatDateShort(startDate),
429 rule.count,
430 dateFormatter.formatTime(startDate),
431 dateFormatter.formatTime(endDate) ], 5);
432 } else {
433 var untilDate = rule.endDate.getInTimezone(kDefaultTimezone);
434 detailsString = calGetString(
435 "sun-calendar-event-dialog",
436 "repeatDetailsUntil",
437 [ ruleString,
438 dateFormatter.formatDateShort(startDate),
439 dateFormatter.formatDateShort(untilDate),
440 dateFormatter.formatTime(startDate),
441 dateFormatter.formatTime(endDate) ], 5);
442 }
443 } else {
444 detailsString = calGetString(
445 "sun-calendar-event-dialog",
446 "repeatDetailsInfinite",
447 [ ruleString,
448 dateFormatter.formatDateShort(startDate),
449 dateFormatter.formatTime(startDate),
450 dateFormatter.formatTime(endDate) ], 4);
451 }
452 }
453 return detailsString;
454 }
455 }
456 return null;
457 }
458
459 function splitRecurrenceRules(recurrenceInfo) {
460 var ritems = recurrenceInfo.getRecurrenceItems({});
461 var rules = [];
462 var exceptions = [];
463 for each (var r in ritems) {
464 if (r.isNegative) {
465 exceptions.push(r);
466 } else {
467 rules.push(r);
468 }
469 }
470 return [rules, exceptions];
471 }
472
473 function checkRecurrenceRule(aRule, aArray) {
474 for each (var comp in aArray) {
475 var ruleComp = aRule.getComponent(comp, {});
476 if (ruleComp && ruleComp.length > 0) {
477 return true;
478 }
479 }
480 return false;
481 }
482
483 function dispose() {
484 var args = window.arguments[0];
485 if (args.job && args.job.dispose) {
486 args.job.dispose();
487 }
488 }
489
490 function editReminder() {
491 var customReminder =
492 document.getElementById("reminder-custom-menuitem");
493 var args = new Object();
494 args.reminder = customReminder.reminder;
495 var savedWindow = window;
496 args.onOk = function(reminder) {
497 customReminder.reminder = reminder;
498 };
499
500 window.setCursor("wait");
501
502 // open the dialog modally
503 openDialog(
504 "chrome://calendar/content/sun-calendar-event-dialog-reminder.xul",
505 "_blank",
506 "chrome,titlebar,modal,resizable",
507 args);
508 }
509
510 function updateReminderDetails() {
511 // find relevant elements in the document
512 var reminderPopup = document.getElementById("item-alarm");
513 var reminderDetails = document.getElementById("reminder-details");
514 var reminder = document.getElementById("reminder-custom-menuitem").reminder;
515
516 // first of all collapse the details text. if we fail to
517 // create a details string, we simply don't show anything.
518 reminderDetails.setAttribute("collapsed", "true");
519
520 // don't try to show the details text
521 // for anything but a custom recurrence rule.
522 if (reminderPopup.value == "custom" && reminder) {
523 var unitString;
524 switch (reminder.unit) {
525 case 'minutes':
526 unitString = Number(reminder.length) <= 1 ?
527 calGetString(
528 "sun-calendar-event-dialog",
529 "reminderCustomUnitMinute") :
530 calGetString(
531 "sun-calendar-event-dialog",
532 "reminderCustomUnitMinutes");
533 break;
534 case 'hours':
535 unitString = Number(reminder.length) <= 1 ?
536 calGetString(
537 "sun-calendar-event-dialog",
538 "reminderCustomUnitHour") :
539 calGetString(
540 "sun-calendar-event-dialog",
541 "reminderCustomUnitHours");
542 break;
543 case 'days':
544 unitString = Number(reminder.length) <= 1 ?
545 calGetString(
546 "sun-calendar-event-dialog",
547 "reminderCustomUnitDay") :
548 calGetString(
549 "sun-calendar-event-dialog",
550 "reminderCustomUnitDays");
551 break;
552 }
553
554 var relationString;
555 switch (reminder.relation) {
556 case 'START':
557 relationString = calGetString(
558 "sun-calendar-event-dialog",
559 "reminderCustomRelationStart");
560 break;
561 case 'END':
562 relationString = calGetString(
563 "sun-calendar-event-dialog",
564 "reminderCustomRelationEnd");
565 break;
566 }
567
568 var originString;
569 if (reminder.origin && reminder.origin < 0) {
570 originString = calGetString(
571 "sun-calendar-event-dialog",
572 "reminderCustomOriginEnd");
573 } else {
574 originString = calGetString(
575 "sun-calendar-event-dialog",
576 "reminderCustomOriginBegin");
577 }
578
579 var detailsString = calGetString(
580 "sun-calendar-event-dialog",
581 "reminderCustomTitle",
582 [ reminder.length,
583 unitString,
584 relationString,
585 originString], 4);
586
587 var lines = detailsString.split("\n");
588 reminderDetails.removeAttribute("collapsed");
589 while (reminderDetails.childNodes.length > lines.length) {
590 reminderDetails.removeChild(reminderDetails.lastChild);
591 }
592 var numChilds = reminderDetails.childNodes.length;
593 for (var i = 0; i < lines.length; i++) {
594 if (i >= numChilds) {
595 var newNode = reminderDetails.childNodes[0].cloneNode(true);
596 reminderDetails.appendChild(newNode);
597 }
598 var node = reminderDetails.childNodes[i];
599 node.setAttribute('value', lines[i]);
600 }
601 }
602 }
603
604 var gLastAlarmSelection = 0;
605
606 function loadReminder(item) {
607 // select 'no reminder' by default
608 var reminderPopup = document.getElementById("item-alarm");
609 reminderPopup.selectedIndex = 0;
610 gLastAlarmSelection = 0;
611 if (!item.alarmOffset) {
612 return;
613 }
614
615 // try to match the reminder setting with the available popup items
616 var origin = "1";
617 if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_END) {
618 origin = "-1";
619 }
620 var duration = item.alarmOffset.clone();
621 var relation = "END";
622 if (duration.isNegative) {
623 duration.isNegative = false;
624 duration.normalize();
625 relation = "START";
626 }
627 var matchingItem = null;
628 var menuItems = reminderPopup.getElementsByTagName("menuitem");
629 var numItems = menuItems.length;
630 for (var i=0; i<numItems; i++) {
631 var menuitem = menuItems[i];
632 if (menuitem.hasAttribute("length")) {
633 if (menuitem.getAttribute("origin") == origin &&
634 menuitem.getAttribute("relation") == relation) {
635 var unit = menuitem.getAttribute("unit");
636 var length = menuitem.getAttribute("length");
637 if (unit == "days") {
638 length = length * 60 * 60 * 24;
639 } else if (unit == "hours") {
640 length = length * 60 * 60;
641 } else if (unit == "minutes") {
642 length = length * 60;
643 } else {
644 continue;
645 }
646 if (duration.inSeconds == length) {
647 matchingItem = menuitem;
648 break;
649 }
650 }
651 }
652 }
653
654 if (matchingItem) {
655 var numChilds = reminderPopup.childNodes[0].childNodes.length;
656 for (var i = 0; i < numChilds; i++) {
657 var node = reminderPopup.childNodes[0].childNodes[i];
658 if (node == matchingItem) {
659 reminderPopup.selectedIndex = i;
660 break;
661 }
662 }
663 } else {
664 reminderPopup.value = 'custom';
665 var customReminder =
666 document.getElementById("reminder-custom-menuitem");
667 var reminder = {};
668 if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
669 reminder.origin = "1";
670 } else {
671 reminder.origin = "-1";
672 }
673 var offset = item.alarmOffset.clone();
674 var relation = "END";
675 if (offset.isNegative) {
676 offset.isNegative = false;
677 offset.normalize();
678 relation = "START";
679 }
680 reminder.relation = relation;
681 if (offset.minutes) {
682 var minutes = offset.minutes +
683 offset.hours * 60 +
684 offset.days * 24 * 60 +
685 offset.weeks * 60 * 24 * 7;
686 reminder.unit = 'minutes';
687 reminder.length = minutes;
688 } else if (offset.hours) {
689 var hours = offset.hours + offset.days * 24 + offset.weeks * 24 * 7;
690 reminder.unit = 'hours';
691 reminder.length = hours;
692 } else {
693 var days = offset.days + offset.weeks * 7;
694 reminder.unit = 'days';
695 reminder.length = days;
696 }
697 customReminder.reminder = reminder;
698 }
699
700 // remember the selected index
701 gLastAlarmSelection = reminderPopup.selectedIndex;
702 }
703
704 function saveReminder(item) {
705 var reminderPopup = document.getElementById("item-alarm");
706 if (reminderPopup.value == 'none') {
707 item.alarmOffset = null;
708 item.alarmLastAck = null;
709 item.alarmRelated = null;
710 } else {
711 var menuitem = reminderPopup.selectedItem;
712
713 // custom reminder entries carry their own reminder object
714 // with them, pre-defined entries specify the necessary information
715 // as attributes attached to the menuitem elements.
716 var reminder = menuitem.reminder;
717 if (!reminder) {
718 reminder = {};
719 reminder.length = menuitem.getAttribute('length');
720 reminder.unit = menuitem.getAttribute('unit');
721 reminder.relation = menuitem.getAttribute('relation');
722 reminder.origin = menuitem.getAttribute('origin');
723 }
724
725 var duration = Components.classes["@mozilla.org/calendar/duration;1"]
726 .createInstance(Components.interfaces.calIDuration);
727
728 duration[reminder.unit] = Number(reminder.length);
729 if (reminder.relation != "END") {
730 duration.isNegative = true;
731 }
732 duration.normalize();
733 item.alarmOffset = duration;
734
735 if (Number(reminder.origin) >= 0) {
736 item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
737 } else {
738 item.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_END;
739 }
740 }
741 }
742
743 function commonUpdateReminder() {
744 // if a custom reminder has been selected, we show the appropriate
745 // dialog in order to allow the user to specify the details.
746 // the result will be placed in the 'reminder-custom-menuitem' tag.
747 var reminderPopup = document.getElementById("item-alarm");
748 if (reminderPopup.value == 'custom') {
749 // show the dialog.
750 // don't pop up the dialog if this happens during
751 // initialization of the dialog.
752 if (reminderPopup.hasAttribute("last-value")) {
753 editReminder();
754 }
755
756 // Now check if the resulting custom reminder is valid.
757 // possibly we receive an invalid reminder if the user cancels the
758 // dialog. In that case we revert to the previous selection of the
759 // reminder drop down.
760 if (!document.getElementById("reminder-custom-menuitem").reminder) {
761 reminderPopup.selectedIndex = gLastAlarmSelection;
762 }
763 }
764
765 // remember the current reminder drop down selection index.
766 gLastAlarmSelection = reminderPopup.selectedIndex;
767 reminderPopup.setAttribute("last-value", reminderPopup.value);
768
769 // possibly the selected reminder conflicts with the item.
770 // for example an end-relation combined with a task without duedate
771 // is an invalid state we need to take care of. we take the same
772 // approach as with recurring tasks. in case the reminder is related
773 // to the entry date we check the entry date automatically and disable
774 // the checkbox. the same goes for end related reminder and the due date.
775 if (isToDo(window.calendarItem)) {
776
777 // custom reminder entries carry their own reminder object
778 // with them, pre-defined entries specify the necessary information
779 // as attributes attached to the menuitem elements.
780 var menuitem = reminderPopup.selectedItem;
781 if (menuitem.value == 'none') {
782 enableElementWithLock("todo-has-entrydate", "reminder-lock");
783 enableElementWithLock("todo-has-duedate", "reminder-lock");
784 } else {
785 var reminder = menuitem.reminder;
786 if (!reminder) {
787 reminder = {};
788 reminder.length = menuitem.getAttribute('length');
789 reminder.unit = menuitem.getAttribute('unit');
790 reminder.relation = menuitem.getAttribute('relation');
791 reminder.origin = menuitem.getAttribute('origin');
792 }
793
794 // if this reminder is related to the entry date...
795 if (Number(reminder.origin) > 0) {
796
797 // ...automatically check 'has entrydate'.
798 if (!getElementValue("todo-has-entrydate", "checked")) {
799 setElementValue("todo-has-entrydate", "true", "checked");
800
801 // make sure gStartTime is properly initialized
802 updateEntryDate();
803 }
804
805 // disable the checkbox to indicate that we need
806 // the entry-date. the 'disabled' state will be
807 // revoked if the user turns off the repeat pattern.
808 disableElementWithLock("todo-has-entrydate", "reminder-lock");
809 enableElementWithLock("todo-has-duedate", "reminder-lock");
810 }
811
812 // if this reminder is related to the due date...
813 if (Number(reminder.origin) < 0) {
814
815 // ...automatically check 'has duedate'.
816 if (!getElementValue("todo-has-duedate", "checked")) {
817 setElementValue("todo-has-duedate", "true", "checked");
818
819 // make sure gStartTime is properly initialized
820 updateDueDate();
821 }
822
823 // disable the checkbox to indicate that we need
824 // the entry-date. the 'disabled' state will be
825 // revoked if the user turns off the repeat pattern.
826 disableElementWithLock("todo-has-duedate", "reminder-lock");
827 enableElementWithLock("todo-has-entrydate", "reminder-lock");
828 }
829 }
830 }
831
832 updateReminderDetails();
833 }