1 <?xml version="1.0" encoding="UTF-8"?>
2 <!--
3 - ***** BEGIN LICENSE BLOCK *****
4 - Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 -
6 - The contents of this file are subject to the Mozilla Public License Version
7 - 1.1 (the "License"); you may not use this file except in compliance with
8 - the License. You may obtain a copy of the License at
9 - http://www.mozilla.org/MPL/
10 -
11 - Software distributed under the License is distributed on an "AS IS" basis,
12 - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 - for the specific language governing rights and limitations under the
14 - License.
15 -
16 - The Original Code is calendar views.
17 -
18 - The Initial Developer of the Original Code is
19 - Oracle Corporation
20 - Portions created by the Initial Developer are Copyright (C) 2005
21 - the Initial Developer. All Rights Reserved.
22 -
23 - Contributor(s):
24 - Vladimir Vukicevic <vladimir@pobox.com>
25 - Stefan Sitter <ssitter@googlemail.com>
26 - Clint Talbert <cmtalbert@myfastmail.com>
27 - Michael Büttner <michael.buettner@sun.com>
28 - Philipp Kewisch <mozilla@kewis.ch>
29 - Markus Adrario <MarkusAdrario@web.de>
30 -
31 - Alternatively, the contents of this file may be used under the terms of
32 - either the GNU General Public License Version 2 or later (the "GPL"), or
33 - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 - in which case the provisions of the GPL or the LGPL are applicable instead
35 - of those above. If you wish to allow use of your version of this file only
36 - under the terms of either the GPL or the LGPL, and not to allow others to
37 - use your version of this file under the terms of the MPL, indicate your
38 - decision by deleting the provisions above and replace them with the notice
39 - and other provisions required by the GPL or the LGPL. If you do not delete
40 - the provisions above, a recipient may use your version of this file under
41 - the terms of any one of the MPL, the GPL or the LGPL.
42 -
43 - ***** END LICENSE BLOCK *****
44 -->
45
46 <!-- Note that this file depends on helper functions in calUtils.js-->
47
48 <bindings id="calendar-month-view-bindings"
49 xmlns="http://www.mozilla.org/xbl"
50 xmlns:html="http://www.w3.org/1999/xhtml"
51 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
52 xmlns:xbl="http://www.mozilla.org/xbl">
53
54 <binding id="calendar-month-day-box-item" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
55 <content>
56 <xul:vbox flex="1">
57 <xul:hbox>
58 <xul:image anonid="shadow-left-image" class="calendar-event-box-shadow-left"/>
59 <xul:box anonid="event-container" flex="1">
60 <xul:box class="calendar-event-selection" orient="horizontal" flex="1">
61 <xul:stack anonid="eventbox"
62 class="calendar-event-box-container"
63 flex="1">
64 <xul:hbox class="calendar-event-details">
65 <xul:image anonid="item-icon"
66 class="calendar-month-day-box-item-image"/>
67 <xul:label anonid="item-label"
68 class="calendar-month-day-box-item-label"
69 xbl:inherits="context"/>
70 <xul:vbox align="left"
71 flex="1"
72 xbl:inherits="context">
73 <xul:label anonid="event-name"
74 crop="end"
75 flex="1"
76 style="margin: 0;"/>
77 <xul:textbox anonid="event-name-textbox"
78 class="plain"
79 crop="end"
80 hidden="true"
81 style="background: transparent !important;"
82 wrap="true"/>
83 <xul:spacer flex="1"/>
84 </xul:vbox>
85 <xul:vbox pack="center">
86 <xul:image anonid="alarm-image"
87 class="alarm-image"
88 xbl:inherits="flashing"
89 hidden="true"/>
90 </xul:vbox>
91 </xul:hbox>
92 <xul:image anonid="gradient"
93 class="calendar-event-box-gradient"
94 hidden="true" height="1px"/>
95 </xul:stack>
96 </xul:box>
97 <xul:calendar-category-box anonid="category-box"/>
98 </xul:box>
99 <xul:image anonid="shadow-right-image" class="calendar-event-box-shadow-right"/>
100 </xul:hbox>
101 <xul:box anonid="shadow-container" orient="horizontal">
102 <xul:image class="calendar-event-box-shadow-edge-left"/>
103 <xul:image class="calendar-event-box-shadow-bottom" flex="1"/>
104 <xul:image class="calendar-event-box-shadow-edge-right"/>
105 </xul:box>
106 </xul:vbox>
107 </content>
108 <implementation>
109 <constructor><![CDATA[
110 var gradient = document.getAnonymousElementByAttribute(this,
111 "anonid",
112 "gradient");
113 var container = document.getAnonymousElementByAttribute(this,
114 "anonid",
115 "event-container");
116 if (gradient) {
117 gradient.removeAttribute("hidden");
118 }
119
120 container.setAttribute("class", "calendar-item");
121 container.setAttribute("class", this.getAttribute("class"));
122 container.setAttribute("item-calendar", this.getAttribute("item-calendar"));
123 this.removeAttribute("class");
124 this.removeAttribute("item-calendar");
125 ]]>
126 </constructor>
127
128 <property name="occurrence">
129 <getter><![CDATA[
130 return this.mOccurrence;
131 ]]></getter>
132 <setter><![CDATA[
133 this.mOccurrence = val;
134 var icon = document.getAnonymousElementByAttribute(this,"anonid","item-icon");
135 if (isEvent(val)) {
136 icon.setAttribute("type", "event");
137 if (val.startDate.isDate) {
138 icon.setAttribute("type", "all-day");
139 } else {
140 var label = document.getAnonymousElementByAttribute(this,"anonid","item-label");
141 var df = Components.classes["@mozilla.org/calendar/datetime-formatter;1"].
142 getService(Components.interfaces.calIDateTimeFormatter);
143 var timezone = this.calendarView? this.calendarView.mTimezone:
144 calendarDefaultTimezone();
145 label.value = df.formatTime(
146 val.startDate.getInTimezone(timezone));
147 label.setAttribute("time", "true");
148 }
149 } else if (isToDo(val)) {
150 icon.setAttribute("type", "todo");
151 if (val.isCompleted) {
152 icon.setAttribute("completed", "true");
153 }
154 }
155
156 this.setEditableLabel();
157 this.setCSSClasses();
158 return val;
159 ]]></setter>
160 </property>
161 <property name="parentBox"
162 onget="return this.mParentBox;"
163 onset="this.mParentBox = val;"/>
164 <method name="removeShadows">
165 <body><![CDATA[
166 removeAnonymousElement(this, "shadow-left-image");
167 removeAnonymousElement(this, "shadow-right-image");
168 removeAnonymousElement(this, "shadow-container");
169 ]]></body>
170 </method>
171 </implementation>
172
173 <handlers>
174 <handler event="draggesture"><![CDATA[
175 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
176 getService(Components.interfaces.nsIDragService);
177 var transfer = Components.classes["@mozilla.org/widget/transferable;1"].
178 createInstance(Components.interfaces.nsITransferable);
179 transfer.addDataFlavor("text/calendar");
180
181 var item = this.occurrence;
182 if (!isCalendarWritable(item.calendar)) {
183 return;
184 }
185
186 var flavourProvider = {
187 QueryInterface: function(aIID) {
188 ensureIID(
189 [ Components.interfaces.nsIFlavorDataProvider,
190 Components.interfaces.nsISupports], aIID);
191 return this;
192 },
193 item: item,
194
195 getFlavorData: function(aInTransferable, aInFlavor, aOutData, aOutDataLen) {
196 if ((aInFlavor == "application/vnd.x-moz-cal-event") ||
197 (aInFlavor == "application/vnd.x-moz-cal-task")) {
198 aOutData.value = this.item;
199 aOutDataLen.value = 1;
200 } else {
201 ASSERT(false, "error:"+aInFlavor);
202 }
203 }
204 };
205
206 if (isEvent(item)) {
207 transfer.addDataFlavor("application/vnd.x-moz-cal-event");
208 transfer.setTransferData("application/vnd.x-moz-cal-event", flavourProvider, 0);
209 } else if (isToDo(item)) {
210 transfer.addDataFlavor("application/vnd.x-moz-cal-task");
211 transfer.setTransferData("application/vnd.x-moz-cal-task", flavourProvider, 0);
212 }
213
214 // Also set some normal data-types, in case we drag into another app
215 var serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"].
216 createInstance(Components.interfaces.calIIcsSerializer);
217 serializer.addItems([item], 1);
218
219 var supportsString = Components.classes["@mozilla.org/supports-string;1"].
220 createInstance(Components.interfaces.nsISupportsString);
221 supportsString.data = serializer.serializeToString();
222 transfer.setTransferData("text/calendar", supportsString, supportsString.data.length*2);
223 transfer.setTransferData("text/unicode", supportsString, supportsString.data.length*2);
224
225 var action = dragService.DRAGDROP_ACTION_MOVE;
226 var supArray = Components.classes["@mozilla.org/supports-array;1"].
227 createInstance(Components.interfaces.nsISupportsArray);
228 supArray.AppendElement(transfer);
229
230 // OK, now that the data is all set up, start playing with the shadows
231 this.parentBox.monthView.doDeleteItem(item);
232
233 // Figure out how many shadows we're going to need, and how they should
234 // be oriented from the mouse's position
235 var boxes = this.parentBox.monthView.findBoxesForItem(item);
236 this.parentBox.monthView.mShadowLength = boxes.length;
237 for (var i in boxes) {
238 if (boxes[i].box == this.parentBox) {
239 this.parentBox.monthView.mShadowIndex = i;
240 break;
241 }
242 }
243 this.parentBox.addDropShadows();
244
245 // Mozilla doesn't provide any generic way for us to listen for the
246 // drag-end. Add this listener for a best approximation of listening
247 // for that case.
248 var monthbox = this.parentBox;
249 monthbox.monthView.dropListener = function checkStillDragging() {
250 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
251 getService(Components.interfaces.nsIDragService);
252 var session;
253 try {
254 session = dragService.getCurrentSession();
255 } catch (ex) {}
256 if (!session) {
257 monthbox.removeDropShadows();
258 monthbox.addItem(item);
259 window.removeEventListener("mouseover",
260 monthbox.monthView.dropListener,
261 true);
262 }
263 };
264 window.addEventListener("mouseover",
265 monthbox.monthView.dropListener, true);
266
267 dragService.invokeDragSession(this, supArray, null, action);
268 ]]></handler>
269 </handlers>
270 </binding>
271
272 <binding id="calendar-month-day-box">
273 <content>
274 <xul:vbox flex="1">
275 <xul:label anonid="day-label" crop="end" class="calendar-month-day-box-date-label"/>
276 <xul:vbox anonid="day-items"/>
277 </xul:vbox>
278 </content>
279
280 <implementation>
281 <field name="mDate">null</field>
282 <!-- mItemData will always be kept sorted in display order -->
283 <field name="mItemData">[]</field>
284 <field name="mMonthView">null</field>
285 <field name="mShowMonthLabel">false</field>
286
287 <property name="date">
288 <getter>return this.mDate;</getter>
289 <setter>this.setDate(val); return val;</setter>
290 </property>
291
292 <property name="monthView">
293 <getter><![CDATA[
294 return this.mMonthView;
295 ]]></getter>
296 <setter><![CDATA[
297 this.mMonthView = val;
298 return val;
299 ]]></setter>
300 </property>
301
302 <property name="selected">
303 <getter><![CDATA[
304 var sel = this.getAttribute("selected");
305 if (sel && sel == "true") {
306 return true;
307 }
308
309 return false;
310 ]]></getter>
311 <setter><![CDATA[
312 if (val)
313 this.setAttribute("selected", "true");
314 else
315 this.removeAttribute("selected");
316 return val;
317 ]]></setter>
318 </property>
319
320 <property name="dayitems">
321 <getter>return document.getAnonymousElementByAttribute(this, "anonid", "day-items");</getter>
322 </property>
323
324 <property name="showMonthLabel">
325 <getter><![CDATA[
326 return this.mShowMonthLabel;
327 ]]></getter>
328 <setter><![CDATA[
329 if (this.mShowMonthLabel == val) {
330 return val;
331 }
332 this.mShowMonthLabel = val;
333
334 if (!this.mDate) {
335 return val;
336 }
337 var daylabel = document.getAnonymousElementByAttribute(this, "anonid", "day-label");
338
339 if (val) {
340 var monthName = calGetString("dateFormat", "month." + (this.mDate.month+1) + ".Mmm");
341 daylabel.setAttribute("value", this.mDate.day + " " + monthName);
342 } else {
343 daylabel.setAttribute("value", this.mDate.day);
344 }
345 return val;
346 ]]></setter>
347 </property>
348
349 <method name="setDate">
350 <parameter name="aDate"/>
351 <body><![CDATA[
352 if (!aDate)
353 throw Components.results.NS_ERROR_NULL_POINTER;
354
355 // Remove all the old events
356 this.mItemData = new Array();
357 while(this.dayitems.hasChildNodes()) {
358 this.dayitems.removeChild(this.dayitems.lastChild);
359 }
360
361 if (this.mDate && this.mDate.compare(aDate) == 0)
362 return;
363
364 this.mDate = aDate;
365 var daylabel = document.getAnonymousElementByAttribute(this, "anonid", "day-label");
366
367 if (this.mShowMonthLabel)
368 {
369 var monthName = calGetString("dateFormat", "month." + (aDate.month+1) + ".Mmm");
370 daylabel.setAttribute("value", aDate.day + " " + monthName);
371 } else {
372 daylabel.setAttribute("value", aDate.day);
373 }
374 ]]></body>
375 </method>
376
377 <method name="addItem">
378 <parameter name="aItem"/>
379 <body><![CDATA[
380 for each (ed in this.mItemData) {
381 if (aItem.hashId == ed.item.hashId)
382 {
383 this.deleteItem(aItem);
384 break;
385 }
386 }
387
388 function comptor(a, b) {
389 var aIsEvent = isEvent(a.item);
390 var aIsTodo = isToDo(a.item);
391
392 var bIsEvent = isEvent(b.item);
393 var bIsTodo = isToDo(b.item);
394
395 if ((!aIsEvent && !aIsTodo) || (!bIsEvent && !bIsTodo)) {
396 // XXX ????
397 dump ("Don't know how to sort these two events: " + a.item + " " + b.item + "\n");
398 return 0;
399 }
400
401 // sort todos before events
402 if (aIsTodo && bIsEvent) return -1;
403 if (aIsEvent && bIsTodo) return 1;
404
405 // XXX how do I sort todos?
406 if (aIsTodo && bIsTodo) {
407 return 0;
408 }
409
410 if (aIsEvent && bIsEvent) {
411 // sort all day events before events with a duration
412 if (a.item.startDate.isDate && !b.item.startDate.isDate) return -1;
413 if (!a.item.startDate.isDate && b.item.startDate.isDate) return 1;
414
415 var cmp;
416
417 cmp = a.item.startDate.compare(b.item.startDate);
418 if (cmp != 0)
419 return cmp;
420
421 cmp = a.item.endDate.compare(b.item.endDate);
422 if (cmp != 0)
423 return cmp;
424
425 if (a.item.title < b.item.title)
426 return -1;
427 if (a.item.title > b.item.title)
428 return 1;
429 }
430
431 return 0;
432 }
433
434 // insert the new item block (while keeping the array sorted),
435 // and then relayout.
436 // Note: don't use Array.Sort, because that's quicksort, which
437 // is not good for an already mostly-sorted array
438 var newItem = {item: aItem};
439 for (i = 0; i<this.mItemData.length; ++i) {
440 if (comptor(this.mItemData[i], newItem) > 0)
441 break;
442 }
443 this.mItemData.splice(i, 0, newItem);
444
445 this.relayout();
446 ]]></body>
447 </method>
448
449 <method name="selectItem">
450 <parameter name="aItem"/>
451 <body><![CDATA[
452 for each (var itd in this.mItemData) {
453 if (aItem && (itd.item.hashId == aItem.hashId)) {
454 itd.box.selected = true;
455 }
456 }
457 ]]></body>
458 </method>
459
460 <method name="unselectItem">
461 <parameter name="aItem"/>
462 <body><![CDATA[
463 for each (var itd in this.mItemData) {
464 if (aItem && (itd.item.hashId == aItem.hashId)) {
465 itd.box.selected = false;
466 }
467 }
468 ]]></body>
469 </method>
470
471 <method name="deleteItem">
472 <parameter name="aItem"/>
473 <body><![CDATA[
474 var deleted = [];
475
476 var origLen = this.mItemData.length;
477 this.mItemData = this.mItemData.filter(
478 function(itd) {
479 if (aItem.hashId == itd.item.hashId)
480 {
481 deleted.push(itd);
482 return false;
483 }
484 return true;
485 });
486
487 if (deleted.length > 0) {
488 for each (itd in deleted) {
489 if (itd.box)
490 this.dayitems.removeChild(itd.box);
491 }
492 // no need to relayout; all we did was delete
493 //this.relayout();
494 }
495 ]]></body>
496 </method>
497
498 <method name="relayout">
499 <body><![CDATA[
500 function createXULElement(el) {
501 return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
502 }
503
504
505 for (var i = 0; i < this.mItemData.length; i++) {
506 var itd = this.mItemData[i];
507
508 if (!itd.box) {
509 // find what element to insert before
510 var before = null;
511 for (var j = i+1; !before && this.mItemData[j]; j++)
512 before = this.mItemData[j].box;
513
514 var box = createXULElement("calendar-month-day-box-item");
515 box.setAttribute("context", this.getAttribute("item-context") || this.getAttribute("context"));
516 box.setAttribute("class", "calendar-item");
517 box.setAttribute("item-calendar", itd.item.calendar.uri.spec);
518 box.setAttribute("tooltip", "itemTooltip");
519
520 if (itd.item.hashId in this.monthView.mFlashingEvents) {
521 box.setAttribute("flashing", "true");
522 }
523
524 var categoriesSelectorList = "";
525 var categoriesProperty = itd.item.getProperty("CATEGORIES");
526 if (categoriesProperty) {
527 var categoriesArray = categoriesStringToArray(categoriesProperty);
528 var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
529 categoriesSelectorList = cssClassesArray.join(" ");
530 }
531
532 box.setAttribute("item-category", categoriesSelectorList);
533
534 this.dayitems.insertBefore(box, before);
535
536 box.calendarView = this.monthView;
537 box.item = itd.item;
538 box.occurrence = itd.item;
539 box.parentBox = this;
540 itd.box = box;
541 }
542 }
543 ]]></body>
544 </method>
545
546 <!-- While you might expect 'dragexit' to be fired when we drag outside
547 - the node in question, this isn't actually the case. So, we need
548 - to keep track of these shadows on the month-view itself, so they can
549 - be properly removed.
550 -->
551 <method name="addDropShadows">
552 <body><![CDATA[
553 // Only allow one set of drop-boxes
554 if (this.monthView.mDropShadows) {
555 return;
556 }
557
558 this.monthView.mDropShadows = [];
559 var shadowStart = this.mDate.clone();
560 shadowStart.day -= this.monthView.mShadowIndex;
561 for (var i = 0; i < this.monthView.mShadowLength; i++) {
562 var dropbox = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "box");
563 dropbox.setAttribute("dropbox", "true");
564 dropbox.setAttribute("flex", "1");
565
566 var box = this.monthView.findBoxForDate(shadowStart);
567 if (!box) {
568 // Dragging to the end of a view
569 shadowStart.day += 1;
570 continue;
571 }
572 box.box.dayitems.insertBefore(dropbox, box.box.dayitems.firstChild);
573 this.monthView.mDropShadows.push(dropbox);
574
575 shadowStart.day += 1;
576 }
577 ]]></body>
578 </method>
579 <method name="removeDropShadows">
580 <body><![CDATA[
581 if (!this.monthView.mDropShadows) {
582 return;
583 }
584
585 for each (shadow in this.monthView.mDropShadows) {
586 shadow.parentNode.removeChild(shadow);
587 }
588
589 this.monthView.mDropShadows = null;
590 ]]></body>
591 </method>
592 </implementation>
593
594 <handlers>
595 <handler event="mousedown"><![CDATA[
596 event.stopPropagation();
597
598 if (this.mDate)
599 this.monthView.selectedDay = this.mDate;
600 ]]></handler>
601 <handler event="dblclick"><![CDATA[
602 event.stopPropagation();
603 this.monthView.controller.createNewEvent();
604 ]]></handler>
605 <handler event="dragenter"><![CDATA[
606 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
607 getService(Components.interfaces.nsIDragService);
608 var session = dragService.getCurrentSession();
609 if (session) {
610 session.canDrop = true;
611 this.removeDropShadows();
612 this.addDropShadows();
613 }
614 ]]></handler>
615
616 <handler event="dragover"><![CDATA[
617 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
618 getService(Components.interfaces.nsIDragService);
619 var session = dragService.getCurrentSession();
620 session.canDrop = true;
621 ]]></handler>
622 <handler event="dragexit"><![CDATA[
623 if (event.originalTarget != this) {
624 return;
625 }
626 this.removeDropShadows();
627 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
628 getService(Components.interfaces.nsIDragService);
629 var session = dragService.getCurrentSession();
630 session.canDrop = false;
631 ]]></handler>
632
633 <handler event="dragdrop"><![CDATA[
634 var dragService = Components.classes["@mozilla.org/widget/dragservice;1"].
635 getService(Components.interfaces.nsIDragService);
636 var session = dragService.getCurrentSession();
637
638 var transfer = Components.classes["@mozilla.org/widget/transferable;1"].
639 createInstance(Components.interfaces.nsITransferable);
640 transfer.addDataFlavor("application/x-moz-cal-event");
641 session.getData(transfer, 0);
642 var flavor = {};
643 var data = {};
644
645 // nsITransferable sucks when it comes to trying to add extra flavors.
646 // This will throw NS_ERROR_FAILURE, so as a workaround, we use the
647 // sourceNode property and get the event that way
648 //transfer.getAnyTransferData(flavor, data, {});
649
650 var newStart;
651 var newEnd;
652
653 var boxDate = this.mDate;
654
655 if (!session.sourceNode || !session.sourceNode.occurrence) {
656 return;
657 }
658
659 event.stopPropagation();
660 var item = session.sourceNode.occurrence;
661 var beginMove = session.sourceNode.mParentBox.date;
662 var duration = boxDate.subtractDate(beginMove);
663
664 // Since both boxDate and beginMove are dates (note datetimes),
665 // subtractDate will only give us a non-zero number of hours on DST
666 // changes. While strictly speaking, subtractDate's behavior is
667 // correct, we need to move the event a discrete number of days here.
668 if (duration.hours == 23) {
669 // crossing DT/DST border (spring)
670 duration.hours++;
671 duration.normalize();
672 } else if (duration.hours == 1) {
673 // crossing DST/DT border (autumn)
674 duration.hours--;
675 }
676
677 if (isEvent(item)) {
678 newStart = item.startDate.clone();
679 newStart.addDuration(duration);
680 newEnd = item.endDate.clone();
681 newEnd.addDuration(duration);
682 } else if (isToDo(item)) {
683 if (item.entryDate) {
684 newStart = item.entryDate.clone();
685 newStart.addDuration(duration);
686 }
687 if (item.dueDate) {
688 newEnd = item.dueDate.clone();
689 newEnd.addDuration(duration);
690 }
691 }
692
693 this.removeDropShadows();
694
695 window.removeEventListener("mouseover",
696 this.monthView.dropListener,
697 true);
698
699 this.monthView.controller.modifyOccurrence(item, newStart, newEnd);
700 ]]></handler>
701
702 <handler event="click" button="0"><![CDATA[
703 this.monthView.setSelectedItems(0, []);
704 ]]></handler>
705 </handlers>
706 </binding>
707
708 <binding id="calendar-month-view-column-header">
709 <content>
710 <xul:hbox flex="1">
711 <xul:spacer flex="1"/>
712 <xul:label anonid="label" crop="right" class="calendar-month-view-column-header-label" />
713 <xul:spacer flex="1"/>
714 </xul:hbox>
715 </content>
716
717 <implementation>
718 <field name="mIndex">-1</field>
719
720 <constructor><![CDATA[
721 if (this.mIndex == -1) {
722 var attrIndex = this.getAttribute("index");
723 if (attrIndex)
724 this.index = parseInt(attrIndex);
725 }
726 ]]></constructor>
727
728 <property name="index">
729 <getter>return this.mIndex;</getter>
730 <setter><![CDATA[
731 this.mIndex = val % 7;
732
733 var label = document.getAnonymousElementByAttribute(this, "anonid", "label");
734 var dayName = calGetString("dateFormat", "day." + (this.mIndex+1) + ".name");
735 label.setAttribute("value", dayName);
736
737 return this.mIndex;
738 ]]></setter>
739 </property>
740 </implementation>
741 </binding>
742
743 <binding id="calendar-month-view">
744 <content>
745 <xul:vbox flex="1">
746 <xul:hbox anonid="headerbox" equalsize="always"/>
747
748 <xul:grid anonid="monthgrid" flex="1">
749 <xul:columns anonid="monthgridcolumns" equalsize="always">
750 <xul:column flex="1" class="calendar-month-view-grid-column"/>
751 <xul:column flex="1" class="calendar-month-view-grid-column"/>
752 <xul:column flex="1" class="calendar-month-view-grid-column"/>
753 <xul:column flex="1" class="calendar-month-view-grid-column"/>
754 <xul:column flex="1" class="calendar-month-view-grid-column"/>
755 <xul:column flex="1" class="calendar-month-view-grid-column"/>
756 <xul:column flex="1" class="calendar-month-view-grid-column"/>
757 </xul:columns>
758
759 <xul:rows anonid="monthgridrows" equalsize="always">
760 <xul:row flex="1" class="calendar-month-view-grid-row">
761 <xul:calendar-month-day-box/>
762 <xul:calendar-month-day-box/>
763 <xul:calendar-month-day-box/>
764 <xul:calendar-month-day-box/>
765 <xul:calendar-month-day-box/>
766 <xul:calendar-month-day-box/>
767 <xul:calendar-month-day-box/>
768 </xul:row>
769 <xul:row flex="1" class="calendar-month-view-grid-row">
770 <xul:calendar-month-day-box/>
771 <xul:calendar-month-day-box/>
772 <xul:calendar-month-day-box/>
773 <xul:calendar-month-day-box/>
774 <xul:calendar-month-day-box/>
775 <xul:calendar-month-day-box/>
776 <xul:calendar-month-day-box/>
777 </xul:row>
778 <xul:row flex="1" class="calendar-month-view-grid-row">
779 <xul:calendar-month-day-box/>
780 <xul:calendar-month-day-box/>
781 <xul:calendar-month-day-box/>
782 <xul:calendar-month-day-box/>
783 <xul:calendar-month-day-box/>
784 <xul:calendar-month-day-box/>
785 <xul:calendar-month-day-box/>
786 </xul:row>
787 <xul:row flex="1" class="calendar-month-view-grid-row">
788 <xul:calendar-month-day-box/>
789 <xul:calendar-month-day-box/>
790 <xul:calendar-month-day-box/>
791 <xul:calendar-month-day-box/>
792 <xul:calendar-month-day-box/>
793 <xul:calendar-month-day-box/>
794 <xul:calendar-month-day-box/>
795 </xul:row>
796 <xul:row flex="1" class="calendar-month-view-grid-row">
797 <xul:calendar-month-day-box/>
798 <xul:calendar-month-day-box/>
799 <xul:calendar-month-day-box/>
800 <xul:calendar-month-day-box/>
801 <xul:calendar-month-day-box/>
802 <xul:calendar-month-day-box/>
803 <xul:calendar-month-day-box/>
804 </xul:row>
805 <xul:row flex="1" class="calendar-month-view-grid-row">
806 <xul:calendar-month-day-box/>
807 <xul:calendar-month-day-box/>
808 <xul:calendar-month-day-box/>
809 <xul:calendar-month-day-box/>
810 <xul:calendar-month-day-box/>
811 <xul:calendar-month-day-box/>
812 <xul:calendar-month-day-box/>
813 </xul:row>
814 </xul:rows>
815 </xul:grid>
816 </xul:vbox>
817 </content>
818
819 <implementation implements="calICalendarView">
820
821 <!-- constructor -->
822 <constructor><![CDATA[
823 function createXULElement(el) {
824 return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
825 }
826
827 var headerbox = document.getAnonymousElementByAttribute(this, "anonid", "headerbox")
828 for (var i = 0; i < 7; i++) {
829 var hdr = createXULElement("calendar-month-view-column-header");
830 hdr.setAttribute("flex", "1");
831 headerbox.appendChild(hdr);
832 hdr.index = i;
833 }
834
835 this.mTimezone = floating();
836 var alarmService = Components.classes['@mozilla.org/calendar/alarm-service;1']
837 .getService(Components.interfaces.calIAlarmService);
838 alarmService.addObserver(this.mObserver);
839 ]]></constructor>
840
841 <destructor><![CDATA[
842 if (this.mCalendar) {
843 this.mCalendar.removeObserver(this.mObserver);
844 }
845 var alarmService = Components.classes['@mozilla.org/calendar/alarm-service;1']
846 .getService(Components.interfaces.calIAlarmService);
847 alarmService.removeObserver(this.mObserver);
848 ]]></destructor>
849
850 <!-- fields -->
851
852 <field name="mCalendar">null</field>
853 <field name="mController">null</field>
854 <field name="mStartDate">null</field>
855 <field name="mEndDate">null</field>
856 <field name="mDateBoxes">null</field>
857 <field name="mTimezone">null</field>
858
859 <field name="mSelectedItems">[]</field>
860 <field name="mSelectedDayBox">null</field>
861
862 <field name="mShowDaysOutsideMonth">true</field>
863 <field name="mTasksInView">false</field>
864 <field name="mShowCompleted">false</field>
865 <field name="mShowFullMonth">true</field>
866 <field name="mWeekStartOffset">0</field>
867 <field name="mDaysOffArray">[0,6]</field>
868 <field name="mDisplayDaysOff">true</field>
869 <field name="mDropShadows">null</field>
870 <field name="mShadowIndex">0</field>
871 <field name="mFlashingEvents">new Object()</field>
872
873 <!-- other methods -->
874 <method name="setAttribute">
875 <parameter name="aAttr"/>
876 <parameter name="aVal"/>
877 <body><![CDATA[
878 var needsrelayout = false;
879 if (aAttr == "context" || aAttr == "item-context")
880 needsrelayout = true;
881
882 var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
883
884 if (needsrelayout)
885 this.relayout();
886
887 return ret;
888 ]]></body>
889 </method>
890
891 <!-- calICalendarView -->
892
893 <property name="supportsDisjointDates" readonly="true"
894 onget="return false;"/>
895 <property name="hasDisjointDates" readonly="true"
896 onget="return false;"/>
897
898 <property name="displayCalendar">
899 <getter><![CDATA[
900 return this.mCalendar;
901 ]]></getter>
902 <setter><![CDATA[
903 if (this.mCalendar != val) {
904 this.mCalendar = val;
905 this.mCalendar.addObserver(this.mObserver);
906 this.refresh();
907 }
908 return val;
909 ]]></setter>
910 </property>
911
912 <property name="weekStartOffset">
913 <getter><![CDATA[
914 return this.mWeekStartOffset;
915 ]]></getter>
916 <setter><![CDATA[
917 this.mWeekStartOffset = val;
918 return val;
919 ]]></setter>
920 </property>
921
922 <property name="displayDaysOff">
923 <getter><![CDATA[
924 return this.mDisplayDaysOff;
925 ]]></getter>
926 <setter><![CDATA[
927 this.mDisplayDaysOff = val;
928 return val;
929 ]]></setter>
930 </property>
931
932 <property name="daysOffArray">
933 <getter><![CDATA[
934 return this.mDaysOffArray;
935 ]]></getter>
936 <setter><![CDATA[
937 this.mDaysOffArray = val;
938 return val;
939 ]]></setter>
940 </property>
941
942 <property name="controller"
943 onget="return this.mController;"
944 onset="return this.mController = val;"/>
945
946 <property name="startDate" readonly="true"
947 onget="return this.mStartDate"/>
948
949 <property name="endDate" readonly="true">
950 <getter><![CDATA[
951 return this.mEndDate;
952 ]]></getter>
953 </property>
954
955 <!-- the end date that should be used for getItems and similar queries -->
956 <property name="queryEndDate" readonly="true">
957 <getter><![CDATA[
958 var end = this.endDate;
959 if (!end)
960 return null;
961
962 end = end.clone();
963 end.day += 1;
964 end.isDate = true;
965
966 return end;
967 ]]></getter>
968 </property>
969
970 <property name="tasksInView">
971 <getter><![CDATA[
972 return this.mTasksInView;
973 ]]></getter>
974 <setter><![CDATA[
975 this.mTasksInView = val;
976 return val;
977 ]]></setter>
978 </property>
979
980 <property name="showCompleted">
981 <getter><![CDATA[
982 return this.mShowCompleted;
983 ]]></getter>
984 <setter><![CDATA[
985 this.mShowCompleted = val;
986 return val;
987 ]]></setter>
988 </property>
989
990 <property name="showFullMonth">
991 <getter><![CDATA[
992 return this.mShowFullMonth;
993 ]]></getter>
994 <setter><![CDATA[
995 this.mShowFullMonth = val;
996 return val;
997 ]]></setter>
998 </property>
999
1000 <method name="getSelectedItems">
1001 <parameter name="aCount"/>
1002 <body><![CDATA[
1003 aCount.value = this.mSelectedItems.length;
1004 return this.mSelectedItems;
1005 ]]></body>
1006 </method>
1007 <method name="setSelectedItems">
1008 <parameter name="aCount"/>
1009 <parameter name="aItems"/>
1010 <parameter name="aSuppressEvent"/>
1011 <body><![CDATA[
1012 if (this.mSelectedItems.length) {
1013 for each (var item in this.mSelectedItems) {
1014 var oldboxes = this.findBoxesForItem(item);
1015 for each (oldbox in oldboxes) {
1016 oldbox.box.unselectItem(item);
1017 }
1018 }
1019 }
1020
1021 this.mSelectedItems = aItems || [];
1022
1023 if (this.mSelectedItems.length) {
1024 for each (var item in this.mSelectedItems) {
1025 var newboxes = this.findBoxesForItem(item);
1026 for each (newbox in newboxes) {
1027 newbox.box.selectItem(item);
1028 }
1029 }
1030 }
1031
1032 if (!aSuppressEvent) {
1033 this.fireEvent("itemselect", this.mSelectedItems);
1034 }
1035 ]]></body>
1036 </method>
1037
1038 <property name="selectedDay">
1039 <getter><![CDATA[
1040 if (this.mSelectedDayBox)
1041 return this.mSelectedDayBox.date.clone();
1042
1043 return null;
1044 ]]></getter>
1045 <setter><![CDATA[
1046 if (this.mSelectedDayBox)
1047 this.mSelectedDayBox.box.selected = false;
1048
1049 var realVal = val;
1050 if (!realVal.isDate) {
1051 realVal = val.clone();
1052 realVal.isDate = true;
1053 }
1054 var box = this.findBoxForDate(realVal);
1055 if (box) {
1056 box.box.selected = true;
1057 this.mSelectedDayBox = box;
1058 }
1059 this.fireEvent("dayselect", realVal);
1060 return val;
1061 ]]></setter>
1062 </property>
1063
1064 <property name="timezone">
1065 <getter><![CDATA[
1066 return this.mTimezone;
1067 ]]></getter>
1068 <setter><![CDATA[
1069 this.mTimezone = val;
1070 return val;
1071 ]]></setter>
1072 </property>
1073
1074 <method name="fireEvent">
1075 <parameter name="aEventName"/>
1076 <parameter name="aEventDetail"/>
1077 <body><![CDATA[
1078 var event = document.createEvent('Events');
1079 event.initEvent(aEventName, true, false);
1080 event.detail = aEventDetail;
1081 this.dispatchEvent(event);
1082 ]]></body>
1083 </method>
1084
1085 <method name="showDate">
1086 <parameter name="aDate"/>
1087 <body><![CDATA[
1088 aDate = aDate.getInTimezone(this.mTimezone);
1089
1090 // We might need to do some adjusting here to make sure we still
1091 // display whole month even with alternative week-start days
1092 var startDate = aDate.startOfMonth;
1093 var endDate = aDate.endOfMonth;
1094 if (startDate.weekday < this.mWeekStartOffset) {
1095 // Go back one week to make sure we display this day
1096 startDate.day -= 7;
1097 }
1098
1099 if (endDate.weekday < this.mWeekStartOffset) {
1100 // Go back one week so we don't display any extra days
1101 endDate.day -= 7;
1102 }
1103
1104 this.setDateRange(startDate, endDate);
1105 this.selectedDay = aDate;
1106 ]]></body>
1107 </method>
1108
1109 <method name="setDateRange">
1110 <parameter name="aStartDate"/>
1111 <parameter name="aEndDate"/>
1112 <body><![CDATA[
1113 if (this.mTimezone != aStartDate.timezone) {
1114 aStartDate = aStartDate.getInTimezone(this.mTimezone);
1115 aEndDate = aEndDate.getInTimezone(this.mTimezone);
1116 }
1117
1118 this.mStartDate = aStartDate.startOfWeek;
1119 this.mEndDate = aEndDate.endOfWeek;
1120
1121 this.mStartDate.day += this.mWeekStartOffset;
1122 this.mEndDate.day += this.mWeekStartOffset;
1123
1124 this.refresh();
1125 ]]></body>
1126 </method>
1127
1128 <method name="setDateList">
1129 <parameter name="aCount"/>
1130 <parameter name="aDates"/>
1131 <body><![CDATA[
1132 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
1133 ]]></body>
1134 </method>
1135
1136 <method name="getDateList">
1137 <parameter name="aCount"/>
1138 <body><![CDATA[
1139 if (!this.mStartDate || !this.mEndDate) {
1140 aCount.value = 0;
1141 return [];
1142 }
1143
1144 var results = [];
1145 var curDate = this.mStartDate.clone();
1146 curDate.isDate = true;
1147
1148 while (curDate.compare(this.mEndDate) <= 0) {
1149 results.push(curDate.clone());
1150 curDate.day += 1;
1151 }
1152 aCount.value = results.length;
1153 return results;
1154 ]]></body>
1155 </method>
1156
1157 <!-- public properties and methods -->
1158
1159 <!-- whether to show days outside of the current month -->
1160 <property name="showDaysOutsideMonth">
1161 <getter><![CDATA[
1162 return this.mShowDaysOutsideMonth;
1163 ]]></getter>
1164 <setter><![CDATA[
1165 if (this.mShowDaysOutsideMonth != val) {
1166 this.mShowDaysOutsideMonth = val;
1167 this.refresh();
1168 }
1169 return val;
1170 ]]></setter>
1171 </property>
1172
1173 <!-- private properties and methods -->
1174
1175 <property name="monthgrid" readonly="true"
1176 onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'monthgrid');"/>
1177
1178 <property name="monthgridrows" readonly="true"
1179 onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'monthgridrows');"/>
1180
1181 <field name="mRefreshQueue">[]</field>
1182 <field name="mRefreshPending">null</field>
1183
1184 <method name="popRefreshQueue">
1185 <body><![CDATA[
1186 var pendingRefresh = this.mRefreshPending;
1187 if (pendingRefresh) {
1188 if (pendingRefresh instanceof Components.interfaces.calIOperation) {
1189 this.mRefreshPending = null;
1190 pendingRefresh.cancel(null);
1191 } else {
1192 if(this.mRefreshQueue.length > 0) {
1193 this.relayout();
1194 }
1195 return;
1196 }
1197 }
1198
1199 var refreshJob = this.mRefreshQueue.pop();
1200 if (!refreshJob) {
1201 return;
1202 }
1203
1204 if (!this.startDate || !this.endDate)
1205 return;
1206
1207 this.relayout();
1208
1209 if (!this.mCalendar)
1210 return;
1211
1212 var filter = this.mCalendar.ITEM_FILTER_CLASS_OCCURRENCES;
1213 if (this.showCompleted) {
1214 filter |= this.mCalendar.ITEM_FILTER_COMPLETED_ALL;
1215 } else {
1216 filter |= this.mCalendar.ITEM_FILTER_COMPLETED_NO;
1217 }
1218
1219 if (this.mTasksInView)
1220 filter |= this.mCalendar.ITEM_FILTER_TYPE_ALL;
1221 else
1222 filter |= this.mCalendar.ITEM_FILTER_TYPE_EVENT;
1223
1224 this.mRefreshPending = true;
1225 pendingRefresh = this.mCalendar.getItems(filter,
1226 0,
1227 this.startDate,
1228 this.queryEndDate,
1229 this.mOperationListener);
1230 if (pendingRefresh && pendingRefresh.isPending) { // support for calIOperation
1231 this.mRefreshPending = pendingRefresh;
1232 }
1233 ]]></body>
1234 </method>
1235
1236 <method name="refresh">
1237 <body><![CDATA[
1238 var refreshJob = {};
1239 this.mRefreshQueue.push(refreshJob);
1240 this.popRefreshQueue();
1241 ]]></body>
1242 </method>
1243
1244 <method name="relayout">
1245 <body><![CDATA[
1246 function createXULElement(el) {
1247 return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
1248 }
1249
1250 // Adjust headers based on the starting day of the week, if necessary
1251 var headerbox = document.getAnonymousElementByAttribute(this, "anonid", "headerbox");
1252
1253 if (headerbox.firstChild.index != this.mWeekStartOffset) {
1254 var i = 0;
1255 for each(header in headerbox.childNodes) {
1256 header.index = (i + this.mWeekStartOffset) % 7;
1257 i++;
1258 }
1259 }
1260
1261 if (this.mSelectedItems.length) {
1262 this.mSelectedItems = [];
1263 }
1264
1265 // Clear out the old selection, since it won't be valid after relayout
1266 if (this.mSelectedDayBox) {
1267 this.mSelectedDayBox.box.selected = false;
1268 }
1269
1270 if (!this.mStartDate || !this.mEndDate)
1271 throw NS_ERROR_FAILURE;
1272
1273 // Days that are not in the main month on display are displayed with
1274 // a gray background. Unless the month actually starts on a Sunday,
1275 // this means that mStartDate.month is 1 month less than the main month
1276 var mainMonth = this.mStartDate.month;
1277 if (this.mStartDate.day != 1) {
1278 mainMonth++;
1279 mainMonth = mainMonth % 12;
1280 }
1281
1282 var dateBoxes = [];
1283 var today = this.today();
1284
1285 // This gets set to true, telling us to collapse the rest of the rows
1286 var finished = false;
1287 var dateList = this.getDateList({})
1288
1289 var rows = this.monthgridrows.childNodes;
1290
1291 // Iterate through each monthgridrow and set up the day-boxes that
1292 // are its child nodes. Remember, childNodes is not a normal array,
1293 // so don't use the in operator if you don't want extra properties
1294 // coming out.
1295 for (var i = 0; i < rows.length; i++) {
1296 var row = rows[i];
1297 // If we've already assigned all of the day-boxes that we need, just
1298 // collapse the rest of the rows, otherwise expand them if needed.
1299 if (finished) {
1300 row.setAttribute("collapsed", true);
1301 continue;
1302 } else {
1303 row.removeAttribute("collapsed");
1304 }
1305 for (var j = 0; j < row.childNodes.length; j++) {
1306 var daybox = row.childNodes[j];
1307 var date = dateList[dateBoxes.length];
1308
1309 daybox.setAttribute("context", this.getAttribute("context"));
1310 daybox.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));
1311
1312 // Set the box-class depending on if this box displays a day in
1313 // the month being currently shown or not.
1314 var boxClass;
1315 if (this.showFullMonth) {
1316 boxClass = "calendar-month-day-box-" +
1317 (mainMonth == date.month ? "current-month" : "other-month");
1318 } else {
1319 boxClass = "calendar-month-day-box-current-month";
1320 }
1321
1322 function matchesDayOff(dayOffNum) { return dayOffNum == date.weekday; }
1323 if (this.mDaysOffArray.some(matchesDayOff)) {
1324 boxClass = "calendar-month-day-box-day-off " + boxClass;
1325 }
1326
1327 // Set up date relations
1328 switch (date.compare(today)) {
1329 case -1:
1330 daybox.setAttribute("relation", "past");
1331 break;
1332 case 0:
1333 daybox.setAttribute("relation", "today");
1334 break;
1335 case 1:
1336 daybox.setAttribute("relation", "future");
1337 break;
1338 }
1339
1340 daybox.setAttribute("class", boxClass);
1341
1342 daybox.setDate(date);
1343 if (date.day == 1 || date.day == date.endOfMonth.day) {
1344 daybox.showMonthLabel = true;
1345 } else {
1346 daybox.showMonthLabel = false;
1347 }
1348 daybox.monthView = this;
1349
1350 // add the box and its data to our stored array
1351 var boxdata = {
1352 date: date,
1353 row: row,
1354 box: daybox
1355 };
1356
1357 dateBoxes.push(boxdata);
1358
1359 // If we've now assigned all of our dates, set this to true so we
1360 // know we can just collapse the rest of the rows.
1361 if (dateBoxes.length == dateList.length) {
1362 finished = true;
1363 }
1364 }
1365 }
1366
1367 // If we're not showing a full month, then add a few extra labels to
1368 // help the user orient themselves in the view.
1369 if (!this.mShowFullMonth) {
1370 dateBoxes[0].box.showMonthLabel = true;
1371 dateBoxes[dateBoxes.length-1].box.showMonthLabel = true;
1372 }
1373
1374 // Store these, so that we can access them later
1375 this.mDateBoxes = dateBoxes;
1376 this.hideDaysOff();
1377
1378 // Highlight today, if it's in the range of the view
1379 if (today.compare(dateList[0]) != -1 &&
1380 today.compare(dateList[dateList.length-1]) != 1) {
1381 this.findBoxForDate(today).box.setAttribute("today", "true");
1382 }
1383 ]]></body>
1384 </method>
1385
1386 <method name="hideDaysOff">
1387 <body><![CDATA[
1388 var columns = document.getAnonymousElementByAttribute(this, "anonid", "monthgridcolumns").childNodes;
1389 var headerkids = document.getAnonymousElementByAttribute(this, "anonid", "headerbox").childNodes;
1390 for (var i in columns) {
1391 var dayForColumn = (Number(i) + this.mWeekStartOffset) % 7;
1392 var dayOff = (this.mDaysOffArray.indexOf(dayForColumn) != -1);
1393 columns[i].collapsed = dayOff && !this.mDisplayDaysOff;
1394 headerkids[i].collapsed = dayOff && !this.mDisplayDaysOff;
1395 }
1396 ]]></body>
1397 </method>
1398
1399 <method name="findBoxForDate">
1400 <parameter name="aDate"/>
1401 <body><![CDATA[
1402 for each (box in this.mDateBoxes) {
1403 if (box.date.compare(aDate) == 0)
1404 return box;
1405 }
1406 return null;
1407 ]]></body>
1408 </method>
1409
1410 <method name="findBoxesForItem">
1411 <parameter name="aItem"/>
1412 <body><![CDATA[
1413 var targetDate = null;
1414 var finishDate = null;
1415 var boxes = new Array();
1416
1417 // All our boxes are in default tz, so we need these times in them too.
1418 if (isEvent(aItem)) {
1419 targetDate = aItem.startDate.getInTimezone(this.mTimezone);
1420 finishDate = aItem.endDate.getInTimezone(this.mTimezone);
1421 } else if (isToDo(aItem)) {
1422 if (aItem.entryDate) {
1423 targetDate = aItem.entryDate.getInTimezone(this.mTimezone);
1424 if (aItem.dueDate) {
1425 finishDate = aItem.dueDate.getInTimezone(this.mTimezone);
1426 }
1427 }
1428 }
1429
1430 if (!targetDate)
1431 return boxes;
1432
1433 if (!finishDate) {
1434 var maybeBox = this.findBoxForDate(targetDate);
1435 if (maybeBox) {
1436 boxes.push(maybeBox);
1437 }
1438 return boxes;
1439 }
1440
1441 if (!targetDate.isDate) {
1442 // Reset the time to 00:00, so that we really get all the boxes
1443 targetDate.hour = 0;
1444 targetDate.minute = 0;
1445 targetDate.second = 0;
1446 }
1447
1448 if (targetDate.compare(finishDate) == 0) {
1449 // Zero length events are silly, but we have to handle them
1450 var box = this.findBoxForDate(targetDate);
1451 if (box) {
1452 boxes.push(box);
1453 }
1454 }
1455
1456 while (targetDate.compare(finishDate) == -1) {
1457 var box = this.findBoxForDate(targetDate);
1458
1459 // This might not exist, if the event spans the view start or end
1460 if (box) {
1461 boxes.push(box);
1462 }
1463 targetDate.day += 1;
1464 }
1465
1466 return boxes;
1467 ]]></body>
1468 </method>
1469
1470 <method name="doAddItem">
1471 <parameter name="aItem"/>
1472 <body><![CDATA[
1473 var boxes = this.findBoxesForItem(aItem);
1474
1475 if (!boxes.length)
1476 return;
1477
1478 for each (box in boxes) {
1479 box.box.addItem(aItem);
1480 }
1481 ]]></body>
1482 </method>
1483
1484 <method name="doDeleteItem">
1485 <parameter name="aItem"/>
1486 <body><![CDATA[
1487 var boxes = this.findBoxesForItem(aItem);
1488
1489 if (!boxes.length)
1490 return;
1491
1492 function isNotItem(a) {
1493 return (a.hashId != aItem.hashId);
1494 }
1495 var oldLength = this.mSelectedItems.length;
1496 this.mSelectedItems = this.mSelectedItems.filter(isNotItem);
1497
1498 for each (box in boxes) {
1499 box.box.deleteItem(aItem);
1500 }
1501
1502 // If a deleted event was selected, we need to announce that the
1503 // selection changed.
1504 if (oldLength != this.mSelectedItems.length) {
1505 this.fireEvent("itemselect", this.mSelectedItems);
1506 }
1507 ]]></body>
1508 </method>
1509
1510 <method name="today">
1511 <body><![CDATA[
1512 var date = createDateTime();
1513 date.jsDate = new Date();
1514 date = date.getInTimezone(this.mTimezone);
1515 date.isDate = true;
1516 return date;
1517 ]]></body>
1518 </method>
1519
1520 <!-- our private observers and listeners -->
1521
1522 <field name="mOperationListener"><![CDATA[
1523 ({
1524 calView: this,
1525
1526 QueryInterface: function (aIID) {
1527 if (!aIID.equals(Components.interfaces.calIOperationListener) &&
1528 !aIID.equals(Components.interfaces.nsISupports)) {
1529 throw Components.results.NS_ERROR_NO_INTERFACE;
1530 }
1531
1532 return this;
1533 },
1534
1535 onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
1536 // Fire viewloaded event
1537 this.calView.fireEvent('viewloaded', aOperationType);
1538
1539 // signal that the current operation finished.
1540 this.calView.mRefreshPending = null;
1541
1542 // immediately start the next job on the queue.
1543 this.calView.popRefreshQueue();
1544 },
1545 onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
1546 if (!Components.isSuccessCode(aStatus))
1547 return;
1548
1549 for each (var item in aItems) {
1550 this.calView.doAddItem(item);
1551 }
1552 }
1553 })
1554 ]]></field>
1555
1556 <field name="mObserver"><![CDATA[
1557 // the calIObserver, calICompositeObserver, calIAlarmServiceObserver
1558 ({
1559 calView: this,
1560 mBatchCount: 0,
1561
1562 QueryInterface: function (aIID) {
1563 if (!aIID.equals(Components.interfaces.calIObserver) &&
1564 !aIID.equals(Components.interfaces.calICompositeObserver) &&
1565 !aIID.equals(Components.interfaces.calIAlarmServiceObserver) &&
1566 !aIID.equals(Components.interfaces.nsISupports)) {
1567 throw Components.results.NS_ERROR_NO_INTERFACE;
1568 }
1569
1570 return this;
1571 },
1572
1573 onStartBatch: function() {
1574 this.mBatchCount++;
1575 },
1576 onEndBatch: function() {
1577 this.mBatchCount--;
1578 if (this.mBatchCount == 0) {
1579 this.calView.refresh();
1580 }
1581 },
1582 onLoad: function() {
1583 this.calView.refresh();
1584 },
1585 onAddItem: function (aItem) {
1586 if (this.mBatchCount) {
1587 return;
1588 }
1589
1590 if (isToDo(aItem)) {
1591 if (!this.calView.mTasksInView) {
1592 return;
1593 }
1594 if (aItem.isCompleted && !this.calView.mShowCompleted) {
1595 return;
1596 }
1597 }
1598
1599 var occs = aItem.getOccurrencesBetween(this.calView.startDate,
1600 this.calView.queryEndDate,
1601 {});
1602 for each (var occ in occs)
1603 this.calView.doAddItem(occ);
1604 },
1605 onModifyItem: function (aNewItem, aOldItem) {
1606 if (this.mBatchCount) {
1607 return;
1608 }
1609
1610 if (isToDo(aNewItem) && isToDo(aOldItem) &&
1611 !this.calView.mTasksInView) {
1612 return;
1613 }
1614
1615 var occs;
1616 occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
1617 this.calView.queryEndDate,
1618 {});
1619 for each (var occ in occs)
1620 this.calView.doDeleteItem(occ);
1621
1622 if (isToDo(aNewItem)) {
1623 if (!this.calView.mTasksInView) {
1624 return;
1625 }
1626 if (aNewItem.isCompleted && !this.calView.mShowCompleted) {
1627 return;
1628 }
1629 }
1630 occs = aNewItem.getOccurrencesBetween(this.calView.startDate,
1631 this.calView.queryEndDate,
1632 {});
1633 for each (var occ in occs)
1634 this.calView.doAddItem(occ);
1635 },
1636 onDeleteItem: function (aItem) {
1637 if (this.mBatchCount) {
1638 return;
1639 }
1640
1641 if (isToDo(aItem)) {
1642 if (!this.calView.mTasksInView) {
1643 return;
1644 }
1645 if (aItem.isCompleted && !this.calView.mShowCompleted) {
1646 return;
1647 }
1648 }
1649
1650 var occs = aItem.getOccurrencesBetween(this.calView.startDate,
1651 this.calView.queryEndDate,
1652 {});
1653 for each (var occ in occs) {
1654 this.calView.doDeleteItem(occ);
1655 }
1656 },
1657 onError: function (aErrNo, aMessage) { },
1658
1659 onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
1660 if (aName == "suppressAlarms" &&
1661 aCalendar.getProperty("capabilities.alarms.popup.supported") !== false &&
1662 getPrefSafe("calendar.alarms.indicator.show", true)) {
1663 this.calView.refresh();
1664 }
1665 },
1666 onPropertyDeleting: function(aCalendar, aName) {
1667 this.onPropertyChanged(aCalendar, aName, null, null);
1668 },
1669
1670 //
1671 // calIAlarmServiceObserver stuff
1672 //
1673 onAlarm: function onAlarm(aAlarmItem) {
1674 this.calView.flashAlarm(aAlarmItem, false);
1675 },
1676
1677 onRemoveAlarmsByItem: function onRemoveAlarmsByItem(aItem) {
1678 // Stop the flashing for the item.
1679 this.calView.flashAlarm(aItem, true);
1680 },
1681
1682 onRemoveAlarmsByCalendar: function onRemoveAlarmsByCalendar(aCalendar) {
1683 // Stop the flashing for all items of this calendar
1684 for each (var item in this.calView.mFlashingEvents) {
1685 if (item.calendar.id == aCalendar.id) {
1686 this.calView.flashAlarm(item, true);
1687 }
1688 }
1689 },
1690
1691 //
1692 // calICompositeObserver stuff
1693 // XXXvv we can be smarter about how we handle this stuff
1694 //
1695 onCalendarAdded: function (aCalendar) {
1696 this.calView.refresh();
1697 },
1698
1699 onCalendarRemoved: function (aCalendar) {
1700 this.calView.refresh();
1701 },
1702
1703 onDefaultCalendarChanged: function (aNewDefaultCalendar) {
1704 // don't care, for now
1705 }
1706 })
1707 ]]></field>
1708
1709 <method name="flashAlarm">
1710 <parameter name="aAlarmItem"/>
1711 <parameter name="aStop"/>
1712 <body><![CDATA[
1713 var showIndicator = getPrefSafe("calendar.alarms.indicator.show", true);
1714 var totaltime = getPrefSafe("calendar.alarms.indicator.totaltime", 3600);
1715
1716 if (!aStop && (!showIndicator || totaltime < 1)) {
1717 // No need to animate if the indicator should not be shown.
1718 return;
1719 }
1720
1721 // Make sure the flashing attribute is set or reset on all visible
1722 // boxes.
1723 var boxes = this.findBoxesForItem(aAlarmItem);
1724 for each (var box in boxes) {
1725 for each (var itemData in box.box.mItemData) {
1726 if (itemData.item.hasSameIds(aAlarmItem)) {
1727 if (aStop) {
1728 itemData.box.removeAttribute("flashing");
1729 } else {
1730 itemData.box.setAttribute("flashing", "true");
1731 }
1732 }
1733 }
1734 }
1735
1736 if (!aStop) {
1737 // Set up a timer to stop the flashing after the total time.
1738 var this_ = this;
1739 this.mFlashingEvents[aAlarmItem.hashId] = aAlarmItem;
1740 setTimeout(function() { this_.flashAlarm(aAlarmItem, true) }, totaltime);
1741 } else {
1742 // We are done flashing, prevent newly created event boxes from flashing.
1743 delete this.mFlashingEvents[aAlarmItem.hashId];
1744 }
1745 ]]></body>
1746 </method>
1747 </implementation>
1748
1749 <handlers>
1750 <handler event="keypress"><![CDATA[
1751 const kKE = Components.interfaces.nsIDOMKeyEvent;
1752 if (event.keyCode == kKE.DOM_VK_BACK_SPACE ||
1753 event.keyCode == kKE.DOM_VK_DELETE)
1754 {
1755 if (!this.activeInPlaceEdit && this.mSelectedItems.length && this.controller) {
1756 this.controller.deleteOccurrences(this.mSelectedItems.length,
1757 this.mSelectedItems,
1758 event.ctrlKey,
1759 false);
1760 }
1761 }
1762 ]]></handler>
1763 </handlers>
1764 </binding>
1765
1766 </bindings>