gecko-dev/calendar/base/content/calendar-month-view.xml

1539 lines
52 KiB
XML

<?xml version="1.0"?>
<!--
- ***** 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 calendar views.
-
- The Initial Developer of the Original Code is
- Oracle Corporation
- Portions created by the Initial Developer are Copyright (C) 2005
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Vladimir Vukicevic <vladimir@pobox.com>
- Stefan Sitter <ssitter@googlemail.com>
- Clint Talbert <cmtalbert@myfastmail.com>
-
- 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 *****
-->
<!-- Note that this file depends on helper functions in calendarUtils.js-->
<bindings id="calendar-month-view-bindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="calendar-month-day-box-item" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
<content>
<xul:hbox flex="1" class="calendar-item">
<xul:label anonid="item-label" class="calendar-month-day-box-item-label" xbl:inherits="context"/>
<xul:vbox class="calendar-event-box-container" xbl:inherits="context" flex="1" align="left">
<xul:label anonid="event-name" style="margin: 0px;" flex="1" crop="end"/>
<xul:textbox class="plain" style="background: transparent !important"
anonid="event-name-textbox" crop="end" hidden="true" wrap="true"/>
<xul:spacer flex="1"/>
</xul:vbox>
</xul:hbox>
</content>
<implementation>
<property name="occurrence">
<getter><![CDATA[
return this.mOccurrence;
]]></getter>
<setter><![CDATA[
this.mOccurrence = val;
var str = null;
// if val is an event and not an all day event, show start time in title
if (val instanceof Components.interfaces.calIEvent && !val.startDate.isDate) {
var df = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
.getService(Components.interfaces.calIDateTimeFormatter);
str = df.formatTime(val.startDate.getInTimezone(this.calendarView.mTimezone));
} else if (val instanceof Components.interfaces.calITodo) {
// yeah, this should really be a little picture instead of a "*"
str = "* ";
} else {
str = " ";
}
var label = document.getAnonymousElementByAttribute(this, "anonid", "item-label");
label.value = str;
this.setEditableLabel();
this.setCSSClasses();
return val;
]]></setter>
</property>
<property name="parentBox"
onget="return this.mParentBox;"
onset="this.mParentBox = val;"/>
</implementation>
<handlers>
<handler event="draggesture"><![CDATA[
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
.getService(Components.interfaces.nsIDragService);
var transfer = Components.classes["@mozilla.org/widget/transferable;1"]
.createInstance(Components.interfaces.nsITransferable);
var item = this.occurrence;
transfer.addDataFlavor("text/calendar");
var flavourProvider = {
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIFlavorDataProvider) ||
aIID.equals(Components.interfaces.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
item: item,
getFlavorData: function(aInTransferable, aInFlavor, aOutData, aOutDataLen) {
if ((aInFlavor == "application/vnd.x-moz-cal-event") ||
(aInFlavor == "application/vnd.x-moz-cal-task")) {
aOutData.value = this.item;
aOutDataLen.value = 1;
return Components.results.NS_OK;
} else {
alert("error:"+aInFlavor);
}
}
};
if (item instanceof Components.interfaces.calIEvent) {
transfer.addDataFlavor("application/vnd.x-moz-cal-event");
transfer.setTransferData("application/vnd.x-moz-cal-event", flavourProvider, 0);
} else if (item instanceof Components.interfaces.calITodo) {
transfer.addDataFlavor("application/vnd.x-moz-cal-task");
transfer.setTransferData("application/vnd.x-moz-cal-task", flavourProvider, 0);
}
// Also set some normal data-types, in case we drag into another app
var supportsString = Components.classes["@mozilla.org/supports-string;1"]
.createInstance(Components.interfaces.nsISupportsString);
supportsString.data = item.icalComponent.serializeToICS();
transfer.setTransferData("text/calendar", supportsString, supportsString.data.length*2);
transfer.setTransferData("text/unicode", supportsString, supportsString.data.length*2);
var action = dragService.DRAGDROP_ACTION_MOVE;
var supArray = Components.classes["@mozilla.org/supports-array;1"]
.createInstance(Components.interfaces.nsISupportsArray);
supArray.AppendElement(transfer);
// OK, now that the data is all set up, start playing with the shadows
this.parentBox.monthView.doDeleteItem(item);
// Figure out how many shadows we're going to need, and how they should
// be oriented from the mouse's position
var boxes = this.parentBox.monthView.findBoxesForItem(item);
this.parentBox.monthView.mShadowLength = boxes.length;
for (var i in boxes) {
if (boxes[i].box == this.parentBox) {
this.parentBox.monthView.mShadowIndex = i;
break;
}
}
this.parentBox.addDropShadows();
// Mozilla doesn't provide any generic way for us to listen for the
// drag-end. Add this listener for a best approximation of listening
// for that case.
var monthbox = this.parentBox;
monthbox.monthView.dropListener = function checkStillDragging() {
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
.getService(Components.interfaces.nsIDragService);
var session;
try {
session = dragService.getCurrentSession();
} catch (ex) {}
if (!session) {
monthbox.removeDropShadows();
monthbox.addItem(item);
window.removeEventListener("mouseover",
monthbox.monthView.dropListener,
true);
}
};
window.addEventListener("mouseover",
monthbox.monthView.dropListener, true);
dragService.invokeDragSession(this, supArray, null, action);
]]></handler>
</handlers>
</binding>
<binding id="calendar-month-day-box">
<content>
<xul:vbox flex="1">
<xul:label anonid="day-label" crop="end" class="calendar-month-day-box-date-label"/>
<xul:vbox anonid="day-items"/>
</xul:vbox>
</content>
<implementation>
<field name="mDate">null</field>
<!-- mItemData will always be kept sorted in display order -->
<field name="mItemData">[]</field>
<field name="mMonthView">null</field>
<field name="mShowMonthLabel">false</field>
<property name="date">
<getter>return this.mDate;</getter>
<setter>this.setDate(val); return val;</setter>
</property>
<property name="monthView">
<getter><![CDATA[
return this.mMonthView;
]]></getter>
<setter><![CDATA[
this.mMonthView = val;
return val;
]]></setter>
</property>
<property name="selected">
<getter><![CDATA[
var sel = this.getAttribute("selected");
if (sel && sel == "true") {
return true;
}
return false;
]]></getter>
<setter><![CDATA[
if (val)
this.setAttribute("selected", "true");
else
this.removeAttribute("selected");
return val;
]]></setter>
</property>
<property name="dayitems">
<getter>return document.getAnonymousElementByAttribute(this, "anonid", "day-items");</getter>
</property>
<property name="showMonthLabel">
<getter><![CDATA[
return this.mShowMonthLabel;
]]></getter>
<setter><![CDATA[
if (this.mShowMonthLabel == val) {
return val;
}
this.mShowMonthLabel = val;
if (!this.mDate) {
return val;
}
var daylabel = document.getAnonymousElementByAttribute(this, "anonid", "day-label");
if (val) {
var monthName = calGetString("dateFormat", "month." + (this.mDate.month+1) + ".Mmm");
daylabel.setAttribute("value", this.mDate.day + " " + monthName);
} else {
daylabel.setAttribute("value", this.mDate.day);
}
return val;
]]></setter>
</property>
<method name="setDate">
<parameter name="aDate"/>
<body><![CDATA[
if (!aDate)
throw Components.results.NS_ERROR_NULL_POINTER;
// Remove all the old events
this.mItemData = new Array();
while(this.dayitems.hasChildNodes()) {
this.dayitems.removeChild(this.dayitems.lastChild);
}
if (this.mDate && this.mDate.compare(aDate) == 0)
return;
this.mDate = aDate;
var daylabel = document.getAnonymousElementByAttribute(this, "anonid", "day-label");
if (this.mShowMonthLabel)
{
var monthName = calGetString("dateFormat", "month." + (aDate.month+1) + ".Mmm");
daylabel.setAttribute("value", aDate.day + " " + monthName);
} else {
daylabel.setAttribute("value", aDate.day);
}
]]></body>
</method>
<method name="addItem">
<parameter name="aItem"/>
<body><![CDATA[
for each (ed in this.mItemData) {
if (aItem.hasSameIds(ed.item))
{
this.deleteItem(aItem);
break;
}
}
// insert the new item block, and then relayout
this.mItemData.push({ item: aItem });
this.relayout();
]]></body>
</method>
<method name="selectItem">
<parameter name="aItem"/>
<body><![CDATA[
for each (var itd in this.mItemData) {
if (itd.item.hasSameIds(aItem)) {
itd.box.selected = true;
}
}
]]></body>
</method>
<method name="unselectItem">
<parameter name="aItem"/>
<body><![CDATA[
for each (var itd in this.mItemData) {
if (itd.item.hasSameIds(aItem)) {
itd.box.selected = false;
}
}
]]></body>
</method>
<method name="deleteItem">
<parameter name="aItem"/>
<body><![CDATA[
var deleted = [];
var origLen = this.mItemData.length;
this.mItemData = this.mItemData.filter(
function(itd) {
if (aItem.hasSameIds(itd.item))
{
deleted.push(itd);
return false;
}
return true;
});
if (deleted.length > 0) {
for each (itd in deleted) {
if (itd.box)
this.dayitems.removeChild(itd.box);
}
// no need to relayout; all we did was delete
//this.relayout();
}
]]></body>
</method>
<method name="relayout">
<body><![CDATA[
function createXULElement(el) {
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
}
this.mItemData.sort(
function (a, b) {
var aIsEvent = (a.item instanceof Components.interfaces.calIEvent);
var aIsTodo = (a.item instanceof Components.interfaces.calITodo);
var bIsEvent = (b.item instanceof Components.interfaces.calIEvent);
var bIsTodo = (b.item instanceof Components.interfaces.calITodo);
if ((!aIsEvent && !aIsTodo) || (!bIsEvent && !bIsTodo)) {
// XXX ????
dump ("Don't know how to sort these two events: " + a.item + " " + b.item + "\n");
return 0;
}
// sort todos before events
if (aIsTodo && bIsEvent) return -1;
if (aIsEvent && bIsTodo) return 1;
// XXX how do I sort todos?
if (aIsTodo && bIsTodo) {
return 0;
}
if (aIsEvent && bIsEvent) {
var cmp;
cmp = a.item.startDate.compare(b.item.startDate);
if (cmp != 0)
return cmp;
cmp = a.item.endDate.compare(b.item.endDate);
if (cmp != 0)
return cmp;
if (a.item.title < b.item.title)
return -1;
if (a.item.title > b.item.title)
return 1;
}
return 0;
});
for (var i = 0; i < this.mItemData.length; i++) {
var itd = this.mItemData[i];
if (!itd.box) {
// find what element to insert before
var before = null;
for (var j = i+1; !before && this.mItemData[j]; j++)
before = this.mItemData[j].box;
var box = createXULElement("calendar-month-day-box-item");
box.setAttribute("context", this.getAttribute("item-context") || this.getAttribute("context"));
box.setAttribute("class", "calendar-item");
box.setAttribute("item-calendar", itd.item.calendar.uri.spec);
box.setAttribute("tooltip", "itemTooltip");
var categoriesSelectorList = "";
if (itd.item.getProperty("CATEGORIES") != null) {
var categoriesList = itd.item.getProperty("CATEGORIES").split(",");
for (var i = 0; i < categoriesList.length; i++ ) {
// Remove illegal chars.
categoriesList[i] = categoriesList[i].replace(' ','_');
categoriesList[i] = categoriesList[i].toLowerCase();
}
categoriesSelectorList = categoriesList.join(" ");
}
box.setAttribute("item-category", categoriesSelectorList);
this.dayitems.insertBefore(box, before);
box.calendarView = this.monthView;
box.item = itd.item;
box.occurrence = itd.item;
box.parentBox = this;
itd.box = box;
}
}
]]></body>
</method>
<!-- While you might expect 'dragexit' to be fired when we drag outside
- the node in question, this isn't actually the case. So, we need
- to keep track of these shadows on the month-view itself, so they can
- be properly removed.
-->
<method name="addDropShadows">
<body><![CDATA[
// Only allow one set of drop-boxes
if (this.monthView.mDropShadows) {
return;
}
this.monthView.mDropShadows = [];
var shadowStart = this.mDate.clone();
shadowStart.day -= this.monthView.mShadowIndex;
shadowStart.normalize();
for (var i = 0; i < this.monthView.mShadowLength; i++) {
var dropbox = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "box");
dropbox.setAttribute("dropbox", "true");
dropbox.setAttribute("flex", "1");
var box = this.monthView.findBoxForDate(shadowStart);
if (!box) {
// Dragging to the end of a view
shadowStart.day += 1;
shadowStart.normalize();
continue;
}
box.box.dayitems.insertBefore(dropbox, box.box.dayitems.firstChild);
this.monthView.mDropShadows.push(dropbox);
shadowStart.day += 1;
shadowStart.normalize();
}
]]></body>
</method>
<method name="removeDropShadows">
<body><![CDATA[
if (!this.monthView.mDropShadows) {
return;
}
for each (shadow in this.monthView.mDropShadows) {
shadow.parentNode.removeChild(shadow);
}
this.monthView.mDropShadows = null;
]]></body>
</method>
</implementation>
<handlers>
<handler event="mousedown"><![CDATA[
event.stopPropagation();
if (this.mDate)
this.monthView.selectedDay = this.mDate;
]]></handler>
<handler event="dblclick"><![CDATA[
event.stopPropagation();
this.monthView.controller.createNewEvent();
]]></handler>
<handler event="dragenter"><![CDATA[
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
.getService(Components.interfaces.nsIDragService);
var session = dragService.getCurrentSession();
session.canDrop = true;
this.removeDropShadows();
this.addDropShadows();
]]></handler>
<handler event="dragover"><![CDATA[
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
.getService(Components.interfaces.nsIDragService);
var session = dragService.getCurrentSession();
session.canDrop = true;
]]></handler>
<handler event="dragexit"><![CDATA[
if (event.originalTarget != this) {
return;
}
this.removeDropShadows();
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
.getService(Components.interfaces.nsIDragService);
var session = dragService.getCurrentSession();
session.canDrop = false;
]]></handler>
<handler event="dragdrop"><![CDATA[
var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
.getService(Components.interfaces.nsIDragService);
var session = dragService.getCurrentSession();
var transfer = Components.classes["@mozilla.org/widget/transferable;1"]
.createInstance(Components.interfaces.nsITransferable);
transfer.addDataFlavor("application/x-moz-cal-event");
session.getData(transfer, 0);
var flavor = {};
var data = {};
// nsITransferable sucks when it comes to trying to add extra flavors.
// This will throw NS_ERROR_FAILURE, so as a workaround, we use the
// sourceNode property and get the event that way
//transfer.getAnyTransferData(flavor, data, {});
var newStart;
var newEnd;
var boxDate = this.mDate;
var offset = this.monthView.mShadowIndex;
function adjustDate(date) {
var newDate = date.clone();
newDate.day = boxDate.day - offset;
newDate.month = boxDate.month;
newDate.year = boxDate.year;
return newDate;
}
if (!session.sourceNode || !session.sourceNode.occurrence) {
return;
}
event.stopPropagation();
var item = session.sourceNode.occurrence;
if (item instanceof Components.interfaces.calIEvent) {
var beginMove = item.startDate.clone();
beginMove.isDate = true;
var duration = boxDate.subtractDate(beginMove);
newStart = item.startDate.clone();
newStart.addDuration(duration);
newStart.normalize();
newEnd = item.endDate.clone();
newEnd.addDuration(duration);
newEnd.normalize();
} else if (item instanceof Components.interfaces.calITodo) {
if (item.entryDate && item.dueDate) {
var beginMove = item.entryDate.clone();
beginMove.isDate = true;
var duration = boxDate.subtractDate(beginMove);
newStart = item.entryDate.clone();
newStart.addDuration(duration);
newStart.normalize();
newEnd = item.dueDate.clone();
newEnd.addDuration(duration);
newEnd.normalize();
} else if (item.entryDate) {
newStart = adjustDate(item.entryDate);
} else { // only due date
newEnd = adjustDate(item.dueDate);
}
}
this.removeDropShadows();
window.removeEventListener("mouseover",
this.monthView.dropListener, true);
this.monthView.controller.modifyOccurrence(item, newStart, newEnd);
]]></handler>
</handlers>
</binding>
<binding id="calendar-month-view-column-header">
<content>
<xul:hbox flex="1">
<xul:spacer flex="1"/>
<xul:label anonid="label" crop="right" class="calendar-month-view-column-header-label" />
<xul:spacer flex="1"/>
</xul:hbox>
</content>
<implementation>
<field name="mIndex">-1</field>
<constructor><![CDATA[
if (this.mIndex == -1) {
var attrIndex = this.getAttribute("index");
if (attrIndex)
this.index = parseInt(attrIndex);
}
]]></constructor>
<property name="index">
<getter>return this.mIndex;</getter>
<setter><![CDATA[
this.mIndex = val % 7;
var label = document.getAnonymousElementByAttribute(this, "anonid", "label");
var dayName = calGetString("dateFormat", "day." + (this.mIndex+1) + ".name");
label.setAttribute("value", dayName);
return this.mIndex;
]]></setter>
</property>
</implementation>
</binding>
<binding id="calendar-month-view">
<content>
<xul:vbox flex="1">
<xul:hbox anonid="headerbox" equalsize="always"/>
<xul:grid anonid="monthgrid" flex="1">
<xul:columns anonid="monthgridcolumns" equalsize="always">
<xul:column flex="1" class="calendar-month-view-grid-column"/>
<xul:column flex="1" class="calendar-month-view-grid-column"/>
<xul:column flex="1" class="calendar-month-view-grid-column"/>
<xul:column flex="1" class="calendar-month-view-grid-column"/>
<xul:column flex="1" class="calendar-month-view-grid-column"/>
<xul:column flex="1" class="calendar-month-view-grid-column"/>
<xul:column flex="1" class="calendar-month-view-grid-column"/>
</xul:columns>
<xul:rows anonid="monthgridrows" equalsize="always">
<xul:row flex="1" class="calendar-month-view-grid-row">
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
</xul:row>
<xul:row flex="1" class="calendar-month-view-grid-row">
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
</xul:row>
<xul:row flex="1" class="calendar-month-view-grid-row">
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
</xul:row>
<xul:row flex="1" class="calendar-month-view-grid-row">
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
</xul:row>
<xul:row flex="1" class="calendar-month-view-grid-row">
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
</xul:row>
<xul:row flex="1" class="calendar-month-view-grid-row">
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
<xul:calendar-month-day-box/>
</xul:row>
</xul:rows>
</xul:grid>
</xul:vbox>
</content>
<implementation implements="calICalendarView">
<!-- constructor -->
<constructor><![CDATA[
function createXULElement(el) {
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
}
var headerbox = document.getAnonymousElementByAttribute(this, "anonid", "headerbox")
for (var i = 0; i < 7; i++) {
var hdr = createXULElement("calendar-month-view-column-header");
hdr.setAttribute("flex", "1");
headerbox.appendChild(hdr);
hdr.index = i;
}
]]></constructor>
<destructor><![CDATA[
if (this.mCalendar)
this.mCalendar.removeObserver(this.mObserver);
]]></destructor>
<!-- fields -->
<field name="mCalendar">null</field>
<field name="mController">null</field>
<field name="mStartDate">null</field>
<field name="mEndDate">null</field>
<field name="mDateBoxes">null</field>
<field name="mTimezone">"floating"</field>
<field name="mSelectedItems">[]</field>
<field name="mSelectedDayBox">null</field>
<field name="mShowDaysOutsideMonth">true</field>
<field name="mTasksInView">false</field>
<field name="mShowFullMonth">true</field>
<field name="mWeekStartOffset">0</field>
<field name="mDaysOffArray">[0,6]</field>
<field name="mDisplayDaysOff">true</field>
<field name="mDropShadows">null</field>
<field name="mShadowIndex">0</field>
<!-- other methods -->
<method name="setAttribute">
<parameter name="aAttr"/>
<parameter name="aVal"/>
<body><![CDATA[
var needsrelayout = false;
if (aAttr == "context" || aAttr == "item-context")
needsrelayout = true;
var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
if (needsrelayout)
this.relayout();
return ret;
]]></body>
</method>
<!-- calICalendarView -->
<property name="supportsDisjointDates" readonly="true"
onget="return false;"/>
<property name="hasDisjointDates" readonly="true"
onget="return false;"/>
<property name="displayCalendar">
<getter><![CDATA[
return this.mCalendar;
]]></getter>
<setter><![CDATA[
if (this.mCalendar != val) {
this.mCalendar = val;
this.mCalendar.addObserver(this.mObserver);
this.refresh();
}
return val;
]]></setter>
</property>
<property name="weekStartOffset">
<getter><![CDATA[
return this.mWeekStartOffset;
]]></getter>
<setter><![CDATA[
this.mWeekStartOffset = val;
return val;
]]></setter>
</property>
<property name="displayDaysOff">
<getter><![CDATA[
return this.mDisplayDaysOff;
]]></getter>
<setter><![CDATA[
this.mDisplayDaysOff = val;
return val;
]]></setter>
</property>
<property name="daysOffArray">
<getter><![CDATA[
return this.mDaysOffArray;
]]></getter>
<setter><![CDATA[
this.mDaysOffArray = val;
return val;
]]></setter>
</property>
<property name="controller"
onget="return this.mController;"
onset="return this.mController = val;"/>
<property name="startDate" readonly="true"
onget="return this.mStartDate"/>
<property name="endDate" readonly="true">
<getter><![CDATA[
return this.mEndDate;
]]></getter>
</property>
<!-- the end date that should be used for getItems and similar queries -->
<property name="queryEndDate" readonly="true">
<getter><![CDATA[
var end = this.endDate;
if (!end)
return null;
end = end.clone();
end.day += 1;
end.isDate = true;
end.normalize();
return end;
]]></getter>
</property>
<property name="tasksInView">
<getter><![CDATA[
return this.mTasksInView;
]]></getter>
<setter><![CDATA[
this.mTasksInView = val;
return val;
]]></setter>
</property>
<property name="showFullMonth">
<getter><![CDATA[
return this.mShowFullMonth;
]]></getter>
<setter><![CDATA[
this.mShowFullMonth = val;
return val;
]]></setter>
</property>
<method name="getSelectedItems">
<parameter name="aCount"/>
<body><![CDATA[
aCount.value = this.mSelectedItems.length;
return this.mSelectedItems;
]]></body>
</method>
<method name="setSelectedItems">
<parameter name="aCount"/>
<parameter name="aItems"/>
<parameter name="aSuppressEvent"/>
<body><![CDATA[
if (this.mSelectedItems.length) {
for each (var item in this.mSelectedItems) {
var oldboxes = this.findBoxesForItem(item);
for each (oldbox in oldboxes) {
oldbox.box.unselectItem(item);
}
}
}
this.mSelectedItems = aItems || [];
if (this.mSelectedItems.length) {
for each (var item in this.mSelectedItems) {
var newboxes = this.findBoxesForItem(item);
for each (newbox in newboxes) {
newbox.box.selectItem(item);
}
}
}
if (!aSuppressEvent) {
this.fireEvent("itemselect", this.mSelectedItems);
}
]]></body>
</method>
<property name="selectedDay">
<getter><![CDATA[
if (this.mSelectedDayBox)
return this.mSelectedDayBox.date.clone();
return null;
]]></getter>
<setter><![CDATA[
if (this.mSelectedDayBox)
this.mSelectedDayBox.box.selected = false;
var realVal = val;
if (!realVal.isDate) {
realVal = val.clone();
realVal.isDate = true;
}
var box = this.findBoxForDate(realVal);
if (box) {
box.box.selected = true;
this.mSelectedDayBox = box;
}
this.fireEvent("dayselect", realVal);
return val;
]]></setter>
</property>
<property name="timezone">
<getter><![CDATA[
return this.mTimezone;
]]></getter>
<setter><![CDATA[
this.mTimezone = val;
return val;
]]></setter>
</property>
<method name="fireEvent">
<parameter name="aEventName"/>
<parameter name="aEventDetail"/>
<body><![CDATA[
var event = document.createEvent('Events');
event.initEvent(aEventName, true, false);
event.detail = aEventDetail;
this.dispatchEvent(event);
]]></body>
</method>
<method name="showDate">
<parameter name="aDate"/>
<body><![CDATA[
aDate = aDate.getInTimezone(this.mTimezone);
// We might need to do some adjusting here to make sure we still
// display whole month even with alternative week-start days
var startDate = aDate.startOfMonth;
var endDate = aDate.endOfMonth;
if (startDate.weekday < this.mWeekStartOffset) {
// Go back one week to make sure we display this day
startDate.day -= 7;
}
if (endDate.weekday < this.mWeekStartOffset) {
// Go back one week so we don't display any extra days
endDate.day -= 7;
}
this.setDateRange(startDate, endDate);
this.selectedDay = aDate;
]]></body>
</method>
<method name="setDateRange">
<parameter name="aStartDate"/>
<parameter name="aEndDate"/>
<body><![CDATA[
if (this.mTimezone != aStartDate.timezone) {
aStartDate = aStartDate.getInTimezone(this.mTimezone);
aEndDate = aEndDate.getInTimezone(this.mTimezone);
}
this.mStartDate = aStartDate.startOfWeek;
this.mEndDate = aEndDate.endOfWeek;
this.mStartDate.day += this.mWeekStartOffset;
this.mEndDate.day += this.mWeekStartOffset;
this.mStartDate.normalize();
this.mEndDate.normalize();
this.refresh();
]]></body>
</method>
<method name="setDateList">
<parameter name="aCount"/>
<parameter name="aDates"/>
<body><![CDATA[
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
]]></body>
</method>
<method name="getDateList">
<parameter name="aCount"/>
<body><![CDATA[
if (!this.mStartDate || !this.mEndDate) {
aCount.value = 0;
return [];
}
var results = [];
var curDate = this.mStartDate.clone();
curDate.isDate = true;
curDate.normalize();
while (curDate.compare(this.mEndDate) <= 0) {
results.push(curDate.clone());
curDate.day += 1;
curDate.normalize();
}
aCount.value = results.length;
return results;
]]></body>
</method>
<!-- public properties and methods -->
<!-- whether to show days outside of the current month -->
<property name="showDaysOutsideMonth">
<getter><![CDATA[
return this.mShowDaysOutsideMonth;
]]></getter>
<setter><![CDATA[
if (this.mShowDaysOutsideMonth != val) {
this.mShowDaysOutsideMonth = val;
this.refresh();
}
return val;
]]></setter>
</property>
<!-- private properties and methods -->
<property name="monthgrid" readonly="true"
onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'monthgrid');"/>
<property name="monthgridrows" readonly="true"
onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'monthgridrows');"/>
<field name="mRefreshQueue">[]</field>
<field name="mRefreshPending">false</field>
<method name="popRefreshQueue">
<body><![CDATA[
if (this.mRefreshPending) {
return;
}
var refreshJob = this.mRefreshQueue.pop();
if (!refreshJob) {
return;
}
if (!this.startDate || !this.endDate)
return;
this.relayout();
if (!this.mCalendar)
return;
var filter = this.mCalendar.ITEM_FILTER_COMPLETED_ALL |
this.mCalendar.ITEM_FILTER_CLASS_OCCURRENCES;
if (this.mTasksInView)
filter |= this.mCalendar.ITEM_FILTER_TYPE_ALL;
else
filter |= this.mCalendar.ITEM_FILTER_TYPE_EVENT;
this.mRefreshPending = true;
this.mCalendar.getItems(filter,
0,
this.startDate,
this.queryEndDate,
this.mOperationListener);
]]></body>
</method>
<method name="refresh">
<body><![CDATA[
var refreshJob = {};
this.mRefreshQueue.push(refreshJob);
this.popRefreshQueue();
]]></body>
</method>
<method name="relayout">
<body><![CDATA[
function createXULElement(el) {
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
}
// Adjust headers based on the starting day of the week, if necessary
var headerbox = document.getAnonymousElementByAttribute(this, "anonid", "headerbox");
if (headerbox.firstChild.index != this.mWeekStartOffset) {
var i = 0;
for each(header in headerbox.childNodes) {
header.index = (i + this.mWeekStartOffset) % 7;
i++;
}
}
if (this.mSelectedItems.length) {
this.mSelectedItems = [];
}
// Clear out the old selection, since it won't be valid after relayout
if (this.mSelectedDayBox) {
this.mSelectedDayBox.box.selected = false;
}
if (!this.mStartDate || !this.mEndDate)
throw NS_ERROR_FAILURE;
// Days that are not in the main month on display are displayed with
// a gray background. Unless the month actually starts on a Sunday,
// this means that mStartDate.month is 1 month less than the main month
var mainMonth = this.mStartDate.month;
if (this.mStartDate.day != 1) {
mainMonth++;
mainMonth = mainMonth % 12;
}
var dateBoxes = [];
// clear out the old today
var oldToday = document.getAnonymousElementByAttribute(this, "today", "true");
if (oldToday) {
oldToday.removeAttribute("today");
}
// This gets set to true, telling us to collapse the rest of the rows
var finished = false;
var dateList = this.getDateList({})
var rows = this.monthgridrows.childNodes;
// Iterate through each monthgridrow and set up the day-boxes that
// are its child nodes. Remember, childNodes is not a normal array,
// so don't use the in operator if you don't want extra properties
// coming out.
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
// If we've already assigned all of the day-boxes that we need, just
// collapse the rest of the rows, otherwise expand them if needed.
if (finished) {
row.setAttribute("collapsed", true);
continue;
} else {
row.removeAttribute("collapsed");
}
for (var j = 0; j < row.childNodes.length; j++) {
var daybox = row.childNodes[j];
var date = dateList[dateBoxes.length];
daybox.setAttribute("context", this.getAttribute("context"));
daybox.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));
// Set the box-class depending on if this box displays a day in
// the month being currently shown or not.
var boxClass;
if (this.showFullMonth) {
boxClass = "calendar-month-day-box-" +
(mainMonth == date.month ? "current-month" : "other-month");
} else {
boxClass = "calendar-month-day-box-current-month";
}
function matchesDayOff(dayOffNum) { return dayOffNum == date.weekday; }
if (this.mDaysOffArray.some(matchesDayOff)) {
boxClass = "calendar-month-day-box-day-off " + boxClass;
}
daybox.setAttribute("class", boxClass);
daybox.setDate(date);
if (date.day == 1 || date.day == date.endOfMonth.day) {
daybox.showMonthLabel = true;
} else {
daybox.showMonthLabel = false;
}
daybox.monthView = this;
// add the box and its data to our stored array
var boxdata = {
date: date,
row: row,
box: daybox
};
dateBoxes.push(boxdata);
// If we've now assigned all of our dates, set this to true so we
// know we can just collapse the rest of the rows.
if (dateBoxes.length == dateList.length) {
finished = true;
}
}
}
// If we're not showing a full month, then add a few extra labels to
// help the user orient themselves in the view.
if (!this.mShowFullMonth) {
dateBoxes[0].box.showMonthLabel = true;
dateBoxes[dateBoxes.length-1].box.showMonthLabel = true;
}
// Store these, so that we can access them later
this.mDateBoxes = dateBoxes;
this.hideDaysOff();
// Highlight today, if it's in the range of the view
var today = this.today();
if (today.compare(dateList[0]) != -1 &&
today.compare(dateList[dateList.length-1]) != 1) {
this.findBoxForDate(today).box.setAttribute("today", "true");
}
]]></body>
</method>
<method name="hideDaysOff">
<body><![CDATA[
var columns = document.getAnonymousElementByAttribute(this, "anonid", "monthgridcolumns").childNodes;
var headerkids = document.getAnonymousElementByAttribute(this, "anonid", "headerbox").childNodes;
for (var i in columns) {
var dayForColumn = (Number(i) + this.mWeekStartOffset) % 7;
var dayOff = (this.mDaysOffArray.indexOf(dayForColumn) != -1);
columns[i].collapsed = dayOff && !this.mDisplayDaysOff;
headerkids[i].collapsed = dayOff && !this.mDisplayDaysOff;
}
]]></body>
</method>
<method name="findBoxForDate">
<parameter name="aDate"/>
<body><![CDATA[
for each (box in this.mDateBoxes) {
if (box.date.compare(aDate) == 0)
return box;
}
return null;
]]></body>
</method>
<method name="findBoxesForItem">
<parameter name="aItem"/>
<body><![CDATA[
var targetDate = null;
var finishDate = null;
var boxes = new Array();
// All our boxes are in default tz, so we need these times in them too.
if (aItem instanceof Components.interfaces.calIEvent) {
targetDate = aItem.startDate.getInTimezone(this.mTimezone);
finishDate = aItem.endDate.getInTimezone(this.mTimezone);
} else if (aItem instanceof Components.interfaces.calITodo) {
if (aItem.entryDate) {
targetDate = aItem.entryDate.getInTimezone(this.mTimezone);
if (aItem.dueDate) {
finishDate = aItem.dueDate.getInTimezone(this.mTimezone);
}
}
}
if (!targetDate)
return boxes;
if (!finishDate) {
var maybeBox = this.findBoxForDate(targetDate);
if (maybeBox) {
boxes.push(maybeBox);
}
return boxes;
}
if (!targetDate.isDate) {
// Reset the time to 00:00, so that we really get all the boxes
targetDate.hour = 0;
targetDate.minute = 0;
targetDate.second = 0;
}
if (targetDate.compare(finishDate) == 0) {
// Zero length events are silly, but we have to handle them
var box = this.findBoxForDate(targetDate);
if (box) {
boxes.push(box);
}
}
while (targetDate.compare(finishDate) == -1) {
var box = this.findBoxForDate(targetDate);
// This might not exist, if the event spans the view start or end
if (box) {
boxes.push(box);
}
targetDate.day += 1;
targetDate.normalize();
}
return boxes;
]]></body>
</method>
<method name="doAddItem">
<parameter name="aItem"/>
<body><![CDATA[
var boxes = this.findBoxesForItem(aItem);
if (!boxes.length)
return;
for each (box in boxes) {
box.box.addItem(aItem);
}
]]></body>
</method>
<method name="doDeleteItem">
<parameter name="aItem"/>
<body><![CDATA[
var boxes = this.findBoxesForItem(aItem);
if (!boxes.length)
return;
function isNotItem(a) {
return !a.hasSameIds(aItem);
}
this.mSelectedItems = this.mSelectedItems.filter(isNotItem);
for each (box in boxes) {
box.box.deleteItem(aItem);
}
]]></body>
</method>
<method name="today">
<body><![CDATA[
var date = Components.classes["@mozilla.org/calendar/datetime;1"]
.createInstance(Components.interfaces.calIDateTime);
date.jsDate = new Date();
date = date.getInTimezone(this.mTimezone);
date.isDate = true;
return date;
]]></body>
</method>
<!-- our private observers and listeners -->
<field name="mOperationListener"><![CDATA[
({
calView: this,
QueryInterface: function (aIID) {
if (!aIID.equals(Components.interfaces.calIOperationListener) &&
!aIID.equals(Components.interfaces.nsISupports)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
// signal that the current operation finished.
this.calView.mRefreshPending = false;
// immediately start the next job on the queue.
this.calView.popRefreshQueue();
},
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
if (!Components.isSuccessCode(aStatus))
return;
for each (var item in aItems) {
this.calView.doAddItem(item);
}
}
})
]]></field>
<field name="mObserver"><![CDATA[
// the calIObserver, and calICompositeObserver
({
calView: this,
QueryInterface: function (aIID) {
if (!aIID.equals(Components.interfaces.calIObserver) &&
!aIID.equals(Components.interfaces.calICompositeObserver) &&
!aIID.equals(Components.interfaces.nsISupports)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
onStartBatch: function() {
this.calView.mBatchCount++;
},
onEndBatch: function() {
this.mBatchCount--;
if (this.mBatchCount == 0) {
this.calView.refresh();
}
},
onLoad: function() {
this.calView.refresh();
},
onAddItem: function (aItem) {
if (this.mBatchCount) {
return;
}
if (aItem instanceof Components.interfaces.calITodo &&
!this.calView.mTasksInView)
return;
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
this.calView.queryEndDate,
{});
for each (var occ in occs)
this.calView.doAddItem(occ);
},
onModifyItem: function (aNewItem, aOldItem) {
if (this.mBatchCount) {
return;
}
if (aNewItem instanceof Components.interfaces.calITodo &&
aOldItem instanceof Components.interfaces.calITodo &&
!this.calView.mTasksInView)
return;
var occs;
occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
this.calView.queryEndDate,
{});
for each (var occ in occs)
this.calView.doDeleteItem(occ);
if (aNewItem instanceof Components.interfaces.calITodo &&
!this.mTasksInView) {
return;
}
occs = aNewItem.getOccurrencesBetween(this.calView.startDate,
this.calView.queryEndDate,
{});
for each (var occ in occs)
this.calView.doAddItem(occ);
},
onDeleteItem: function (aItem) {
if (this.mBatchCount) {
return;
}
if (aItem instanceof Components.interfaces.calITodo &&
!this.calView.mTasksInView)
return;
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
this.calView.queryEndDate,
{});
for each (var occ in occs) {
this.calView.doDeleteItem(occ);
}
},
onError: function (aErrNo, aMessage) { },
//
// calICompositeObserver stuff
// XXXvv we can be smarter about how we handle this stuff
//
onCalendarAdded: function (aCalendar) {
this.calView.refresh();
},
onCalendarRemoved: function (aCalendar) {
this.calView.refresh();
},
onDefaultCalendarChanged: function (aNewDefaultCalendar) {
// don't care, for now
}
})
]]></field>
</implementation>
<handlers>
<handler event="keypress"><![CDATA[
const kKE = Components.interfaces.nsIDOMKeyEvent;
if (event.keyCode == kKE.DOM_VK_BACK_SPACE ||
event.keyCode == kKE.DOM_VK_DELETE)
{
if (!this.activeInPlaceEdit && this.mSelectedItems.length && this.controller) {
for each (var item in this.mSelectedItems) {
item = (event.ctrlKey) ? item.parentItem : item;
this.controller.deleteOccurrence(item);
}
}
}
]]></handler>
</handlers>
</binding>
</bindings>
<!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->