gecko-dev/calendar/prototypes/wcap/sun-calendar-event-dialog-a...

3050 строки
115 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 Sun Microsystems code.
-
- The Initial Developer of the Original Code is Sun Microsystems.
- Portions created by the Initial Developer are Copyright (C) 2006
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Michael Buettner <michael.buettner@sun.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 *****
-->
<!DOCTYPE dialog
[
<!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
<!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
<!ENTITY % dtd3 SYSTEM "chrome://calendar/locale/sun-calendar-event-dialog.dtd" > %dtd3;
<!ENTITY % calendar-event-dialogDTD SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd">
%calendar-event-dialogDTD;
]>
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<!-- ############################################################################# -->
<!-- 'attendees-list'-binding -->
<!-- ############################################################################# -->
<!-- id's are evil, use anonid -->
<binding id="attendees-list">
<content>
<xul:listbox anonid="listbox" seltype="multiple" rows="-1" flex="1">
<xul:listcols>
<xul:listcol/>
<xul:listcol flex="1"/>
</xul:listcols>
<xul:listitem anonid="item" class="addressingWidgetItem" allowevents="true">
<xul:listcell class="addressingWidgetCell" align="center" pack="center">
<xul:image id="attendeeCol1#1" anonid="icon"/>
</xul:listcell>
<xul:listcell class="addressingWidgetCell">
<xul:textbox id="attendeeCol2#1" anonid="input" class="plain textbox-addressingWidget uri-element"
type="autocomplete" flex="1"
searchSessions="addrbook" timeout="300" maxrows="4"
autoFill="true" autoFillAfterMatch="true" forceComplete="true"
minResultsForPopup="1" ignoreBlurWhileSearching="true"
oninput="this.setAttribute('dirty','true');">
<xul:image class="person-icon" onclick="this.parentNode.select();"/>
</xul:textbox>
</xul:listcell>
</xul:listitem>
</xul:listbox>
</content>
<implementation>
<field name="mMaxAttendees">0</field>
<field name="mContentHeight">0</field>
<field name="mRowHeight">0</field>
<field name="mNumColumns">0</field>
<field name="mIOService">null</field>
<field name="mDirectoryServerObserver">null</field>
<field name="mHeaderParser">null</field>
<field name="mPrefs">null</field>
<field name="mPrefBranchInternal">null</field>
<field name="mIsOffline">0</field>
<field name="mLDAPSession">null</field>
<field name="mSessionAdded">0</field>
<field name="mOwnerID">null</field>
<field name="mUserID">null</field>
<field name="mOrganizerID">null</field>
<field name="mIsReadOnly">false</field>
<field name="mIsOrganizer">false</field>
<field name="mPopupOpen">false</field>
<constructor>
<![CDATA[
this.mMaxAttendees = 0;
var self = this;
var load = function loadHandler() { self.onLoad(); };
window.addEventListener("load", load, true);
var unload = function unloadHandler() { self.onUnload(); };
window.addEventListener("unload", unload, true);
var observer = {
observe: function(subject, topic, value) {
// catch the exception and ignore it, so that if LDAP setup
// fails, the entire window doesn't get horked
try {
self.setupAutocomplete();
}
catch (ex) {}
}
}
this.mDirectoryServerObserver = observer;
this.mHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
// First get the preferences service
try {
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
this.mPrefs = prefService.getBranch(null);
this.mPrefBranchInternal = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
}
catch (ex) {}
this.mIOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
this.mIsOffline = this.mIOService.offline;
]]>
</constructor>
<method name="onLoad">
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var template = document.getAnonymousElementByAttribute(this, "anonid", "item");
// we need to enfore several layout constraints which can't be modelled
// with plain xul and css, at least as far as i know.
const kStylesheet = "chrome://calendar/content/sun-calendar-event-dialog.css";
for each(var stylesheet in document.styleSheets) {
if (stylesheet.href == kStylesheet) {
// the height of the text blocks contained in the grid items needs
// to have the same height as the items of the attendee-list.
var height = template.boxObject.height-1;
stylesheet.insertRule(".freebusy-grid { min-height: "+height+"px; }", 0);
break;
}
}
this.onInitialize();
// this trigger the continous update chain, which
// effectively calls this.onModify() on predefined
// time intervals [each second].
var self = this;
var callback = function func() {
setTimeout(callback, 1000);
self.onModify();
}
callback();
]]>
</body>
</method>
<method name="onInitialize">
<body>
<![CDATA[
var args = window.arguments[0];
var organizer = args.organizer;
var attendees = args.attendees;
var calendar = args.calendar;
// set 'mIsReadOnly' if the calendar is read-only
if (calendar && calendar.readOnly)
this.mIsReadOnly = true;
// assume we're the organizer [in case that the calendar
// does not support the concept of identities].
this.mIsOrganizer = true;
try {
this.mUserID = "";
this.mOwnerID = "";
this.mOrganizerID = "";
var provider = calendar.QueryInterface(Components.interfaces.calIWcapCalendar);
this.mOwnerID = provider.ownerId;
this.mUserID = provider.session.userId;
this.mOrganizerID = ((organizer == null || organizer.id == null)
? this.mOwnerID // sensible default
: organizer.id);
// set 'mIsOrganizer' if the current calid originally scheduled this event.
this.mIsOrganizer = false;
if(this.mOwnerID == this.mOrganizerID)
this.mIsOrganizer = true;
}
catch(e) {}
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var template = document.getAnonymousElementByAttribute(this, "anonid", "item");
template.focus();
if(this.mIsReadOnly || !this.mIsOrganizer)
listbox.setAttribute("disabled","true");
// TODO: the organizer should show up in the attendee list, but this information
// should be based on the organizer contained in the appropriate field of calIItemBase.
// This is currently not supported, since we're still missing calendar identities.
if(this.mOrganizerID && this.mOrganizerID != "") {
if(!organizer) {
organizer = this.createAttendee();
organizer.id = this.mOrganizerID;
organizer.role = "CHAIR";
organizer.participationStatus = "ACCEPTED";
} else {
if(!organizer.id)
organizer.id = this.mOrganizerID;
if(!organizer.role)
organizer.role = "CHAIR";
if(!organizer.participationStatus)
organizer.participationStatus = "ACCEPTED";
}
try {
var provider = calendar.QueryInterface(Components.interfaces.calIWcapCalendar);
var organizerCalendar = provider.session.getCalendarByCalId(this.mOrganizerID);
var props = organizerCalendar.getCalendarProperties("X-S1CS-CALPROPS-COMMON-NAME",{});
if(props.length > 0)
organizer.commonName = props[0];
}
catch(e) {}
this.appendAttendee(organizer,listbox,template,true);
}
var numRowsAdded = 0;
if(attendees.length > 0) {
for each(var attendee in attendees) {
this.appendAttendee(attendee,listbox,template,false);
numRowsAdded++;
}
}
if(numRowsAdded == 0) {
this.appendAttendee(null,listbox,template,false);
}
// detach the template item from the listbox, but hold the reference.
// until this function returns we add at least a single copy of this template back again.
listbox.removeChild(template);
this.addDirectoryServerObserver();
this.setFocus(this.mMaxAttendees);
]]>
</body>
</method>
<method name="onUnload">
<body>
<![CDATA[
this.removeDirectoryServerObserver();
this.releaseAutoCompleteState();
this.mIOService = null;
this.mLDAPSession = null;
]]>
</body>
</method>
<!-- appends a new row using an existing attendee structure -->
<method name="appendAttendee">
<parameter name="aAttendee"/>
<parameter name="aParentNode"/>
<parameter name="aTemplateNode"/>
<parameter name="aDisableIfOrganizer"/>
<body>
<![CDATA[
// create a new listbox item and append it to our parent control.
var newNode = aTemplateNode.cloneNode(true);
var input = document.getAnonymousElementByAttribute(newNode, "anonid", "input");
var icon = document.getAnonymousElementByAttribute(newNode, "anonid", "icon");
// We always clone the first row. The problem is that the first row
// could be focused. When we clone that row, we end up with a cloned
// XUL textbox that has a focused attribute set. Therefore we think
// we're focused and don't properly refocus. The best solution to this
// would be to clone a template row that didn't really have any presentation,
// rather than using the real visible first row of the listbox.
// For now we'll just put in a hack that ensures the focused attribute
// is never copied when the node is cloned.
if (input.getAttribute('focused') != '')
input.removeAttribute('focused');
aParentNode.appendChild(newNode);
// the template could have its fields disabled,
// that's why we need to reset their status.
input.removeAttribute("disabled");
icon.removeAttribute("disabled");
if(this.mIsReadOnly || !this.mIsOrganizer) {
input.setAttribute("disabled","true");
icon.setAttribute("disabled","true");
}
// disable the input-field [name <email>] if this attendee
// appears to be the organizer.
if (aDisableIfOrganizer) {
if (aAttendee) {
if (this.mOrganizerID && this.mOrganizerID != "") {
if (aAttendee.id == this.mOrganizerID) {
input.setAttribute("disabled","true");
}
}
}
}
this.mMaxAttendees++;
var rowNumber = this.mMaxAttendees;
if (rowNumber >= 0)
{
icon.setAttribute("id", "attendeeCol1#" + rowNumber);
input.setAttribute("id", "attendeeCol2#" + rowNumber);
}
if(!aAttendee) {
aAttendee = this.createAttendee();
}
// construct the display string from common name and/or email address.
var inputValue = aAttendee.commonName;
if(inputValue) {
var email = aAttendee.id;
if(email && email.length) {
if (email.indexOf("mailto:") == 0)
email = email.split("mailto:")[1]
inputValue += ' <'+email+'>';
}
} else {
var email = aAttendee.id;
if(email && email.length) {
if (email.indexOf("mailto:") == 0)
email = email.split("mailto:")[1]
inputValue = email;
}
}
// remove leading spaces
while (inputValue && inputValue[0] == " " )
inputValue = inputValue.substring(1, inputValue.length);
input.setAttribute("value", inputValue);
input.value = inputValue;
input.attendee = aAttendee;
input.setAttribute("dirty","true");
if (aAttendee) {
if (this.mOrganizerID && this.mOrganizerID != "") {
if (aAttendee.id == this.mOrganizerID) {
icon.setAttribute("class","status-icon");
icon.setAttribute("status",aAttendee.participationStatus);
return true;
}
}
}
icon.setAttribute("class","role-icon");
icon.setAttribute("role",aAttendee.role);
return true;
]]>
</body>
</method>
<!-- appends a new empty row -->
<method name="appendNewRow">
<parameter name="aSetFocus"/>
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var listitem1 = this.getListItem(1);
if (listbox && listitem1) {
var newAttendee = this.createAttendee();
var nextDummy = this.getNextDummyRow();
var newNode = listitem1.cloneNode(true);
var input = document.getAnonymousElementByAttribute(newNode, "anonid", "input");
var icon = document.getAnonymousElementByAttribute(newNode, "anonid", "icon");
// the template could have its fields disabled,
// that's why we need to reset their status.
input.removeAttribute("disabled");
icon.removeAttribute("disabled");
if(this.mIsReadOnly || !this.mIsOrganizer) {
input.setAttribute("disabled","true");
icon.setAttribute("disabled","true");
}
this.mMaxAttendees++;
var rowNumber = this.mMaxAttendees;
if (rowNumber >= 0)
{
icon.setAttribute("id", "attendeeCol1#" + rowNumber);
input.setAttribute("id", "attendeeCol2#" + rowNumber);
}
input.value = null;
input.removeAttribute("value");
input.attendee = newAttendee;
// this copies the autocomplete sessions list from first attendee
if (this.mLDAPSession && this.mSessionAdded) {
input.syncSessions(document.getElementById('attendeeCol2#1'));
}
// set role and participation status
//status.setAttribute("status",newAttendee.participationStatus);
//role.setAttribute("role",newAttendee.role);
icon.setAttribute("class","role-icon");
icon.setAttribute("role","REQ-PARTICIPANT");
// We always clone the first row. The problem is that the first row
// could be focused. When we clone that row, we end up with a cloned
// XUL textbox that has a focused attribute set. Therefore we think
// we're focused and don't properly refocus. The best solution to this
// would be to clone a template row that didn't really have any presentation,
// rather than using the real visible first row of the listbox.
// For now we'll just put in a hack that ensures the focused attribute
// is never copied when the node is cloned.
if (input.getAttribute('focused') != '')
input.removeAttribute('focused');
if (nextDummy)
listbox.replaceChild(newNode, nextDummy);
else
listbox.appendChild(newNode);
// focus on new input widget
if(aSetFocus)
this.setFocus(this.mMaxAttendees);
this.onModify();
}
]]>
</body>
</method>
<property name="attendees">
<getter>
<![CDATA[
var attendees = [];
var i=1;
var inputField;
while ((inputField = this.getInputElement(i))) {
var fieldValue = inputField.value;
if (fieldValue == null)
fieldValue = inputField.getAttribute("value");
if (fieldValue != "") {
// the inputfield already has a reference to the attendee
// object, we just need to fill in the name.
var attendee = inputField.attendee.clone();
attendee.role = this.getRoleElement(i).getAttribute("role");
//attendee.participationStatus = this.getStatusElement(i).getAttribute("status");
// break the list of potentially many attendees back into individual names
var emailAddresses = {};
var names = {};
var fullNames = {};
var numAddresses = this.mHeaderParser.parseHeadersWithArray(fieldValue,emailAddresses,names,fullNames);
if(emailAddresses.value.length > 0) {
// if the new address has no 'mailto'-prefix but seems
// to look like an email-address, we prepend the prefix.
// this also allows for non-email-addresses.
var email = emailAddresses.value[0];
if (email.indexOf("mailto:") != 0)
if (email.indexOf("@") >= 0)
email = "mailto:" + email;
attendee.id = email;
}
if(names.value.length > 0) {
attendee.commonName = names.value[0];
}
var addAttendee = true;
if (this.mOrganizerID && this.mOrganizerID != "") {
if (attendee.id == this.mOrganizerID) {
if (i == 1) {
addAttendee = false;
}
}
}
// append the attendee object to the list of attendees.
if (addAttendee)
attendees.push(attendee);
}
i++;
}
return attendees;
]]>
</getter>
</property>
<property name="organizer">
<getter>
<![CDATA[
var i=1;
var inputField;
while ((inputField = this.getInputElement(i))) {
var fieldValue = inputField.value;
if (fieldValue == null)
fieldValue = inputField.getAttribute("value");
if (fieldValue != "") {
// the inputfield already has a reference to the attendee
// object, we just need to fill in the name.
var attendee = inputField.attendee.clone();
//attendee.role = this.getRoleElement(i).getAttribute("role");
attendee.participationStatus = this.getStatusElement(i).getAttribute("status");
// break the list of potentially many attendees back into individual names
var emailAddresses = {};
var names = {};
var fullNames = {};
var numAddresses = this.mHeaderParser.parseHeadersWithArray(fieldValue,emailAddresses,names,fullNames);
if(emailAddresses.value.length > 0) {
// if the new address has no 'mailto'-prefix but seems
// to look like an email-address, we prepend the prefix.
// this also allows for non-email-addresses.
var email = emailAddresses.value[0];
if (email.indexOf("mailto:") != 0)
if (email.indexOf("@") >= 0)
email = "mailto:" + email;
attendee.id = email;
}
if(names.value.length > 0) {
attendee.commonName = names.value[0];
}
if (this.mOrganizerID && this.mOrganizerID != "") {
if (attendee.id == this.mOrganizerID) {
return attendee;
}
}
}
i++;
}
return null;
]]>
</getter>
</property>
<method name="onModify">
<body>
<![CDATA[
var list = [];
for (var i=1; i<=this.mMaxAttendees; i++)
{
// retrieve the string from the appropriate row
var input = this.getInputElement(i);
var fieldValue = input.value;
// parse the string to break this down to individual names and addresses
var email = "";
var emailAddresses = {};
var names = {};
var fullNames = {};
var numAddresses = this.mHeaderParser.parseHeadersWithArray(fieldValue,emailAddresses,names,fullNames);
if(emailAddresses.value.length > 0) {
// if the new address has no 'mailto'-prefix but seems
// to look like an email-address, we prepend the prefix.
// this also allows for non-email-addresses.
email = emailAddresses.value[0];
if (email.indexOf("mailto:") != 0)
if (email.indexOf("@") >= 0)
email = "mailto:" + email;
}
var isdirty = false;
if(input.hasAttribute("dirty"))
isdirty = input.getAttribute("dirty");
input.removeAttribute("dirty");
var entry = { dirty: isdirty, calid: email };
list.push(entry);
}
var event = document.createEvent('Events');
event.initEvent('modify', true, false);
event.details = list;
this.dispatchEvent(event);
]]>
</body>
</method>
<property name="documentSize">
<getter>
<![CDATA[
return this.mRowHeight * this.mMaxAttendees;
]]>
</getter>
</property>
<method name="fitDummyRows">
<body>
<![CDATA[
var self = this;
var func = function func() {
self.calcContentHeight();
self.createOrRemoveDummyRows();
}
setTimeout(func,0);
]]>
</body>
</method>
<method name="calcContentHeight">
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var items = listbox.getElementsByTagNameNS('*','listitem');
this.mContentHeight = 0;
if (items.length > 0) {
var i = 0;
do {
this.mRowHeight = items[i].boxObject.height;
++i;
} while (i < items.length && !this.mRowHeight);
this.mContentHeight = this.mRowHeight*items.length;
}
]]>
</body>
</method>
<method name="createOrRemoveDummyRows">
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var listboxHeight = listbox.boxObject.height;
// remove rows to remove scrollbar
var kids = listbox.childNodes;
for (var i = kids.length-1; this.mContentHeight > listboxHeight && i >= 0; --i) {
if (kids[i].hasAttribute("_isDummyRow")) {
this.mContentHeight -= this.mRowHeight;
listbox.removeChild(kids[i]);
}
}
// add rows to fill space
if (this.mRowHeight) {
while (this.mContentHeight+this.mRowHeight < listboxHeight) {
this.createDummyItem(listbox);
this.mContentHeight += this.mRowHeight;
}
}
]]>
</body>
</method>
<method name="createDummyCell">
<parameter name="aParent"/>
<body>
<![CDATA[
var cell = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","listcell");
cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
if (aParent)
aParent.appendChild(cell);
return cell;
]]>
</body>
</method>
<method name="createDummyItem">
<parameter name="aParent"/>
<body>
<![CDATA[
var titem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","listitem");
titem.setAttribute("_isDummyRow", "true");
titem.setAttribute("class", "dummy-row");
for (var i=this.numColumns; i>0; i--)
this.createDummyCell(titem);
if (aParent)
aParent.appendChild(titem);
return titem;
]]>
</body>
</method>
<!-- gets the next row from the top down -->
<method name="getNextDummyRow">
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var kids = listbox.childNodes;
for (var i = 0; i < kids.length; ++i) {
if (kids[i].hasAttribute("_isDummyRow"))
return kids[i];
}
return null;
]]>
</body>
</method>
<!-- This method returns the <xul:listitem> at row numer 'aRow' -->
<method name="getListItem">
<parameter name="aRow"/>
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
if (listbox && aRow > 0) {
var listitems = listbox.getElementsByTagNameNS('*','listitem');
if (listitems && listitems.length >= aRow)
return listitems[aRow-1];
}
return 0;
]]>
</body>
</method>
<method name="getRowByInputElement">
<parameter name="aElement"/>
<body>
<![CDATA[
var row = 0;
while(aElement && aElement.localName != "listitem")
aElement = aElement.parentNode;
if (aElement) {
while (aElement) {
if (aElement.localName == "listitem")
++row;
aElement = aElement.previousSibling;
}
}
return row;
]]>
</body>
</method>
<!-- This method returns the <xul:textbox> that contains
the name of the attendee at row number 'aRow' -->
<method name="getInputElement">
<parameter name="aRow"/>
<body>
<![CDATA[
return document.getElementById("attendeeCol2#" + aRow);
]]>
</body>
</method>
<method name="getRoleElement">
<parameter name="aRow"/>
<body>
<![CDATA[
return document.getElementById("attendeeCol1#" + aRow);
]]>
</body>
</method>
<method name="getStatusElement">
<parameter name="aRow"/>
<body>
<![CDATA[
return document.getElementById("attendeeCol1#" + aRow);
]]>
</body>
</method>
<method name="setFocus">
<parameter name="aRow"/>
<body>
<![CDATA[
var self = this;
var set_focus = function func() {
// do we need to scroll in order to see the selected row?
var node = self.getListItem(aRow);
var listbox = document.getAnonymousElementByAttribute(self, "anonid", "listbox");
var firstVisibleRow = listbox.getIndexOfFirstVisibleRow();
var numOfVisibleRows = listbox.getNumberOfVisibleRows();
if (aRow <= firstVisibleRow)
listbox.scrollToIndex(aRow-1);
else
if (aRow-1 >= (firstVisibleRow+numOfVisibleRows))
listbox.scrollToIndex(aRow-numOfVisibleRows);
var input = document.getAnonymousElementByAttribute(node, "anonid", "input");
input.focus();
}
setTimeout(set_focus,0);
]]>
</body>
</method>
<property name="firstVisibleRow">
<getter>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
return listbox.getIndexOfFirstVisibleRow();
]]>
</getter>
</property>
<method name="createAttendee">
<body>
<![CDATA[
var attendee = createAttendee();
attendee.id = "";
attendee.rsvp = true;
attendee.role = "REQ-PARTICIPANT";
attendee.participationStatus = "NEEDS-ACTION";
return attendee;
]]>
</body>
</method>
<property name="numColumns">
<getter>
<![CDATA[
if (!this.mNumColumns) {
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var listCols = listbox.getElementsByTagNameNS('*','listcol');
this.mNumColumns = listCols.length;
if (!this.mNumColumns)
this.mNumColumns = 1;
}
return this.mNumColumns;
]]>
</getter>
</property>
<property name="ratio">
<setter>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var rowcount = listbox.getRowCount();
listbox.scrollToIndex(Math.floor(rowcount*val));
return val;
]]>
</setter>
</property>
<method name="arrowHit">
<parameter name="aElement"/>
<parameter name="aDirection"/>
<body>
<![CDATA[
var row = this.getRowByInputElement(aElement) + aDirection;
if (row) {
if(row > this.mMaxAttendees) {
this.appendNewRow(true);
} else {
var input = this.getInputElement(row);
if(input.hasAttribute("disabled"))
return;
this.setFocus(row);
}
var event = document.createEvent('Events');
event.initEvent('rowchange', true, false);
event.details = row;
this.dispatchEvent(event);
}
]]>
</body>
</method>
<method name="deleteHit">
<parameter name="aElement"/>
<body>
<![CDATA[
// don't delete the row if it's the last one remaining
if (this.mMaxAttendees <= 1)
return;
var row = this.getRowByInputElement(aElement);
this.deleteRow(row);
if(row > 1)
row = row-1;
this.setFocus(row);
this.onModify();
var event = document.createEvent('Events');
event.initEvent('rowchange', true, false);
event.details = row;
this.dispatchEvent(event);
]]>
</body>
</method>
<method name="deleteRow">
<parameter name="aRow"/>
<body>
<![CDATA[
// reset id's in order to not break the sequence
var maxAttendees = this.mMaxAttendees;
this.removeRow(aRow);
var numberOfCols = this.numColumns;
for (var row = aRow+1; row<=maxAttendees; row++) {
for (var col=1; col<=numberOfCols; col++) {
var colID = "attendeeCol" + col + "#" + row;
var elem = document.getElementById(colID);
if(elem)
elem.setAttribute("id", "attendeeCol" + (col) + "#" + (row-1));
}
}
]]>
</body>
</method>
<method name="removeRow">
<parameter name="aRow"/>
<body>
<![CDATA[
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
var nodeToRemove = this.getListItem(aRow)
nodeToRemove.parentNode.removeChild(nodeToRemove);
this.fitDummyRows();
this.mMaxAttendees--;
]]>
</body>
</method>
<!-- ############################################################################# -->
<!-- LDAP support -->
<!-- ############################################################################# -->
<method name="setupAutocomplete">
<body>
<![CDATA[
var autocompleteLdap = false;
var autocompleteDirectory = null;
var prevAutocompleteDirectory = this.mCurrentAutocompleteDirectory;
var i;
autocompleteLdap = this.mPrefs.getBoolPref("ldap_2.autoComplete.useDirectory");
if (autocompleteLdap)
autocompleteDirectory = this.mPrefs.getCharPref(
"ldap_2.autoComplete.directoryServer");
// use a temporary to do the setup so that we don't overwrite the
// global, then have some problem and throw an exception, and leave the
// global with a partially setup session. we'll assign the temp
// into the global after we're done setting up the session
var LDAPSession;
if (this.mLDAPSession) {
LDAPSession = this.mLDAPSession;
} else {
LDAPSession = Components.classes[
"@mozilla.org/autocompleteSession;1?type=ldap"].createInstance()
.QueryInterface(Components.interfaces.nsILDAPAutoCompleteSession);
}
if (autocompleteDirectory && !this.mIsOffline) {
// Add observer on the directory server we are autocompleting against
// only if current server is different from previous.
// Remove observer if current server is different from previous
this.mCurrentAutocompleteDirectory = autocompleteDirectory;
if (prevAutocompleteDirectory) {
if (prevAutocompleteDirectory != this.mCurrentAutocompleteDirectory) {
this.removeDirectorySettingsObserver(prevAutocompleteDirectory);
this.addDirectorySettingsObserver();
}
}
else
this.addDirectorySettingsObserver();
// fill in the session params if there is a session
//
if (LDAPSession) {
var serverURL = Components.classes[
"@mozilla.org/network/ldap-url;1"].
createInstance().QueryInterface(
Components.interfaces.nsILDAPURL);
try {
serverURL.spec = this.mPrefs.getComplexValue(autocompleteDirectory +".uri",
Components.interfaces.nsISupportsString).data;
} catch (ex) {
dump("ERROR: " + ex + "\n");
}
LDAPSession.serverURL = serverURL;
// get the login to authenticate as, if there is one
//
var login = "";
try {
login = this.mPrefs.getComplexValue(
autocompleteDirectory + ".auth.dn",
Components.interfaces.nsISupportsString).data;
} catch (ex) {
// if we don't have this pref, no big deal
}
// set the LDAP protocol version correctly
var protocolVersion;
try {
protocolVersion = this.mPrefs.getCharPref(autocompleteDirectory +
".protocolVersion");
} catch (ex) {
// if we don't have this pref, no big deal
}
if (protocolVersion == "2") {
LDAPSession.version =
Components.interfaces.nsILDAPConnection.VERSION2;
}
// find out if we need to authenticate, and if so, tell the LDAP
// autocomplete session how to prompt for a password. This window
// is being used to parent the authprompter.
//
LDAPSession.login = login;
if (login != "") {
var windowWatcherSvc = Components.classes[
"@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
var domWin =
window.QueryInterface(Components.interfaces.nsIDOMWindow);
var authPrompter =
windowWatcherSvc.getNewAuthPrompter(domWin);
LDAPSession.authPrompter = authPrompter;
}
// don't search on non-CJK strings shorter than this
//
try {
LDAPSession.minStringLength = this.mPrefs.getIntPref(
autocompleteDirectory + ".autoComplete.minStringLength");
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default.
}
// don't search on CJK strings shorter than this
//
try {
LDAPSession.cjkMinStringLength = this.mPrefs.getIntPref(
autocompleteDirectory + ".autoComplete.cjkMinStringLength");
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default.
}
// we don't try/catch here, because if this fails, we're outta luck
//
var ldapFormatter = Components.classes[
"@mozilla.org/ldap-autocomplete-formatter;1?type=addrbook"]
.createInstance().QueryInterface(
Components.interfaces.nsIAbLDAPAutoCompFormatter);
// override autocomplete name format?
//
try {
ldapFormatter.nameFormat =
this.mPrefs.getComplexValue(autocompleteDirectory +
".autoComplete.nameFormat",
Components.interfaces.nsISupportsString).data;
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsAbLDAPAutoCompFormatter use its default.
}
// override autocomplete mail address format?
//
try {
ldapFormatter.addressFormat =
this.mPrefs.getComplexValue(autocompleteDirectory +
".autoComplete.addressFormat",
Components.interfaces.nsISupportsString).data;
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsAbLDAPAutoCompFormatter use its default.
}
try {
// figure out what goes in the comment column, if anything
//
// 0 = none
// 1 = name of addressbook this card came from
// 2 = other per-addressbook format
//
var showComments = 0;
showComments = this.mPrefs.getIntPref(
"mail.autoComplete.commentColumn");
switch (showComments) {
case 1:
// use the name of this directory
//
ldapFormatter.commentFormat = this.mPrefs.getComplexValue(
autocompleteDirectory + ".description",
Components.interfaces.nsISupportsString).data;
break;
case 2:
// override ldap-specific autocomplete entry?
//
try {
ldapFormatter.commentFormat =
this.mPrefs.getComplexValue(autocompleteDirectory +
".autoComplete.commentFormat",
Components.interfaces.nsISupportsString).data;
} catch (innerException) {
// if nothing has been specified, use the ldap
// organization field
ldapFormatter.commentFormat = "[o]";
}
break;
case 0:
default:
// do nothing
}
} catch (ex) {
// if something went wrong while setting up comments, try and
// proceed anyway
}
// set the session's formatter, which also happens to
// force a call to the formatter's getAttributes() method
// -- which is why this needs to happen after we've set the
// various formats
//
LDAPSession.formatter = ldapFormatter;
// override autocomplete entry formatting?
//
try {
LDAPSession.outputFormat =
this.mPrefs.getComplexValue(autocompleteDirectory +
".autoComplete.outputFormat",
Components.interfaces.nsISupportsString).data;
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default.
}
// override default search filter template?
//
try {
LDAPSession.filterTemplate = this.mPrefs.getComplexValue(
autocompleteDirectory + ".autoComplete.filterTemplate",
Components.interfaces.nsISupportsString).data;
} catch (ex) {
// if this pref isn't there, no big deal. just let
// nsLDAPAutoCompleteSession use its default
}
// override default maxHits (currently 100)
//
try {
// XXXdmose should really use .autocomplete.maxHits,
// but there's no UI for that yet
//
LDAPSession.maxHits =
this.mPrefs.getIntPref(autocompleteDirectory + ".maxHits");
} catch (ex) {
// if this pref isn't there, or is out of range, no big deal.
// just let nsLDAPAutoCompleteSession use its default.
}
if (!this.mSessionAdded) {
// if we make it here, we know that session initialization has
// succeeded; add the session for all recipients, and
// remember that we've done so
var autoCompleteWidget;
for (i=1; i <= this.mMaxAttendees; i++)
{
autoCompleteWidget = this.getInputElement(i);
if (autoCompleteWidget)
{
autoCompleteWidget.addSession(LDAPSession);
// ldap searches don't insert a default entry with the default domain appended to it
// so reduce the minimum results for a popup to 2 in this case.
autoCompleteWidget.minResultsForPopup = 2;
}
}
this.mSessionAdded = true;
}
}
} else {
if (this.mCurrentAutocompleteDirectory) {
// Remove observer on the directory server since we are not doing Ldap
// autocompletion.
this.removeDirectorySettingsObserver(this.mCurrentAutocompleteDirectory);
this.mCurrentAutocompleteDirectory = null;
}
if (this.mLDAPSession && this.mSessionAdded) {
for (i=1; i <= this.mMaxAttendees; i++)
this.getInputElement(i).removeSession(this.mLDAPSession);
this.mSessionAdded = false;
}
}
this.mLDAPSession = LDAPSession;
//gSetupLdapAutocomplete = true;
]]>
</body>
</method>
<method name="addDirectoryServerObserver">
<body>
<![CDATA[
this.mPrefBranchInternal.addObserver("ldap_2.autoComplete.useDirectory",
this.mDirectoryServerObserver, false);
this.mPrefBranchInternal.addObserver("ldap_2.autoComplete.directoryServer",
this.mDirectoryServerObserver, false);
]]>
</body>
</method>
<method name="removeDirectoryServerObserver">
<body>
<![CDATA[
this.mPrefBranchInternal.removeObserver("ldap_2.autoComplete.useDirectory", this.mDirectoryServerObserver);
this.mPrefBranchInternal.removeObserver("ldap_2.autoComplete.directoryServer", this.mDirectoryServerObserver);
]]>
</body>
</method>
<method name="addDirectorySettingsObserver">
<body>
<![CDATA[
this.mPrefBranchInternal.addObserver(this.mCurrentAutocompleteDirectory, this.mDirectoryServerObserver, false);
]]>
</body>
</method>
<method name="removeDirectorySettingsObserver">
<parameter name="aPrefString"/>
<body>
<![CDATA[
this.mPrefBranchInternal.removeObserver(aPrefString, this.mDirectoryServerObserver);
]]>
</body>
</method>
<method name="releaseAutoCompleteState">
<body>
<![CDATA[
if (this.mLDAPSession && this.mSessionAdded) {
for (i=1; i <= this.mMaxAttendees; i++) {
this.getInputElement(i).removeSession(this.mLDAPSession);
}
}
this.mSessionAdded = false;
this.mLDAPSession = null;
]]>
</body>
</method>
<!-- ############################################################################# -->
<!-- End of implementation for the 'attendees-list'-binding -->
<!-- ############################################################################# -->
</implementation>
<handlers>
<handler event="click" button="0">
<![CDATA[
if (event.originalTarget.hasAttribute("role")) {
if (event.originalTarget.hasAttribute("disabled")) {
if (event.originalTarget.getAttribute("disabled")) {
return;
}
}
var role = event.originalTarget.getAttribute("role");
if (role == "CHAIR") {
event.originalTarget.setAttribute("role","REQ-PARTICIPANT");
} else if (role == "REQ-PARTICIPANT") {
event.originalTarget.setAttribute("role","OPT-PARTICIPANT");
} else if (role == "OPT-PARTICIPANT") {
event.originalTarget.setAttribute("role","CHAIR");
}
return;
}
if (event.originalTarget.hasAttribute("status")) {
if (event.originalTarget.hasAttribute("disabled")) {
if (event.originalTarget.getAttribute("disabled")) {
return;
}
}
var status = event.originalTarget.getAttribute("status");
if (status == "NEEDS-ACTION") {
event.originalTarget.setAttribute("status","ACCEPTED");
} else if (status == "ACCEPTED") {
event.originalTarget.setAttribute("status","DECLINED");
} else if (status == "DECLINED") {
event.originalTarget.setAttribute("status","TENTATIVE");
} else if (status == "TENTATIVE") {
event.originalTarget.setAttribute("status","ACCEPTED");
}
return;
}
if(this.mIsReadOnly || !this.mIsOrganizer)
return;
if (event.originalTarget == null ||
(event.originalTarget.localName != "listboxbody" &&
event.originalTarget.localName != "listcell" &&
event.originalTarget.localName != "listitem"))
return;
var lastInput = this.getInputElement(this.mMaxAttendees);
if (lastInput && lastInput.value)
this.appendNewRow(true);
]]>
</handler>
<handler event="popupshown">
<![CDATA[
this.mPopupOpen = true;
]]>
</handler>
<handler event="popuphidden">
<![CDATA[
this.mPopupOpen = false;
]]>
</handler>
<handler event="keydown">
<![CDATA[
if(this.mIsReadOnly || !this.mIsOrganizer)
return;
if(event.originalTarget.localName == "input") {
switch(event.keyCode) {
case 46:
case 8:
if (!event.originalTarget.value)
this.deleteHit(event.originalTarget);
event.stopPropagation();
break;
case 13:
this.arrowHit(event.originalTarget, 1);
event.stopPropagation();
event.preventDefault();
break;
}
}
]]>
</handler>
<handler event="keypress" phase="capturing">
<![CDATA[
// in case we're currently showing the autocompletion popup
// don't care about keypress-events and let them go. otherwise
// this event indicates the user wants to travel between
// the different attendees. in this case we set the focus
// appropriately and stop the event propagation.
if(this.mPopupOpen)
return;
if(this.mIsReadOnly || !this.mIsOrganizer)
return;
if(event.originalTarget.localName == "input") {
switch(event.keyCode) {
case KeyEvent.DOM_VK_UP:
this.arrowHit(event.originalTarget, -1);
event.stopPropagation();
break;
case KeyEvent.DOM_VK_DOWN:
this.arrowHit(event.originalTarget, 1);
event.stopPropagation();
break;
case KeyEvent.DOM_VK_TAB:
this.arrowHit(event.originalTarget, event.shiftKey ? -1 : +1);
event.stopPropagation();
event.preventDefault();
break;
case KeyEvent.DOM_VK_RETURN:
event.stopPropagation();
event.preventDefault();
break;
}
}
]]>
</handler>
<handler event="input">
<![CDATA[
this.setupAutocomplete();
]]>
</handler>
</handlers>
</binding>
<!-- the 'selection-bar' binding implements the vertical bar that provides
a visual indication for the time range the event is configured for. -->
<binding id="selection-bar">
<content>
<xul:scrollbox anonid="scrollbox" width="0" orient="horizontal" flex="1">
<xul:box class="selection-bar" anonid="selection-bar">
<xul:box width="3" style="cursor: e-resize"/>
<xul:spacer flex="1" style="cursor: -moz-grab"/>
<xul:box width="3" style="cursor: e-resize"/>
</xul:box>
</xul:scrollbox>
</content>
<implementation>
<field name="mRange">0</field>
<field name="mStartHour">8</field>
<field name="mEndHour">19</field>
<field name="mStartHourDefault">8</field>
<field name="mEndHourDefault">19</field>
<field name="mContentWidth">0</field>
<field name="mHeaderHeight">0</field>
<field name="mRatio">0</field>
<field name="mBaseDate">null</field>
<field name="mStartDate">null</field>
<field name="mEndDate">null</field>
<field name="mMouseX">0</field>
<field name="mMouseY">0</field>
<field name="mDragState">0</field>
<field name="mMargin">0</field>
<field name="mWidth">0</field>
<field name="mForce24Hours">false</field>
<field name="mZoomFactor">100</field>
<property name="zoomFactor">
<getter>
<![CDATA[
return this.mZoomFactor;
]]>
</getter>
<setter>
<![CDATA[
this.mZoomFactor = val;
return val;
]]>
</setter>
</property>
<property name="force24Hours">
<getter>
<![CDATA[
return this.mForce24Hours;
]]>
</getter>
<setter>
<![CDATA[
this.mForce24Hours = val;
this.initTimeRange();
this.update();
return val;
]]>
</setter>
</property>
<property name="ratio">
<setter>
<![CDATA[
this.mRatio = val;
this.update();
return val;
]]>
</setter>
</property>
<constructor>
<![CDATA[
this.initTimeRange();
// the basedate is the date/time from which the display
// of the timebar starts. the range is the number of days
// we should be able to show. the start- and enddate
// is the time the event is scheduled for.
this.mRange = Number(this.getAttribute("range"));
]]>
</constructor>
<property name="baseDate">
<setter>
<![CDATA[
// we need to convert the date/time in question in
// order to calculate with hours that are aligned
// with our timebar display.
var kDefaultTimezone = calendarDefaultTimezone();
this.mBaseDate = val.getInTimezone(kDefaultTimezone);
this.mBaseDate.isDate = true;
this.mBaseDate.makeImmutable();
return val;
]]>
</setter>
</property>
<property name="startDate">
<setter>
<![CDATA[
// currently we *always* set the basedate to be
// equal to the startdate. we'll most probably
// want to change this later.
this.baseDate = val;
// we need to convert the date/time in question in
// order to calculate with hours that are aligned
// with our timebar display.
var kDefaultTimezone = calendarDefaultTimezone();
this.mStartDate = val.getInTimezone(kDefaultTimezone);
this.mStartDate.makeImmutable();
return val;
]]>
</setter>
<getter>
<![CDATA[
return this.mStartDate;
]]>
</getter>
</property>
<property name="endDate">
<setter>
<![CDATA[
// we need to convert the date/time in question in
// order to calculate with hours that are aligned
// with our timebar display.
var kDefaultTimezone = calendarDefaultTimezone();
this.mEndDate = val.getInTimezone(kDefaultTimezone);
if(this.mEndDate.isDate)
this.mEndDate.day += 1;
this.mEndDate.normalize();
this.mEndDate.makeImmutable();
return val;
]]>
</setter>
<getter>
<![CDATA[
return this.mEndDate;
]]>
</getter>
</property>
<method name="init">
<parameter name="width"/>
<parameter name="height"/>
<body>
<![CDATA[
this.mContentWidth = width;
this.mHeaderHeight = height + 2;
var num_hours = this.mEndHour - this.mStartHour;
var hour_width = this.mContentWidth / num_hours;
this.mWidth = hour_width*1;
this.mMargin = 0;
this.update();
]]>
</body>
</method>
<!-- given some specific date this method calculates the
corrposonding offset in fractional hours -->
<method name="date2offset">
<parameter name="date"/>
<body>
<![CDATA[
var num_hours = this.mEndHour - this.mStartHour;
var diff = date.subtractDate(this.mBaseDate);
var offset = diff.days * num_hours;
var hours = (diff.hours - this.mStartHour) + (diff.minutes / 60.0);
if(hours < 0)
hours = 0;
if(hours > num_hours)
hours = num_hours;
offset += hours;
return offset;
]]>
</body>
</method>
<method name="update">
<body>
<![CDATA[
if(!this.mStartDate || !this.mEndDate)
return;
// calculate the relation of startdate/basedate and enddate/startdate.
var offset = this.mStartDate.subtractDate(this.mBaseDate);
var duration = this.mEndDate.subtractDate(this.mStartDate);
// calculate how much pixels a single hour and a single day take up.
var num_hours = this.mEndHour - this.mStartHour;
var hour_width = this.mContentWidth / num_hours;
// calculate the offset in fractional hours that corrospond
// to our start- and end-time.
var start_offset_in_hours = this.date2offset(this.mStartDate);
var end_offset_in_hours = this.date2offset(this.mEndDate);
var duration_in_hours = end_offset_in_hours - start_offset_in_hours;
// calculate width & margin for the selection bar based on the
// relation of startdate/basedate and enddate/startdate.
// this is a simple conversion from hours to pixels.
this.mWidth = duration_in_hours*hour_width;
this.mMargin = start_offset_in_hours*hour_width;
// calculate the difference between content and container in pixels.
// the container is the window showing this control, the content is the
// total number of pixels the selection bar can theoretically take up.
var total_width = this.mContentWidth * this.mRange - this.parentNode.boxObject.width;
// calculate the current scroll offset.
var offset = Math.floor(total_width * this.mRatio);
// the final margin is the difference between
// the date-based margin and the scroll-based margin.
this.mMargin -= offset;
// set the styles based on the calculations above for the 'selection-bar'.
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
var style = "width: "+this.mWidth+"px; margin-left: "+this.mMargin+"px; margin-top: "+this.mHeaderHeight+"px;";
selectionbar.setAttribute("style",style);
var event = document.createEvent('Events');
event.initEvent('timechange', true, false);
event.startDate = this.mStartDate;
event.endDate = this.mEndDate.clone();
if(event.endDate.isDate)
event.endDate.day -= 1;
event.endDate.makeImmutable();
this.dispatchEvent(event);
]]>
</body>
</method>
<method name="setWidth">
<parameter name="width"/>
<body>
<![CDATA[
var scrollbox = document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
scrollbox.setAttribute("width",width);
]]>
</body>
</method>
<method name="initTimeRange">
<body>
<![CDATA[
this.mStartHour = this.mStartHourDefault;
this.mEndHour = this.mEndHourDefault;
var pb2 = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch2);
// get default start/end times from prefs and set on the
// view. if we hit an error (eg because sunbird's pref
// infrastructure hasn't created the pref yet), the
// defaults will do
try {
this.mStartHour = pb2.getIntPref("calendar.view.defaultstarthour");
this.mEndHour = pb2.getIntPref("calendar.view.defaultendhour");
} catch (ex) {}
if(this.force24Hours) {
this.mStartHour = 0;
this.mEndHour = 24;
}
]]>
</body>
</method>
<method name="moveTime">
<parameter name="time"/>
<parameter name="delta"/>
<body>
<![CDATA[
var num_hours = this.mEndHour - this.mStartHour;
var hour_width = this.mContentWidth / num_hours;
var minutes_per_pixel = 60 / hour_width;
var newTime = time.clone();
var minute_shift = minutes_per_pixel * delta;
var clip_minutes = 60 * this.zoomFactor / 100;
if(delta > 0) {
if(time.isDate) {
if(minute_shift >= clip_minutes) {
newTime.day++;
newTime.normalize();
}
} else {
if(minute_shift >= clip_minutes) {
newTime.minute -= newTime.minute % clip_minutes;
newTime.minute += clip_minutes;
newTime.normalize();
}
}
} else if(delta < 0) {
if(time.isDate) {
if(-minute_shift >= clip_minutes) {
newTime.day--;
newTime.normalize();
}
} else {
if(-minute_shift >= clip_minutes) {
newTime.minute -= newTime.minute % clip_minutes;
newTime.minute -= clip_minutes;
newTime.normalize();
}
}
}
if(!newTime.isDate) {
if(newTime.hour < this.mStartHour) {
newTime.hour = this.mEndHour-1;
newTime.day--;
}
if(newTime.hour >= this.mEndHour) {
newTime.hour = this.mStartHour;
newTime.day++;
}
}
newTime.normalize();
return newTime;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="mousedown">
<![CDATA[
var element = event.target;
this.mMouseX = event.clientX - element.boxObject.x;
this.mMouseY = event.clientY - element.boxObject.y;
if(this.mMouseX >= this.mMargin) {
if(this.mMouseX < (this.mMargin+this.mWidth)) {
this.mDragState = 1;
window.setCursor("-moz-grab");
if(this.mMouseX <= (this.mMargin+3)) {
window.setCursor("e-resize");
this.mDragState = 2;
} else if(this.mMouseX >= (this.mMargin+this.mWidth-3)) {
window.setCursor("e-resize");
this.mDragState = 3;
}
}
}
]]>
</handler>
<handler event="mousemove">
<![CDATA[
if(this.mDragState == 1) {
var element = event.target;
var mouseX = event.clientX - element.boxObject.x;
var mouseY = event.clientY - element.boxObject.y;
var delta = mouseX - this.mMouseX;
var newStart = this.moveTime(this.mStartDate,delta);
if(newStart.compare(this.mStartDate) != 0) {
newEnd = this.moveTime(this.mEndDate,delta);
// we need to adapt this date in case we're dealing with
// an all-day event. this is because setting 'endDate' will
// automatically add one day extra for all-day events.
if(newEnd.isDate) {
newEnd.day -= 1;
newEnd.normalize();
}
this.startDate = newStart;
this.endDate = newEnd;
this.mMouseX = mouseX;
this.mMouseY = mouseY;
this.update();
}
} else if(this.mDragState == 2) {
var element = event.target;
var mouseX = event.clientX - element.boxObject.x;
var mouseY = event.clientY - element.boxObject.y;
var delta = mouseX - this.mMouseX;
var newStart = this.moveTime(this.mStartDate,delta);
if(newStart.compare(this.mStartDate) != 0) {
this.startDate = newStart;
this.mMouseX = mouseX;
this.mMouseY = mouseY;
this.update();
}
} else if(this.mDragState == 3) {
var element = event.target;
var mouseX = event.clientX - element.boxObject.x;
var mouseY = event.clientY - element.boxObject.y;
var delta = mouseX - this.mMouseX;
var newEnd = this.moveTime(this.mEndDate,delta);
if(newEnd.compare(this.mEndDate) != 0) {
// we need to adapt this date in case we're dealing with
// an all-day event. this is because setting 'endDate' will
// automatically add one day extra for all-day events.
if(newEnd.isDate) {
newEnd.day -= 1;
newEnd.normalize();
}
// don't allow all-day events to be shorter than a single day.
if(!newEnd.isDate ||
(newEnd.compare(this.startDate) >= 0)) {
this.endDate = newEnd;
this.mMouseX = mouseX;
this.mMouseY = mouseY;
this.update();
}
}
}
]]>
</handler>
<handler event="mouseup">
<![CDATA[
this.mDragState = 0;
window.setCursor("auto");
]]>
</handler>
</handlers>
</binding>
<!-- id's are evil, use anonid -->
<binding id="attendees-page">
<content>
<xul:box orient="vertical" flex="1">
<xul:box orient="horizontal" align="center" pack="end">
<xul:spacer flex="1"/>
<xul:label value="&event.freebusy.suggest.slot;"/>
<xul:button label="&event.freebusy.previous.slot;" dir="normal" class="left-icon" anonid="previous-slot" oncommand="onPreviousSlot();"/>
<xul:button label="&event.freebusy.next.slot;" dir="reverse" class="right-icon" anonid="next-slot" oncommand="onNextSlot();"/>
<xul:spacer style="width: 10em"/>
<xul:label value="&event.freebusy.zoom;"/>
<xul:image class="zoom-out-icon"/>
<xul:menulist anonid="zoom-menulist" oncommand="onZoomFactor(this.value);">
<xul:menupopup>
<xul:menuitem label="400%" value="25"/>
<xul:menuitem label="200%" value="50"/>
<xul:menuitem label="100%" value="100"/>
<xul:menuitem label="50%" value="200"/>
<xul:menuitem label="25%" value="400"/>
</xul:menupopup>
</xul:menulist>
<xul:image class="zoom-in-icon"/>
</xul:box>
<xul:box orient="horizontal" flex="1">
<xul:box orient="vertical" flex="1">
<xul:box class="attendee-spacer-top"/>
<xul:attendees-list flex="1" anonid="attendees-list"/>
<xul:box class="attendee-spacer-bottom"/>
</xul:box>
<xul:splitter anonid="splitter"/>
<xul:box orient="vertical">
<xul:stack flex="1">
<xul:box orient="vertical" flex="1">
<xul:freebusy-timebar anonid="timebar" xbl:inherits="range"/>
<xul:freebusy-grid flex="1" anonid="freebusy-grid" xbl:inherits="range"/>
</xul:box>
<xul:selection-bar anonid="selection-bar" xbl:inherits="range"/>
</xul:stack>
<xul:scrollbar orient="horizontal" anonid="horizontal-scrollbar" maxpos="100"/>
</xul:box>
<xul:box orient="vertical" anonid="vertical-scrollbar-box" collapsed="true">
<xul:box class="attendee-spacer-top"/>
<xul:scrollbar orient="vertical" flex="1" anonid="vertical-scrollbar" maxpos="100"/>
<xul:box class="attendee-spacer-bottom"/>
</xul:box>
</xul:box>
<xul:box orient="horizontal">
<xul:box orient="vertical" flex="1">
<xul:box orient="horizontal" align="center">
<xul:box class="legend" status="FREE"/>
<xul:label value="&event.freebusy.legend.free;"/>
</xul:box>
<xul:box orient="horizontal" align="center">
<xul:box class="legend" status="BUSY"/>
<xul:label value="&event.freebusy.legend.busy;"/>
</xul:box>
<xul:box orient="horizontal" align="center">
<xul:box class="legend" status="UNKNOWN"/>
<xul:label value="&event.freebusy.legend.unknown;"/>
</xul:box>
<xul:box orient="horizontal">
<xul:button label="&event.freebusy.minus;" oncommand="onMinus();"/>
<xul:button label="&event.freebusy.plus;" oncommand="onPlus();"/>
</xul:box>
<xul:box orient="horizontal" collapsed="true">
<xul:label value="&event.organizer.label;" disabled="true"/>
<xul:textbox anonid="event-organizer" disabled="true" flex="true"/>
</xul:box>
</xul:box>
<xul:grid>
<xul:columns>
<xul:column/>
<xul:column flex="1"/>
</xul:columns>
<xul:rows>
<xul:row align="center">
<xul:spacer/>
<xul:checkbox anonid="all-day" oncommand="changeAllDay();" label="&event.alldayevent.label;"/>
</xul:row>
<xul:row align="center">
<xul:label value="&newevent.from.label;"/>
<xul:datetimepicker anonid="event-starttime" onchange="updateStartTime();"/>
<xul:label anonid="timezone-starttime"
crop="right"
class="text-link"
flex="1"
collapsed="true"
hyperlink="true"
onclick="editStartTimezone()"/>
</xul:row>
<xul:row align="center">
<xul:label value="&newevent.to.label;"/>
<xul:datetimepicker anonid="event-endtime" onchange="updateEndTime();"/>
<xul:label anonid="timezone-endtime"
crop="right"
class="text-link"
flex="1"
collapsed="true"
hyperlink="true"
onclick="editEndTimezone()"/>
</xul:row>
</xul:rows>
</xul:grid>
</xul:box>
<xul:separator class="groove"/>
</xul:box>
</content>
<implementation>
<field name="mStartDate">null</field>
<field name="mEndDate">null</field>
<field name="mStartTimezone">null</field>
<field name="mEndTimezone">null</field>
<field name="mDuration">null</field>
<field name="mStartHour">8</field>
<field name="mEndHour">19</field>
<field name="mStartHourDefault">8</field>
<field name="mEndHourDefault">19</field>
<field name="mOwnerID">null</field>
<field name="mUserID">null</field>
<field name="mOrganizerID">null</field>
<field name="mIsReadOnly">false</field>
<field name="mIsOrganizer">false</field>
<field name="mIgnoreUpdate">false</field>
<field name="mDisplayTimezone">true</field>
<field name="mUndoStack">[]</field>
<field name="mForce24Hours">false</field>
<field name="mZoomFactor">100</field>
<property name="zoomFactor">
<getter>
<![CDATA[
return this.mZoomFactor;
]]>
</getter>
<setter>
<![CDATA[
if(this.mZoomFactor == val)
return val;
this.mZoomFactor = val;
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
timebar.zoomFactor = this.mZoomFactor;
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
selectionbar.zoomFactor = this.mZoomFactor;
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
grid.zoomFactor = this.mZoomFactor;
// calling onResize() will update the scrollbars and everything else
// that needs to adopt the previously made changes. we need to call
// this after the changes have actually been made...
this.onResize();
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
if (scrollbar.hasAttribute("maxpos")) {
var curpos = scrollbar.getAttribute("curpos");
var maxpos = scrollbar.getAttribute("maxpos");
var ratio = curpos/maxpos;
timebar.scroll = ratio;
grid.scroll = ratio;
selectionbar.ratio = ratio;
}
return val;
]]>
</setter>
</property>
<property name="force24Hours">
<getter>
<![CDATA[
return this.mForce24Hours;
]]>
</getter>
<setter>
<![CDATA[
if(this.mForce24Hours == val)
return val;
this.mForce24Hours = val;
this.initTimeRange();
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
timebar.force24Hours = this.mForce24Hours;
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
selectionbar.force24Hours = this.mForce24Hours;
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
grid.force24Hours = this.mForce24Hours;
// calling onResize() will update the scrollbars and everything else
// that needs to adopt the previously made changes. we need to call
// this after the changes have actually been made...
this.onResize();
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
if (!scrollbar.hasAttribute("maxpos"))
return;
var curpos = scrollbar.getAttribute("curpos");
var maxpos = scrollbar.getAttribute("maxpos");
var ratio = curpos/maxpos;
timebar.scroll = ratio;
grid.scroll = ratio;
selectionbar.ratio = ratio;
return val;
]]>
</setter>
</property>
<constructor>
<![CDATA[
var args = window.arguments[0];
this.initTimeRange();
var self = this;
var load = function loadHandler() { self.onLoad(); };
window.addEventListener("load", load, true);
var resize = function resizeHandler() { self.onResize(); };
window.addEventListener("resize", resize, true);
var freebusy = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
var modify = function modifyHandler(event) {
self.onResize();
freebusy.onModify(event);
};
this.addEventListener('modify', modify, true);
var focus = function focusHandler(event) {
var scrollbar = document.getAnonymousElementByAttribute(self, "anonid", "vertical-scrollbar");
var attendees = document.getAnonymousElementByAttribute(self, "anonid", "attendees-list");
var maxpos = scrollbar.getAttribute("maxpos");
scrollbar.setAttribute("curpos",event.details/attendees.mMaxAttendees*maxpos);
};
this.addEventListener('rowchange', focus, true);
]]>
</constructor>
<method name="onZoomFactor">
<parameter name="aValue"/>
<body>
<![CDATA[
this.zoomFactor = parseInt(aValue);
]]>
</body>
</method>
<method name="initTimeRange">
<body>
<![CDATA[
this.mStartHour = this.mStartHourDefault;
this.mEndHour = this.mEndHourDefault;
var pb2 = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch2);
// get default start/end times from prefs and set on the
// view. if we hit an error (eg because sunbird's pref
// infrastructure hasn't created the pref yet), the
// defaults will do
try {
this.mStartHour = pb2.getIntPref("calendar.view.defaultstarthour");
this.mEndHour = pb2.getIntPref("calendar.view.defaultendhour");
} catch (ex) {}
if(this.force24Hours) {
this.mStartHour = 0;
this.mEndHour = 24;
}
]]>
</body>
</method>
<method name="loadDateTime">
<parameter name="aStartDate"/>
<parameter name="aEndDate"/>
<body>
<![CDATA[
this.mDuration = aEndDate.subtractDate(aStartDate);
var kDefaultTimezone = calendarDefaultTimezone();
this.mStartTimezone = aStartDate.timezone;
this.mEndTimezone = aEndDate.timezone;
this.mStartDate = aStartDate.getInTimezone(kDefaultTimezone);
this.mEndDate = aEndDate.getInTimezone(kDefaultTimezone);
this.mStartDate.normalize();
this.mEndDate.normalize();
this.mStartDate.makeImmutable();
this.mEndDate.makeImmutable();
]]>
</body>
</method>
<method name="saveDateTime">
<body>
<![CDATA[
var kDefaultTimezone = calendarDefaultTimezone();
var startTime = this.mStartDate.getInTimezone(this.mStartTimezone);
var endTime = this.mEndDate.getInTimezone(this.mEndTimezone);
this.mStartDate = startTime;
this.mEndDate = endTime;
}
]]>
</body>
</method>
<method name="propagateDateTime">
<body>
<![CDATA[
// fill the controls
this.updateDateTime();
// tell the timebar about the new start/enddate
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
timebar.startDate = this.mStartDate;
timebar.endDate = this.mEndDate;
timebar.refresh();
// tell the selection-bar about the new start/enddate
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
selectionbar.startDate = this.mStartDate;
selectionbar.endDate = this.mEndDate;
selectionbar.update();
// tell the freebusy grid about the new start/enddate
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
var refresh = (grid.startDate == null) ||
(grid.startDate.compare(this.mStartDate) != 0) ||
(grid.endDate == null) ||
(grid.endDate.compare(this.mEndDate) != 0);
grid.startDate = this.mStartDate;
grid.endDate = this.mEndDate;
if(refresh)
grid.forceRefresh();
// expand to 24hrs if the new range is outside of the default range.
var kDefaultTimezone = calendarDefaultTimezone();
var startTime = this.mStartDate.getInTimezone(kDefaultTimezone);
var endTime = this.mEndDate.getInTimezone(kDefaultTimezone);
if((startTime.hour < this.mStartHour) ||
(endTime.hour > this.mEndHour) ||
(startTime.isDate)) {
this.force24Hours = true;
}
]]>
</body>
</method>
<!-- assumes this.mStartDate and this.mEndDate are up to date. -->
<method name="updateDateTime">
<body>
<![CDATA[
// convert to default timezone if the timezone option
// is *not* checked, otherwise keep the specific timezone
// and display the labels in order to modify the timezone.
if(this.mDisplayTimezone) {
var startTime = this.mStartDate.getInTimezone(this.mStartTimezone);
var endTime = this.mEndDate.getInTimezone(this.mEndTimezone);
if (startTime.isDate) {
document.getAnonymousElementByAttribute(this, "anonid", "all-day").setAttribute("checked","true");
}
// in the case where the timezones are different but
// the timezone of the endtime is "UTC", we convert
// the endtime into the timezone of the starttime.
if(startTime && endTime) {
if(startTime.timezone != endTime.timezone) {
if(endTime.timezone == "UTC") {
endTime = endTime.getInTimezone(startTime.timezone);
}
}
}
// before feeding the date/time value into the control we need
// to set the timezone to 'floating' in order to avoid the
// automatic conversion back into the OS timezone.
startTime.timezone = "floating";
endTime.timezone = "floating";
document.getAnonymousElementByAttribute(this, "anonid", "event-starttime").value = startTime.jsDate;
document.getAnonymousElementByAttribute(this, "anonid", "event-endtime").value = endTime.jsDate;
} else {
var kDefaultTimezone = calendarDefaultTimezone();
var startTime = this.mStartDate.getInTimezone(kDefaultTimezone);
var endTime = this.mEndDate.getInTimezone(kDefaultTimezone);
if (startTime.isDate) {
document.getAnonymousElementByAttribute(this, "anonid", "all-day").setAttribute("checked","true");
}
// before feeding the date/time value into the control we need
// to set the timezone to 'floating' in order to avoid the
// automatic conversion back into the OS timezone.
startTime.timezone = "floating";
endTime.timezone = "floating";
document.getAnonymousElementByAttribute(this, "anonid", "event-starttime").value = startTime.jsDate;
document.getAnonymousElementByAttribute(this, "anonid", "event-endtime").value = endTime.jsDate;
}
this.updateTimezone();
this.updateAllDay();
]]>
</body>
</method>
<method name="timezoneString">
<parameter name="date"/>
<body>
<![CDATA[
var fragments = date.split('/');
var num = fragments.length;
if(num <= 1)
return fragments[0];
return fragments[num-2]+'/'+fragments[num-1];
]]>
</body>
</method>
<!-- assumes this.mStartDate and this.mEndDate are up to date. -->
<method name="updateTimezone">
<body>
<![CDATA[
this.mIgnoreUpdate = true;
if(this.mDisplayTimezone) {
var startTimezone = this.mStartTimezone;
var endTimezone = this.mEndTimezone;
var equalTimezones = false;
if(startTimezone && endTimezone) {
if(startTimezone == endTimezone || endTimezone == "UTC") {
equalTimezones = true;
}
}
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime");
var tzEnd = document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime");
if(startTimezone != null) {
tzStart.removeAttribute('collapsed');
tzStart.value = this.timezoneString(startTimezone);
} else {
tzStart.setAttribute('collapsed','true');
}
// we never display the second timezone if both are equal
if(endTimezone != null && !equalTimezones) {
tzEnd.removeAttribute('collapsed');
tzEnd.value = this.timezoneString(endTimezone);
} else {
tzEnd.setAttribute('collapsed','true');
}
} else {
document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime").setAttribute('collapsed','true');
document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime").setAttribute('collapsed','true');
}
this.mIgnoreUpdate = false;
]]>
</body>
</method>
<method name="updateStartTime">
<body>
<![CDATA[
if(this.mIgnoreUpdate)
return;
var startWidgetId = "event-starttime";
var endWidgetId = "event-endtime";
var startWidget = document.getAnonymousElementByAttribute(this, "anonid", startWidgetId);
var endWidget = document.getAnonymousElementByAttribute(this, "anonid", endWidgetId);
// jsDate is always in OS timezone, thus we create a calIDateTime
// object from the jsDate representation and simply set the new
// timezone instead of converting.
var kDefaultTimezone = calendarDefaultTimezone();
var start = jsDateToDateTime(startWidget.value);
start = start.getInTimezone(kDefaultTimezone);
if(this.mDisplayTimezone) {
start.timezone = this.mStartTimezone;
}
this.mStartDate = start.clone();
start.addDuration(this.mDuration);
start = start.getInTimezone(this.mEndTimezone);
if(this.mDisplayTimezone) {
start.timezone = this.mEndTimezone;
}
this.mEndDate = start;
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
var allDay = allDayElement.getAttribute("checked") == "true";
if(allDay) {
this.mStartDate.isDate = true;
this.mEndDate.isDate = true;
}
this.propagateDateTime();
]]>
</body>
</method>
<method name="updateEndTime">
<body>
<![CDATA[
if(this.mIgnoreUpdate)
return;
var startWidgetId = "event-starttime";
var endWidgetId = "event-endtime";
var startWidget = document.getAnonymousElementByAttribute(this, "anonid", startWidgetId);
var endWidget = document.getAnonymousElementByAttribute(this, "anonid", endWidgetId);
var saveStartTime = this.mStartDate;
var saveEndTime = this.mEndDate;
var kDefaultTimezone = calendarDefaultTimezone();
var start = jsDateToDateTime(startWidget.value);
start = start.getInTimezone(kDefaultTimezone);
if(this.mDisplayTimezone) {
start.timezone = this.mStartTimezone;
}
this.mStartDate = start;
var end = jsDateToDateTime(endWidget.value);
end = end.getInTimezone(kDefaultTimezone);
var timezone = this.mEndTimezone;
if(timezone == "UTC") {
if(this.mStartDate && this.mStartTimezone != this.mEndTimezone) {
timezone = this.mStartTimezone;
}
}
if(this.mDisplayTimezone) {
end.timezone = timezone;
}
this.mEndDate = end;
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
var allDay = allDayElement.getAttribute("checked") == "true";
if(allDay) {
this.mStartDate.isDate = true;
this.mEndDate.isDate = true;
}
// calculate the new duration of start/end-time.
// don't allow for negative durations.
var warning = false;
if(this.mEndDate.compare(this.mStartDate) >= 0) {
this.mDuration = end.subtractDate(start);
} else {
this.mStartDate = saveStartTime;
this.mEndDate = saveEndTime;
warning = true;
}
this.propagateDateTime();
if(warning) {
var callback = function func() {
var promptService =
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
promptService.alert(null,document.title,"The end date you entered occurs before the start date.");
}
setTimeout(callback, 1);
}
]]>
</body>
</method>
<method name="editStartTimezone">
<body>
<![CDATA[
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime");
if(tzStart.hasAttribute("disabled"))
return;
var self = this;
var args = new Object();
args.time = this.mStartDate.getInTimezone(this.mStartTimezone);
args.onOk = function(datetime) {
var equalTimezones = false;
if(self.mStartTimezone && self.mEndTimezone) {
if(self.mStartTimezone == self.mEndTimezone) {
equalTimezones = true;
}
}
self.mStartTimezone = datetime.timezone;
if(equalTimezones)
self.mEndTimezone = datetime.timezone;
self.propagateDateTime();
};
// open the dialog modally
openDialog("chrome://calendar/content/sun-calendar-event-dialog-timezone.xul", "_blank", "chrome,titlebar,modal,resizable", args);
]]>
</body>
</method>
<method name="editEndTimezone">
<body>
<![CDATA[
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime");
if(tzStart.hasAttribute("disabled"))
return;
var self = this;
var args = new Object();
args.time = gEndTime.getInTimezone(gEndTimezone);
args.onOk = function(datetime) {
var equalTimezones = false;
if(self.mStartTimezone && self.mEndTimezone) {
if(self.mStartTimezone == self.mEndTimezone) {
equalTimezones = true;
}
}
if(equalTimezones)
self.mStartTimezone = datetime.timezone;
self.mEndTimezone = datetime.timezone;
self.propagateDateTime();
};
// open the dialog modally
openDialog("chrome://calendar/content/sun-calendar-event-dialog-timezone.xul", "_blank", "chrome,titlebar,modal,resizable", args);
]]>
</body>
</method>
<method name="updateAllDay">
<body>
<![CDATA[
if(this.mIgnoreUpdate)
return;
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
var allDay = allDayElement.getAttribute("checked") == "true";
var startpicker = document.getAnonymousElementByAttribute(this, "anonid", "event-starttime");
var endpicker = document.getAnonymousElementByAttribute(this, "anonid", "event-endtime");
if(allDay) {
startpicker.setAttribute("timepickerdisabled","true");
endpicker.setAttribute("timepickerdisabled","true");
} else {
startpicker.removeAttribute("timepickerdisabled");
endpicker.removeAttribute("timepickerdisabled");
}
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime");
var tzEnd = document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime");
// disable the timezone links if 'allday' is checked OR the
// calendar of this item is read-only. in any other case we
// enable the links.
if(allDay) {
tzStart.setAttribute("disabled","true");
tzEnd.setAttribute("disabled","true");
tzStart.removeAttribute("class");
tzEnd.removeAttribute("class");
} else {
tzStart.removeAttribute("disabled");
tzEnd.removeAttribute("disabled");
tzStart.setAttribute("class","text-link");
tzEnd.setAttribute("class","text-link");
}
]]>
</body>
</method>
<method name="changeAllDay">
<body>
<![CDATA[
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
var allDay = allDayElement.getAttribute("checked") == "true";
this.mStartDate = this.mStartDate.clone();
this.mEndDate = this.mEndDate.clone();
if(allDay) {
this.mStartDate.isDate = true;
this.mEndDate.isDate = true;
} else {
this.mStartDate.isDate = false;
this.mEndDate.isDate = false;
}
this.mStartDate.normalize();
this.mEndDate.normalize();
this.propagateDateTime();
// after propagating the modified times we enforce some constraints
// on the zoom-factor. in case this events is now said to be all-day,
// we automatically enforce a 25% zoom-factor and disable the control.
var zoom = document.getAnonymousElementByAttribute(this, "anonid", "zoom-menulist");
if(allDay) {
zoom.value = "400";
zoom.setAttribute("disabled","true");
this.zoomFactor = zoom.value;
this.force24Hours = true;
} else {
zoom.removeAttribute("disabled");
}
]]>
</body>
</method>
<property name="startDate">
<getter>
<![CDATA[
return this.mStartDate;
]]>
</getter>
</property>
<property name="endDate">
<getter>
<![CDATA[
return this.mEndDate;
]]>
</getter>
</property>
<property name="attendees">
<getter>
<![CDATA[
var attendeelist = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
return attendeelist.attendees;
]]>
</getter>
</property>
<property name="organizer">
<getter>
<![CDATA[
var attendeelist = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
return attendeelist.organizer;
]]>
</getter>
</property>
<method name="onResize">
<body>
<![CDATA[
// don't do anything if we haven't been initialized.
if(!this.mStartDate || !this.mEndDate)
return;
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
var gridScrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
grid.fitDummyRows();
var ratio = grid.boxObject.width / grid.documentSize;
var maxpos = gridScrollbar.getAttribute("maxpos");
var inc = maxpos*ratio/(1-ratio);
gridScrollbar.setAttribute("pageincrement",inc);
var attendees = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
var attendeesScrollbar = document.getAnonymousElementByAttribute(this, "anonid", "vertical-scrollbar");
var box = document.getAnonymousElementByAttribute(this, "anonid", "vertical-scrollbar-box");
attendees.fitDummyRows();
var ratio = attendees.boxObject.height / attendees.documentSize;
if(ratio < 1) {
box.removeAttribute("collapsed");
var maxpos = attendeesScrollbar.getAttribute("maxpos");
var inc = maxpos*ratio/(1-ratio);
attendeesScrollbar.setAttribute("pageincrement",inc);
} else {
box.setAttribute("collapsed","true");
}
]]>
</body>
</method>
<method name="onLoad">
<body>
<![CDATA[
var args = window.arguments[0];
var startTime = args.startTime;
var endTime = args.endTime;
var calendar = args.calendar;
this.mDisplayTimezone = args.displayTimezone;
this.onChangeCalendar(calendar);
// we need to enfore several layout constraints which can't be modelled
// with plain xul and css, at least as far as i know.
const kStylesheet = "chrome://calendar/content/sun-calendar-event-dialog.css";
for each(var stylesheet in document.styleSheets) {
if (stylesheet.href == kStylesheet) {
// make the dummy-spacer #1 [top] the same height as the timebar
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
stylesheet.insertRule(".attendee-spacer-top { height: "+timebar.boxObject.height+"px; }", 0);
// make the dummy-spacer #2 [bottom] the same height as the scrollbar
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
stylesheet.insertRule(".attendee-spacer-bottom { height: "+scrollbar.boxObject.height+"px; }", 0);
break;
}
}
var zoom = document.getAnonymousElementByAttribute(this, "anonid", "zoom-menulist");
zoom.value = "100";
// Check if an all-day event has been passed in (to adapt endDate).
if (startTime.isDate) {
startTime = startTime.clone();
endTime = endTime.clone();
endTime.day -= 1;
// The date/timepicker uses jsDate internally. Because jsDate does
// not know the concept of dates we end up displaying times unequal
// to 00:00 for all-day events depending on local timezone setting.
// Calling normalize() recalculates that times to represent 00:00
// in local timezone.
endTime.normalize();
startTime.normalize();
// for all-day events we expand to 24hrs, set zoom-factor to 25%
// and disable the zoom-control.
this.force24Hours = true;
zoom.value = "400";
zoom.setAttribute("disabled","true");
this.zoomFactor = zoom.value;
}
this.loadDateTime(startTime,endTime);
this.propagateDateTime();
this.updateButtons();
]]>
</body>
</method>
<method name="onChangeCalendar">
<parameter name="calendar"/>
<body>
<![CDATA[
var args = window.arguments[0];
var organizer = args.organizer;
// set 'mIsReadOnly' if the calendar is read-only
if (calendar && calendar.readOnly)
this.mIsReadOnly = true;
// assume we're the organizer [in case that the calendar
// does not support the concept of identities].
this.mIsOrganizer = true;
//var organizerRow = document.getAnonymousElementByAttribute(this, "anonid", "event-organizer-row");
//organizerRow.setAttribute("collapsed","true");
try {
this.mUserID = "";
this.mOwnerID = "";
this.mOrganizerID = "";
var provider = calendar.QueryInterface(Components.interfaces.calIWcapCalendar);
this.mOwnerID = provider.ownerId;
this.mUserID = provider.session.userId;
this.mOrganizerID = ((organizer == null || organizer.id == null)
? this.mOwnerID // sensible default
: organizer.id);
// set 'mIsOrganizer' if the current calid originally scheduled this event.
this.mIsOrganizer = false;
if(this.mOwnerID == this.mOrganizerID)
this.mIsOrganizer = true;
// display the organizer [try to get the common name from the calid]
//TODO: either remove this row or enable this again -> organizerRow.removeAttribute("collapsed");
var organizer = document.getAnonymousElementByAttribute(this, "anonid", "event-organizer");
organizer.value = this.mOrganizerID;
var organizerCalendar = provider.session.getCalendarByCalId(this.mOrganizerID);
var props = organizerCalendar.getCalendarProperties("X-S1CS-CALPROPS-COMMON-NAME",{});
if(props.length > 0)
organizer.value = props[0];
}
catch(e) {}
if(this.mIsReadOnly || !this.mIsOrganizer) {
document.getAnonymousElementByAttribute(this, "anonid", "next-slot").setAttribute('disabled', 'true');
document.getAnonymousElementByAttribute(this, "anonid", "previous-slot").setAttribute('disabled', 'true');
}
var freebusy = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
freebusy.onChangeCalendar(calendar);
]]>
</body>
</method>
<method name="updateButtons">
<body>
<![CDATA[
var previousButton = document.getAnonymousElementByAttribute(this, "anonid", "previous-slot");
if(this.mUndoStack.length > 0) {
previousButton.removeAttribute('disabled');
} else {
previousButton.setAttribute('disabled', 'true');
}
]]>
</body>
</method>
<method name="onNextSlot">
<body>
<![CDATA[
// store the current setting in the undo-stack.
var currentSlot = {};
currentSlot.startTime = this.mStartDate;
currentSlot.endTime = this.mEndDate;
this.mUndoStack.push(currentSlot);
// ask the grid for the next possible timeslot.
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
var duration = this.mEndDate.subtractDate(this.mStartDate);
var start = grid.nextSlot();
var end = start.clone();
end.addDuration(duration);
if(start.isDate) {
end.day++;
end.normalize();
}
this.mStartDate = start.clone();
this.mEndDate = end.clone();
var endDate = this.mEndDate.clone();
// Check if an all-day event has been passed in (to adapt endDate).
if (this.mStartDate.isDate) {
this.mEndDate.day -= 1;
// The date/timepicker uses jsDate internally. Because jsDate does
// not know the concept of dates we end up displaying times unequal
// to 00:00 for all-day events depending on local timezone setting.
// Calling normalize() recalculates that times to represent 00:00
// in local timezone.
this.mEndDate.normalize();
this.mStartDate.normalize();
endDate.normalize();
}
this.mStartDate.makeImmutable();
this.mEndDate.makeImmutable();
endDate.makeImmutable();
this.propagateDateTime();
// scroll the grid/timebar such that the current time is visible
this.scrollToCurrentTime();
this.updateButtons();
]]>
</body>
</method>
<method name="onPreviousSlot">
<body>
<![CDATA[
var previousSlot = this.mUndoStack.pop();
if(!previousSlot)
return;
// in case the new starttime happens to be scheduled
// on a different day, we also need to update the
// complete freebusy informations and appropriate
// underlying arrays holding the information.
var refresh = previousSlot.startTime.day != this.mStartDate.day;
this.mStartDate = previousSlot.startTime.clone();
this.mEndDate = previousSlot.endTime.clone();
var endDate = this.mEndDate.clone();
this.propagateDateTime();
// scroll the grid/timebar such that the current time is visible
this.scrollToCurrentTime();
this.updateButtons();
if(refresh) {
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
grid.forceRefresh();
}
]]>
</body>
</method>
<method name="onMinus">
<body>
<![CDATA[
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
var ratio = timebar.scroll;
ratio -= timebar.step;
if(ratio <= 0.0)
ratio = 0.0;
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
var maxpos = scrollbar.getAttribute("maxpos");
scrollbar.setAttribute("curpos",ratio*maxpos);
]]>
</body>
</method>
<method name="onPlus">
<body>
<![CDATA[
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
var ratio = timebar.scroll;
ratio += timebar.step;
if(ratio >= 1.0)
ratio = 1.0;
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
var maxpos = scrollbar.getAttribute("maxpos");
scrollbar.setAttribute("curpos",ratio*maxpos);
]]>
</body>
</method>
<method name="scrollToCurrentTime">
<body>
<![CDATA[
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
var ratio = (this.mStartDate.hour-this.mStartHour) * timebar.step;
if(ratio <= 0.0)
ratio = 0.0;
if(ratio >= 1.0)
ratio = 1.0;
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
var maxpos = scrollbar.getAttribute("maxpos");
scrollbar.setAttribute("curpos",ratio*maxpos);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="DOMMouseScroll" phase="capturing">
<![CDATA[
// ignore mouse scrolling for now...
event.stopPropagation();
]]>
</handler>
<handler event="DOMAttrModified">
<![CDATA[
if(event.attrName == "width") {
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
selectionbar.setWidth(selectionbar.boxObject.width);
return;
}
// synchronize grid and attendee list
var target = event.originalTarget;
if(target.hasAttribute("anonid")) {
if(target.getAttribute("anonid") == "input") {
if(event.attrName == "focused") {
if(event.newValue == "true") {
var attendees = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
if(grid.firstVisibleRow != attendees.firstVisibleRow)
grid.firstVisibleRow = attendees.firstVisibleRow;
}
}
}
}
var scrollbar = event.originalTarget.parentNode;
if (!scrollbar.hasAttribute("maxpos"))
return;
if(scrollbar.getAttribute("anonid") == "vertical-scrollbar") {
var attendees = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
if(event.attrName == "curpos") {
var maxpos = scrollbar.getAttribute("maxpos");
attendees.ratio = event.newValue/maxpos;
}
grid.firstVisibleRow = attendees.firstVisibleRow;
}
else if(scrollbar.getAttribute("anonid") == "horizontal-scrollbar") {
if(event.attrName == "curpos") {
var maxpos = scrollbar.getAttribute("maxpos");
var ratio = event.newValue/maxpos;
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
timebar.scroll = ratio;
grid.scroll = ratio;
selectionbar.ratio = ratio;
}
}
]]>
</handler>
<handler event="timebar">
<![CDATA[
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
selectionbar.init(event.details,event.height);
]]>
</handler>
<handler event="timechange">
<![CDATA[
var start = event.startDate.getInTimezone(this.mStartTimezone);
var end = event.endDate.getInTimezone(this.mEndTimezone);
start.normalize();
end.normalize();
this.loadDateTime(start,end);
// fill the controls
this.updateDateTime();
// tell the timebar about the new start/enddate
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
timebar.startDate = this.mStartDate;
timebar.endDate = this.mEndDate;
timebar.refresh();
// tell the freebusy grid about the new start/enddate
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
var refresh = (grid.startDate == null) ||
(grid.startDate.compare(this.mStartDate) != 0) ||
(grid.endDate == null) ||
(grid.endDate.compare(this.mEndDate) != 0);
grid.startDate = this.mStartDate;
grid.endDate = this.mEndDate;
if(refresh)
grid.forceRefresh();
]]>
</handler>
</handlers>
</binding>
</bindings>