diff options
Diffstat (limited to 'calendar/base/content/calendar-unifinder.js')
-rw-r--r-- | calendar/base/content/calendar-unifinder.js | 959 |
1 files changed, 959 insertions, 0 deletions
diff --git a/calendar/base/content/calendar-unifinder.js b/calendar/base/content/calendar-unifinder.js new file mode 100644 index 000000000..45e250a1c --- /dev/null +++ b/calendar/base/content/calendar-unifinder.js @@ -0,0 +1,959 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* exported gCalendarEventTreeClicked, unifinderDoubleClick, unifinderKeyPress, + * focusSearch, toggleUnifinder + */ + +/** + * U N I F I N D E R + * + * This is a hacked in interface to the unifinder. We will need to + * improve this to make it usable in general. + * + * NOTE: Including this file will cause a load handler to be added to the + * window. + */ + +Components.utils.import("resource://calendar/modules/calUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +// Set this to true when the calendar event tree is clicked to allow for +// multiple selection +var gCalendarEventTreeClicked = false; + +// Store the start and enddate, because the providers can't be trusted when +// dealing with all-day events. So we need to filter later. See bug 306157 + +var kDefaultTimezone; +var gUnifinderNeedsRefresh = true; + +/** + * Checks if the unifinder is hidden + * + * @return Returns true if the unifinder is hidden. + */ +function isUnifinderHidden() { + return document.getElementById("bottom-events-box").hidden; +} + +/** + * Returns the current filter applied to the unifinder. + * + * @return The string name of the applied filter. + */ +function getCurrentUnifinderFilter() { + return document.getElementById("event-filter-menulist").selectedItem.value; +} + +/** + * Observer for the calendar event data source. This keeps the unifinder + * display up to date when the calendar event data is changed + * + * @see calIObserver + * @see calICompositeObserver + */ +var unifinderObserver = { + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.calICompositeObserver, + Components.interfaces.nsIObserver, + Components.interfaces.calIObserver + ]), + + // calIObserver: + onStartBatch: function() { + }, + + onEndBatch: function() { + refreshEventTree(); + }, + + onLoad: function() { + if (isUnifinderHidden() && !gUnifinderNeedsRefresh) { + // If the unifinder is hidden, all further item operations might + // produce invalid entries in the unifinder. From now on, ignore + // those operations and refresh as soon as the unifinder is shown + // again. + gUnifinderNeedsRefresh = true; + unifinderTreeView.clearItems(); + } + }, + + onAddItem: function(aItem) { + if (isEvent(aItem) && + !gUnifinderNeedsRefresh && + unifinderTreeView.mFilter.isItemInFilters(aItem) + ) { + this.addItemToTree(aItem); + } + }, + + onModifyItem: function(aNewItem, aOldItem) { + this.onDeleteItem(aOldItem); + this.onAddItem(aNewItem); + }, + + onDeleteItem: function(aDeletedItem) { + if (isEvent(aDeletedItem) && !gUnifinderNeedsRefresh) { + this.removeItemFromTree(aDeletedItem); + } + }, + + onError: function(aCalendar, aErrNo, aMessage) {}, + + onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) { + switch (aName) { + case "disabled": + refreshEventTree(); + break; + } + }, + + onPropertyDeleting: function(aCalendar, aName) { + this.onPropertyChanged(aCalendar, aName, null, null); + }, + + // calICompositeObserver: + onCalendarAdded: function(aAddedCalendar) { + if (!aAddedCalendar.getProperty("disabled")) { + addItemsFromCalendar(aAddedCalendar, + addItemsFromSingleCalendarInternal); + } + }, + + onCalendarRemoved: function(aDeletedCalendar) { + if (!aDeletedCalendar.getProperty("disabled")) { + deleteItemsFromCalendar(aDeletedCalendar); + } + }, + + onDefaultCalendarChanged: function(aNewDefaultCalendar) {}, + + /** + * Add an unifinder item to the tree. It is safe to call these for any + * event. The functions will determine whether or not anything actually + * needs to be done to the tree. + * + * @return aItem The item to add to the tree. + */ + addItemToTree: function(aItem) { + let items; + let filter = unifinderTreeView.mFilter; + + if (filter.startDate && filter.endDate) { + items = aItem.getOccurrencesBetween(filter.startDate, filter.endDate, {}); + } else { + items = [aItem]; + } + unifinderTreeView.addItems(items.filter(filter.isItemInFilters, filter)); + }, + + /** + * Remove an item from the unifinder tree. It is safe to call these for any + * event. The functions will determine whether or not anything actually + * needs to be done to the tree. + * + * @return aItem The item to remove from the tree. + */ + removeItemFromTree: function(aItem) { + let items; + let filter = unifinderTreeView.mFilter; + if (filter.startDate && filter.endDate && (aItem.parentItem == aItem)) { + items = aItem.getOccurrencesBetween(filter.startDate, filter.endDate, {}); + } else { + items = [aItem]; + } + // XXX: do we really still need this, we are always checking it in the refreshInternal + unifinderTreeView.removeItems(items.filter(filter.isItemInFilters, filter)); + }, + + observe: function(aSubject, aTopic, aPrefName) { + switch (aPrefName) { + case "calendar.date.format": + case "calendar.timezone.local": + refreshEventTree(); + break; + } + } +}; + +/** + * Called when the window is loaded to prepare the unifinder. This function is + * used to add observers, event listeners, etc. + */ +function prepareCalendarUnifinder() { + // Only load once + window.removeEventListener("load", prepareCalendarUnifinder, false); + let unifinderTree = document.getElementById("unifinder-search-results-tree"); + + // Add pref observer + let branch = Services.prefs.getBranch(""); + branch.addObserver("calendar.", unifinderObserver, false); + + // Check if this is not the hidden window, which has no UI elements + if (unifinderTree) { + // set up our calendar event observer + let ccalendar = getCompositeCalendar(); + ccalendar.addObserver(unifinderObserver); + + kDefaultTimezone = calendarDefaultTimezone(); + + // Set up the filter + unifinderTreeView.mFilter = new calFilter(); + + // Set up the unifinder views. + unifinderTreeView.treeElement = unifinderTree; + unifinderTree.view = unifinderTreeView; + + // Listen for changes in the selected day, so we can update if need be + let viewDeck = getViewDeck(); + viewDeck.addEventListener("dayselect", unifinderDaySelect, false); + viewDeck.addEventListener("itemselect", unifinderItemSelect, true); + + // Set up sortDirection and sortActive, in case it persisted + let sorted = unifinderTree.getAttribute("sort-active"); + let sortDirection = unifinderTree.getAttribute("sort-direction"); + if (!sortDirection || sortDirection == "undefined") { + sortDirection = "ascending"; + } + let tree = document.getElementById("unifinder-search-results-tree"); + let treecols = tree.getElementsByTagName("treecol"); + for (let i = 0; i < treecols.length; i++) { + let col = treecols[i]; + let content = col.getAttribute("itemproperty"); + if (sorted && sorted.length > 0) { + if (sorted == content) { + unifinderTreeView.sortDirection = sortDirection; + unifinderTreeView.selectedColumn = col; + } + } + } + // Display something upon first load. onLoad doesn't work properly for + // observers + if (!isUnifinderHidden()) { + gUnifinderNeedsRefresh = false; + refreshEventTree(); + } + } +} + +/** + * Called when the window is unloaded to clean up any observers and listeners + * added. + */ +function finishCalendarUnifinder() { + let ccalendar = getCompositeCalendar(); + ccalendar.removeObserver(unifinderObserver); + + // Remove pref observer + let branch = Services.prefs.getBranch(""); + branch.removeObserver("calendar.", unifinderObserver, false); + + let viewDeck = getViewDeck(); + if (viewDeck) { + viewDeck.removeEventListener("dayselect", unifinderDaySelect, false); + viewDeck.removeEventListener("itemselect", unifinderItemSelect, true); + } + + // Persist the sort + let unifinderTree = document.getElementById("unifinder-search-results-tree"); + let sorted = unifinderTreeView.selectedColumn; + if (sorted) { + unifinderTree.setAttribute("sort-active", sorted.getAttribute("itemproperty")); + unifinderTree.setAttribute("sort-direction", unifinderTreeView.sortDirection); + } else { + unifinderTree.removeAttribute("sort-active"); + unifinderTree.removeAttribute("sort-direction"); + } +} + +/** + * Event listener for the view deck's dayselect event. + */ +function unifinderDaySelect() { + let filter = getCurrentUnifinderFilter(); + if (filter == "current" || filter == "currentview") { + refreshEventTree(); + } +} + +/** + * Event listener for the view deck's itemselect event. + */ +function unifinderItemSelect(aEvent) { + unifinderTreeView.setSelectedItems(aEvent.detail); +} + +/** + * Helper function to display event datetimes in the unifinder. + * + * @param aDatetime A calIDateTime object to format. + * @return The passed date's formatted in the default timezone. + */ +function formatUnifinderEventDateTime(aDatetime) { + return cal.getDateFormatter().formatDateTime(aDatetime.getInTimezone(kDefaultTimezone)); +} + +/** + * Handler function for double clicking the unifinder. + * + * @param event The DOM doubleclick event. + */ +function unifinderDoubleClick(event) { + // We only care about button 0 (left click) events + if (event.button != 0) { + return; + } + + // find event by id + let calendarEvent = unifinderTreeView.getItemFromEvent(event); + + if (calendarEvent) { + modifyEventWithDialog(calendarEvent, null, true); + } else { + createEventWithDialog(); + } +} + +/** + * Handler function for selection in the unifinder. + * + * @param event The DOM selection event. + */ +function unifinderSelect(event) { + let tree = unifinderTreeView.treeElement; + if (!tree.view.selection || tree.view.selection.getRangeCount() == 0) { + return; + } + + let selectedItems = []; + gCalendarEventTreeClicked = true; + + // Get the selected events from the tree + let start = {}; + let end = {}; + let numRanges = tree.view.selection.getRangeCount(); + + for (let range = 0; range < numRanges; range++) { + tree.view.selection.getRangeAt(range, start, end); + + for (let i = start.value; i <= end.value; i++) { + try { + selectedItems.push(unifinderTreeView.getItemAt(i)); + } catch (e) { + WARN("Error getting Event from row: " + e + "\n"); + } + } + } + + if (selectedItems.length == 1) { + // Go to the day of the selected item in the current view. + currentView().goToDay(selectedItems[0].startDate); + } + + // Set up the selected items in the view. Pass in true, so we don't end + // up in a circular loop + currentView().setSelectedItems(selectedItems.length, selectedItems, true); + currentView().centerSelectedItems(); + calendarController.onSelectionChanged({ detail: selectedItems }); + document.getElementById("unifinder-search-results-tree").focus(); +} + +/** + * Handler function for keypress in the unifinder. + * + * @param aEvent The DOM Key event. + */ +function unifinderKeyPress(aEvent) { + const kKE = Components.interfaces.nsIDOMKeyEvent; + switch (aEvent.keyCode) { + case 13: + // Enter, edit the event + editSelectedEvents(); + aEvent.stopPropagation(); + aEvent.preventDefault(); + break; + case kKE.DOM_VK_BACK_SPACE: + case kKE.DOM_VK_DELETE: + deleteSelectedEvents(); + aEvent.stopPropagation(); + aEvent.preventDefault(); + break; + } +} + +/** + * Tree controller for unifinder search results + */ +var unifinderTreeView = { + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsITreeView]), + + // Provide a default tree that holds all the functions used here to avoid + // cludgy if (this.tree) { this.tree.rowCountChanged(...); } constructs. + tree: { + rowCountChanged: function() {}, + beginUpdateBatch: function() {}, + endUpdateBatch: function() {}, + invalidate: function() {} + }, + + treeElement: null, + doingSelection: false, + mFilter: null, + mSelectedColumn: null, + sortDirection: null, + + /** + * Returns the currently selected column in the unifinder (used for sorting). + */ + get selectedColumn() { + return this.mSelectedColumn; + }, + + /** + * Sets the currently selected column in the unifinder (used for sorting). + */ + set selectedColumn(aCol) { + let tree = document.getElementById("unifinder-search-results-tree"); + let treecols = tree.getElementsByTagName("treecol"); + for (let col of treecols) { + if (col.getAttribute("sortActive")) { + col.removeAttribute("sortActive"); + col.removeAttribute("sortDirection"); + } + if (aCol.getAttribute("itemproperty") == col.getAttribute("itemproperty")) { + col.setAttribute("sortActive", "true"); + col.setAttribute("sortDirection", this.sortDirection); + } + } + return (this.mSelectedColumn = aCol); + }, + + /** + * Event functions + */ + + eventArray: [], + eventIndexMap: {}, + + /** + * Add an item to the unifinder tree. + * + * @param aItemArray An array of items to add. + * @param aDontSort If true, the items will only be appended. + */ + addItems: function(aItemArray, aDontSort) { + this.eventArray = this.eventArray.concat(aItemArray); + let newCount = (this.eventArray.length - aItemArray.length - 1); + this.tree.rowCountChanged(newCount, aItemArray.length); + + if (aDontSort) { + this.calculateIndexMap(); + } else { + this.sortItems(); + } + }, + + /** + * Remove items from the unifinder tree. + * + * @param aItemArray An array of items to remove. + */ + removeItems: function(aItemArray) { + let indexesToRemove = []; + // Removing items is a bit tricky. Our getItemRow function takes the + // index from a cached map, so removing an item from the array will + // remove the wrong indexes. We don't want to just invalidate the map, + // since this will cause O(n^2) behavior. Instead, we keep a sorted + // array of the indexes to remove: + for (let item of aItemArray) { + let row = this.getItemRow(item); + if (row > -1) { + if (!indexesToRemove.length || row <= indexesToRemove[0]) { + indexesToRemove.unshift(row); + } else { + indexesToRemove.push(row); + } + } + } + + // Then we go through the indexes to remove, and remove then from the + // array. We subtract one delta for each removed index to make sure the + // correct element is removed from the array and the correct + // notification is sent. + this.tree.beginUpdateBatch(); + for (let delta = 0; delta < indexesToRemove.length; delta++) { + let index = indexesToRemove[delta]; + this.eventArray.splice(index - delta, 1); + this.tree.rowCountChanged(index - delta, -1); + } + this.tree.endUpdateBatch(); + + // Finally, we recalculate the index map once. This way we end up with + // (given that Array.unshift doesn't loop but just prepends or maps + // memory smartly) O(3n) behavior. Lets hope its worth it. + this.calculateIndexMap(true); + }, + + /** + * Clear all items from the unifinder. + */ + clearItems: function() { + let oldCount = this.eventArray.length; + this.eventArray = []; + this.tree.rowCountChanged(0, -oldCount); + this.calculateIndexMap(); + }, + + /** + * Sets the items that should be in the unifinder. This removes all items + * that were previously in the unifinder. + */ + setItems: function(aItemArray, aDontSort) { + let oldCount = this.eventArray.length; + this.eventArray = aItemArray.slice(0); + this.tree.rowCountChanged(oldCount - 1, this.eventArray.length - oldCount); + + if (aDontSort) { + this.calculateIndexMap(); + } else { + this.sortItems(); + } + }, + + /** + * Recalculate the index map that improves performance when accessing + * unifinder items. This is usually done automatically when adding/removing + * items. + * + * @param aDontInvalidate (optional) Don't invalidate the tree, i.e if + * you correctly issued rowCountChanged + * notices. + */ + calculateIndexMap: function(aDontInvalidate) { + this.eventIndexMap = {}; + for (let i = 0; i < this.eventArray.length; i++) { + this.eventIndexMap[this.eventArray[i].hashId] = i; + } + + if (!aDontInvalidate) { + this.tree.invalidate(); + } + }, + + /** + * Sort the items in the unifinder by the currently selected column. + */ + sortItems: function() { + if (this.selectedColumn) { + let modifier = (this.sortDirection == "descending" ? -1 : 1); + let sortKey = unifinderTreeView.selectedColumn.getAttribute("itemproperty"); + let sortType = cal.getSortTypeForSortKey(sortKey); + // sort (key,item) entries + cal.sortEntry.mSortKey = sortKey; + cal.sortEntry.mSortStartedDate = now(); + let entries = this.eventArray.map(cal.sortEntry, cal.sortEntry); + entries.sort(cal.sortEntryComparer(sortType, modifier)); + this.eventArray = entries.map(cal.sortEntryItem); + } + this.calculateIndexMap(); + }, + + /** + * Get the index of the row associated with the passed item. + * + * @param item The item to search for. + * @return The row index of the passed item. + */ + getItemRow: function(item) { + if (this.eventIndexMap[item.hashId] === undefined) { + return -1; + } + return this.eventIndexMap[item.hashId]; + }, + + /** + * Get the item at the given row index. + * + * @param item The row index to get the item for. + * @return The item at the given row. + */ + getItemAt: function(aRow) { + return this.eventArray[aRow]; + }, + + /** + * Get the calendar item from the given DOM event + * + * @param event The DOM mouse event to get the item for. + * @return The item under the mouse position. + */ + getItemFromEvent: function(event) { + let row = this.tree.getRowAt(event.clientX, event.clientY); + + if (row > -1) { + return this.getItemAt(row); + } + return null; + }, + + /** + * Change the selection in the unifinder. + * + * @param aItemArray An array of items to select. + */ + setSelectedItems: function(aItemArray) { + if (this.doingSelection || !this.tree || !this.tree.view) { + return; + } + + this.doingSelection = true; + + // If no items were passed, get the selected items from the view. + aItemArray = aItemArray || currentView().getSelectedItems({}); + + calendarUpdateDeleteCommand(aItemArray); + + /** + * The following is a brutal hack, caused by + * http://lxr.mozilla.org/mozilla1.0/source/layout/xul/base/src/tree/src/nsTreeSelection.cpp#555 + * and described in bug 168211 + * http://bugzilla.mozilla.org/show_bug.cgi?id=168211 + * Do NOT remove anything in the next 3 lines, or the selection in the tree will not work. + */ + this.treeElement.onselect = null; + this.treeElement.removeEventListener("select", unifinderSelect, true); + this.tree.view.selection.selectEventsSuppressed = true; + this.tree.view.selection.clearSelection(); + + if (aItemArray && aItemArray.length == 1) { + // If only one item is selected, scroll to it + let rowToScrollTo = this.getItemRow(aItemArray[0]); + if (rowToScrollTo > -1) { + this.tree.ensureRowIsVisible(rowToScrollTo); + this.tree.view.selection.select(rowToScrollTo); + } + } else if (aItemArray && aItemArray.length > 1) { + // If there is more than one item, just select them all. + for (let item of aItemArray) { + let row = this.getItemRow(item); + this.tree.view.selection.rangedSelect(row, row, true); + } + } + + // This needs to be in a setTimeout + setTimeout(() => unifinderTreeView.resetAllowSelection(), 1); + }, + + /** + * Due to a selection issue described in bug 168211 this method is needed to + * re-add the selection listeners selection listeners. + */ + resetAllowSelection: function() { + if (!this.tree) { + return; + } + /** + * Do not change anything in the following lines, they are needed as + * described in the selection observer above + */ + this.doingSelection = false; + + this.tree.view.selection.selectEventsSuppressed = false; + this.treeElement.addEventListener("select", unifinderSelect, true); + }, + + /** + * Tree View Implementation + * @see nsITreeView + */ + get rowCount() { + return this.eventArray.length; + }, + + + // TODO this code is currently identical to the task tree. We should create + // an itemTreeView that these tree views can inherit, that contains this + // code, and possibly other code related to sorting and storing items. See + // bug 432582 for more details. + getCellProperties: function(aRow, aCol) { + let rowProps = this.getRowProperties(aRow); + let colProps = this.getColumnProperties(aCol); + return rowProps + (rowProps && colProps ? " " : "") + colProps; + }, + getRowProperties: function(aRow) { + let properties = []; + let item = this.eventArray[aRow]; + if (item.priority > 0 && item.priority < 5) { + properties.push("highpriority"); + } else if (item.priority > 5 && item.priority < 10) { + properties.push("lowpriority"); + } + + // Add calendar name atom + properties.push("calendar-" + formatStringForCSSRule(item.calendar.name)); + + // Add item status atom + if (item.status) { + properties.push("status-" + item.status.toLowerCase()); + } + + // Alarm status atom + if (item.getAlarms({}).length) { + properties.push("alarm"); + } + + // Task categories + properties = properties.concat(item.getCategories({}) + .map(formatStringForCSSRule)); + + return properties.join(" "); + }, + getColumnProperties: function(aCol) { return ""; }, + + isContainer: function() { + return false; + }, + + isContainerOpen: function(aRow) { + return false; + }, + + isContainerEmpty: function(aRow) { + return false; + }, + + isSeparator: function(aRow) { + return false; + }, + + isSorted: function(aRow) { + return false; + }, + + canDrop: function(aRow, aOrientation) { + return false; + }, + + drop: function(aRow, aOrientation) {}, + + getParentIndex: function(aRow) { + return -1; + }, + + hasNextSibling: function(aRow, aAfterIndex) {}, + + getLevel: function(aRow) { + return 0; + }, + + getImageSrc: function(aRow, aOrientation) {}, + + getProgressMode: function(aRow, aCol) {}, + + getCellValue: function(aRow, aCol) { + return null; + }, + + getCellText: function(row, column) { + let calendarEvent = this.eventArray[row]; + + switch (column.element.getAttribute("itemproperty")) { + case "title": { + return (calendarEvent.title ? calendarEvent.title.replace(/\n/g, " ") : ""); + } + case "startDate": { + return formatUnifinderEventDateTime(calendarEvent.startDate); + } + case "endDate": { + let eventEndDate = calendarEvent.endDate.clone(); + // XXX reimplement + // let eventEndDate = getCurrentNextOrPreviousRecurrence(calendarEvent); + if (calendarEvent.startDate.isDate) { + // display enddate is ical enddate - 1 + eventEndDate.day = eventEndDate.day - 1; + } + return formatUnifinderEventDateTime(eventEndDate); + } + case "categories": { + return calendarEvent.getCategories({}).join(", "); + } + case "location": { + return calendarEvent.getProperty("LOCATION"); + } + case "status": { + return getEventStatusString(calendarEvent); + } + case "calendar": { + return calendarEvent.calendar.name; + } + default: { + return false; + } + } + }, + + setTree: function(tree) { + this.tree = tree; + }, + + toggleOpenState: function(aRow) {}, + + cycleHeader: function(col) { + if (!this.selectedColumn) { + this.sortDirection = "ascending"; + } else if (!this.sortDirection || this.sortDirection == "descending") { + this.sortDirection = "ascending"; + } else { + this.sortDirection = "descending"; + } + this.selectedColumn = col.element; + this.sortItems(); + }, + + isEditable: function(aRow, aCol) { + return false; + }, + + setCellValue: function(aRow, aCol, aValue) {}, + setCellText: function(aRow, aCol, aValue) {}, + + performAction: function(aAction) {}, + + performActionOnRow: function(aAction, aRow) {}, + + performActionOnCell: function(aAction, aRow, aCol) {}, + + outParameter: {} // used to obtain dates during sort +}; + +/** + * Refresh the unifinder tree by getting items from the composite calendar and + * applying the current filter. + */ +function refreshEventTree() { + let field = document.getElementById("unifinder-search-field"); + if (field) { + unifinderTreeView.mFilter.filterText = field.value; + } + + addItemsFromCalendar(getCompositeCalendar(), + addItemsFromCompositeCalendarInternal); +} + +/** + * EXTENSION_POINTS + * Filters the passed event array according to the currently applied filter. + * Afterwards, applies the items to the unifinder view. + * + * If you are implementing a new filter, you can overwrite this function and + * filter the items accordingly and afterwards call this function with the + * result. + * + * @param eventArray The array of items to be set in the unifinder. + */ +function addItemsFromCompositeCalendarInternal(eventArray) { + let newItems = eventArray.filter(unifinderTreeView.mFilter.isItemInFilters, + unifinderTreeView.mFilter); + unifinderTreeView.setItems(newItems); + + // Select selected events in the tree. Not passing the argument gets the + // items from the view. + unifinderTreeView.setSelectedItems(); +} + +function addItemsFromSingleCalendarInternal(eventArray) { + let newItems = eventArray.filter(unifinderTreeView.mFilter.isItemInFilters, + unifinderTreeView.mFilter); + unifinderTreeView.setItems(unifinderTreeView.eventArray.concat(newItems)); + + // Select selected events in the tree. Not passing the argument gets the + // items from the view. + unifinderTreeView.setSelectedItems(); +} + +function addItemsFromCalendar(aCalendar, aAddItemsInternalFunc) { + if (isUnifinderHidden()) { + // If the unifinder is hidden, don't refresh the events to reduce needed + // getItems calls. + return; + } + let refreshListener = { + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calIOperationListener]), + mEventArray: [], + + onOperationComplete: function(aOpCalendar, aStatus, aOperationType, aId, aDateTime) { + let refreshTreeInternalFunc = function() { + aAddItemsInternalFunc(refreshListener.mEventArray); + }; + setTimeout(refreshTreeInternalFunc, 0); + }, + + onGetResult: function(aOpCalendar, aStatus, aItemType, aDetail, aCount, aItems) { + refreshListener.mEventArray = refreshListener.mEventArray.concat(aItems); + } + }; + + let filter = 0; + + filter |= aCalendar.ITEM_FILTER_TYPE_EVENT; + + // Not all xul might be there yet... + if (!document.getElementById("unifinder-search-field")) { + return; + } + unifinderTreeView.mFilter.applyFilter(getCurrentUnifinderFilter()); + + if (unifinderTreeView.mFilter.startDate && unifinderTreeView.mFilter.endDate) { + filter |= aCalendar.ITEM_FILTER_CLASS_OCCURRENCES; + } + + aCalendar.getItems(filter, + 0, + unifinderTreeView.mFilter.startDate, + unifinderTreeView.mFilter.endDate, + refreshListener); +} + +function deleteItemsFromCalendar(aCalendar) { + let filter = unifinderTreeView.mFilter; + let items = unifinderTreeView.eventArray.filter(item => item.calendar.id == aCalendar.id); + + unifinderTreeView.removeItems(items.filter(filter.isItemInFilters, filter)); +} + +/** + * Focuses the unifinder search field + */ +function focusSearch() { + document.getElementById("unifinder-search-field").focus(); +} + +/** + * Toggles the hidden state of the unifinder. + */ +function toggleUnifinder() { + // Toggle the elements + goToggleToolbar("bottom-events-box", "calendar_show_unifinder_command"); + goToggleToolbar("calendar-view-splitter"); + + unifinderTreeView.treeElement.view = unifinderTreeView; + + // When the unifinder is hidden, refreshEventTree is not called. Make sure + // the event tree is refreshed now. + if (!isUnifinderHidden() && gUnifinderNeedsRefresh) { + gUnifinderNeedsRefresh = false; + refreshEventTree(); + } + + // Make sure the selection is correct + if (unifinderTreeView.doingSelection) { + unifinderTreeView.resetAllowSelection(); + } + unifinderTreeView.setSelectedItems(); +} + +window.addEventListener("load", prepareCalendarUnifinder, false); +window.addEventListener("unload", finishCalendarUnifinder, false); |