releases-comm-central/calendar/base/content/agenda-listbox.js

1084 строки
36 KiB
JavaScript

/* ***** 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) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Berend Cornelius <berend.cornelius@sun.com>
* Philipp Kewisch <mozilla@kewis.ch>
*
* 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 ***** */
function Synthetic(aOpen, aDuration) {
this.open = aOpen;
this.duration = aDuration;
}
var agendaListbox = {
agendaListboxControl: null,
pendingRefresh: null,
kDefaultTimezone: null,
showsToday: false
};
/**
* Initialize the agenda listbox, used on window load.
*/
agendaListbox.init =
function initAgendaListbox() {
this.agendaListboxControl = document.getElementById("agenda-listbox");
this.agendaListboxControl.removeAttribute("suppressonselect");
var showTodayHeader = (document.getElementById("today-header-hidden").getAttribute("checked") == "true");
var showTomorrowHeader = (document.getElementById("tomorrow-header-hidden").getAttribute("checked") == "true");
var showSoonHeader = (document.getElementById("nextweek-header-hidden").getAttribute("checked") == "true");
this.today = new Synthetic(showTodayHeader, 1);
this.addPeriodListItem(this.today, "today-header");
this.tomorrow = new Synthetic(showTomorrowHeader, 1);
var soondays = getPrefSafe("calendar.agendaListbox.soondays", 5);
this.soon = new Synthetic(showSoonHeader, soondays);
this.periods = [this.today, this.tomorrow, this.soon];
// Make sure the agenda listbox is unloaded
var self = this;
window.addEventListener("unload",
function unload_agendaListbox() {
self.uninit();
},
false);
};
/**
* Clean up the agenda listbox, used on window unload.
*/
agendaListbox.uninit =
function uninit() {
if (this.calendar) {
this.calendar.removeObserver(this.calendarObserver);
}
for each (var period in this.periods) {
if (period.listItem) {
period.listItem.getCheckbox()
.removeEventListener("CheckboxStateChange",
this.onCheckboxChange,
true);
}
}
};
/**
* Adds a period item to the listbox. This is a section of the today pane like
* "Today", "Tomorrow", and is usually a <agenda-checkbox-richlist-item> tag. A
* copy of the template node is made and added to the agenda listbox.
*
* @param aPeriod The period item to add.
* @param aItemId The id of an <agenda-checkbox-richlist-item> to add to,
* without the "-hidden" suffix.
*/
agendaListbox.addPeriodListItem =
function addPeriodListItem(aPeriod, aItemId) {
aPeriod.listItem = document.getElementById(aItemId + "-hidden").cloneNode(true);
agendaListbox.agendaListboxControl.appendChild(aPeriod.listItem);
aPeriod.listItem.id = aItemId;
aPeriod.listItem.getCheckbox().setChecked(aPeriod.open);
aPeriod.listItem.getCheckbox().addEventListener("CheckboxStateChange", this.onCheckboxChange, true);
}
/**
* Remove a period item from the agenda listbox.
* @see agendaListbox::addPeriodListItem
*/
agendaListbox.removePeriodListItem =
function removePeriodListItem(aPeriod) {
if (aPeriod.listItem) {
aPeriod.listItem.getCheckbox().removeEventListener("CheckboxStateChange", this.onCheckboxChange, true);
if (aPeriod.listItem) {
this.agendaListboxControl.removeChild(aPeriod.listItem);
aPeriod.listItem = null;
}
}
}
/**
* Handler function called when changing the checkbox state on period items.
*
* @param event The DOM event that triggered the checkbox state change.
*/
agendaListbox.onCheckboxChange =
function onCheckboxChange(event) {
var periodCheckbox = event.target;
var lopen = (periodCheckbox.getAttribute("checked") == "true");
var listItem = getParentNodeOrThis(periodCheckbox, "agenda-checkbox-richlist-item");
var period = listItem.getItem();
period.open= lopen;
// as the agenda-checkboxes are only transient we have to set the "checked"
// attribute at their hidden origins to make that attribute persistent.
document.getElementById(listItem.id + "-hidden").setAttribute("checked",
periodCheckbox.getAttribute("checked"));
if (lopen) {
agendaListbox.refreshCalendarQuery(period.start, period.end);
} else {
listItem = listItem.nextSibling;
do {
var leaveloop = (listItem == null);
if (!leaveloop) {
var nextItemSibling = listItem.nextSibling;
leaveloop = (!agendaListbox.isEventListItem(listItem));
if (!leaveloop) {
agendaListbox.agendaListboxControl.removeChild(listItem);
listItem = nextItemSibling;
}
}
} while (!leaveloop);
}
calendarController.onSelectionChanged({detail: []});
};
/**
* Handler function called when an agenda listbox item is selected
*
* @param aListItem The agenda-base-richlist-item that was selected.
*/
agendaListbox.onSelect =
function onSelect(aListItem) {
var listbox = document.getElementById("agenda-listbox");
listbox.focus();
listbox.removeAttribute("disabled");
var item = aListItem || listbox.selectedItem;
if (aListItem) {
listbox.selectedItem = item;
}
if (item) {
item.removeAttribute("disabled");
}
calendarController.onSelectionChanged({detail: agendaListbox.getSelectedItems()});
}
/**
* Handler function called when the agenda listbox becomes focused
*/
agendaListbox.onFocus =
function onFocus() {
var listbox = document.getElementById("agenda-listbox");
listbox.removeAttribute("disabled");
this.enableListItems();
calendarController.onSelectionChanged({detail: agendaListbox.getSelectedItems()});
}
/**
* Handler function called when the agenda listbox loses focus.
*/
agendaListbox.onBlur =
function onBlur() {
var item = document.getElementById("agenda-listbox").selectedItem;
if (item) {
item.setAttribute("disabled","true");
}
calendarController.onSelectionChanged({detail: []});
}
/**
* Enables all child nodes of the agenda listbox
*/
agendaListbox.enableListItems =
function enableListItems() {
var childNodes = document.getElementById("agenda-listbox").childNodes;
for (var i = 0;i < childNodes.length; i++) {
var listItem = childNodes[i];
listItem.removeAttribute("disabled");
}
}
/**
* Handler function called when a key was pressed on the agenda listbox
*/
agendaListbox.onKeyPress =
function onKeyPress(aEvent) {
var listItem = aEvent.target;
if (listItem.localName == "richlistbox") {
listItem = listItem.selectedItem;
}
switch(aEvent.keyCode) {
case aEvent.DOM_VK_RETURN:
document.getElementById('agenda_edit_event_command').doCommand();
break;
case aEvent.DOM_VK_DELETE:
document.getElementById('agenda_delete_event_command').doCommand();
aEvent.stopPropagation();
aEvent.preventDefault();
break;
case aEvent.DOM_VK_LEFT:
if (!this.isEventListItem(listItem)) {
listItem.getCheckbox().setChecked(false);
}
break;
case aEvent.DOM_VK_RIGHT:
if (!this.isEventListItem(listItem)) {
listItem.getCheckbox().setChecked(true);
}
break;
}
}
/**
* Calls the event dialog to edit the currently selected item
*/
agendaListbox.editSelectedItem =
function editSelectedItem() {
var listItem = document.getElementById("agenda-listbox").selectedItem;
if (listItem) {
modifyEventWithDialog(listItem.occurrence, null, true);
}
}
/**
* Finds the appropriate period for the given item, i.e finds "Tomorrow" if the
* item occurrs tomorrow.
*
* @param aItem The item to find the period for.
*/
agendaListbox.findPeriodsForItem =
function findPeriodsForItem(aItem) {
var retPeriods = [];
for (var i = 0; i < this.periods.length; i++) {
if (this.periods[i].open) {
if (checkIfInRange(aItem, this.periods[i].start, this.periods[i].end)) {
retPeriods.push(this.periods[i]);
}
}
}
return retPeriods;
};
/**
* Gets the start of the earliest period shown in the agenda listbox
*/
agendaListbox.getStart =
function getStart(){
var retStart = null;
for (var i = 0; i < this.periods.length; i++) {
if (this.periods[i].open) {
retStart = this.periods[i].start;
break;
}
}
return retStart;
}
/**
* Gets the end of the latest period shown in the agenda listbox
*/
agendaListbox.getEnd =
function getEnd(){
var retEnd = null;
for (var i = this.periods.length - 1; i >= 0; i--) {
if (this.periods[i].open) {
retEnd = this.periods[i].end;
break;
}
}
return retEnd;
}
/**
* Adds an item to an agenda period before another existing item.
*
* @param aNewItem The calIItemBase to add.
* @param aAgendaItem The existing item to insert before.
* @param aPeriod The period to add the item to.
* @param visible If true, the item should be visible.
* @return The newly created XUL element.
*/
agendaListbox.addItemBefore =
function addItemBefore(aNewItem, aAgendaItem, aPeriod, visible) {
var newelement = null;
if (aNewItem.startDate.isDate) {
newelement = createXULElement("agenda-allday-richlist-item");
} else {
newelement = createXULElement("agenda-richlist-item")
}
// set the item at the richlistItem. When the duration of the period
// is bigger than 1 (day) the starttime of the item has to include
// information about the day of the item
if (aAgendaItem == null) {
this.agendaListboxControl.appendChild(newelement);
} else {
this.agendaListboxControl.insertBefore(newelement, aAgendaItem);
}
newelement.setOccurrence(aNewItem, (aPeriod.duration > 1));
newelement.removeAttribute("selected");
return newelement;
}
/**
* Adds an item to the agenda listbox. This function finds the correct period
* for the item and inserts it correctly so the period stays sorted.
*
* @param aItem The calIItemBase to add.
* @return The newly created XUL element.
*/
agendaListbox.addItem =
function addItem(aItem) {
if (!isEvent(aItem)) {
return null;
}
var periods = this.findPeriodsForItem(aItem);
if (periods.length == 0) {
return null;
}
for (var i = 0; i < periods.length; i++) {
period = periods[i];
var complistItem = period.listItem;
var visible = complistItem.getCheckbox().checked;
var newlistItem = null;
if ((aItem.startDate.isDate) && (period.duration == 1)) {
if (this.getListItems(aItem, period).length == 0) {
this.addItemBefore(aItem, period.listItem.nextSibling, period, visible);
}
} else {
do {
var prevlistItem = complistItem;
var complistItem = complistItem.nextSibling;
if (!this.isEventListItem(complistItem)) {
newlistItem = this.addItemBefore(aItem, complistItem, period, visible);
break;
} else {
var compitem = complistItem.occurrence;
if (this.isSameEvent(aItem, compitem)) {
// The same event occurs on several calendars but we only
// display the first one.
// TODO: find a way to display this special circumstance
break;
} else if (this.isBefore(aItem, compitem)) {
if (this.isSameEvent(aItem, compitem)) {
newlistItem = this.addItemBefore(aItem, complistItem, period, visible);
break
} else {
newlistItem = this.addItemBefore(aItem, complistItem, period, visible);
break;
}
}
}
} while (complistItem)
}
}
return newlistItem;
};
/**
* Checks if the given item happens before the comparison item.
*
* @param aItem The item to compare.
* @param aCompItem The item to compare with.
* @return True, if the aItem happens before aCompItem.
*/
agendaListbox.isBefore =
function isBefore(aItem, aCompItem) {
if (aCompItem.startDate.day == aItem.startDate.day) {
if (aItem.startDate.isDate) {
return true;
} else if (aCompItem.startDate.isDate) {
return false;
}
}
var comp = aItem.startDate.compare(aCompItem.startDate);
if (comp == 0) {
comp = aItem.endDate.compare(aCompItem.endDate);
}
return (comp <= 0);
}
/**
* Gets the listitems for a given item, possibly in a given period.
*
* @param aItem The item to get the list items for.
* @param aPeriod (optional) the period to search in.
* @return An array of list items for the given item.
*/
agendaListbox.getListItems =
function getListItems(aItem, aPeriod) {
var retlistItems = new Array();
var periods = [aPeriod];
if (!aPeriod) {
var periods = this.findPeriodsForItem(aItem);
}
if (periods.length > 0) {
for (var i = 0; i < periods.length; i++) {
period = periods[i];
var complistItem = period.listItem;
do {
var complistItem = complistItem.nextSibling;
var leaveloop = (!this.isEventListItem(complistItem));
if (!leaveloop) {
if (this.isSameEvent(aItem, complistItem.occurrence)){
retlistItems.push(complistItem);
break;
}
}
} while (!leaveloop)
}
}
return retlistItems;
}
/**
* Removes the given item from the agenda listbox
*
* @param aItem The item to remove.
* @param aMoveSelection If true, the selection will be moved to the next
* sibling that is not an period item.
* @return Returns true if the removed item was selected.
*/
agendaListbox.deleteItem =
function deleteItem(aItem, aMoveSelection) {
var isSelected = false;
var listItems = this.getListItems(aItem);
if (listItems.length > 0) {
for (var i = listItems.length - 1; i >= 0; i--) {
var listItem = listItems[i];
var isSelected2 = listItem.selected;
if (isSelected2 && !isSelected) {
isSelected = true;
if (aMoveSelection) {
this.moveSelection();
}
}
this.agendaListboxControl.removeChild(listItem);
}
}
return isSelected;
}
/**
* Compares two items to see if they have the same id and their start date
* matches
*
* @param aItem The item to compare.
* @param aCompItem The item to compare with.
* @return True, if the items match with the above noted criteria.
*/
agendaListbox.isSameEvent =
function isSameEvent(aItem, aCompItem) {
return ((aItem.id == aCompItem.id) &&
(aItem[calGetStartDateProp(aItem)].compare(aCompItem[calGetStartDateProp(aCompItem)]) == 0));
}
/**
* Checks if the currently selected node in the listbox is an Event item (not a
* period item).
*
* @return True, if the node is not a period item.
*/
agendaListbox.isEventSelected =
function isEventSelected() {
var listItem = this.agendaListboxControl.selectedItem;
if (listItem) {
return (this.isEventListItem(listItem));
}
return false;
}
/**
* Delete the selected item from its calendar (if it is an event item)
*
* @param aDoNotConfirm If true, the user will not be prompted.
*/
agendaListbox.deleteSelectedItem =
function deleteSelectedItem(aDoNotConfirm) {
var listItem = this.agendaListboxControl.selectedItem;
if (this.isEventListItem(listItem)) {
var selectedItems = [listItem.occurrence];
calendarViewController.deleteOccurrences(selectedItems.length,
selectedItems,
false,
aDoNotConfirm);
}
}
/**
* If a Period item is targeted by the passed DOM event, opens the event dialog
* with the period's start date prefilled.
*
* @param aEvent The DOM event that targets the period.
*/
agendaListbox.createNewEvent =
function createNewEvent(aEvent) {
if (!this.isEventListItem(aEvent.target)){
// Create new event for the date currently displayed in the agenda. Setting
// isDate = true automatically makes the start time be the next full hour.
var eventStart = agendaListbox.today.start.clone();
eventStart.isDate = true;
createEventWithDialog(getSelectedCalendar(), eventStart);
}
}
/**
* Rebuilds the agenda popup menu from the agenda-menu-box, disabling items if a
* period was selected.
*
* XXX Why is this needed?
*
*/
agendaListbox.buildAgendaPopupMenu =
function enableAgendaPopupMenu() {
var listItem = this.agendaListboxControl.selectedItem;
var enabled = this.isEventListItem(listItem);
var popup = document.getElementById("agenda-menu");
while (popup.hasChildNodes()) {
popup.removeChild(popup.firstChild);
}
var menuitems = document.getElementById("agenda-menu-box").childNodes;
for (var i= 0; i < menuitems.length; i++) {
setBooleanAttribute(menuitems[i], "disabled", !enabled);
popup.appendChild(menuitems[i].cloneNode(true));
}
}
/**
* Refreshes the agenda listbox. If aStart or aEnd is not passed, the agenda
* listbox's limiting dates will be used.
*
* @param aStart (optional) The start date for the item query.
* @param aEnd (optional) The end date for the item query.
*/
agendaListbox.refreshCalendarQuery =
function refreshCalendarQuery(aStart, aEnd) {
if (this.mBatchCount > 0) {
return;
}
var pendingRefresh = this.pendingRefresh;
if (pendingRefresh) {
if (calInstanceOf(pendingRefresh, Components.interfaces.calIOperation)) {
this.pendingRefresh = null;
pendingRefresh.cancel(null);
} else {
return;
}
}
if ((!aStart) && (!aEnd)) {
this.removeListItems();
}
if (!aStart) {
aStart = this.getStart();
}
if (!aEnd) {
aEnd = this.getEnd();
}
if (aStart && aEnd) {
var filter = this.calendar.ITEM_FILTER_CLASS_OCCURRENCES |
this.calendar.ITEM_FILTER_TYPE_EVENT;
this.pendingRefresh = true;
pendingRefresh = this.calendar.getItems(filter, 0, aStart, aEnd,
this.calendarOpListener);
if (pendingRefresh && pendingRefresh.isPending) { // support for calIOperation
this.pendingRefresh = pendingRefresh;
}
}
};
/**
* Sets up the calendar for the agenda listbox.
*/
agendaListbox.setupCalendar =
function setupCalendar() {
this.init();
if (this.calendar == null) {
this.calendar = getCompositeCalendar();
}
if (this.calendar) {
// XXX This always gets called, does that happen on purpose?
this.calendar.removeObserver(this.calendarObserver);
}
this.calendar.addObserver(this.calendarObserver);
if (this.mListener){
this.mListener.updatePeriod();
}
};
/**
* Refreshes the period dates, especially when a period is showing "today".
* Usually called at midnight to update the agenda pane. Also retrieves the
* items from the calendar.
*
* @see #refreshCalendarQuery
* @param newDate The first date to show if the agenda pane doesn't show
* today.
*/
agendaListbox.refreshPeriodDates =
function refreshPeriodDates(newDate) {
this.kDefaultTimezone = calendarDefaultTimezone();
// Today: now until midnight of tonight
var oldshowstoday = this.showstoday;
this.showstoday = this.showsToday(newDate);
if ((this.showstoday) && (!oldshowstoday)) {
this.addPeriodListItem(this.tomorrow, "tomorrow-header");
this.addPeriodListItem(this.soon, "nextweek-header");
} else if (!this.showstoday) {
this.removePeriodListItem(this.tomorrow);
this.removePeriodListItem(this.soon);
}
newDate.isDate = true;
for (var i = 0; i < this.periods.length; i++) {
var curPeriod = this.periods[i];
newDate.hour = newDate.minute = newDate.second = 0;
if ((i == 0) && (this.showstoday)){
curPeriod.start = now();
} else {
curPeriod.start = newDate.clone();
}
newDate.day += curPeriod.duration;
curPeriod.end = newDate.clone();
curPeriod.listItem.setItem(curPeriod, this.showstoday);
}
this.refreshCalendarQuery();
};
/**
* Adds a listener to this agenda listbox.
*
* @param aListener The listener to add.
*/
agendaListbox.addListener =
function addListener(aListener) {
this.mListener = aListener;
}
/**
* Checks if the agenda listbox is showing "today". Without arguments, this
* function assumes the today attribute of the agenda listbox.
*
* @param aStartDate (optional) The day to check if its "today".
* @return Returns true if today is shown.
*/
agendaListbox.showsToday =
function showsToday(aStartDate) {
var lstart = aStartDate;
if (!lstart) {
lstart = this.today.start;
}
var lshowsToday = (sameDay(now(), lstart));
if (lshowsToday) {
this.periods = [this.today, this.tomorrow, this.soon];
} else {
this.periods = [this.today];
}
return lshowsToday;
};
/**
* Moves the selection. Moves down unless the next item is a period item, in
* which case the selection moves up.
*/
agendaListbox.moveSelection =
function moveSelection() {
var selindex = this.agendaListboxControl.selectedIndex;
if ( !this.isEventListItem(this.agendaListboxControl.selectedItem.nextSibling)) {
this.agendaListboxControl.goUp();
} else {
this.agendaListboxControl.goDown();
}
}
/**
* Gets an array of selected items. If a period node is selected, it is not
* included.
*
* @return An array with all selected items.
*/
agendaListbox.getSelectedItems =
function getSelectedItems() {
var selindex = this.agendaListboxControl.selectedIndex;
var items = [];
if (this.isEventListItem(this.agendaListboxControl.selectedItem)) {
// If at some point we support selecting multiple items, this array can
// be expanded.
items = [this.agendaListboxControl.selectedItem.occurrence];
}
return items;
}
/**
* Checks if the passed node in the listbox is an Event item (not a
* period item).
*
* @param aListItem The node to check for.
* @return True, if the node is not a period item.
*/
agendaListbox.isEventListItem =
function isEventListItem(aListItem) {
var isEventListItem = (aListItem != null);
if (isEventListItem) {
var localName = aListItem.localName;
isEventListItem = ((localName == "agenda-richlist-item") ||
(localName == "agenda-allday-richlist-item"));
}
return isEventListItem;
}
/**
* Removes all Event items, keeping the period items intact.
*/
agendaListbox.removeListItems =
function removeListItems() {
var listItem = this.agendaListboxControl.lastChild;
if (listItem) {
var leaveloop = false;
do {
var newlistItem = null;
if (listItem) {
newlistItem = listItem.previousSibling;
} else {
leaveloop = true;
}
if (this.isEventListItem(listItem)) {
if (!listItem.isSameNode(this.agendaListboxControl.firstChild)) {
this.agendaListboxControl.removeChild(listItem);
} else {
leaveloop = true;
}
}
listItem = newlistItem;
} while (!leaveloop)
}
}
/**
* Gets the list item node by its associated event's hashId.
*
* @return The XUL node if successful, otherwise null.
*/
agendaListbox.getListItemByHashId =
function getListItemByHashId(ahashId) {
var listItem = this.agendaListboxControl.firstChild;
var leaveloop = false;
do {
if (this.isEventListItem(listItem)) {
if (listItem.occurrence.hashId == ahashId) {
return listItem;
}
}
listItem = listItem.nextSibling;
leaveloop = (listItem == null);
} while (!leaveloop)
return null;
}
/**
* The operation listener used for calendar queries.
* Implements calIOperationListener.
*/
agendaListbox.calendarOpListener = {
agendaListbox : agendaListbox
};
/**
* Called when all items have been retrieved from the calendar.
* @see calIOperationListener
*/
agendaListbox.calendarOpListener.onOperationComplete =
function listener_onOperationComplete(calendar, status, optype, id,
detail) {
// signal that the current operation finished.
this.agendaListbox.pendingRefresh = null;
setCurrentEvent();
};
/**
* Called when an item has been retrieved, adds all items to the agenda listbox.
* @see calIOperationListener
*/
agendaListbox.calendarOpListener.onGetResult =
function listener_onGetResult(calendar, status, itemtype, detail, count, items) {
if (!Components.isSuccessCode(status))
return;
items.forEach(this.agendaListbox.addItem, this.agendaListbox);
};
/**
* Calendar and composite observer, used to keep agenda listbox up to date.
* @see calIObserver
* @see calICompositeObserver
*/
agendaListbox.calendarObserver = {
agendaListbox: agendaListbox
};
agendaListbox.calendarObserver.QueryInterface =
function agenda_QI(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;
};
// calIObserver:
agendaListbox.calendarObserver.onStartBatch = function agenda_onBatchStart() {
this.mBatchCount++;
};
agendaListbox.calendarObserver.onEndBatch =
function() {
this.mBatchCount--;
if (this.mBatchCount == 0) {
// Rebuild everything
this.agendaListbox.refreshCalendarQuery();
}
};
agendaListbox.calendarObserver.onLoad = function() {
this.agendaListbox.refreshCalendarQuery();
};
agendaListbox.calendarObserver.onAddItem =
function observer_onAddItem(item)
{
if (this.mBatchCount) {
return;
}
if (!isEvent(item)) {
return;
}
// get all sub items if it is a recurring item
var occs = this.getOccurrencesBetween(item);
occs.forEach(this.agendaListbox.addItem, this.agendaListbox);
setCurrentEvent();
};
agendaListbox.calendarObserver.getOccurrencesBetween =
function getOccurrencesBetween(aItem) {
var occs = [];
var start = this.agendaListbox.getStart();
var end = this.agendaListbox.getEnd();
if (start && end) {
occs = aItem.getOccurrencesBetween(start, end, {});
}
return occs;
}
agendaListbox.calendarObserver.onDeleteItem =
function observer_onDeleteItem(item, rebuildFlag) {
this.onLocalDeleteItem(item, true);
};
agendaListbox.calendarObserver.onLocalDeleteItem =
function observer_onLocalDeleteItem(item, moveSelection) {
if (this.mBatchCount) {
return false;
}
if (!isEvent(item)) {
return false;
}
var selectedItemHashId = -1;
// get all sub items if it is a recurring item
var occs = this.getOccurrencesBetween(item);
for (var i = 0; i < occs.length; i++) {
var isSelected = this.agendaListbox.deleteItem(occs[i], moveSelection);
if (isSelected) {
selectedItemHashId = occs[i].hashId;
}
}
return selectedItemHashId;
};
agendaListbox.calendarObserver.onModifyItem =
function observer_onModifyItem(newItem, oldItem) {
if (this.mBatchCount) {
return;
}
var selectedItemHashId = this.onLocalDeleteItem(oldItem, false);
if (!isEvent(newItem)) {
return;
}
this.onAddItem(newItem);
if (selectedItemHashId != -1) {
var listItem = agendaListbox.getListItemByHashId(selectedItemHashId);
if (listItem) {
agendaListbox.agendaListboxControl.clearSelection();
agendaListbox.agendaListboxControl.ensureElementIsVisible(listItem);
agendaListbox.agendaListboxControl.selectedItem = listItem;
}
}
setCurrentEvent();
};
agendaListbox.calendarObserver.onError = function(cal, errno, msg) {};
agendaListbox.calendarObserver.onPropertyChanged = function(aCalendar, aName, aValue, aOldValue) {
switch (aName) {
case "disabled":
this.agendaListbox.refreshCalendarQuery();
break;
case "color":
for (var node = agendaListbox.agendaListboxControl.firstChild;
node;
node = node.nextSibling) {
// Change color on all nodes that don't do so themselves, which
// is currently only he agenda-richlist-item
if (node.localName != "agenda-richlist-item") {
continue;
}
node.refreshColor();
}
break;
}
};
agendaListbox.calendarObserver.onPropertyDeleting = function(aCalendar, aName) {
this.onPropertyChanged(aCalendar, aName, null, null);
};
agendaListbox.calendarObserver.onCalendarRemoved =
function agenda_calRemove(aCalendar) {
this.agendaListbox.refreshCalendarQuery();
};
agendaListbox.calendarObserver.onCalendarAdded =
function agenda_calAdd(aCalendar) {
this.agendaListbox.refreshCalendarQuery();
};
agendaListbox.calendarObserver.onDefaultCalendarChanged = function(aCalendar) {
};
/**
* Updates the event considered "current". This goes through all "today" items
* and sets the "current" attribute on all list items that are currently
* occurring.
*
* @see scheduleNextCurrentEventUpdate
*/
function setCurrentEvent() {
if (agendaListbox.showsToday() && agendaListbox.today.open) {
var msScheduleTime = -1;
var complistItem = agendaListbox.tomorrow.listItem.previousSibling;
var removelist = [];
var anow = now();
var msuntillend = 0;
var msuntillstart = 0;
do {
var leaveloop = (!agendaListbox.isEventListItem(complistItem));
if (!leaveloop) {
msuntillstart = complistItem.occurrence.startDate
.getInTimezone(agendaListbox.kDefaultTimezone)
.subtractDate(anow).inSeconds;
if (msuntillstart <= 0) {
var msuntillend = complistItem.occurrence.endDate
.getInTimezone(agendaListbox.kDefaultTimezone)
.subtractDate(anow).inSeconds;
if (msuntillend >= 0) {
complistItem.setAttribute("current", "true");
if ((msuntillend < msScheduleTime) || (msScheduleTime == -1)){
msScheduleTime = msuntillend;
}
} else {
removelist.push(complistItem);
}
} else {
complistItem.removeAttribute("current");
}
if ((msScheduleTime == -1) || (msuntillstart < msScheduleTime)) {
if (msuntillstart > 0) {
msScheduleTime = msuntillstart;
}
}
}
if (!leaveloop) {
complistItem = complistItem.previousSibling;
}
} while (!leaveloop)
if (msScheduleTime > -1) {
scheduleNextCurrentEventUpdate(setCurrentEvent, msScheduleTime * 1000);
}
}
if (removelist) {
if (removelist.length > 0) {
for (var i = 0;i < removelist.length; i++) {
agendaListbox.agendaListboxControl.removeChild(removelist[i]);
}
}
}
}
var gEventTimer;
/**
* Creates a timer that will fire after the next event is current.
* Pass in a function as aRefreshCallback that should be called at that time.
*
* @param aRefreshCallback The function to call when the next event is
* current.
* @param aMsUntil The number of milliseconds until the next event
* is current.
*/
function scheduleNextCurrentEventUpdate(aRefreshCallback, aMsUntil) {
// Is an nsITimer/callback extreme overkill here? Yes, but it's necessary to
// workaround bug 291386. If we don't, we stand a decent chance of getting
// stuck in an infinite loop.
var udCallback = {
notify: function(timer) {
aRefreshCallback();
}
};
if (!gEventTimer) {
// Observer for wake after sleep/hibernate/standby to create new timers and refresh UI
var wakeObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic == "wake_notification") {
aRefreshCallback();
}
}
};
// Add observer
var observerService = Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
observerService.addObserver(wakeObserver, "wake_notification", false);
// Remove observer on unload
window.addEventListener("unload",
function() {
observerService.removeObserver(wakeObserver, "wake_notification");
}, false);
gEventTimer = Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
} else {
gEventTimer.cancel();
}
gEventTimer.initWithCallback(udCallback, aMsUntil, gEventTimer.TYPE_ONE_SHOT);
}