mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-04 16:15:25 +00:00
2216 lines
79 KiB
XML
2216 lines
79 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>
|
|
-
|
|
- 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-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 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 {
|
|
box = makeTimeBox(String(theHour) + ":00", 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.preventBubble();
|
|
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 preventBubble())
|
|
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>
|
|
|
|
<!-- properties -->
|
|
<property name="pixelsPerMinute">
|
|
<getter><![CDATA[
|
|
return this.mPixPerMin;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (val <= 0.0)
|
|
throw "Bad pixpermin";
|
|
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;
|
|
this.recalculateStartEndMinutes();
|
|
}
|
|
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
onget="return this.mDate;"
|
|
onset="return (this.mDate = val);" />
|
|
<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) {
|
|
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.id == aOccurrence.id &&
|
|
chunk.event.startDate.compare(aOccurrence.startDate) == 0)
|
|
{
|
|
return chunk;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setAttribute">
|
|
<parameter name="aAttr"/>
|
|
<parameter name="aVal"/>
|
|
<body><![CDATA[
|
|
var needsrelayout = false;
|
|
if (aAttr == "orient") {
|
|
if (this.getAttribute("orient") != aVal)
|
|
needsrelayout = 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) {
|
|
this.relayout();
|
|
}
|
|
|
|
return ret;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="internalDeleteEvent">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
var itemIndex = -1;
|
|
var i;
|
|
for (i = 0; i < this.mEvents.length; i++) {
|
|
occ = this.mEvents[i].event;
|
|
if (occ.id == aOccurrence.id &&
|
|
occ.startDate.compare(aOccurrence.startDate) == 0)
|
|
{
|
|
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>
|
|
|
|
<method name="getStartEndMinutesForOccurrence">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
var stdate = aOccurrence.startDate;
|
|
var enddate = aOccurrence.endDate;
|
|
|
|
if (stdate.timezone != this.mTimezone)
|
|
stdate = stdate.getInTimezone (this.mTimezone);
|
|
|
|
if (enddate.timezone != this.mTimezone)
|
|
enddate = enddate.getInTimezone (this.mTimezone);
|
|
|
|
return { start: stdate.hour * 60 + stdate.minute,
|
|
end: enddate.hour * 60 + enddate.minute };
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="addEvent">
|
|
<parameter name="aOccurrence"/>
|
|
<body><![CDATA[
|
|
this.internalDeleteEvent(aOccurrence);
|
|
|
|
var mins = this.getStartEndMinutesForOccurrence(aOccurrence);
|
|
|
|
var chunk = {
|
|
startMinute: mins.start,
|
|
endMinute: mins.end,
|
|
event: aOccurrence
|
|
};
|
|
this.mEvents.push(chunk);
|
|
this.relayout();
|
|
]]></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.lastChild)
|
|
this.bgbox.removeChild(this.bgbox.lastChild);
|
|
while (this.topbox.lastChild)
|
|
this.topbox.removeChild(this.topbox.lastChild);
|
|
]]></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.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);
|
|
// chunkBox.setAttribute("style", "background: #eeeeff; padding: 0px 1px 0px 1px");
|
|
|
|
// chunkBox.setAttribute("flex", "1");
|
|
|
|
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 (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.startDate.isDate)
|
|
// continue;
|
|
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// XXX ok, yeah, this stuff needs some JS helper love
|
|
var estart = col.mDate.clone();
|
|
estart.isDate = false;
|
|
estart.hour = 0;
|
|
estart.minute = dragState.startMin + col.mStartMin;
|
|
estart.normalize();
|
|
|
|
var eend = col.mDate.clone();
|
|
eend.isDate = false;
|
|
eend.hour = 0;
|
|
eend.minute = dragState.endMin + col.mStartMin;
|
|
eend.normalize();
|
|
|
|
if (dragState.dragType == "new") {
|
|
col.calendarView.controller.createNewEvent(col.calendarView.displayCalendar,
|
|
estart,
|
|
eend);
|
|
} else if (dragState.dragType == "move" ||
|
|
dragState.dragType == "modify-start" ||
|
|
dragState.dragType == "modify-end")
|
|
{
|
|
col.calendarView.controller.modifyOccurrence(dragState.dragOccurrence, estart, eend);
|
|
}
|
|
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;
|
|
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");
|
|
|
|
// cheat and call the sweep move event handler;
|
|
// this will take care of setting up the sizes right for everything
|
|
document.calendarEventColumnDragging = this;
|
|
//this.onEventSweepMouseMove(event);
|
|
|
|
//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;
|
|
|
|
this.fgboxes.startlabel.setAttribute("value", starthr + ":" + (startmin < 10 ? "0" : "") + startmin);
|
|
this.fgboxes.endlabel.setAttribute("value", endhr + ":" + (endmin < 10 ? "0" : "") + endmin);
|
|
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="dblclick"><![CDATA[
|
|
if (this.calendarView.controller) {
|
|
this.calendarView.controller.createNewEvent (this.mCalendarView.displayCalendar,
|
|
this.mDate);
|
|
}
|
|
]]></handler>
|
|
|
|
<!-- mouse down handler, in empty event column regions. Starts sweeping out a new
|
|
- event.
|
|
-->
|
|
<handler event="mousedown" button="0"><![CDATA[
|
|
// select this column
|
|
this.calendarView.selectedDay = this.mDate;
|
|
|
|
// 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);
|
|
}
|
|
|
|
this.fgboxes.box.setAttribute("dragging", "true");
|
|
this.fgboxes.dragbox.setAttribute("dragging", "true");
|
|
|
|
// cheat and call the sweep move event handler;
|
|
// this will take care of setting up the sizes right for everything
|
|
document.calendarEventColumnDragging = this;
|
|
this.onEventSweepMouseMove(event);
|
|
|
|
window.addEventListener("mousemove", this.onEventSweepMouseMove, false);
|
|
window.addEventListener("mouseup", this.onEventSweepMouseUp, false);
|
|
]]></handler>
|
|
|
|
|
|
</handlers>
|
|
</binding>
|
|
|
|
<!--
|
|
- An individual event box, to be inserted into a column.
|
|
-->
|
|
<binding id="calendar-event-box">
|
|
|
|
<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:textbox class="plain" style="background: transparent !important"
|
|
anonid="event-name" crop="right" readonly="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.eventNameElement.onblur = function() { self.stopEditing(); };
|
|
]]></constructor>
|
|
|
|
<!-- fields -->
|
|
<field name="mOccurrence">null</field>
|
|
<field name="mParentColumn">null</field>
|
|
<field name="mCalendarView">null</field>
|
|
<property name="calendarView"
|
|
onget="return this.mCalendarView;"
|
|
onset="return (this.mCalendarView = val);" />
|
|
|
|
<!-- 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-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="eventNameElement" readonly="true"
|
|
onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'event-name').inputField;"/>
|
|
|
|
<property name="occurrence">
|
|
<getter><![CDATA[
|
|
return this.mOccurrence;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
this.mOccurrence = val;
|
|
var evl = this.eventNameElement;
|
|
|
|
if (val) {
|
|
evl.value = val.title;
|
|
} else {
|
|
evl.value = "Untitled Event";
|
|
}
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="selected">
|
|
<getter><![CDATA[
|
|
return this.mSelected;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (val && !this.mSelected) {
|
|
this.mSelected = true;
|
|
this.setAttribute("selected", "true");
|
|
} else if (!val && this.mSelected) {
|
|
this.mSelected = null;
|
|
this.removeAttribute("selected");
|
|
}
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="parentColumn"
|
|
onget="return this.mParentColumn;"
|
|
onset="return (this.mParentColumn = val);"/>
|
|
|
|
<property name="startMinute" readonly="true"
|
|
onget="if (!this.mOccurrence) return 0; return this.mOccurrence.startDate.hour * 60 + this.mOccurrence.startDate.minute"/>
|
|
|
|
<property name="endMinute" readonly="true"
|
|
onget="if (!this.mOccurrence) return 0; return this.mOccurrence.endDate.hour * 60 + this.mOccurrence.endDate.minute"/>
|
|
|
|
<method name="startEditing">
|
|
<body><![CDATA[
|
|
this.editingTimer = null;
|
|
this.eventNameElement.removeAttribute("readonly");
|
|
this.mOriginalTextLabel = this.eventNameElement.value;
|
|
this.mEditing = true;
|
|
this.eventNameElement.focus();
|
|
this.eventNameElement.select();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="stopEditing">
|
|
<body><![CDATA[
|
|
this.mEditing = false;
|
|
if (this.eventNameElement.value != this.mOriginalTextLabel) {
|
|
var clone = this.mOccurrence.clone();
|
|
clone.title = this.eventNameElement.value;
|
|
clone.calendar.modifyItem(clone, this.mOccurrence, null);
|
|
|
|
// Note that as soon as we do the modifyItem, this element ceases to exist,
|
|
// so don't bother trying to modify anything further here! ('this' exists,
|
|
// because it's being kept alive, but our child content etc. is all gone)
|
|
}
|
|
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="click"><![CDATA[
|
|
var evTime = Date.now();
|
|
if (this.mMouseDownTime && (evTime - this.mMouseDownTime > 1000)) {
|
|
// not even a click!
|
|
return;
|
|
}
|
|
|
|
if (this.mLastClickTime && (evTime - this.mLastClickTime < 350)) {
|
|
// double click
|
|
if (this.editingTimer) {
|
|
clearTimeout(this.editingTimer);
|
|
this.editingTimer = null;
|
|
}
|
|
|
|
if (this.calendarView.controller && this.mOccurrence) {
|
|
var occurrence = (event.ctrlKey) ? this.mOccurrence.parentItem : this.mOccurrence;
|
|
this.calendarView.controller.modifyOccurrence(occurrence);
|
|
}
|
|
} else {
|
|
// start single click timeout
|
|
this.calendarView.selectedItem = this.mOccurrence;
|
|
var self = this;
|
|
if (this.editingTimer) clearTimeout(this.editingTimer);
|
|
this.editingTimer = setTimeout(function () { self.startEditing(); }, 350);
|
|
this.mLastClickTime = evTime;
|
|
}
|
|
]]></handler>
|
|
|
|
<handler event="mousedown"><![CDATA[
|
|
event.preventBubble();
|
|
|
|
if (this.mEditing)
|
|
return;
|
|
|
|
this.mInMouseDown = true;
|
|
this.mMouseX = event.screenX;
|
|
this.mMouseY = event.screenY;
|
|
this.mMouseDownTime = Date.now();
|
|
]]></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;
|
|
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>
|
|
</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;" label="R"
|
|
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" flex="1" equalsize="always" />
|
|
</xul:box>
|
|
<xul:box anonid="headerbox">
|
|
<xul:box anonid="headertimespacer"/>
|
|
<xul:box anonid="headerdaybox" flex="1" equalsize="always" />
|
|
</xul:box>
|
|
<xul:box 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" flex="1" equalsize="always" />
|
|
</xul:box>
|
|
</xul:box>
|
|
</content>
|
|
|
|
<implementation implements="calICalendarView">
|
|
|
|
<field name="mResizeHandler">null</field>
|
|
<constructor><![CDATA[
|
|
var self = this;
|
|
this.mResizeHandler = function() { self.onResize(); };
|
|
window.addEventListener("resize", this.mResizeHandler, true);
|
|
this.reorient();
|
|
]]></constructor>
|
|
|
|
<field name="mTimeoutScheduled">false</field>
|
|
<field name="mLastSize">0</field>
|
|
<method name="onResize">
|
|
<parameter name="aRealSelf"/>
|
|
<body><![CDATA[
|
|
var self = this;
|
|
if (aRealSelf) {
|
|
self = aRealSelf;
|
|
self.mTimeoutScheduled = false;
|
|
}
|
|
|
|
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.mTimeoutScheduled)
|
|
setTimeout(self.onResize, 0, self);
|
|
return;
|
|
}
|
|
|
|
self.mLastSize = size;
|
|
|
|
var minutes = self.mEndMin - self.mStartMin;
|
|
var ppm = size / minutes;
|
|
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="mSelectedItem">null</field>
|
|
<field name="mSelectedDayCol">null</field>
|
|
|
|
<field name="mStartMin">8*60</field>
|
|
<field name="mEndMin">20*60</field>
|
|
|
|
<field name="mObserver"><![CDATA[
|
|
// the calIObserver, and calICompositeObserver
|
|
({
|
|
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;
|
|
},
|
|
|
|
calView: 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) {
|
|
//dump ("++ AddItem " + aItem + "\n");
|
|
if (!(aItem instanceof Components.interfaces.calIEvent))
|
|
return;
|
|
aItem = aItem.QueryInterface(Components.interfaces.calIEvent);
|
|
|
|
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);
|
|
}
|
|
},
|
|
onModifyItem: function (aNewItem, aOldItem) {
|
|
//dump ("++ ModifyItem\n");
|
|
if (!(aOldItem instanceof Components.interfaces.calIEvent))
|
|
return;
|
|
|
|
if (!(aNewItem instanceof Components.interfaces.calIEvent))
|
|
return;
|
|
|
|
var occs;
|
|
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);
|
|
}
|
|
},
|
|
onDeleteItem: function (aItem) {
|
|
//dump ("++ DeleteItem\n");
|
|
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.doDeleteEvent(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) {
|
|
//dump ("view onCalendarAdded\n");
|
|
this.calView.refresh();
|
|
},
|
|
|
|
onCalendarRemoved: function (aCalendar) {
|
|
//dump ("view onCalendarRemoved\n");
|
|
this.calView.refresh();
|
|
},
|
|
|
|
onDefaultCalendarChanged: function (aNewDefaultCalendar) {
|
|
// don't care, for now
|
|
}
|
|
})
|
|
]]></field>
|
|
|
|
<field name="mOperationListener"><![CDATA[
|
|
({
|
|
calView: this,
|
|
|
|
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDetail) {
|
|
// nothing to do
|
|
//dump ("+++ OnOperationComplete (detail: " + aDetail + ")\n");
|
|
},
|
|
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
|
|
if (!Components.isSuccessCode(aStatus))
|
|
return;
|
|
|
|
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>
|
|
|
|
|
|
<method name="showDate">
|
|
<parameter name="aDate"/>
|
|
<body><![CDATA[
|
|
var targetDate = aDate.clone();
|
|
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 = aDate;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setDateRange">
|
|
<parameter name="aStartDate"/>
|
|
<parameter name="aEndDate"/>
|
|
<body><![CDATA[
|
|
//dump ("setDateRange\n");
|
|
this.mDateList = null;
|
|
this.mStartDate = aStartDate.clone();
|
|
this.mStartDate.makeImmutable();
|
|
this.mEndDate = aEndDate.clone();
|
|
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 (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)
|
|
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;
|
|
}
|
|
}
|
|
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="selectedItem">
|
|
<getter><![CDATA[
|
|
return this.mSelectedItem;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
if (this.mSelectedItem != val) {
|
|
if (this.mSelectedItem) {
|
|
var col = this.findColumnForEvent(this.mSelectedItem);
|
|
if (col) {
|
|
col.column.selectOccurrence(null);
|
|
} else {
|
|
dump ("Thought I had a selected occurrence (id: " + this.mSelectedItem.id + "), but couldn't find a column for it!\n");
|
|
}
|
|
}
|
|
|
|
if (val) {
|
|
var col = this.findColumnForEvent(val);
|
|
if (col)
|
|
col.column.selectOccurrence(val);
|
|
else
|
|
val = null;
|
|
}
|
|
|
|
return (this.mSelectedItem = val);
|
|
}
|
|
]]></setter>
|
|
</property>
|
|
|
|
<property name="pixelsPerMinute">
|
|
<getter>return this.mPixPerMin</getter>
|
|
<setter>this.setPixelsPerMin(val); return 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.mStartMin != aStartMin ||
|
|
this.mEndMin != aEndMin)
|
|
{
|
|
this.mStartMin = aStartMin;
|
|
this.mEndMin = aEndMin;
|
|
|
|
this.relayout();
|
|
}
|
|
]]></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', '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");
|
|
|
|
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");
|
|
} 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");
|
|
}
|
|
|
|
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.relayout();
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="refresh">
|
|
<body><![CDATA[
|
|
if (!this.startDate || !this.endDate)
|
|
return;
|
|
|
|
// recreate our columns as necessary
|
|
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
|
|
|
|
this.mCalendar.getItems(this.mCalendar.ITEM_FILTER_COMPLETED_ALL |
|
|
this.mCalendar.ITEM_FILTER_TYPE_EVENT |
|
|
this.mCalendar.ITEM_FILTER_CLASS_OCCURRENCES,
|
|
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");
|
|
|
|
while (daybox.lastChild)
|
|
daybox.removeChild(daybox.lastChild);
|
|
|
|
while (headerdaybox.lastChild)
|
|
headerdaybox.removeChild(headerdaybox.lastChild);
|
|
|
|
while (labeldaybox.lastChild)
|
|
labeldaybox.removeChild(labeldaybox.lastChild);
|
|
|
|
this.mDateColumns = new Array();
|
|
|
|
if (!computedDateList || computedDateList.length == 0)
|
|
return;
|
|
|
|
// update timebar
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.setStartEndMinutes(this.mStartMin, this.mEndMin);
|
|
|
|
var counter = 0;
|
|
for each (var d in computedDateList) {
|
|
var dayEventsBox = createXULElement("calendar-event-column");
|
|
dayEventsBox.setAttribute("flex", "1");
|
|
dayEventsBox.setAttribute("class", "calendar-event-column-" + (counter % 2 == 0 ? "even" : "odd"));
|
|
dayEventsBox.setAttribute("context", this.getAttribute("context"));
|
|
dayEventsBox.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));
|
|
daybox.appendChild(dayEventsBox);
|
|
|
|
dayEventsBox.setStartEndMinutes(this.mStartMin, this.mEndMin);
|
|
dayEventsBox.setAttribute("orient", orient);
|
|
dayEventsBox.date = d;
|
|
dayEventsBox.calendarView = this;
|
|
|
|
var dayHeaderBox = createXULElement("box");
|
|
dayHeaderBox.setAttribute("class", "calendar-event-column-header");
|
|
dayHeaderBox.setAttribute("flex", "1");
|
|
headerdaybox.appendChild(dayHeaderBox);
|
|
|
|
dayHeaderBox.setAttribute("orient", orient);
|
|
|
|
var labelbox = createXULElement("box");
|
|
labelbox.setAttribute("flex", "1");
|
|
labelbox.setAttribute("orient", orient);
|
|
labelbox.setAttribute("class", "calendar-day-label-box");
|
|
|
|
var label;
|
|
label = createXULElement("label");
|
|
//label.setAttribute("flex", "1");
|
|
label.setAttribute("value", (d.month + 1) + "/" + d.day);
|
|
label.setAttribute("class", "calendar-day-label-date");
|
|
labelbox.appendChild(label);
|
|
|
|
// XXX Localize me, get me out of here
|
|
var dayNames = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ];
|
|
var dayNamesShort = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];
|
|
|
|
label = createXULElement("label");
|
|
//label.setAttribute("flex", "1");
|
|
label.setAttribute("value", dayNamesShort[d.weekday]);
|
|
label.setAttribute("class", "calendar-day-label-name");
|
|
labelbox.appendChild(label);
|
|
|
|
labeldaybox.appendChild(labelbox);
|
|
|
|
d.isDate = true;
|
|
|
|
this.mDateColumns.push ( { date: d, column: dayEventsBox, header: dayHeaderBox } );
|
|
}
|
|
|
|
// fix pixels-per-minute
|
|
this.onResize();
|
|
]]></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="findColumnForEvent">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
var estart = aEvent.startDate;
|
|
var eend = aEvent.endDate;
|
|
|
|
return this.findColumnForDate(estart);
|
|
]]></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 col = this.findColumnForEvent(aEvent);
|
|
if (!col)
|
|
return;
|
|
|
|
var column = col.column;
|
|
var header = col.header;
|
|
|
|
var estart = aEvent.startDate;
|
|
if (estart.isDate) {
|
|
// add it to header
|
|
} else {
|
|
column.addEvent(aEvent);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="doDeleteEvent">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
var col = this.findColumnForEvent(aEvent);
|
|
if (!col)
|
|
return;
|
|
|
|
var column = col.column;
|
|
var header = col.header;
|
|
|
|
var estart = aEvent.startDate;
|
|
if (estart.isDate) {
|
|
// remove from header
|
|
} else {
|
|
column.deleteEvent(aEvent);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="setPixelsPerMin">
|
|
<parameter name="pixPerMin"/>
|
|
<body><![CDATA[
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.pixelsPerMinute = pixPerMin;
|
|
|
|
for each (var col in this.mDateColumns) {
|
|
col.column.pixelsPerMinute = pixPerMin;
|
|
}
|
|
]]></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.selectedItem && this.controller) {
|
|
var occurrence = (event.ctrlKey) ? this.selectedItem.parentItem : this.selectedItem;
|
|
this.controller.deleteOccurrence(occurrence);
|
|
}
|
|
}
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
</bindings>
|
|
|
|
<!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
|