mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
1246 lines
42 KiB
XML
1246 lines
42 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;
|
|
|
|
function formatTime(aTime) {
|
|
var m = aTime.minute;
|
|
return (aTime.hour + ":" + (m < 10 ? "0" : "") + m);
|
|
}
|
|
|
|
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) {
|
|
str = 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;
|
|
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);
|
|
}
|
|
|
|
// Remove all the old events
|
|
this.mItemData = new Array();
|
|
while(this.hasChildNodes()) {
|
|
this.dayitems.removeChild(this.dayitems.lastChild);
|
|
}
|
|
]]></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="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="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;
|
|
}
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="timezone">
|
|
<getter><![CDATA[
|
|
return this.mTimezone;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mTimezone = val;
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
|
|
<method name="showDate">
|
|
<parameter name="aDate"/>
|
|
<body><![CDATA[
|
|
// 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.getInTimezone(this.mTimezone);
|
|
]]></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);
|
|
|
|
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
|
|
if (!newDuration.isNegative) {
|
|
// 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;
|
|
|
|
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";
|
|
}
|
|
// XXX isWeekend?
|
|
if (date.weekday == 0 || date.weekday == 6)
|
|
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;
|
|
|
|
// 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;
|
|
]]></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;
|
|
}
|
|
|
|
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");
|
|
|
|
if (date.weekday == 0 || date.weekday == 6)
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
]]></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;
|
|
}
|
|
|
|
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>
|
|
|
|
<!-- 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) {
|
|
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
|
|
this.calView.queryEndDate,
|
|
{});
|
|
for each (var occ in occs)
|
|
this.calView.doAddItem(occ);
|
|
},
|
|
onModifyItem: function (aNewItem, aOldItem) {
|
|
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 (!(aItem instanceof Components.interfaces.calIEvent))
|
|
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; -*- -->
|