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

1345 строки
45 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>
-
- 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 *****
-->
<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>
<xul:label anonid="item-label" crop="end" 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="right"/>
<xul:textbox class="plain" style="background: transparent !important"
anonid="event-name-textbox" crop="right" 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");
if (label.firstChild)
label.removeChild(label.firstChild);
label.appendChild(document.createTextNode(str));
this.setEditableLabel();
this.setCSSClasses();
]]></setter>
</property>
</implementation>
</binding>
<binding id="calendar-month-day-box">
<content>
<xul:vbox>
<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");
]]></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 sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
var props = sbs.createBundle("chrome://calendar/locale/dateFormat.properties");
var monthName = props.GetStringFromName("month." + (this.mDate.month+1) + ".Mmm");
daylabel.setAttribute("value", this.mDate.day + " " + monthName);
} else {
daylabel.setAttribute("value", aDate.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 sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
var props = sbs.createBundle("chrome://calendar/locale/dateFormat.properties");
var monthName = props.GetStringFromName("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))
{
return;
}
}
// 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.id == aItem.id) {
itd.box.selected = true;
}
}
]]></body>
</method>
<method name="unselectItem">
<parameter name="aItem"/>
<body><![CDATA[
for each (var itd in this.mItemData) {
if (itd.item.id == aItem.id) {
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;
itd.box = box;
}
}
]]></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>
</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 sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService);
var props = sbs.createBundle("chrome://calendar/locale/dateFormat.properties");
var label = document.getAnonymousElementByAttribute(this, "anonid", "label");
var dayName = props.GetStringFromName("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: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>
<!-- 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="mSelectedItem">null</field>
<field name="mSelectedDayBox">null</field>
<field name="mShowDaysOutsideMonth">true</field>
<field name="mTasksInView">true</field>
<field name="mShowFullMonth">true</field>
<field name="mWeekStartOffset">0</field>
<field name="mDaysOffArray">[0,6]</field>
<field name="mDisplayDaysOff">true</field>
<field name="mSelectionObserver"><![CDATA[
({ calView: this,
onSelectionChanged: function selectionObserverCallback(itemSelectionArray) {
// The selection manager is smart enough to call this only when
// the selection really did change, so don't bother checking
if (this.calView.mSelectedItem) {
var oldboxes = this.calView.findBoxesForItem(this.calView.mSelectedItem);
for each (oldbox in oldboxes) {
oldbox.box.unselectItem(this.calView.mSelectedItem);
}
}
this.calView.mSelectedItem = itemSelectionArray[0];
var newboxes = this.calView.findBoxesForItem(this.calView.mSelectedItem);
for each (newbox in newboxes) {
newbox.box.selectItem(this.calView.mSelectedItem);
}
}
})
]]></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;
]]></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;
]]></setter>
</property>
<property name="showFullMonth">
<getter><![CDATA[
return this.mShowFullMonth;
]]></getter>
<setter><![CDATA[
this.mShowFullMonth = val;
]]></setter>
</property>
<property name="selectedItem">
<getter><![CDATA[
return this.mSelectedItem;
]]></getter>
<setter><![CDATA[
if (this.mSelectedItem) {
var oldboxes = this.findBoxesForItem(this.mSelectedItem);
for each (oldbox in oldboxes) {
oldbox.box.unselectItem(this.mSelectedItem);
}
}
this.mSelectedItem = val;
if (this.mSelectedItem) {
var newboxes = this.findBoxesForItem(this.mSelectedItem);
for each (newbox in newboxes) {
newbox.box.selectItem(this.mSelectedItem);
}
if (this.mController.selectionManager)
this.mController.selectionManager.replaceSelection(this.mSelectedItem);
}
]]></setter>
</property>
<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");
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"/>
<body><![CDATA[
var event = document.createEvent('Events');
event.initEvent(aEventName, true, false);
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');"/>
<method name="refresh">
<body><![CDATA[
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.mCalendar.getItems(filter,
0,
this.startDate,
this.queryEndDate,
this.mOperationListener);
]]></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.mSelectedItem) {
this.selectedItem = null;
}
// 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;
// If we've already drawn a view once, then in almost all cases we
// can reuse most of the grid. We may need to add or subtract a row
// but this is still much faster than recreating all rows.
if (!this.mDateBoxes) {
// There are no dateBoxes, so we must start from scratch.
this.createDayGrid();
return;
}
var oldDuration = this.mDateBoxes[this.mDateBoxes.length-1].date.subtractDate(this.mDateBoxes[0].date);
var newDuration = this.mEndDate.subtractDate(this.mStartDate);
newDuration.isNegative = true;
newDuration.normalize();
newDuration.addDuration(oldDuration);
/* Reuse is disabled. bug 335169 */
this.createDayGrid();
return;
if ((newDuration.days + newDuration.weeks*7) == 0) {
// OK, our grid is perfect, so just reuse it exactly.
this.reuseExistingGrid();
return;
} else if ((newDuration.days + newDuration.weeks*7) != 7) {
// This case shouldn't ever happen, at least until we introduce
// the ability to remove weekends
this.createDayGrid();
return;
}
// OK, so we're off by one week. We either need to add or subtract
// a row depending on whether the newDuration is positive or negative
//
// at this point the following holds:
// newDuration = duration of old view - duration of current view.
if (newDuration.isNegative) {
// newDuration is negative, which means that the duration of the old
// view was shorter. The new month takes up an extra week, so we
// need to create a new row and fill it with day-boxes
var newGridRow = createXULElement("row");
newGridRow.setAttribute("flex", "1");
newGridRow.setAttribute("class", "calendar-month-view-grid-row");
var newRowIndex = this.monthgridrows.length;
this.monthgridrows.appendChild(newGridRow);
for (var i = 0; i < 7; i++) {
var box = createXULElement("calendar-month-day-box");
box.setAttribute("context", this.getAttribute("context"));
box.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));
box.monthView = this;
newGridRow.appendChild(box);
var boxdata = {
date: null,
row: newRowIndex,
box: box
};
this.mDateBoxes.push(boxdata);
}
} else {
// The old month took up an extra week, so remove a row
this.monthgridrows.removeChild(this.monthgridrows.lastChild);
this.mDateBoxes.splice(this.mDateBoxes.length - 7, 7);
}
this.reuseExistingGrid();
]]></body>
</method>
<method name="createDayGrid">
<body><![CDATA[
function createXULElement(el) {
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
}
// clear out the old grid, if one exists
var gridrows = this.monthgridrows;
while (gridrows.hasChildNodes()) {
gridrows.removeChild(gridrows.lastChild);
}
var dateBoxes = [];
// 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;
}
// Right now, we always show 7 days in a week. Everytime this
// reaches 0, we start a new week, so this will need to change once
// removing days off is implemented.
var dayCount = 7;
var curRow = null;
// get today's date
var today = this.today();
for each (var date in this.getDateList({})) {
var box = createXULElement("calendar-month-day-box");
box.setAttribute("context", this.getAttribute("context"));
box.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));
if (dayCount == 7) {
// start adding to a new row
curRow = createXULElement("row");
curRow.setAttribute("flex", "1");
curRow.setAttribute("class", "calendar-month-view-grid-row");
gridrows.appendChild(curRow);
dayCount = 0;
}
dayCount++;
//XXX rename these css classes
// Set the box-class depending on if this box displays a day in the
// main month being shown or not.
var boxClass;
if (this.showFullMonth) {
boxClass = "calendar-month-day-box-" +
(mainMonth == date.month ? "even" : "odd");
} else {
boxClass = "calendar-month-day-box-even";
}
function matchesDayOff(dayOffNum) { return dayOffNum == date.weekday; }
if (this.mDaysOffArray.some(matchesDayOff)) {
boxClass = "calendar-month-day-box-weekend " + boxClass;
}
box.setAttribute("class", boxClass);
curRow.appendChild(box);
box.setDate(date);
if (date.day == 1 || date.day == date.endOfMonth.day) {
box.showMonthLabel = true;
}
box.monthView = this;
// highlight today
if (date.compare(today) == 0) {
box.setAttribute("today", "true");
}
// add the box and its data to our stored array
var boxdata = {
date: date,
row: curRow,
box: box
};
dateBoxes.push(boxdata);
}
// 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();
]]></body>
</method>
<method name="reuseExistingGrid">
<body><![CDATA[
// These counters keep track of where we are in the iteration
// of old rows/boxes, so we know which one to update
var dateBoxCount = 0;
var gridRowCount = 0;
// Right now, we always show 7 days in a week. Everytime this
// reaches 0, we start a new week, so this will need to change once
// removing days off is implemented.
var dayCount = 7;
var curRow = null;
// 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;
}
// get today's date
var today = this.today();
for each (var date in this.getDateList({})) {
var box;
// Get the next box that we haven't updated from the previously
// stored array of boxes/data.
this.mDateBoxes[dateBoxCount].date = date;
box = this.mDateBoxes[dateBoxCount].box;
// These might have changed. Since we don't expose a 'refresh'
// method, make sure we set them again.
box.setAttribute("context", this.getAttribute("context"));
box.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));
if (dayCount == 7) {
// start adding to the next row
curRow = this.monthgridrows.childNodes[gridRowCount];
gridRowCount++;
dayCount = 0;
}
dayCount ++;
//XXX rename these css classes
// Set the box-class depending on if this box displays a day in the
// main month being shown or not.
var boxClass = "calendar-month-day-box-" +
((mainMonth == date.month) ? "even" : "odd");
function matchesDayOff(dayOffNum) { return dayOffNum == date.weekday; }
if (this.mDaysOffArray.some(matchesDayOff)) {
boxClass = "calendar-month-day-box-weekend " + boxClass;
}
box.setAttribute("class", boxClass);
box.setDate(date);
if (date.day == 1 || date.day == date.endOfMonth.day) {
box.showMonthLabel = true;
}
// highlight today
if (date.compare(today) == 0) {
box.setAttribute("today", "true");
}
// The box and its data is already in the array. We fixed the
// date at the beginning, when we got it from the array.
dateBoxCount++;
}
// 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) {
this.mDateBoxes[0].showMonthLabel = true;
this.mDateBoxes[this.mDateBoxes.length-1].showMonthLabel = true;
}
this.hideDaysOff();
]]></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) {
boxes.push(this.findBoxForDate(targetDate));
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
return [this.findBoxForDate(targetDate)];
}
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;
if (this.mSelectedItem == aItem)
this.mSelectedItem = null;
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) {
if (this.calView.controller.selectionManager &&
this.calView.controller.selectionManager.selectedEvents[0])
this.calView.selectedItem = (this.calView.controller
.selectionManager.selectedEvents[0]);
},
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;
}
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;
}
var occs;
occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
this.calView.queryEndDate,
{});
for each (var occ in occs)
this.calView.doDeleteItem(occ);
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;
}
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
this.calView.queryEndDate,
{});
for each (var occ in occs) {
this.calView.doDeleteItem(occ);
}
},
//XXXvv Alarm could, in theory, flash the event or something
onAlarm: function (aAlarmItem) { },
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.selectedItem && this.controller) {
var item = (event.ctrlKey) ? this.selectedItem.parentItem : this.selectedItem;
this.controller.deleteOccurrence(item);
}
}
]]></handler>
</handlers>
</binding>
</bindings>
<!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->