mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
2904 lines
105 KiB
XML
2904 lines
105 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.vukicevic@oracle.com>
|
|
- Thomas Benisch <thomas.benisch@sun.com>
|
|
- Dan Mosedale <dan.mosedale@oracle.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-multiday-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">
|
|
|
|
<!--
|
|
- This is the time bar that displays time divisions to the side
|
|
- or top of a multiday view.
|
|
-->
|
|
<binding id="calendar-time-bar">
|
|
<content>
|
|
<xul:box xbl:inherits="orient,width,height" flex="1" anonid="topbox">
|
|
</xul:box>
|
|
</content>
|
|
|
|
<implementation>
|
|
<field name="mPixPerMin">0.6</field>
|
|
<field name="mStartMin">8*60</field>
|
|
<field name="mEndMin">20*60</field>
|
|
|
|
<constructor>
|
|
this.relayout();
|
|
</constructor>
|
|
|
|
<method name="setAttribute">
|
|
<parameter name="aAttr"/>
|
|
<parameter name="aVal"/>
|
|
<body><![CDATA[
|
|
var needsrelayout = false;
|
|
if (aAttr == "orient") {
|
|
if (this.getAttribute("orient") != aVal)
|
|
needsrelayout = true;
|
|
}
|
|
|
|
// this should be done using lookupMethod(), see bug 286629
|
|
var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
|
|
|
|
if (needsrelayout) {
|
|
this.relayout();
|
|
}
|
|
|
|
return ret;
|
|
]]></body>
|
|
</method>
|
|
|
|
<property name="pixelsPerMinute"
|
|
onget="return this.mPixPerMin"
|
|
onset="if (this.mPixPerMin != val) { this.mPixPerMin = val; this.relayout(); } return val;"/>
|
|
|
|
<method name="setStartEndMinutes">
|
|
<parameter name="aStartMin"/>
|
|
<parameter name="aEndMin"/>
|
|
<body><![CDATA[
|
|
if (aStartMin < 0 || aStartMin > aEndMin)
|
|
throw Components.results.NS_ERROR_INVALID_ARG;
|
|
if (aEndMin < 0 || aEndMin > 24*60)
|
|
throw Components.results.NS_ERROR_INVALID_ARG;
|
|
|
|
if (this.mStartMin != aStartMin ||
|
|
this.mEndMin != aEndMin)
|
|
{
|
|
this.mStartMin = aStartMin;
|
|
this.mEndMin = aEndMin;
|
|
|
|
this.relayout();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="relayout">
|
|
<body><![CDATA[
|
|
//dump ("calendar-time-bar: relayout\n");
|
|
function createXULElement(el) {
|
|
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
|
|
}
|
|
|
|
var topbox = document.getAnonymousElementByAttribute(this, "anonid", "topbox");
|
|
var orient = topbox.getAttribute("orient");
|
|
var otherorient = "vertical";
|
|
if (!orient) orient = "horizontal";
|
|
if (orient == "vertical") otherorient = "horizontal";
|
|
|
|
//dump ("calendar-time-bar: orient: " + orient + " other: " + otherorient + "\n");
|
|
|
|
function makeTimeBox(timestr, size) {
|
|
var box = createXULElement("box");
|
|
box.setAttribute("orient", orient);
|
|
|
|
if (orient == "horizontal") {
|
|
box.setAttribute("width", size);
|
|
} else {
|
|
box.setAttribute("height", size);
|
|
}
|
|
|
|
var label = createXULElement("label");
|
|
label.setAttribute("class", "calendar-time-bar-label");
|
|
label.setAttribute("value", timestr);
|
|
label.setAttribute("align", "center");
|
|
|
|
box.appendChild(label);
|
|
|
|
return box;
|
|
}
|
|
|
|
while (topbox.lastChild)
|
|
topbox.removeChild(topbox.lastChild);
|
|
|
|
var formatter =
|
|
Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
|
|
.getService(Components.interfaces.nsIScriptableDateFormat);
|
|
var timeString;
|
|
var theMin = this.mStartMin;
|
|
var theHour = Math.floor(theMin / 60);
|
|
var durLeft = this.mEndMin - this.mStartMin;
|
|
|
|
while (durLeft > 0) {
|
|
var dur;
|
|
if (this.mEndMin - theMin < 60) {
|
|
dur = this.mEndMin - theMin;
|
|
} else {
|
|
dur = theMin % 60;
|
|
}
|
|
theMin += dur;
|
|
if (dur == 0) dur = 60;
|
|
|
|
var box;
|
|
if (dur != 60) {
|
|
box = makeTimeBox("", dur * this.mPixPerMin);
|
|
} else {
|
|
timeString = formatter.FormatTime("",
|
|
Components.interfaces.nsIScriptableDateFormat
|
|
.timeFormatNoSeconds, theHour, 0, 0);
|
|
box = makeTimeBox(timeString, dur * this.mPixPerMin);
|
|
}
|
|
|
|
box.setAttribute("class", "calendar-time-bar-box-" + (theHour % 2 == 0 ? "even" : "odd"));
|
|
topbox.appendChild(box);
|
|
|
|
durLeft -= dur;
|
|
theMin += dur;
|
|
theHour++;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
</binding>
|
|
|
|
<!--
|
|
- A simple gripbar that is displayed at the start and end of an
|
|
- event box. Needs to handle being dragged and resizing the
|
|
- event, thus changing its start/end time.
|
|
-->
|
|
<binding id="calendar-event-gripbar">
|
|
<content>
|
|
<xul:box anonid="thebox" flex="1"/>
|
|
</content>
|
|
|
|
<implementation>
|
|
<!-- public -->
|
|
<field name="eventElement">null</field>
|
|
|
|
<property name="parentorient">
|
|
<getter><![CDATA[
|
|
return this.getAttribute("parentorient");
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.setAttribute("parentorient", val);
|
|
var thebox = document.getAnonymousElementByAttribute(this, "anonid", "thebox");
|
|
if (val == "vertical")
|
|
thebox.setAttribute("orient", "horizontal");
|
|
else
|
|
thebox.setAttribute("orient", "vertical");
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<!-- private -->
|
|
<field name="mSide">top</field>
|
|
|
|
<field name="mResizing">false</field>
|
|
<field name="mSizeStartX">0</field>
|
|
<field name="mSizeStartY">0</field>
|
|
|
|
<constructor><![CDATA[
|
|
if (this.getAttribute("side") == "top")
|
|
this.mSide = "top";
|
|
else if (this.getAttribute("side") == "bottom")
|
|
this.mSide = "bottom";
|
|
this.parentorient = this.getAttribute("parentorient");
|
|
]]></constructor>
|
|
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="mousedown" button="0"><![CDATA[
|
|
event.stopPropagation();
|
|
var whichside = this.getAttribute("whichside");
|
|
if (!whichside)
|
|
return;
|
|
|
|
// we make assumptions about our position in the tree here;
|
|
// specifically, this should get us to a <calendar-event-box>
|
|
var evbox = this.parentNode.parentNode;
|
|
// still select it (since we'll stopPropagation())
|
|
evbox.calendarView.selectedItem = evbox.mOccurrence;
|
|
// then start dragging it
|
|
evbox.parentColumn.startSweepingToModifyEvent(evbox, evbox.mOccurrence, whichside, event.screenX, event.screenY);
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<!--
|
|
- A column for displaying event boxes in. One column per
|
|
- day; it manages the layout of the events given via add/deleteEvent.
|
|
-->
|
|
<binding id="calendar-event-column">
|
|
<content>
|
|
<xul:stack anonid="boxstack" flex="1" style="min-width: 1px; min-height: 1px">
|
|
<xul:box anonid="bgbox" flex="1" style="min-width: 1px; min-height: 1px"/>
|
|
<xul:box xbl:inherits="context" anonid="topbox" flex="1" equalsize="always" style="min-width: 1px; min-height: 1px"/>
|
|
<xul:box anonid="fgbox" flex="1" class="fgdragcontainer" style="min-width: 1px; min-height: 1px">
|
|
<xul:box anonid="fgdragspacer" style="display: inherit; overflow: hidden;">
|
|
<xul:spacer flex="1"/>
|
|
<xul:label anonid="fgdragbox-startlabel" class="fgdragbox-label"/>
|
|
</xul:box>
|
|
<xul:box anonid="fgdragbox" class="fgdragbox" />
|
|
<xul:label anonid="fgdragbox-endlabel" class="fgdragbox-label"/>
|
|
</xul:box>
|
|
</xul:stack>
|
|
</content>
|
|
|
|
<implementation>
|
|
<constructor><![CDATA[
|
|
this.mEvents = Array();
|
|
]]></constructor>
|
|
|
|
<!-- fields -->
|
|
<field name="mPixPerMin">0.6</field>
|
|
<field name="mStartMin">8*60</field>
|
|
<field name="mEndMin">20*60</field>
|
|
<field name="mEvents">new Array()</field>
|
|
<field name="mEventMap">null</field>
|
|
<field name="mCalendarView">null</field>
|
|
<field name="mDate">null</field>
|
|
<field name="mTimezone">"UTC"</field>
|
|
<field name="mDragState">null</field>
|
|
<field name="mLayoutBatchCount">0</field>
|
|
<!-- Since we'll often be getting many events in rapid succession, this
|
|
timer helps ensure that we don't re-compute the event map too many
|
|
times in a short interval, and therefore improves performance.-->
|
|
<field name="mEventMapTimeout">null</field>
|
|
|
|
<!-- properties -->
|
|
<property name="pixelsPerMinute">
|
|
<getter><![CDATA[
|
|
return this.mPixPerMin;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (val <= 0.0)
|
|
val = 0.01;
|
|
if (val != this.mPixPerMin) {
|
|
this.mPixPerMin = val;
|
|
this.relayout();
|
|
}
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="selected">
|
|
<getter><![CDATA[
|
|
if (this.getAttribute("selected") == "true")
|
|
return true;
|
|
return false;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (val)
|
|
this.setAttribute("selected", "true");
|
|
else
|
|
this.removeAttribute("selected");
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="date">
|
|
<getter><![CDATA[
|
|
return this.mDate;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mDate = val;
|
|
|
|
if (val.timezone != this.mTimezone) {
|
|
//dump ("++ column tz: " + val.timezone + "\n");
|
|
this.mTimezone = val.timezone;
|
|
if (!this.mLayoutBatchCount) {
|
|
this.recalculateStartEndMinutes();
|
|
}
|
|
}
|
|
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="calendarView"
|
|
onget="return this.mCalendarView;"
|
|
onset="return (this.mCalendarView = val);" />
|
|
|
|
<property
|
|
name="topbox"
|
|
readonly="true">
|
|
<getter><![CDATA[
|
|
return document.getAnonymousElementByAttribute(this, "anonid", "topbox");
|
|
]]></getter>
|
|
</property>
|
|
|
|
<property
|
|
name="bgbox"
|
|
readonly="true">
|
|
<getter><![CDATA[
|
|
return document.getAnonymousElementByAttribute(this, "anonid", "bgbox");
|
|
]]></getter>
|
|
</property>
|
|
|
|
<field name="mFgboxes">null</field>
|
|
<property
|
|
name="fgboxes"
|
|
readonly="true">
|
|
<getter><![CDATA[
|
|
if (this.mFgboxes == null) {
|
|
this.mFgboxes = {
|
|
box: document.getAnonymousElementByAttribute(this, "anonid", "fgbox"),
|
|
dragbox: document.getAnonymousElementByAttribute(this, "anonid", "fgdragbox"),
|
|
dragspacer: document.getAnonymousElementByAttribute(this, "anonid", "fgdragspacer"),
|
|
startlabel: document.getAnonymousElementByAttribute(this, "anonid", "fgdragbox-startlabel"),
|
|
endlabel: document.getAnonymousElementByAttribute(this, "anonid", "fgdragbox-endlabel")
|
|
};
|
|
}
|
|
return this.mFgboxes;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<property
|
|
name="events"
|
|
readonly="true"
|
|
onget="return this.mEvents"/>
|
|
|
|
<!-- methods -->
|
|
|
|
<method name="setStartEndMinutes">
|
|
<parameter name="aStartMin"/>
|
|
<parameter name="aEndMin"/>
|
|
<body><![CDATA[
|
|
if (aStartMin < 0 || aStartMin > aEndMin)
|
|
throw Components.results.NS_ERROR_INVALID_ARG;
|
|
if (aEndMin < 0 || aEndMin > 24*60)
|
|
throw Components.results.NS_ERROR_INVALID_ARG;
|
|
|
|
if (this.mStartMin != aStartMin ||
|
|
this.mEndMin != aEndMin)
|
|
{
|
|
this.mStartMin = aStartMin;
|
|
this.mEndMin = aEndMin;
|
|
|
|
this.relayout();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="selectOccurrence">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
// if we're being asked to unselect, and we
|
|
// don't have a selection, return.
|
|
if (aOccurrence == null && !this.mCurrentSelection)
|
|
return;
|
|
|
|
if (this.mCurrentSelection) {
|
|
this.mCurrentSelection.eventbox.selected = false;
|
|
this.mCurrentSelection = null;
|
|
}
|
|
|
|
if (aOccurrence) {
|
|
var chunk = this.findChunkForOccurrence(aOccurrence);
|
|
if (!chunk || !chunk.eventbox) {
|
|
dump ("++ Couldn't find chunk to select!!!\n");
|
|
return;
|
|
}
|
|
|
|
chunk.eventbox.selected = true;
|
|
this.mCurrentSelection = chunk;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
|
|
<method name="findChunkForOccurrence">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
for each (var chunk in this.mEvents) {
|
|
if (chunk.event.hasSameIds(aOccurrence)) {
|
|
return chunk;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="startLayoutBatchChange">
|
|
<body><![CDATA[
|
|
this.mLayoutBatchCount++;
|
|
]]></body>
|
|
</method>
|
|
<method name="endLayoutBatchChange">
|
|
<body><![CDATA[
|
|
this.mLayoutBatchCount--;
|
|
if (this.mLayoutBatchCount == 0)
|
|
this.relayout();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setAttribute">
|
|
<parameter name="aAttr"/>
|
|
<parameter name="aVal"/>
|
|
<body><![CDATA[
|
|
// this should be done using lookupMethod(), see bug 286629
|
|
var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
|
|
|
|
if (aAttr == "orient" && this.getAttribute("orient") != aVal) {
|
|
this.relayout();
|
|
}
|
|
|
|
return ret;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="internalDeleteEvent">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
var itemIndex = -1;
|
|
var i;
|
|
for (var i in this.mEvents) {
|
|
occ = this.mEvents[i].event;
|
|
if (occ.hasSameIds(aOccurrence))
|
|
{
|
|
itemIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (itemIndex != -1) {
|
|
if (this.mSelectedItem == this.mEvents[itemIndex])
|
|
this.mSelectedItem = null;
|
|
|
|
this.mEvents.splice(itemIndex, 1);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="recalculateStartEndMinutes">
|
|
<body><![CDATA[
|
|
for each (var chunk in this.mEvents) {
|
|
var mins = this.getStartEndMinutesForOccurrence(chunk.event);
|
|
chunk.startMinute = mins.start;
|
|
chunk.endMinute = mins.end;
|
|
}
|
|
|
|
this.relayout();
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- NOTE: This function may not return the true start and end time
|
|
of an occurrence if that occurrence starts or ends on a day
|
|
different than the day of this column -->
|
|
<method name="getStartEndMinutesForOccurrence">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
var stdate = aOccurrence.startDate || aOccurrence.entryDate;
|
|
var enddate = aOccurrence.endDate || aOccurrence.dueDate;
|
|
|
|
if (stdate.timezone != this.mTimezone) {
|
|
stdate = stdate.getInTimezone (this.mTimezone);
|
|
}
|
|
|
|
if (enddate.timezone != this.mTimezone) {
|
|
enddate = enddate.getInTimezone (this.mTimezone);
|
|
}
|
|
|
|
var startHour = stdate.hour;
|
|
var startMinute = stdate.minute;
|
|
var endHour = enddate.hour;
|
|
var endMinute = enddate.minute;
|
|
|
|
// Handle cases where an event begins or ends on a day other than this
|
|
if (stdate.compare(this.mDate) == -1) {
|
|
startHour = 0;
|
|
startMinute = 0;
|
|
}
|
|
if (enddate.compare(this.mDate) == 1) {
|
|
endHour = 24;
|
|
endMinute = 0;
|
|
}
|
|
|
|
return { start: startHour * 60 + startMinute,
|
|
end: endHour * 60 + endMinute };
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="createChunk">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
var mins = this.getStartEndMinutesForOccurrence(aOccurrence);
|
|
|
|
var chunk = {
|
|
startMinute: mins.start,
|
|
endMinute: mins.end,
|
|
event: aOccurrence
|
|
};
|
|
return chunk;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="addEvent">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
this.internalDeleteEvent(aOccurrence);
|
|
|
|
var chunk = this.createChunk(aOccurrence);
|
|
this.mEvents.push(chunk);
|
|
if (this.mEventMapTimeout) {
|
|
clearTimeout(this.mEventMapTimeout);
|
|
}
|
|
var column = this;
|
|
// Fun with scoping...
|
|
this.mEventMapTimeout = setTimeout(function() { column.relayout.call(column) }, 5);
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="deleteEvent">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
if (this.internalDeleteEvent(aOccurrence))
|
|
this.relayout();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="clear">
|
|
<body><![CDATA[
|
|
while (this.bgbox && this.bgbox.hasChildNodes())
|
|
this.bgbox.removeChild(this.bgbox.lastChild);
|
|
while (this.topbox && this.topbox.hasChildNodes())
|
|
this.topbox.removeChild(this.topbox.lastChild);
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="relayout">
|
|
<body><![CDATA[
|
|
|
|
if (this.mLayoutBatchCount > 0)
|
|
return;
|
|
|
|
function createXULElement(el) {
|
|
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
|
|
}
|
|
|
|
this.clear();
|
|
|
|
var orient = this.getAttribute("orient");
|
|
var otherorient = "vertical";
|
|
if (!orient) orient = "horizontal";
|
|
if (orient == "vertical") otherorient = "horizontal";
|
|
|
|
// bgbox is used mainly for drawing the grid. at some point it may
|
|
// also be used for all-day events.
|
|
this.bgbox.setAttribute("orient", orient);
|
|
|
|
var theMin = this.mStartMin;
|
|
while (theMin < this.mEndMin) {
|
|
var dur = theMin % 60;
|
|
theMin += dur;
|
|
if (dur == 0) dur = 60;
|
|
|
|
var box = createXULElement("spacer");
|
|
// we key off this in a CSS selector
|
|
box.setAttribute("orient", orient);
|
|
box.setAttribute("class", "calendar-event-column-linebox");
|
|
|
|
if (orient == "vertical")
|
|
box.setAttribute("height", dur * this.mPixPerMin);
|
|
else
|
|
box.setAttribute("width", dur * this.mPixPerMin);
|
|
|
|
box.setAttribute("style", "min-width: 1px; min-height: 1px;");
|
|
|
|
this.bgbox.appendChild(box);
|
|
theMin += 60;
|
|
}
|
|
|
|
// fgbox is used for dragging events
|
|
this.fgboxes.box.setAttribute("orient", orient);
|
|
document.getAnonymousElementByAttribute(this, "anonid", "fgdragspacer").setAttribute("orient", orient);
|
|
|
|
// this one is set to otherorient, since it will contain
|
|
// child boxes set to "orient" (one for each set of
|
|
// overlapping event areas)
|
|
this.topbox.setAttribute("orient", otherorient);
|
|
|
|
this.mEventMap = this.computeEventMap();
|
|
|
|
for each (var column in this.mEventMap) {
|
|
var xulColumn = createXULElement("box");
|
|
xulColumn.setAttribute("orient", orient);
|
|
xulColumn.setAttribute("flex", "1");
|
|
xulColumn.setAttribute("style", "min-width: 1px; min-height: 1px;");
|
|
this.topbox.appendChild(xulColumn);
|
|
|
|
var numBlocksInserted = 0
|
|
|
|
var curTime = 0;
|
|
for each (var chunk in column) {
|
|
var duration = chunk.duration;
|
|
//dump ("curTime: " + curTime + " duration: " + duration + " ev: " + chunk.event + "\n");
|
|
|
|
// if this chunk isn't entirely visible, we skip it
|
|
if (curTime < this.mStartMin) {
|
|
if (curTime + duration <= this.mStartMin) {
|
|
curTime += duration;
|
|
continue;
|
|
}
|
|
|
|
// offset the duration so that stuff starts at
|
|
// whatever our start time is set to, if this item
|
|
// starts before our start time
|
|
var delta = this.mStartMin - curTime;
|
|
if (delta > 0) {
|
|
duration -= delta;
|
|
curTime += delta;
|
|
}
|
|
}
|
|
|
|
if (chunk.event) {
|
|
var chunkBox = createXULElement("calendar-event-box");
|
|
chunkBox.setAttribute("context", this.getAttribute("item-context") || this.getAttribute("context"));
|
|
chunkBox.setAttribute("style", "min-width: 1px; min-height: 1px;");
|
|
chunkBox.setAttribute("orient", orient);
|
|
if (orient == "vertical")
|
|
chunkBox.setAttribute("height", duration * this.mPixPerMin);
|
|
else
|
|
chunkBox.setAttribute("width", duration * this.mPixPerMin);
|
|
|
|
xulColumn.appendChild(chunkBox);
|
|
|
|
chunkBox.calendarView = this.calendarView;
|
|
chunkBox.occurrence = chunk.event.event;
|
|
chunkBox.parentColumn = this;
|
|
|
|
chunk.event.eventbox = chunkBox;
|
|
} else {
|
|
var chunkBox = createXULElement("spacer");
|
|
chunkBox.setAttribute("context", this.getAttribute("context"));
|
|
chunkBox.setAttribute("style", "min-width: 1px; min-height: 1px;");
|
|
chunkBox.setAttribute("orient", orient);
|
|
chunkBox.setAttribute("class", "calendar-empty-space-box");
|
|
xulColumn.appendChild(chunkBox);
|
|
|
|
if (orient == "vertical")
|
|
chunkBox.setAttribute("height", duration * this.mPixPerMin);
|
|
else
|
|
chunkBox.setAttribute("width", duration * this.mPixPerMin);
|
|
}
|
|
|
|
numBlocksInserted++;
|
|
curTime += duration;
|
|
}
|
|
|
|
if (numBlocksInserted == 0) {
|
|
// if we didn't insert any blocks, then
|
|
// forget about this column
|
|
this.topbox.removeChild(xulColumn);
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="computeEventMap">
|
|
<body><![CDATA[
|
|
//dump ("computeEventMap\n");
|
|
// we need to build a layout data structure
|
|
// that looks like this:
|
|
// [
|
|
// [
|
|
// { duration: 120 /* min */ },
|
|
// { duration: 180, event: ev },
|
|
// { duration: 240 }
|
|
// ],
|
|
// [
|
|
// { duration: 180 },
|
|
// { duration: 120, event: ev2 },
|
|
// { duration: 240 }
|
|
// ]
|
|
// ]
|
|
// Indicating two events that overlap, with each index in the main
|
|
// array indicating one vertical line of events. If an event can't be placed
|
|
// in the first line, it should be placed in the next, and so on.
|
|
|
|
this.mEvents.sort(function eventComparator(a,b) {
|
|
var comp = a.startMinute - b.startMinute;
|
|
if (comp != 0) return comp;
|
|
return b.endMinute - a.endMinute;
|
|
});
|
|
|
|
var eventMap = []
|
|
eventMap.push(new Array());
|
|
|
|
for each (var event in this.mEvents) {
|
|
|
|
if (event.startMinute == null || event.endMinute == null || event.event == null)
|
|
continue;
|
|
|
|
//dump ("=== event: " + event + " " + event.startMinute + "-" + event.endMinute + "\n");
|
|
|
|
var startAt = event.startMinute;
|
|
var endAt = event.endMinute;
|
|
|
|
var curCol = 0;
|
|
while (curCol < eventMap.length) {
|
|
//dump ("+ curCol: " + curCol + "\n");
|
|
var blockIndex = 0;
|
|
var curOffset = 0;
|
|
var finished = false;
|
|
|
|
var prevblock = null;
|
|
|
|
while (blockIndex < eventMap[curCol].length) {
|
|
var block = eventMap[curCol][blockIndex];
|
|
|
|
//dump (" blockIndex: " + blockIndex + " curOffset: " + curOffset + " block.duration: " + block.duration + " (startAt: " + startAt + ")\n");
|
|
if (curOffset <= startAt && curOffset + block.duration > startAt) {
|
|
|
|
// We want to insert the event here. prevblock contains the
|
|
// preceeding block, if any. block contains the block that already
|
|
// exists at this location, e.g.:
|
|
// .....v- startAt
|
|
// ~~----------+----------~~
|
|
// prevblock | block
|
|
// ~~----------+----------~~
|
|
// ^- curOffset
|
|
//
|
|
// The event we're trying to insert starts at startAt,
|
|
// which can be anywhere from curOffset - prevblock.duration to
|
|
// curOffset + block.duration.
|
|
//
|
|
// We need to look at block; if it's an event, then we evict ourselves
|
|
// to the next eventMap index. We also do this if it's free space
|
|
// and we can't fit ourselves here.
|
|
|
|
|
|
// If the previous block is an event, and we
|
|
// need to start in the middle of it, we push
|
|
// to the next column.
|
|
if (prevblock && prevblock.event && startAt < curOffset) {
|
|
//dump ("** break 1\n");
|
|
break;
|
|
}
|
|
|
|
// If the next block is an event, we push to the
|
|
// next column, since we can't break it.
|
|
if (block.event) {
|
|
//dump ("** break 2\n");
|
|
break;
|
|
}
|
|
|
|
// If the next block is free space, but it isn't
|
|
// large enough to hold our event, we push to the
|
|
// next column.
|
|
if (curOffset + block.duration < endAt) {
|
|
//dump ("** break 3\n");
|
|
break;
|
|
}
|
|
|
|
// Otherwise, we are ready to insert the event.
|
|
// Figure out how much to shrink the previous/following
|
|
// blocks.
|
|
|
|
var startDelta = startAt - curOffset;
|
|
if (startDelta < 0 || (prevblock && !prevblock.event)) {
|
|
// we need to shrink or expand the previous free space
|
|
eventMap[curCol][blockIndex-1].duration += startDelta;
|
|
curOffset += startDelta;
|
|
} else if (startDelta > 0) {
|
|
eventMap[curCol].splice(blockIndex, 0, { duration: startDelta });
|
|
curOffset += startDelta;
|
|
blockIndex++;
|
|
}
|
|
|
|
var endDelta = endAt - curOffset;
|
|
if (endDelta > 0) {
|
|
eventMap[curCol][blockIndex].duration -= startDelta;
|
|
}
|
|
|
|
// insert our event block
|
|
eventMap[curCol].splice(blockIndex, 0, { duration: endAt - startAt, event: event });
|
|
|
|
finished = true;
|
|
break;
|
|
}
|
|
|
|
prevblock = block;
|
|
curOffset += block.duration;
|
|
|
|
blockIndex++;
|
|
}
|
|
|
|
// we got to the end of the list, so just add to the end
|
|
if (blockIndex == eventMap[curCol].length && curOffset <= startAt) {
|
|
var delta = startAt - curOffset;
|
|
if (delta)
|
|
eventMap[curCol].push({ duration: delta });
|
|
eventMap[curCol].push({ duration: endAt - startAt, event: event });
|
|
finished = true;
|
|
}
|
|
|
|
if (finished) {
|
|
//dump (eventMap.toSource() + "\n");
|
|
break;
|
|
}
|
|
|
|
if (curCol+1 == eventMap.length) {
|
|
eventMap.push(new Array());
|
|
}
|
|
curCol++;
|
|
}
|
|
}
|
|
|
|
return eventMap;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!--
|
|
- Event sweep handlers
|
|
-->
|
|
<method name="onEventSweepMouseMove">
|
|
<parameter name="event"/>
|
|
<body><![CDATA[
|
|
var col = document.calendarEventColumnDragging;
|
|
if (!col) return;
|
|
|
|
var dragState = col.mDragState;
|
|
|
|
col.fgboxes.box.setAttribute("dragging", "true");
|
|
col.fgboxes.dragbox.setAttribute("dragging", "true");
|
|
|
|
// check if we need to jump a column
|
|
if (dragState.dragType == "move") {
|
|
newcol = col.calendarView.findColumnForClientPoint(event.screenX, event.screenY);
|
|
if (newcol && newcol != col) {
|
|
// kill our drag state
|
|
col.fgboxes.dragbox.removeAttribute("dragging");
|
|
col.fgboxes.box.removeAttribute("dragging");
|
|
|
|
// jump ship
|
|
newcol.acceptInProgressSweep(dragState);
|
|
|
|
// restart event handling
|
|
col.onEventSweepMouseMove(event);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
var pos;
|
|
var sizeattr;
|
|
if (col.getAttribute("orient") == "vertical") {
|
|
pos = event.screenY - col.parentNode.boxObject.screenY - dragState.mouseOffset;
|
|
sizeattr = "height";
|
|
} else {
|
|
pos = event.screenX - col.parentNode.boxObject.screenX - dragState.mouseOffset;
|
|
sizeattr = "width";
|
|
}
|
|
// don't let pos go outside the window edges
|
|
if (pos < 0)
|
|
pos = 0;
|
|
|
|
// snap to 15 minute intervals
|
|
var interval = col.mPixPerMin * 15;
|
|
var curmin = Math.floor(pos/interval) * 15;
|
|
var deltamin = curmin - dragState.origMin;
|
|
|
|
if (dragState.dragType == "new") {
|
|
if (deltamin < 0) {
|
|
dragState.startMin = dragState.origMin + deltamin;
|
|
dragState.endMin = dragState.origMin;
|
|
} else {
|
|
dragState.startMin = dragState.origMin;
|
|
dragState.endMin = dragState.origMin + deltamin;
|
|
}
|
|
} else if (dragState.dragType == "move") {
|
|
// if we're moving, we can only move the start, and the end has to be exactly start+duration
|
|
dragState.startMin = dragState.origMin + deltamin;
|
|
dragState.endMin = dragState.startMin + dragState.limitDurationMin;
|
|
} else if (dragState.dragType == "modify-start") {
|
|
// if we're modifying the start, the end time is fixed.
|
|
dragState.startMin = dragState.origMin + deltamin;
|
|
dragState.endMin = dragState.limitEndMin;
|
|
|
|
// but we need to not go past the end; if we hit
|
|
// the end, then we'll clamp to the previous 15-min interval
|
|
if (dragState.endMin <= dragState.startMin)
|
|
dragState.startMin = Math.floor((dragState.endMin - 15) / 15) * 15;
|
|
} else if (dragState.dragType == "modify-end") {
|
|
// if we're modifying the end, the start time is fixed, and we'll always
|
|
// set the spacer to a constant size.
|
|
dragState.startMin = dragState.limitStartMin;
|
|
dragState.endMin = dragState.origMin + deltamin;
|
|
|
|
// but we need to not go past the start; if we hit
|
|
// the start, then we'll clamp to the next 15-min interval
|
|
if (dragState.endMin <= dragState.startMin)
|
|
dragState.endMin = Math.floor((dragState.startMin + 15) / 15) * 15;
|
|
}
|
|
|
|
// update the box sizes
|
|
col.fgboxes.dragspacer.setAttribute(sizeattr, dragState.startMin * col.mPixPerMin);
|
|
col.fgboxes.dragbox.setAttribute(sizeattr, Math.abs((dragState.endMin - dragState.startMin) * col.mPixPerMin));
|
|
|
|
// update the label
|
|
col.updateDragLabels();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="onEventSweepMouseUp">
|
|
<parameter name="event"/>
|
|
<body><![CDATA[
|
|
var col = document.calendarEventColumnDragging;
|
|
if (!col) return;
|
|
|
|
var dragState = col.mDragState;
|
|
|
|
col.fgboxes.dragbox.removeAttribute("dragging");
|
|
col.fgboxes.box.removeAttribute("dragging");
|
|
|
|
window.removeEventListener("mousemove", col.onEventSweepMouseMove, true);
|
|
window.removeEventListener("mouseup", col.onEventSweepMouseUp, true);
|
|
|
|
document.calendarEventColumnDragging = null;
|
|
|
|
// if the user didn't sweep out at least a few pixels, ignore
|
|
// unless we're in a different column
|
|
if (dragState.origColumn == col) {
|
|
var ignore = false;
|
|
if (col.getAttribute("orient") == "vertical") {
|
|
if (Math.abs(event.screenY - dragState.origLoc) < 3)
|
|
ignore = true;
|
|
} else {
|
|
if (Math.abs(event.screenX - dragState.origLoc) < 3)
|
|
ignore = true;
|
|
}
|
|
|
|
if (ignore) {
|
|
document.calendarEventColumnDragging = null;
|
|
col.mDragState = null;
|
|
return;
|
|
}
|
|
}
|
|
|
|
var newStart;
|
|
var newEnd;
|
|
var startTZ;
|
|
var endTZ;
|
|
|
|
if (dragState.dragType == "new") {
|
|
newStart = col.mDate.clone();
|
|
newStart.isDate = false;
|
|
newEnd = col.mDate.clone();
|
|
newEnd.isDate = false;
|
|
} else {
|
|
var oldStart = dragState.dragOccurrence.startDate || dragState.dragOccurrence.entryDate;
|
|
var oldEnd = dragState.dragOccurrence.endDate || dragState.dragOccurrence.dueDate;
|
|
newStart = oldStart.clone();
|
|
newEnd = oldEnd.clone();
|
|
|
|
// Our views are pegged to the default timezone. If the event
|
|
// isn't also in the timezone, we're going to need to do some
|
|
// tweaking. We could just do this for every eventm but
|
|
// getInTimezone is slow, so it's much better to only do this
|
|
// when the timezones actually differ from the view's.
|
|
if (this.mTimezone != newStart.timezone ||
|
|
this.mTimezone != newEnd.timezone) {
|
|
startTZ = newStart.timezone;
|
|
endTZ = newEnd.timezone;
|
|
newStart = newStart.getInTimezone(col.calendarView.mTimezone);
|
|
newEnd = newEnd.getInTimezone(col.calendarView.mTimezone);
|
|
}
|
|
}
|
|
|
|
if (dragState.dragType == "modify-start" ||
|
|
dragState.dragType == "new") {
|
|
newStart.hour = 0;
|
|
newStart.minute = dragState.startMin + col.mStartMin;
|
|
newStart.normalize();
|
|
}
|
|
|
|
if (dragState.dragType == "modify-end" ||
|
|
dragState.dragType == "new") {
|
|
newEnd.hour = 0;
|
|
newEnd.minute = dragState.endMin + col.mStartMin;
|
|
newEnd.normalize();
|
|
}
|
|
|
|
if (dragState.dragType == "move") {
|
|
// Figure out how much the event moved.
|
|
var duration = col.mDate.subtractDate(dragState.origDate);
|
|
var minutes = dragState.startMin - dragState.origMin;
|
|
if (duration.isNegative) {
|
|
// Adding negative minutes to a negative duration makes the
|
|
// duration more positive, but we want more negative, and
|
|
// vice versa.
|
|
minutes *= -1;
|
|
}
|
|
duration.minutes = minutes;
|
|
duration.normalize();
|
|
|
|
newStart.addDuration(duration);
|
|
newStart.normalize();
|
|
newEnd.addDuration(duration);
|
|
newEnd.normalize();
|
|
}
|
|
|
|
// If we tweaked tzs, put times back in their original ones
|
|
if (startTZ) {
|
|
newStart = newStart.getInTimezone(startTZ);
|
|
}
|
|
if (endTZ) {
|
|
newEnd = newEnd.getInTimezone(endTZ);
|
|
}
|
|
|
|
if (dragState.dragType == "new") {
|
|
col.calendarView.controller.createNewEvent(col.calendarView.displayCalendar,
|
|
newStart,
|
|
newEnd);
|
|
} else if (dragState.dragType == "move" ||
|
|
dragState.dragType == "modify-start" ||
|
|
dragState.dragType == "modify-end")
|
|
{
|
|
col.calendarView.controller.modifyOccurrence(dragState.dragOccurrence,
|
|
newStart, newEnd);
|
|
}
|
|
document.calendarEventColumnDragging = null;
|
|
col.mDragState = null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- This is called by an event box when a grippy on either side is dragged,
|
|
- or when the middle is pressed to drag the event to move it. We create
|
|
- the same type of view that we use to sweep out a new event, but we
|
|
- initialize it based on the event's values and what type of dragging
|
|
- we're doing. In addition, we constrain things like not being able to
|
|
- drag the end before the start and vice versa.
|
|
-->
|
|
<method name="startSweepingToModifyEvent">
|
|
<parameter name="aEventBox"/>
|
|
<parameter name="aOccurrence"/>
|
|
<!-- "start", "end", "middle" -->
|
|
<parameter name="aGrabbedElement"/>
|
|
<!-- mouse screenX/screenY from the event -->
|
|
<parameter name="aMouseX"/>
|
|
<parameter name="aMouseY"/>
|
|
<body><![CDATA[
|
|
//dump ("startSweepingToModify\n");
|
|
this.mDragState = {
|
|
origColumn: this,
|
|
dragOccurrence: aOccurrence,
|
|
mouseOffset: 0
|
|
};
|
|
|
|
var interval = this.mPixPerMin * 15;
|
|
var sizeattr;
|
|
|
|
//dump ("AMY: " + aMouseY + " boY: " + this.parentNode.boxObject.screenY + "\n");
|
|
var frameloc;
|
|
if (this.getAttribute("orient") == "vertical") {
|
|
this.mDragState.origLoc = aMouseY;
|
|
frameloc = aMouseY - this.parentNode.boxObject.screenY;
|
|
sizeattr = "height";
|
|
} else {
|
|
this.mDragState.origLoc = aMouseX;
|
|
frameloc = aMouseX - this.parentNode.boxObject.screenX;
|
|
sizeattr = "width";
|
|
}
|
|
|
|
var mins = this.getStartEndMinutesForOccurrence(aOccurrence);
|
|
|
|
// these are only used to compute durations or to compute UI
|
|
// sizes, so offset by this.mStartMin for sanity here (at the
|
|
// expense of possible insanity later)
|
|
mins.start -= this.mStartMin;
|
|
mins.end -= this.mStartMin;
|
|
|
|
if (aGrabbedElement == "start") {
|
|
this.mDragState.dragType = "modify-start";
|
|
this.mDragState.limitEndMin = mins.end;
|
|
|
|
// snap start
|
|
this.mDragState.origMin = Math.floor(mins.start/15) * 15;
|
|
this.fgboxes.dragspacer.setAttribute(sizeattr, this.mDragState.origMin * this.mPixPerMin);
|
|
this.fgboxes.dragbox.setAttribute(sizeattr, (mins.end - this.mDragState.origMin) * this.mPixPerMin);
|
|
} else if (aGrabbedElement == "end") {
|
|
this.mDragState.dragType = "modify-end";
|
|
this.mDragState.limitStartMin = mins.start;
|
|
|
|
// snap end
|
|
this.mDragState.origMin = Math.floor(mins.end/15) * 15;
|
|
this.fgboxes.dragspacer.setAttribute(sizeattr, mins.start * this.mPixPerMin);
|
|
this.fgboxes.dragbox.setAttribute(sizeattr, (this.mDragState.origMin - mins.start) * this.mPixPerMin);
|
|
} else if (aGrabbedElement == "middle") {
|
|
this.mDragState.dragType = "move";
|
|
this.mDragState.limitDurationMin = mins.end - mins.start;
|
|
|
|
// in a move, origMin will be the min of the start element;
|
|
// so we snap start again, but we keep the duration the same
|
|
// (we move the end based on the duration of the event,
|
|
// not including our snap)
|
|
this.mDragState.origMin = Math.floor(mins.start/15) * 15;
|
|
|
|
// Because we can pass this event to other columns, we also need
|
|
// to track the original column's date too, to get the correct offset
|
|
this.mDragState.origDate = this.mDate;
|
|
this.fgboxes.dragspacer.setAttribute(sizeattr, this.mDragState.origMin * this.mPixPerMin);
|
|
this.fgboxes.dragbox.setAttribute(sizeattr, (mins.end - mins.start) * this.mPixPerMin);
|
|
|
|
// we need to set a mouse offset, since we're not dragging from
|
|
// one end of the element
|
|
if (aEventBox) {
|
|
if (this.getAttribute("orient") == "vertical")
|
|
this.mDragState.mouseOffset = aMouseY - aEventBox.boxObject.screenY;
|
|
else
|
|
this.mDragState.mouseOffset = aMouseX - aEventBox.boxObject.screenX;
|
|
}
|
|
} else {
|
|
dump ("+++ Invalid grabbed element: '" + aGrabbedElement + "'\n");
|
|
}
|
|
|
|
this.fgboxes.box.setAttribute("dragging", "true");
|
|
this.fgboxes.dragbox.setAttribute("dragging", "true");
|
|
|
|
document.calendarEventColumnDragging = this;
|
|
|
|
//dump (">>> drag is: " + this.mDragState.dragType + "\n");
|
|
|
|
window.addEventListener("mousemove", this.onEventSweepMouseMove, false);
|
|
window.addEventListener("mouseup", this.onEventSweepMouseUp, false);
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- called by sibling columns to tell us to take over the sweeping
|
|
- of an event. Used by "move".
|
|
-->
|
|
<method name="acceptInProgressSweep">
|
|
<parameter name="aDragState"/>
|
|
<body><![CDATA[
|
|
this.mDragState = aDragState;
|
|
document.calendarEventColumnDragging = this;
|
|
|
|
this.fgboxes.box.setAttribute("dragging", "true");
|
|
this.fgboxes.dragbox.setAttribute("dragging", "true");
|
|
|
|
// the same event handlers are still valid,
|
|
// because they use document.calendarEventColumnDragging.
|
|
// So we really don't have anything to do here.
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="updateDragLabels">
|
|
<body><![CDATA[
|
|
if (!this.mDragState) return;
|
|
|
|
var realstartmin = this.mDragState.startMin + this.mStartMin;
|
|
var realendmin = this.mDragState.endMin + this.mStartMin;
|
|
|
|
|
|
if (this.mDragState.dragType == "move") {
|
|
realendmin = realstartmin + this.mDragState.limitDurationMin;
|
|
} else if (this.mDragState.dragType == "start") {
|
|
realendmin = this.mDragState.limitEndMin;
|
|
} else if (this.mDragState.dragType == "end") {
|
|
realstartmin = this.mDragSTate.limitStartMin;
|
|
}
|
|
|
|
var starthr = Math.floor(realstartmin / 60);
|
|
var startmin = realstartmin % 60;
|
|
|
|
var endhr = Math.floor(realendmin / 60);
|
|
var endmin = realendmin % 60;
|
|
|
|
var formatter =
|
|
Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
|
|
.getService(Components.interfaces.nsIScriptableDateFormat);
|
|
var startstr = formatter.FormatTime("",
|
|
Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
|
|
starthr, startmin, 0);
|
|
var endstr = formatter.FormatTime("",
|
|
Components.interfaces.nsIScriptableDateFormat.timeFormatNoSeconds,
|
|
endhr, endmin, 0);
|
|
|
|
this.fgboxes.startlabel.setAttribute("value", startstr);
|
|
this.fgboxes.endlabel.setAttribute("value", endstr);
|
|
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="dblclick" button="0"><![CDATA[
|
|
if (this.calendarView.controller) {
|
|
var newStart = this.date.clone();
|
|
newStart.isDate = false;
|
|
newStart.hour = 0;
|
|
|
|
const ROUND_INTERVAL = 15;
|
|
|
|
var interval = this.mPixPerMin * ROUND_INTERVAL;
|
|
var pos;
|
|
if (this.getAttribute("orient") == "vertical") {
|
|
pos = event.screenY - this.parentNode.boxObject.screenY;
|
|
} else {
|
|
pos = event.screenX - this.parentNode.boxObject.screenX;
|
|
}
|
|
newStart.minute = (Math.round(pos/interval) * ROUND_INTERVAL) + this.mStartMin;
|
|
newStart.normalize();
|
|
this.calendarView.controller.createNewEvent(null, newStart, null);
|
|
}
|
|
]]></handler>
|
|
|
|
<!-- mouse down handler, in empty event column regions. Starts sweeping out a new
|
|
- event.
|
|
-->
|
|
<handler event="mousedown"><![CDATA[
|
|
// select this column
|
|
this.calendarView.selectedDay = this.mDate;
|
|
|
|
// Only start sweeping out an event if the left button was clicked
|
|
if (event.button != 0) {
|
|
return;
|
|
}
|
|
|
|
// snap to 15 minute intervals
|
|
var interval = this.mPixPerMin * 15;
|
|
|
|
this.mDragState = {
|
|
origColumn: this,
|
|
dragType: "new",
|
|
mouseOffset: 0
|
|
};
|
|
|
|
if (this.getAttribute("orient") == "vertical") {
|
|
this.mDragState.origLoc = event.screenY;
|
|
this.mDragState.origMin = Math.floor((event.screenY - this.parentNode.boxObject.screenY)/interval) * 15;
|
|
this.fgboxes.dragspacer.setAttribute("height", this.mDragState.origMin * this.mPixPerMin);
|
|
} else {
|
|
this.mDragState.origLoc = event.screenX;
|
|
this.mDragState.origMin = Math.floor((event.screenX - this.parentNode.boxObject.screenX)/interval) * 15;
|
|
this.fgboxes.dragspacer.setAttribute("width", this.mDragState.origMin * this.mPixPerMin);
|
|
}
|
|
|
|
document.calendarEventColumnDragging = this;
|
|
|
|
window.addEventListener("mousemove", this.onEventSweepMouseMove, false);
|
|
window.addEventListener("mouseup", this.onEventSweepMouseUp, false);
|
|
]]></handler>
|
|
|
|
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="calendar-header-container">
|
|
<content>
|
|
<xul:vbox xbl:inherits="selected" anonid="thebox" flex="1" class="calendar-event-column-header"/>
|
|
</content>
|
|
|
|
<implementation>
|
|
<field name="mItemBoxes">null</field>
|
|
<field name="mDate">null</field>
|
|
<field name="mCalendarView">null</field>
|
|
|
|
<constructor><![CDATA[
|
|
this.mItemBoxes = new Array();
|
|
]]></constructor>
|
|
|
|
<property name="mainbox">
|
|
<getter><![CDATA[
|
|
return document.getAnonymousElementByAttribute(this, 'anonid', 'thebox');
|
|
]]></getter>
|
|
</property>
|
|
|
|
<property name="date">
|
|
<getter><![CDATA[
|
|
return this.mDate;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mDate = val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="calendarView">
|
|
<getter><![CDATA[
|
|
return this.mCalendarView;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mCalendarView = val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<method name="addEvent">
|
|
<parameter name="aItem"/>
|
|
<body><![CDATA[
|
|
function createXULElement(el) {
|
|
return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", el);
|
|
}
|
|
var itemBox = createXULElement("calendar-editable-item");
|
|
|
|
this.mainbox.appendChild(itemBox);
|
|
itemBox.calendarView = this.calendarView;
|
|
itemBox.occurrence = aItem;
|
|
var ctxt = this.calendarView.getAttribute("item-context") ||
|
|
this.calendarView.getAttribute("context");
|
|
itemBox.setAttribute("context", ctxt);
|
|
|
|
this.mItemBoxes.push(itemBox);
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="deleteEvent">
|
|
<parameter name="aItem"/>
|
|
<body><![CDATA[
|
|
for (var i in this.mItemBoxes) {
|
|
if (this.mItemBoxes[i].occurrence.id == aItem.id) {
|
|
this.mainbox.removeChild(this.mItemBoxes[i]);
|
|
this.mItemBoxes.splice(i, 1);
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="selectOccurrence">
|
|
<parameter name="aItem"/>
|
|
<body><![CDATA[
|
|
for each (itemBox in this.mItemBoxes) {
|
|
if (aItem && itemBox.occurrence.id == aItem.id) {
|
|
itemBox.selected = true;
|
|
} else {
|
|
itemBox.selected = false;
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="dblclick" button="0"><![CDATA[
|
|
this.calendarView.controller.createNewEvent();
|
|
]]></handler>
|
|
<handler event="mousedown"><![CDATA[
|
|
this.calendarView.selectedDay = this.mDate;
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<!--
|
|
- An individual event box, to be inserted into a column.
|
|
-->
|
|
<binding id="calendar-event-box" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
|
|
|
|
<content>
|
|
<xul:box anonid="eventbox" xbl:inherits="orient,width,height" flex="1">
|
|
<xul:calendar-event-gripbar anonid="gripbar1" whichside="start" xbl:inherits="parentorient=orient"/>
|
|
<xul:vbox class="calendar-event-box-container" xbl:inherits="context,parentorient=orient" flex="1" align="left">
|
|
<xul:label anonid="event-name" 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:calendar-event-gripbar anonid="gripbar2" whichside="end" xbl:inherits="parentorient=orient"/>
|
|
</xul:box>
|
|
</content>
|
|
|
|
<implementation>
|
|
<constructor><![CDATA[
|
|
this.orient = this.getAttribute("orient");
|
|
var otherorient = "vertical";
|
|
if (!orient) orient = "horizontal";
|
|
if (orient == "vertical") otherorient = "horizontal";
|
|
|
|
var self = this;
|
|
this.eventNameInput.onblur =
|
|
function onBlur() { self.stopEditing(true); };
|
|
this.eventNameInput.onkeypress = function onKeyPress(event) {
|
|
// save on enter
|
|
if (event.keyCode == 13)
|
|
self.stopEditing(true);
|
|
// abort on escape
|
|
else if (event.keyCode == 27)
|
|
self.stopEditing(false);
|
|
};
|
|
]]></constructor>
|
|
|
|
<!-- fields -->
|
|
<field name="mParentColumn">null</field>
|
|
|
|
<!-- methods/properties -->
|
|
<method name="setAttribute">
|
|
<parameter name="aAttr"/>
|
|
<parameter name="aVal"/>
|
|
<body><![CDATA[
|
|
var needsrelayout = false;
|
|
if (aAttr == "orient") {
|
|
if (this.getAttribute("orient") != aVal)
|
|
needsrelayout = true;
|
|
}
|
|
|
|
// this should be done using lookupMethod(), see bug 286629
|
|
var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
|
|
|
|
if (needsrelayout) {
|
|
var otherorient = "vertical";
|
|
if (val != "horizontal") otherorient = "horizontal";
|
|
|
|
var eventbox = document.getAnonymousElementByAttribute(this, "anonid", "eventbox");
|
|
eventbox.setAttribute("orient", val);
|
|
eventbox.setAttribute("class", "calendar-item calendar-event-box-" + val);
|
|
var gb1 = document.getAnonymousElementByAttribute(this, "anonid", "gripbar1");
|
|
gb1.parentorient = val;
|
|
var gb2 = document.getAnonymousElementByAttribute(this, "anonid", "gripbar2");
|
|
gb2.parentorient = val;
|
|
}
|
|
|
|
return ret;
|
|
]]></body>
|
|
</method>
|
|
|
|
<property name="parentColumn"
|
|
onget="return this.mParentColumn;"
|
|
onset="return (this.mParentColumn = val);"/>
|
|
|
|
<property name="startMinute">
|
|
<getter><![CDATA[
|
|
if (!this.mOccurrence)
|
|
return 0;
|
|
var startDate = this.mOccurrence.startDate || this.mOccurrence.entryDate;
|
|
return startDate.hour * 60 + startDate.minute;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<property name="endMinute">
|
|
<getter><![CDATA[
|
|
if (!this.mOccurrence)
|
|
return 0;
|
|
var endDate = this.mOccurrence.endDate || this.mOccurrence.dueDate;
|
|
return endDate.hour * 60 + endDate.minute;
|
|
]]></getter>
|
|
</property>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="mousedown" button="0"><![CDATA[
|
|
event.stopPropagation();
|
|
|
|
if (this.mEditing)
|
|
return;
|
|
|
|
this.parentColumn.calendarView.selectedDay = this.parentColumn.mDate;
|
|
this.mInMouseDown = true;
|
|
this.mMouseX = event.screenX;
|
|
this.mMouseY = event.screenY;
|
|
]]></handler>
|
|
|
|
<handler event="mousemove"><![CDATA[
|
|
if (!this.mInMouseDown)
|
|
return;
|
|
var dx = Math.abs(event.screenX - this.mMouseX);
|
|
var dy = Math.abs(event.screenY - this.mMouseY);
|
|
// more than a 3 pixel move?
|
|
if ((dx*dx + dy*dy) > 9) {
|
|
if (this.parentColumn) {
|
|
if (this.editingTimer) {
|
|
clearTimeout(this.editingTimer);
|
|
this.editingTimer = null;
|
|
}
|
|
|
|
this.calendarView.selectedItem = this.mOccurrence;
|
|
|
|
this.mEditing = false;
|
|
if (this.calendarView)
|
|
this.calendarView.activeInPlaceEdit = false;
|
|
|
|
this.parentColumn.startSweepingToModifyEvent(this, this.mOccurrence, "middle", this.mMouseX, this.mMouseY);
|
|
this.mInMouseDown = false;
|
|
}
|
|
}
|
|
]]></handler>
|
|
|
|
<handler event="mouseup"><![CDATA[
|
|
if (this.mEditing)
|
|
return;
|
|
|
|
this.mInMouseDown = false;
|
|
]]></handler>
|
|
|
|
<handler event="mouseover"><![CDATA[
|
|
if (this.calendarView && this.calendarView.controller) {
|
|
event.stopPropagation();
|
|
onMouseOverItem(event);
|
|
}
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="calendar-multiday-view">
|
|
<content>
|
|
<xul:box anonid="mainbox" flex="1">
|
|
<!-- this thing is tricky; its width/height need to be programatically set based on the orientation -->
|
|
<xul:box anonid="labelbox">
|
|
<xul:box anonid="labeltimespacer">
|
|
<xul:button style="min-width: 10px; padding: 1px; background: red;"
|
|
anonid="rotate-button"
|
|
oncommand="var e=this.parentNode.parentNode.parentNode.parentNode; if (e.orient == 'horizontal') e.orient = 'vertical'; else e.orient = 'horizontal';" align="center"/>
|
|
</xul:box>
|
|
<xul:box anonid="labeldaybox" class="calendar-label-day-box" flex="1"
|
|
equalsize="always" />
|
|
<xul:box anonid="labelscrollbarspacer"/>
|
|
</xul:box>
|
|
<xul:box anonid="headerbox">
|
|
<xul:box anonid="headertimespacer"
|
|
class="calendar-header-time-spacer"/>
|
|
<xul:box anonid="headerdaybox" class="calendar-header-day-box"
|
|
flex="1" equalsize="always" />
|
|
<xul:box anonid="headerscrollbarspacer"/>
|
|
</xul:box>
|
|
<xul:scrollbox anonid="childbox" flex="1">
|
|
<!-- the orient of the calendar-time-bar needs to be the opposite of the parent -->
|
|
<xul:calendar-time-bar xbl:inherits="orient" anonid="timebar"/>
|
|
<xul:box anonid="daybox" class="calendar-day-box" flex="1"
|
|
equalsize="always"/>
|
|
</xul:scrollbox>
|
|
</xul:box>
|
|
</content>
|
|
|
|
<implementation implements="calICalendarView">
|
|
|
|
<field name="mResizeHandler">null</field>
|
|
<constructor><![CDATA[
|
|
var self = this;
|
|
this.mResizeHandler = function resizeHandler() { self.onResize(); };
|
|
window.addEventListener("resize", this.mResizeHandler, true);
|
|
var rButton = document.getAnonymousElementByAttribute(this, "anonid", "rotate-button");
|
|
rButton.label = calGetString("calendar", "rotate");
|
|
|
|
// set the flex attribute at the scrollbox-innerbox
|
|
// (this can be removed, after Bug 343555 is fixed)
|
|
var childbox = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "childbox");
|
|
document.getAnonymousElementByAttribute(
|
|
childbox, "class", "box-inherit scrollbox-innerbox").flex = "1";
|
|
|
|
this.reorient();
|
|
]]></constructor>
|
|
|
|
<field name="mLastSize">0</field>
|
|
<method name="onResize">
|
|
<parameter name="aRealSelf"/>
|
|
<body><![CDATA[
|
|
var self = this;
|
|
if (aRealSelf) {
|
|
self = aRealSelf;
|
|
}
|
|
|
|
var timebar = document.getAnonymousElementByAttribute(self, "anonid", "timebar");
|
|
var daybox = document.getAnonymousElementByAttribute(self, "anonid", "daybox");
|
|
|
|
var orient = self.orient;
|
|
var size;
|
|
if (self.orient == "horizontal")
|
|
size = daybox.boxObject.width;
|
|
else
|
|
size = daybox.boxObject.height;
|
|
|
|
if (self.mLastSize > size) {
|
|
self.pixelsPerMinute = 0.01;
|
|
self.mLastSize = size;
|
|
|
|
if (self.orient == "horizontal")
|
|
size = daybox.boxObject.width;
|
|
else
|
|
size = daybox.boxObject.height;
|
|
}
|
|
|
|
self.mLastSize = size;
|
|
|
|
//self.removeAttribute("hidden");
|
|
var minutes = self.mEndMin - self.mStartMin;
|
|
var ppm = size / minutes;
|
|
ppm = Math.round(ppm * 10) / 10;
|
|
if (ppm < self.mMinPPM) {
|
|
ppm = self.mMinPPM;
|
|
}
|
|
self.pixelsPerMinute = ppm;
|
|
]]></body>
|
|
</method>
|
|
|
|
<field name="mController">null</field>
|
|
<field name="mCalendar">null</field>
|
|
<field name="mStartDate">null</field>
|
|
<field name="mEndDate">null</field>
|
|
<!-- mDateList will always be sorted before being set -->
|
|
<field name="mDateList">null</field>
|
|
<!-- array of { date: calIDatetime, column: colbox, header: hdrbox } -->
|
|
<field name="mDateColumns">null</field>
|
|
<field name="mBatchCount">0</field>
|
|
<field name="mPixPerMin">0.6</field>
|
|
<field name="mMinPPM">0.4</field>
|
|
<field name="mSelectedItem">null</field>
|
|
<field name="mSelectedDayCol">null</field>
|
|
|
|
<field name="mDefaultStartMin">8*60</field>
|
|
<field name="mDefaultEndMin">20*60</field>
|
|
<field name="mStartMin">8*60</field>
|
|
<field name="mEndMin">20*60</field>
|
|
<field name="mTasksInView">true</field>
|
|
<field name="mDaysOffArray">[0,6]</field>
|
|
<field name="mTimezone">"UTC"</field>
|
|
|
|
<field name="mSelectionObserver"><![CDATA[
|
|
({ calView: this,
|
|
onSelectionChanged: function selectionObserverCallback(itemSelectionArray) {
|
|
// The selection manager should be smart enough to only call this
|
|
// when the selection really did change, so just do it.
|
|
if (this.calView.mSelectedItem) {
|
|
var cols = this.calView.findColumnsForItem(this.calView.mSelectedItem);
|
|
var selectedStart = this.calView.mSelectedItem.startDate || this.calView.mSelectedItem.entryDate
|
|
for each (col in cols) {
|
|
if (!selectedStart.isDate) {
|
|
col.column.selectOccurrence(null);
|
|
} else {
|
|
col.header.selectOccurrence(null);
|
|
}
|
|
}
|
|
}
|
|
this.calView.mSelectedItem = itemSelectionArray[0];
|
|
if (!this.calView.mSelectedItem)
|
|
return;
|
|
var newSelectedStart = this.calView.mSelectedItem.startDate || this.calView.mSelectedItem.entryDate;
|
|
this.calView.showDate(newSelectedStart);
|
|
var cols = this.calView.findColumnsForItem(this.calView.mSelectedItem);
|
|
for each (col in cols) {
|
|
if (!newSelectedStart.isDate) {
|
|
col.column.selectOccurrence(this.calView.mSelectedItem);
|
|
} else {
|
|
col.header.selectOccurrence(this.calView.mSelectedItem);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]]></field>
|
|
|
|
|
|
<field name="mObserver"><![CDATA[
|
|
// the calIObserver, and calICompositeObserver
|
|
({
|
|
QueryInterface: function QueryInterface(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;
|
|
},
|
|
|
|
calView: this,
|
|
|
|
onStartBatch: function onStartBatch() {
|
|
this.calView.mBatchCount++;
|
|
},
|
|
onEndBatch: function onEndBatch() {
|
|
this.mBatchCount--;
|
|
if (this.mBatchCount == 0) {
|
|
this.calView.refresh();
|
|
}
|
|
},
|
|
onLoad: function onLoad() {
|
|
this.calView.refresh();
|
|
},
|
|
onAddItem: function onAddItem(aItem) {
|
|
if (this.mBatchCount) {
|
|
return;
|
|
}
|
|
|
|
if (aItem instanceof Components.interfaces.calITodo &&
|
|
(!aItem.entryDate || !aItem.dueDate))
|
|
return;
|
|
|
|
if (aItem instanceof Components.interfaces.calITodo &&
|
|
!this.calView.mTasksInView)
|
|
return;
|
|
|
|
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
|
|
this.calView.queryEndDate,
|
|
{});
|
|
//dump ("occs: " + occs.length + "\n");
|
|
for each (var occ in occs) {
|
|
this.calView.doAddEvent(occ);
|
|
}
|
|
|
|
var chunks = this.calView.createChunksForNonDateItems(occs);
|
|
if (this.calView.readjustStartEndMinutes(chunks, false)) {
|
|
this.calView.propagateStartEndMinutes();
|
|
}
|
|
|
|
return;
|
|
},
|
|
onModifyItem: function onModifyItem(aNewItem, aOldItem) {
|
|
if (this.mBatchCount) {
|
|
return;
|
|
}
|
|
|
|
if (aNewItem instanceof Components.interfaces.calITodo &&
|
|
!this.calView.mTasksInView)
|
|
return;
|
|
|
|
var occs;
|
|
|
|
|
|
if (!aOldItem instanceof Components.interfaces.calITodo ||
|
|
(aOldItem.entryDate && aOldItem.dueDate)) {
|
|
occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
|
|
this.calView.queryEndDate,
|
|
{});
|
|
for each (var occ in occs) {
|
|
this.calView.doDeleteEvent(occ);
|
|
}
|
|
}
|
|
if (aNewItem instanceof Components.interfaces.calITodo &&
|
|
(!aNewItem.entryDate || !aNewItem.dueDate))
|
|
return;
|
|
occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
|
|
this.calView.queryEndDate,
|
|
{});
|
|
for each (var occ in occs) {
|
|
this.calView.doDeleteEvent(occ);
|
|
}
|
|
|
|
occs = aNewItem.getOccurrencesBetween(this.calView.startDate,
|
|
this.calView.queryEndDate,
|
|
{});
|
|
for each (var occ in occs) {
|
|
this.calView.doAddEvent(occ);
|
|
}
|
|
|
|
this.calView.readjust();
|
|
},
|
|
onDeleteItem: function onDeleteItem(aItem) {
|
|
if (this.mBatchCount) {
|
|
return;
|
|
}
|
|
|
|
if (aItem instanceof Components.interfaces.calITodo &&
|
|
!this.calView.mTasksInView)
|
|
return;
|
|
|
|
if (aItem instanceof Components.interfaces.calITodo &&
|
|
(!aItem.entryDate || !aItem.dueDate))
|
|
return;
|
|
|
|
var occs = aItem.getOccurrencesBetween(this.calView.startDate,
|
|
this.calView.queryEndDate,
|
|
{});
|
|
for each (var occ in occs) {
|
|
this.calView.doDeleteEvent(occ);
|
|
}
|
|
|
|
this.calView.readjust();
|
|
},
|
|
onError: function onError(aErrNo, aMessage) { },
|
|
|
|
//
|
|
// calICompositeObserver stuff
|
|
// XXXvv we can be smarter about how we handle this stuff
|
|
//
|
|
onCalendarAdded: function onCalendarAdded(aCalendar) {
|
|
//dump ("view onCalendarAdded\n");
|
|
this.calView.refresh();
|
|
},
|
|
|
|
onCalendarRemoved: function onCalendarRemoved(aCalendar) {
|
|
//dump ("view onCalendarRemoved\n");
|
|
this.calView.refresh();
|
|
},
|
|
|
|
onDefaultCalendarChanged:
|
|
function onDefaultCalendarChanged(aNewDefaultCalendar) {
|
|
// don't care, for now
|
|
}
|
|
})
|
|
]]></field>
|
|
|
|
<field name="mOperationListener"><![CDATA[
|
|
({
|
|
calView: this,
|
|
|
|
onOperationComplete:
|
|
function onOperationComplete(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 onGetResult(aCalendar, aStatus, aItemType, aDetail,
|
|
aCount, aItems) {
|
|
if (!Components.isSuccessCode(aStatus))
|
|
return;
|
|
|
|
function hasGoodDates(item) {
|
|
if (item instanceof Components.interfaces.calITodo &&
|
|
(!item.entryDate || !item.dueDate)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
aItems = aItems.filter(hasGoodDates);
|
|
|
|
var chunks = this.calView.createChunksForNonDateItems(aItems);
|
|
|
|
if (this.calView.readjustStartEndMinutes(chunks, false)) {
|
|
this.calView.propagateStartEndMinutes();
|
|
}
|
|
|
|
for each (var item in aItems) {
|
|
this.calView.doAddEvent(item);
|
|
}
|
|
}
|
|
})
|
|
]]></field>
|
|
|
|
|
|
<!-- calICalendarView -->
|
|
<property name="supportsDisjointDates"
|
|
onget="return true"/>
|
|
<property name="hasDisjointDates"
|
|
onget="return (this.mDateList != null);"/>
|
|
|
|
<property name="controller"
|
|
onget="return this.mController;"
|
|
onset="return (this.mController = val);" />
|
|
|
|
<property name="displayCalendar">
|
|
<getter><![CDATA[
|
|
return this.mCalendar;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (this.mCalendar)
|
|
this.mCalendar.removeObserver (this.mObserver);
|
|
this.mCalendar = val;
|
|
this.mCalendar.addObserver (this.mObserver);
|
|
|
|
this.refresh();
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="startDate">
|
|
<getter><![CDATA[
|
|
if (this.mStartDate) return this.mStartDate;
|
|
else if (this.mDateList && this.mDateList.length > 0) return this.mDateList[0];
|
|
else return null;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<property name="endDate">
|
|
<getter><![CDATA[
|
|
if (this.mEndDate) return this.mEndDate;
|
|
else if (this.mDateList && this.mDateList.length > 0) return this.mDateList[this.mDateList.length-1];
|
|
else return null;
|
|
]]></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="timezone">
|
|
<getter><![CDATA[
|
|
return this.mTimezone;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mTimezone = val;
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="daysOffArray">
|
|
<getter><![CDATA[
|
|
return this.mDaysOffArray;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mDaysOffArray = 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[
|
|
var targetDate = aDate.getInTimezone(this.mTimezone);
|
|
targetDate.isDate = true;
|
|
|
|
if (this.mStartDate && this.mEndDate) {
|
|
if (this.mStartDate.compare(targetDate) <= 0 &&
|
|
this.mEndDate.compare(targetDate) >= 0)
|
|
return;
|
|
} else if (this.mDateList) {
|
|
for each (var d in this.mDateList) {
|
|
// if date is already visible, nothing to do
|
|
if (d.compare(targetDate) == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if we're only showing one date, then continue
|
|
// to only show one date; otherwise, show the week.
|
|
if (this.numVisibleDates == 1) {
|
|
this.setDateRange(aDate, aDate);
|
|
} else {
|
|
this.setDateRange(aDate.startOfWeek, aDate.endOfWeek);
|
|
}
|
|
|
|
this.selectedDay = targetDate;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setDateRange">
|
|
<parameter name="aStartDate"/>
|
|
<parameter name="aEndDate"/>
|
|
<body><![CDATA[
|
|
//dump ("setDateRange\n");
|
|
this.mDateList = null;
|
|
|
|
this.mStartDate = aStartDate.getInTimezone(this.mTimezone);
|
|
this.mStartDate.isDate = true;
|
|
this.mStartDate.makeImmutable();
|
|
|
|
this.mEndDate = aEndDate.getInTimezone(this.mTimezone);
|
|
this.mEndDate.isDate = true;
|
|
this.mEndDate.makeImmutable();
|
|
|
|
// this function needs to be smarter, and needs to compare
|
|
// the current date range and add/remove, instead of just
|
|
// replacing.
|
|
|
|
this.refresh();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setDateList">
|
|
<parameter name="aCount"/>
|
|
<parameter name="aDates"/>
|
|
<body><![CDATA[
|
|
this.mStartDate = null;
|
|
this.mEndDate = null;
|
|
|
|
if (aCount == 0) {
|
|
this.mDateList = null;
|
|
} else {
|
|
aDates.sort (function(a, b) { return a.compare(b); });
|
|
this.mDateList = aDates.map(
|
|
function dateMapper(d) {
|
|
if (d.isDate && !d.isMutable)
|
|
return d;
|
|
|
|
var newDate = d.clone();
|
|
newDate.isDate = true;
|
|
newDate.makeImmutable();
|
|
return newDate;
|
|
}
|
|
);
|
|
}
|
|
|
|
this.refresh();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="getDateList">
|
|
<parameter name="aCount"/>
|
|
<body><![CDATA[
|
|
var dates = new Array();
|
|
if (this.mStartDate && this.mEndDate) {
|
|
var d = this.mStartDate.clone();
|
|
while (d.compare(this.mEndDate) <= 0) {
|
|
dates.push(d.clone());
|
|
d.day += 1;
|
|
d.normalize();
|
|
}
|
|
} else if (this.mDateList) {
|
|
for each (var d in this.mDateList)
|
|
dates.push(d.clone());
|
|
}
|
|
|
|
aCount.value = dates.length;
|
|
return dates;
|
|
]]></body>
|
|
</method>
|
|
|
|
<property name="selectedDay">
|
|
<getter><![CDATA[
|
|
if (this.numVisibleDates == 1)
|
|
return this.mDateColumns[0].date;
|
|
|
|
if (this.mSelectedDayCol)
|
|
return this.mSelectedDayCol.date;
|
|
|
|
return null;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
// ignore if just 1 visible, it's always selected,
|
|
// but we don't indicate it
|
|
if (this.numVisibleDates == 1) {
|
|
this.fireEvent("dayselect", val);
|
|
return val;
|
|
}
|
|
|
|
if (this.mSelectedDayCol) {
|
|
this.mSelectedDayCol.column.selected = false;
|
|
this.mSelectedDayCol.header.removeAttribute("selected");
|
|
}
|
|
|
|
if (val) {
|
|
this.mSelectedDayCol = this.findColumnForDate(val);
|
|
if (this.mSelectedDayCol) {
|
|
this.mSelectedDayCol.column.selected = true;
|
|
this.mSelectedDayCol.header.setAttribute("selected", "true");
|
|
} else {
|
|
dump ("XX couldn't find column to select for day: " + val + "\n");
|
|
return null;
|
|
}
|
|
}
|
|
this.fireEvent("dayselect", val);
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="selectedItem">
|
|
<getter><![CDATA[
|
|
return this.mSelectedItem;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (this.mSelectedItem != val) {
|
|
if (this.mSelectedItem) {
|
|
var cols = this.findColumnsForItem(this.mSelectedItem);
|
|
for each (col in cols) {
|
|
col.header.selectOccurrence(null);
|
|
col.column.selectOccurrence(null);
|
|
}
|
|
}
|
|
|
|
if (val) {
|
|
var cols = this.findColumnsForItem(val);
|
|
if (cols.length > 0) {
|
|
var start = val.startDate || val.entryDate;
|
|
for each (col in cols) {
|
|
if(start.isDate) {
|
|
col.header.selectOccurrence(val);
|
|
}
|
|
else {
|
|
col.column.selectOccurrence(val);
|
|
}
|
|
}
|
|
if(this.mController.selectionManager)
|
|
this.mController.selectionManager.replaceSelection(val);
|
|
} else {
|
|
val = null;
|
|
}
|
|
}
|
|
|
|
this.mSelectedItem = val;
|
|
}
|
|
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="pixelsPerMinute">
|
|
<getter>return this.mPixPerMin</getter>
|
|
<setter>this.setPixelsPerMin(val); return val;</setter>
|
|
</property>
|
|
|
|
<property name="activeInPlaceEdit">
|
|
<getter><![CDATA[
|
|
return this.mInPlaceEditActive;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mInPlaceEditActive = val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<!-- private -->
|
|
|
|
<property name="numVisibleDates" readonly="true">
|
|
<getter><![CDATA[
|
|
if (this.mDateList)
|
|
return this.mDateList.length;
|
|
|
|
var count = 0;
|
|
|
|
var d = this.mStartDate.clone();
|
|
while (d.compare(this.mEndDate) <= 0) {
|
|
count++;
|
|
d.day += 1;
|
|
d.normalize();
|
|
}
|
|
|
|
return count;
|
|
]]></getter>
|
|
</property>
|
|
|
|
<property name="orient">
|
|
<getter>return (this.getAttribute("orient") || "vertical");</getter>
|
|
<setter>this.setAttribute("orient", val); return val;</setter>
|
|
</property>
|
|
|
|
<method name="setStartEndMinutes">
|
|
<parameter name="aStartMin"/>
|
|
<parameter name="aEndMin"/>
|
|
<body><![CDATA[
|
|
if (aStartMin < 0 || aStartMin > aEndMin)
|
|
throw Components.results.NS_ERROR_INVALID_ARG;
|
|
if (aEndMin < 0 || aEndMin > 24*60)
|
|
throw Components.results.NS_ERROR_INVALID_ARG;
|
|
|
|
if (this.mDefaultStartMin != aStartMin ||
|
|
this.mDefaultEndMin != aEndMin ||
|
|
this.mStartMin != aStartMin || this.mEndMin != aEndMin)
|
|
{
|
|
this.mDefaultStartMin = aStartMin;
|
|
this.mDefaultEndMin = aEndMin;
|
|
this.mStartMin = aStartMin;
|
|
this.mEndMin = aEndMin;
|
|
|
|
this.refresh();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setAttribute">
|
|
<parameter name="aAttr"/>
|
|
<parameter name="aVal"/>
|
|
<body><![CDATA[
|
|
var needsreorient = false;
|
|
var needsrelayout = false;
|
|
if (aAttr == "orient") {
|
|
if (this.getAttribute("orient") != aVal)
|
|
needsreorient = true;
|
|
}
|
|
|
|
if (aAttr == "context" || aAttr == "item-context")
|
|
needsrelayout = true;
|
|
|
|
// this should be done using lookupMethod(), see bug 286629
|
|
var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
|
|
|
|
if (needsrelayout && !needsreorient)
|
|
this.relayout();
|
|
|
|
if (needsreorient)
|
|
this.reorient();
|
|
|
|
return ret;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="reorient">
|
|
<body><![CDATA[
|
|
var orient = this.getAttribute("orient");
|
|
var otherorient = "vertical";
|
|
if (!orient) orient = "horizontal";
|
|
if (orient == "vertical") otherorient = "horizontal";
|
|
|
|
if (orient == "horizontal")
|
|
this.setPixelsPerMin(1.5);
|
|
else
|
|
this.setPixelsPerMin(0.6);
|
|
|
|
var normalelems = ['mainbox', 'timebar'];
|
|
var otherelems = ['labelbox', 'labeldaybox', 'headertimespacer',
|
|
'headerbox', 'headerdaybox', 'childbox', 'daybox'];
|
|
|
|
for each (var id in normalelems) {
|
|
document.getAnonymousElementByAttribute(this, "anonid", id).setAttribute("orient", orient);
|
|
}
|
|
|
|
for each (var id in otherelems) {
|
|
document.getAnonymousElementByAttribute(this, "anonid", id).setAttribute("orient", otherorient);
|
|
}
|
|
|
|
var labelbox = document.getAnonymousElementByAttribute(this, "anonid", "labelbox");
|
|
var labeltimespacer = document.getAnonymousElementByAttribute(this, "anonid", "labeltimespacer");
|
|
var headerbox = document.getAnonymousElementByAttribute(this, "anonid", "headerbox");
|
|
var headertimespacer = document.getAnonymousElementByAttribute(this, "anonid", "headertimespacer");
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
var childbox = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "childbox");
|
|
var mainbox = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "mainbox");
|
|
|
|
if (orient == "vertical") {
|
|
headerbox.setAttribute("height", 50);
|
|
headerbox.removeAttribute("width");
|
|
|
|
labelbox.setAttribute("height", 30);
|
|
labelbox.removeAttribute("width");
|
|
|
|
var timebarWidth = 100;
|
|
|
|
timebar.setAttribute("width", timebarWidth);
|
|
timebar.removeAttribute("height");
|
|
headertimespacer.setAttribute("width", timebarWidth);
|
|
headertimespacer.removeAttribute("height");
|
|
labeltimespacer.setAttribute("width", timebarWidth);
|
|
labeltimespacer.removeAttribute("height");
|
|
|
|
childbox.setAttribute(
|
|
"style", "overflow-x: hidden; overflow-y: auto;");
|
|
mainbox.setAttribute(
|
|
"style", "overflow-x: auto; overflow-y: hidden;");
|
|
} else {
|
|
headerbox.setAttribute("width", 30);
|
|
headerbox.removeAttribute("height");
|
|
|
|
labelbox.setAttribute("width", 30);
|
|
labelbox.removeAttribute("height");
|
|
|
|
var timebarHeight = 40;
|
|
timebar.setAttribute("height", timebarHeight);
|
|
timebar.removeAttribute("width");
|
|
headertimespacer.setAttribute("height", timebarHeight);
|
|
headertimespacer.removeAttribute("width");
|
|
labeltimespacer.setAttribute("height", timebarHeight);
|
|
labeltimespacer.removeAttribute("width");
|
|
|
|
childbox.setAttribute(
|
|
"style", "overflow-x: auto; overflow-y: hidden;");
|
|
mainbox.setAttribute(
|
|
"style", "overflow-x: hidden; overflow-y: auto;");
|
|
}
|
|
|
|
var boxes = ["daybox", "headerdaybox", "labeldaybox"];
|
|
for each (var boxname in boxes) {
|
|
var box = document.getAnonymousElementByAttribute(this, "anonid", boxname);
|
|
var child = box.firstChild;
|
|
while (child) {
|
|
child.setAttribute("orient", orient);
|
|
child = child.nextSibling;
|
|
}
|
|
}
|
|
this.refresh();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="refresh">
|
|
<body><![CDATA[
|
|
if (!this.startDate || !this.endDate)
|
|
return;
|
|
|
|
// we're throwing out all the events and starting over, we
|
|
// should reset to the default size
|
|
this.mStartMin = this.mDefaultStartMin;
|
|
this.mEndMin = this.mDefaultEndMin;
|
|
|
|
// recreate our columns
|
|
this.relayout();
|
|
|
|
if (!this.mCalendar)
|
|
return;
|
|
|
|
// start our items query; for a disjoint date range
|
|
// we get all the items, and just filter out the ones we don't
|
|
// care about in addItem
|
|
|
|
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);
|
|
}
|
|
|
|
var orient = this.getAttribute("orient");
|
|
var otherorient = "vertical";
|
|
if (!orient) orient = "horizontal";
|
|
if (orient == "vertical") otherorient = "horizontal";
|
|
|
|
var computedDateList;
|
|
if (this.mDateList) {
|
|
computedDateList = this.mDateList;
|
|
} else if (this.mStartDate && this.mEndDate) {
|
|
computedDateList = new Array();
|
|
|
|
var theDate = this.mStartDate.clone();
|
|
while (theDate.compare(this.mEndDate) <= 0) {
|
|
computedDateList.push(theDate.clone());
|
|
theDate.day += 1;
|
|
theDate.normalize();
|
|
}
|
|
}
|
|
|
|
var daybox = document.getAnonymousElementByAttribute(this, "anonid", "daybox");
|
|
var headerdaybox = document.getAnonymousElementByAttribute(this, "anonid", "headerdaybox");
|
|
var labeldaybox = document.getAnonymousElementByAttribute(this, "anonid", "labeldaybox");
|
|
|
|
if (!computedDateList || computedDateList.length == 0)
|
|
return;
|
|
|
|
// update timebar
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.setStartEndMinutes(this.mStartMin, this.mEndMin);
|
|
|
|
var calView = this;
|
|
function setUpDayEventsBox(aDayBox) {
|
|
aDayBox.setAttribute("class", "calendar-event-column-" + (counter % 2 == 0 ? "even" : "odd"));
|
|
aDayBox.setAttribute("context", calView.getAttribute("context"));
|
|
aDayBox.setAttribute("item-context", calView.getAttribute("item-context") || calView.getAttribute("context"));
|
|
aDayBox.startLayoutBatchChange();
|
|
aDayBox.date = d;
|
|
aDayBox.setStartEndMinutes(calView.mStartMin, calView.mEndMin);
|
|
aDayBox.setAttribute("orient", orient);
|
|
aDayBox.calendarView = calView;
|
|
}
|
|
|
|
function setUpDayHeaderBox(aDayBox) {
|
|
aDayBox.date = d;
|
|
aDayBox.calendarView = calView;
|
|
aDayBox.setAttribute("orient", orient);
|
|
}
|
|
|
|
this.mDateColumns = new Array();
|
|
|
|
// get today's date
|
|
var today = this.today();
|
|
var counter = 0;
|
|
var dayboxkids = daybox.childNodes;
|
|
var headerboxkids = headerdaybox.childNodes;
|
|
var labelboxkids = labeldaybox.childNodes;
|
|
|
|
for each (var d in computedDateList) {
|
|
var dayEventsBox;
|
|
if (counter < dayboxkids.length) {
|
|
dayEventsBox = dayboxkids[counter];
|
|
dayEventsBox.removeAttribute("today");
|
|
dayEventsBox.mEvents = new Array();
|
|
} else {
|
|
dayEventsBox = createXULElement("calendar-event-column");
|
|
dayEventsBox.setAttribute("flex", "1");
|
|
daybox.appendChild(dayEventsBox);
|
|
}
|
|
setUpDayEventsBox(dayEventsBox);
|
|
|
|
var dayHeaderBox;
|
|
if (counter < headerboxkids.length) {
|
|
dayHeaderBox = headerboxkids[counter];
|
|
dayHeaderBox.removeAttribute("today");
|
|
// Delete backwards to make sure we get them all
|
|
for (var i = dayHeaderBox.mItemBoxes.length-1; i >= 0; i--) {
|
|
dayHeaderBox.deleteEvent(dayHeaderBox.mItemBoxes[i].occurrence);
|
|
}
|
|
} else {
|
|
dayHeaderBox = createXULElement("calendar-header-container");
|
|
dayHeaderBox.setAttribute("flex", "1");
|
|
headerdaybox.appendChild(dayHeaderBox);
|
|
}
|
|
setUpDayHeaderBox(dayHeaderBox);
|
|
|
|
function matchesDayOff(dayOffNum) { return dayOffNum == d.weekday; }
|
|
if (this.mDaysOffArray.some(matchesDayOff)) {
|
|
dayEventsBox.setAttribute("weekend", "true");
|
|
dayHeaderBox.setAttribute("weekend", "true");
|
|
} else {
|
|
dayEventsBox.removeAttribute("weekend");
|
|
dayHeaderBox.removeAttribute("weekend");
|
|
}
|
|
|
|
// highlight today
|
|
if (this.numVisibleDates > 1 && d.compare(today) == 0) {
|
|
dayEventsBox.setAttribute("today", "true");
|
|
dayHeaderBox.setAttribute("today", "true");
|
|
}
|
|
|
|
var labelbox;
|
|
|
|
if (counter < labelboxkids.length) {
|
|
labelbox = labelboxkids[counter];
|
|
labelbox.firstChild.setAttribute("value", (d.month + 1) + "/" + d.day);
|
|
labelbox.childNodes[1].setAttribute("value", calGetString("dateFormat", "day."+ (d.weekday+1)+ ".Mmm"));
|
|
} else {
|
|
labelbox = createXULElement("box");
|
|
labelbox.setAttribute("flex", "1");
|
|
labelbox.setAttribute("class", "calendar-day-label-box");
|
|
|
|
var label = createXULElement("label");
|
|
label.setAttribute("value", (d.month + 1) + "/" + d.day);
|
|
label.setAttribute("class", "calendar-day-label-date");
|
|
labelbox.appendChild(label);
|
|
labeldaybox.appendChild(labelbox);
|
|
|
|
label = createXULElement("label");
|
|
label.setAttribute("value", calGetString("dateFormat", "day."+ (d.weekday+1)+ ".Mmm"));
|
|
label.setAttribute("class", "calendar-day-label-name");
|
|
labelbox.appendChild(label);
|
|
|
|
labeldaybox.appendChild(labelbox);
|
|
}
|
|
labelbox.setAttribute("orient", orient);
|
|
|
|
// We don't want to actually mess with our original dates, plus
|
|
// they're likely to be immutable.
|
|
var d2 = d.clone();
|
|
d2.isDate = true;
|
|
d2.makeImmutable();
|
|
this.mDateColumns.push ( { date: d2, column: dayEventsBox, header: dayHeaderBox } );
|
|
counter++;
|
|
}
|
|
|
|
// Remove any extra columns that may have been hanging around
|
|
function removeExtraKids(elem) {
|
|
while (counter < elem.childNodes.length) {
|
|
elem.removeChild(elem.childNodes[counter]);
|
|
}
|
|
}
|
|
removeExtraKids(daybox);
|
|
removeExtraKids(headerdaybox);
|
|
removeExtraKids(labeldaybox);
|
|
|
|
// fix pixels-per-minute
|
|
this.onResize();
|
|
for each (col in this.mDateColumns) {
|
|
col.column.endLayoutBatchChange();
|
|
}
|
|
|
|
// adjust scrollbar spacers
|
|
// XXX For performance reasons this method call can be moved to the
|
|
// XXX constructor and the reorient method as soon as the views
|
|
// XXX are constructed statically (24 hrs).
|
|
this.adjustScrollBarSpacers();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="propagateStartEndMinutes">
|
|
<body><![CDATA[
|
|
// update timebar
|
|
var timebar = document.getAnonymousElementByAttribute(this,
|
|
"anonid",
|
|
"timebar");
|
|
timebar.setStartEndMinutes(this.mStartMin, this.mEndMin);
|
|
|
|
// fix pixels-per-minute
|
|
this.onResize();
|
|
|
|
for each (var d in this.mDateColumns) {
|
|
// each of these calls will cause the column in question
|
|
// to call relayout on itself
|
|
d.column.setStartEndMinutes(this.mStartMin, this.mEndMin);
|
|
}
|
|
|
|
return;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="findColumnForDate">
|
|
<parameter name="aDate"/>
|
|
<body><![CDATA[
|
|
for each (var col in this.mDateColumns) {
|
|
if (col.date.compare(aDate) == 0)
|
|
return col;
|
|
}
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
|
|
<method name="findColumnsForItem">
|
|
<parameter name="aItem"/>
|
|
<body><![CDATA[
|
|
var columns = new Array();
|
|
|
|
var tz = this.mDateColumns[0].date.timezone;
|
|
|
|
// Note that these may be dates or datetimes
|
|
var startDate = aItem.startDate || aItem.entryDate;
|
|
var targetDate = startDate.getInTimezone(tz);
|
|
var endDate = aItem.endDate || aItem.dueDate;
|
|
var finishDate = endDate.getInTimezone(tz);
|
|
|
|
if (!targetDate.isDate) {
|
|
// Set the time to 00:00 so that we 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 col = this.findColumnForDate(targetDate);
|
|
if (col) {
|
|
columns.push(col);
|
|
}
|
|
}
|
|
|
|
while (targetDate.compare(finishDate) == -1) {
|
|
var col = this.findColumnForDate(targetDate);
|
|
|
|
// This might not exist if the event spans the view start or end
|
|
if (col) {
|
|
columns.push(col);
|
|
}
|
|
targetDate.day += 1;
|
|
targetDate.normalize();
|
|
}
|
|
|
|
return columns;
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- for the given client-coord-system point, return
|
|
- the calendar-event-column that contains it. If
|
|
- no column contains it, return null.
|
|
-->
|
|
<method name="findColumnForClientPoint">
|
|
<parameter name="aClientX"/>
|
|
<parameter name="aClientY"/>
|
|
<body><![CDATA[
|
|
for each (var col in this.mDateColumns) {
|
|
var bo = col.column.topbox.boxObject;
|
|
if ((aClientX >= bo.screenX) && (aClientX < (bo.screenX + bo.width)) &&
|
|
(aClientY >= bo.screenY) && (aClientY < (bo.screenY + bo.height)))
|
|
{
|
|
return col.column;
|
|
}
|
|
}
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="doAddEvent">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
//dump ("++ doAddevent\n");
|
|
var cols = this.findColumnsForItem(aEvent);
|
|
if (!cols.length)
|
|
return;
|
|
|
|
for each (col in cols) {
|
|
var column = col.column;
|
|
var header = col.header;
|
|
|
|
var estart = aEvent.startDate || aEvent.entryDate;
|
|
if (estart.isDate) {
|
|
header.addEvent(aEvent);
|
|
} else {
|
|
column.addEvent(aEvent);
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="doDeleteEvent">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
var cols = this.findColumnsForItem(aEvent);
|
|
if (!cols.length)
|
|
return;
|
|
|
|
for each (col in cols) {
|
|
var column = col.column;
|
|
var header = col.header;
|
|
|
|
var estart = aEvent.startDate || aEvent.entryDate;
|
|
if (estart.isDate) {
|
|
header.deleteEvent(aEvent);
|
|
} else {
|
|
column.deleteEvent(aEvent);
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setPixelsPerMin">
|
|
<parameter name="pixPerMin"/>
|
|
<body><![CDATA[
|
|
this.mPixPerMin = pixPerMin;
|
|
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.pixelsPerMinute = pixPerMin;
|
|
|
|
for each (var col in this.mDateColumns) {
|
|
col.column.pixelsPerMinute = pixPerMin;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="readjustStartEndMinutes">
|
|
<parameter name="aChunks"/>
|
|
<parameter name="aIsAllChunks"/>
|
|
<body><![CDATA[
|
|
var hasChanged = false;
|
|
const MINS_PER_HOUR = 60;
|
|
const DAY_START_MIN = 0 * MINS_PER_HOUR;
|
|
const DAY_END_MIN = 24 * MINS_PER_HOUR;
|
|
|
|
// Start at the existing boundaries if aIsAllChunks is set,
|
|
// since in that case, we know that the boundaries will only
|
|
// be expanding. Otherwise, start at the defaults.
|
|
var earliestChunkStartMin =
|
|
(aIsAllChunks ? this.mDefaultStartMin : this.mStartMin);
|
|
var latestChunkEndMin =
|
|
(aIsAllChunks ? this.mDefaultEndMin : this.mEndMin);
|
|
|
|
for each (var chunk in aChunks) {
|
|
|
|
if (chunk.startMinute < earliestChunkStartMin) {
|
|
earliestChunkStartMin = chunk.startMinute;
|
|
}
|
|
|
|
if (chunk.endMinute > latestChunkEndMin) {
|
|
latestChunkEndMin = chunk.endMinute;
|
|
}
|
|
|
|
// break, if the start and end time match the day range
|
|
if (earliestChunkStartMin <= DAY_START_MIN
|
|
&& latestChunkEndMin >= DAY_END_MIN) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// round down
|
|
if (earliestChunkStartMin % MINS_PER_HOUR) {
|
|
earliestChunkStartMin -= (earliestChunkStartMin % MINS_PER_HOUR);
|
|
}
|
|
|
|
// round up
|
|
if (latestChunkEndMin % MINS_PER_HOUR) {
|
|
latestChunkEndMin += MINS_PER_HOUR -
|
|
(latestChunkEndMin % MINS_PER_HOUR);
|
|
}
|
|
|
|
// The only reason try/catch is necessary here is to work
|
|
// around XBL lossage. Without it, control never makes it
|
|
// through the try block, even though no exception is ever thrown!
|
|
try {
|
|
if (aIsAllChunks) {
|
|
// Since we've looked at all the in-view chunks,
|
|
// it's safe to either shrink or expand the time shown.
|
|
// Shrinking might be necessary when switching
|
|
// weeks, or if an event has been deleted, moved or
|
|
// shrunk out of the "non-default" area.
|
|
if (this.mStartMin != earliestChunkStartMin) {
|
|
this.mStartMin = earliestChunkStartMin;
|
|
hasChanged = true;
|
|
}
|
|
if (this.mEndMin != latestChunkEndMin) {
|
|
this.mEndMin = latestChunkEndMin;
|
|
hasChanged = true;
|
|
}
|
|
} else {
|
|
// here we are doing incremental adjustment, so we
|
|
// can only expand the time shown, never shrink it
|
|
if (this.mStartMin > earliestChunkStartMin) {
|
|
this.mStartMin = earliestChunkStartMin;
|
|
hasChanged = true;
|
|
}
|
|
if (this.mEndMin < latestChunkEndMin) {
|
|
this.mEndMin = latestChunkEndMin;
|
|
hasChanged = true;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Components.utils.reportError("Unexpected exception in "
|
|
+ "readjustStartEndMinutes: " + e);
|
|
throw e;
|
|
}
|
|
return hasChanged;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="createChunksForNonDateItems">
|
|
<parameter name="aItems"/>
|
|
<body><![CDATA[
|
|
|
|
var chunks = new Array();
|
|
for each (var item in aItems) {
|
|
var startDate = item.startDate || item.entryDate;
|
|
if (startDate.isDate) {
|
|
continue;
|
|
}
|
|
|
|
var cols = this.findColumnsForItem(item);
|
|
if (!cols.length) {
|
|
continue;
|
|
}
|
|
|
|
// add a chunk for each date that has one
|
|
for each (var col in cols) {
|
|
chunks.push(col.column.createChunk(item));
|
|
}
|
|
}
|
|
|
|
return chunks;
|
|
]]></body>
|
|
</method>
|
|
|
|
|
|
<method name="getAllChunks">
|
|
<body><![CDATA[
|
|
var chunks = new Array();
|
|
for each (var col in this.mDateColumns) {
|
|
for each (var chunk in col.column.mEvents) {
|
|
chunks.push(chunk);
|
|
}
|
|
}
|
|
return chunks;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="readjust">
|
|
<body><![CDATA[
|
|
if (this.readjustStartEndMinutes(this.getAllChunks(), true)) {
|
|
var selectedDay = this.selectedDay;
|
|
this.propagateStartEndMinutes();
|
|
if (selectedDay) {
|
|
this.selectedDay = selectedDay;
|
|
}
|
|
}
|
|
]]></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>
|
|
|
|
<method name="adjustScrollBarSpacers">
|
|
<body><![CDATA[
|
|
// get the view's orientation
|
|
var propertyName;
|
|
if (this.getAttribute("orient") == "vertical") {
|
|
propertyName = "width";
|
|
} else {
|
|
propertyName = "height";
|
|
}
|
|
|
|
// get the width/height of the childbox scrollbar
|
|
var childbox = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "childbox");
|
|
var propertyValue = childbox.boxObject.firstChild
|
|
.boxObject[propertyName];
|
|
|
|
// set the same width/height for the label and header box spacers
|
|
var headerScrollBarSpacer = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "headerscrollbarspacer");
|
|
headerScrollBarSpacer.setAttribute(propertyName, propertyValue);
|
|
var labelScrollBarSpacer = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "labelscrollbarspacer");
|
|
labelScrollBarSpacer.setAttribute(propertyName, propertyValue);
|
|
]]></body>
|
|
</method>
|
|
</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 occurrence = (event.ctrlKey) ? this.selectedItem.parentItem : this.selectedItem;
|
|
this.controller.deleteOccurrence(occurrence);
|
|
}
|
|
}
|
|
]]></handler>
|
|
|
|
<handler event="DOMAttrModified"><![CDATA[
|
|
// when a scrollbar appears/disappears in the view,
|
|
// it's disabled attribute is modified
|
|
if (event.attrName != "disabled" ||
|
|
event.originalTarget.localName != "scrollbar" ||
|
|
event.originalTarget.parentNode.getAttribute("anonid") !=
|
|
"childbox") {
|
|
return;
|
|
}
|
|
|
|
// adjust scrollbar spacers
|
|
this.adjustScrollBarSpacers();
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
</bindings>
|
|
|
|
<!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
|