diff options
Diffstat (limited to 'comm/calendar/base/content/calendar-unifinder.js')
-rw-r--r-- | comm/calendar/base/content/calendar-unifinder.js | 988 |
1 files changed, 988 insertions, 0 deletions
diff --git a/comm/calendar/base/content/calendar-unifinder.js b/comm/calendar/base/content/calendar-unifinder.js new file mode 100644 index 0000000000..00839540e3 --- /dev/null +++ b/comm/calendar/base/content/calendar-unifinder.js @@ -0,0 +1,988 @@ +/* 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/. */ + +/* globals calFilter, calFilter, getViewBox, openEventDialogForViewing, + modifyEventWithDialog, createEventWithDialog, currentView, + calendarController, editSelectedEvents, deleteSelectedEvents, + calendarUpdateDeleteCommand, getEventStatusString, goToggleToolbar */ + +/* exported gCalendarEventTreeClicked, unifinderDoubleClick, unifinderKeyPress, + * focusSearch, ensureUnifinderLoaded, 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. + */ + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.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 gUnifinderNeedsRefresh = true; + +/** + * Checks if the unifinder is hidden + * + * @returns Returns true if the unifinder is hidden. + */ +function isUnifinderHidden() { + let tabmail = document.getElementById("tabmail"); + return ( + tabmail.currentTabInfo?.mode.type != "calendar" || + document.getElementById("bottom-events-box").hidden + ); +} + +/** + * Returns the current filter applied to the unifinder. + * + * @returns 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: ChromeUtils.generateQI(["calICompositeObserver", "nsIObserver", "calIObserver"]), + + // calIObserver: + onStartBatch() { + gUnifinderNeedsRefresh = true; + }, + + onEndBatch() { + if (isUnifinderHidden()) { + // 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(); + } else { + refreshEventTree(); + } + }, + + onLoad() {}, + + onAddItem(aItem) { + if (aItem.isEvent() && !gUnifinderNeedsRefresh) { + this.addItemToTree(aItem); + } + }, + + onModifyItem(aNewItem, aOldItem) { + this.onDeleteItem(aOldItem); + this.onAddItem(aNewItem); + }, + + onDeleteItem(aDeletedItem) { + if (aDeletedItem.isEvent() && !gUnifinderNeedsRefresh) { + this.removeItemFromTree(aDeletedItem); + } + }, + + onError(aCalendar, aErrNo, aMessage) {}, + + onPropertyChanged(aCalendar, aName, aValue, aOldValue) { + switch (aName) { + case "disabled": + refreshEventTree(); + break; + } + }, + + onPropertyDeleting(aCalendar, aName) { + this.onPropertyChanged(aCalendar, aName, null, null); + }, + + // calICompositeObserver: + onCalendarAdded(aAddedCalendar) { + if (!aAddedCalendar.getProperty("disabled")) { + if (isUnifinderHidden()) { + gUnifinderNeedsRefresh = true; + } else { + addItemsFromCalendar(aAddedCalendar, addItemsFromSingleCalendarInternal); + } + } + }, + + onCalendarRemoved(aDeletedCalendar) { + if (!aDeletedCalendar.getProperty("disabled")) { + removeItemsFromCalendar(aDeletedCalendar); + } + }, + + onDefaultCalendarChanged(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. + * + * @returns aItem The item to add to the tree. + */ + addItemToTree(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. + * + * @returns aItem The item to remove from the tree. + */ + removeItemFromTree(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() { + refreshEventTree(); + }, +}; + +/** + * Called when calendar component is loaded to prepare the unifinder. This function is + * used to add observers, event listeners, etc. + */ +function prepareCalendarUnifinder() { + let unifinderTree = document.getElementById("unifinder-search-results-tree"); + // Check that this is not the hidden window, which has no UI elements + if (!unifinderTree) { + return; + } + + // Add pref observer + Services.prefs.addObserver("calendar.date.format", unifinderObserver); + Services.obs.addObserver(unifinderObserver, "defaultTimezoneChanged"); + + // set up our calendar event observer + let ccalendar = cal.view.getCompositeCalendar(window); + ccalendar.addObserver(unifinderObserver); + + // 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 viewBox = getViewBox(); + viewBox.addEventListener("dayselect", unifinderDaySelect); + viewBox.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 treecols = unifinderTree.getElementsByTagName("treecol"); + for (let col of treecols) { + let content = col.getAttribute("itemproperty"); + if (sorted && sorted.length > 0) { + if (sorted == content) { + unifinderTreeView.sortDirection = sortDirection; + unifinderTreeView.selectedColumn = col; + } + } + } + + unifinderTreeView.ready = true; + + // Display something upon first load. onLoad doesn't work properly for + // observers + if (!isUnifinderHidden()) { + refreshEventTree(); + } +} + +/** + * Called when the window is unloaded to clean up any observers and listeners + * added. + */ +function finishCalendarUnifinder() { + let ccalendar = cal.view.getCompositeCalendar(window); + ccalendar.removeObserver(unifinderObserver); + + // Remove pref observer + Services.prefs.removeObserver("calendar.date.format", unifinderObserver); + Services.obs.removeObserver(unifinderObserver, "defaultTimezoneChanged"); + + let viewBox = getViewBox(); + if (viewBox) { + viewBox.removeEventListener("dayselect", unifinderDaySelect); + viewBox.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. + * @returns The passed date's formatted in the default timezone. + */ +function formatUnifinderEventDateTime(aDatetime) { + return cal.dtz.formatter.formatDateTime(aDatetime.getInTimezone(cal.dtz.defaultTimezone)); +} + +/** + * 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) { + if (Services.prefs.getBoolPref("calendar.events.defaultActionEdit", true)) { + modifyEventWithDialog(calendarEvent, true); + return; + } + openEventDialogForViewing(calendarEvent); + } 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) { + cal.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, 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) { + switch (aEvent.key) { + case "Enter": + // Enter, edit the event + editSelectedEvents(); + aEvent.stopPropagation(); + aEvent.preventDefault(); + break; + case "Backspace": + case "Delete": + deleteSelectedEvents(); + aEvent.stopPropagation(); + aEvent.preventDefault(); + break; + } +} + +/** + * Tree controller for unifinder search results + */ +var unifinderTreeView = { + QueryInterface: ChromeUtils.generateQI(["nsITreeView"]), + + // Provide a default tree that holds all the functions used here to avoid + // cludgy if (this.tree) { this.tree.rowCountChanged(...); } constructs. + tree: { + rowCountChanged() {}, + beginUpdateBatch() {}, + endUpdateBatch() {}, + invalidate() {}, + }, + + ready: false, + 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); + } + } + this.mSelectedColumn = aCol; + }, + + /** + * Event functions + */ + + eventArray: [], + eventIndexMap: {}, + + /** + * Add an item to the unifinder tree. + * + * @param aItemArray An array of items to add. + */ + addItems(aItemArray) { + this.tree.beginUpdateBatch(); + + let bulkSort = aItemArray.length > this.eventArray.length; + if (bulkSort || !this.selectedColumn) { + // If there's more items being added than already exist, + // just append them and sort the whole list afterwards. + // If there's no selected column, don't sort at all. + let index = this.eventArray.length; + this.eventArray = this.eventArray.concat(aItemArray); + if (bulkSort && this.selectedColumn) { + this.sortItems(); + } else { + this.tree.rowCountChanged(index, aItemArray.length); + } + } else { + // Otherwise, for each item to be added, work out its + // new position in the list and splice it in there. + // This saves a lot of function calls and calculation. + let modifier = this.sortDirection == "descending" ? -1 : 1; + let sortKey = unifinderTreeView.selectedColumn.getAttribute("itemproperty"); + let comparer = cal.unifinder.sortEntryComparer(sortKey); + + let values = this.eventArray.map(item => cal.unifinder.getItemSortKey(item, sortKey)); + for (let item of aItemArray) { + let itemValue = cal.unifinder.getItemSortKey(item, sortKey); + let index = values.findIndex(value => comparer(value, itemValue, modifier) >= 0); + if (index < 0) { + this.eventArray.push(item); + this.tree.rowCountChanged(values.length, 1); + values.push(itemValue); + } else { + this.eventArray.splice(index, 0, item); + this.tree.rowCountChanged(index, 1); + values.splice(index, 0, itemValue); + } + } + } + + this.tree.endUpdateBatch(); + this.calculateIndexMap(true); + }, + + /** + * Remove items from the unifinder tree. + * + * @param aItemArray An array of items to remove. + */ + removeItems(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.prototype.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() { + let oldCount = this.eventArray.length; + this.eventArray = []; + if (this.tree) { + 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(aItemArray) { + let oldCount = this.eventArray.length; + this.eventArray = aItemArray.slice(0); + this.tree.rowCountChanged(oldCount - 1, this.eventArray.length - oldCount); + 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(aDontInvalidate) { + this.eventIndexMap = {}; + for (let i = 0; i < this.eventArray.length; i++) { + this.eventIndexMap[this.eventArray[i].hashId] = i; + } + + if (this.tree && !aDontInvalidate) { + this.tree.invalidate(); + } + }, + + /** + * Sort the items in the unifinder by the currently selected column. + */ + sortItems() { + if (this.selectedColumn) { + let modifier = this.sortDirection == "descending" ? -1 : 1; + let sortKey = unifinderTreeView.selectedColumn.getAttribute("itemproperty"); + + cal.unifinder.sortItems(this.eventArray, sortKey, modifier); + } + this.calculateIndexMap(); + }, + + /** + * Get the index of the row associated with the passed item. + * + * @param item The item to search for. + * @returns The row index of the passed item. + */ + getItemRow(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. + * @returns The item at the given row. + */ + getItemAt(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. + * @returns The item under the mouse position. + */ + getItemFromEvent(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(aItemArray) { + if ( + this.doingSelection || + !this.tree || + !this.tree.view || + !("getSelectedItems" in currentView()) + ) { + 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() { + 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(aRow, aCol) { + let rowProps = this.getRowProperties(aRow); + let colProps = this.getColumnProperties(aCol); + return rowProps + (rowProps && colProps ? " " : "") + colProps; + }, + getRowProperties(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-" + cal.view.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(cal.view.formatStringForCSSRule)); + + return properties.join(" "); + }, + getColumnProperties(aCol) { + return ""; + }, + + isContainer() { + return false; + }, + + isContainerOpen(aRow) { + return false; + }, + + isContainerEmpty(aRow) { + return false; + }, + + isSeparator(aRow) { + return false; + }, + + isSorted(aRow) { + return false; + }, + + canDrop(aRow, aOrientation) { + return false; + }, + + drop(aRow, aOrientation) {}, + + getParentIndex(aRow) { + return -1; + }, + + hasNextSibling(aRow, aAfterIndex) {}, + + getLevel(aRow) { + return 0; + }, + + getImageSrc(aRow, aOrientation) {}, + + getCellValue(aRow, aCol) { + return null; + }, + + getCellText(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(tree) { + this.tree = tree; + }, + + toggleOpenState(aRow) {}, + + cycleHeader(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(aRow, aCol) { + return false; + }, + + setCellValue(aRow, aCol, aValue) {}, + setCellText(aRow, aCol, aValue) {}, + + 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() { + if (!unifinderTreeView.ready) { + return; + } + + let field = document.getElementById("unifinder-search-field"); + if (field) { + unifinderTreeView.mFilter.filterText = field.value; + } + + addItemsFromCalendar( + cal.view.getCompositeCalendar(window), + addItemsFromCompositeCalendarInternal + ); + + gUnifinderNeedsRefresh = false; +} + +/** + * 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(); +} + +async function addItemsFromCalendar(aCalendar, aAddItemsInternalFunc) { + if (isUnifinderHidden()) { + // If the unifinder is hidden, don't refresh the events to reduce needed + // getItems calls. + return; + } + + 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; + } + + let items = await aCalendar.getItemsAsArray( + filter, + 0, + unifinderTreeView.mFilter.startDate, + unifinderTreeView.mFilter.endDate + ); + + let refreshTreeInternalFunc = function () { + aAddItemsInternalFunc(items); + }; + setTimeout(refreshTreeInternalFunc, 0); +} + +function removeItemsFromCalendar(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(); +} + +/** + * The unifinder is hidden if the calendar tab is not selected. When the tab + * is selected, this function is called so that unifinder setup completes. + */ +function ensureUnifinderLoaded() { + if (!isUnifinderHidden() && gUnifinderNeedsRefresh) { + refreshEventTree(); + } +} + +/** + * Toggles the hidden state of the unifinder. + */ +function toggleUnifinder() { + // Toggle the elements + goToggleToolbar("bottom-events-box", "calendar_show_unifinder_command"); + goToggleToolbar("calendar-view-splitter"); + window.dispatchEvent(new CustomEvent("viewresize")); + + unifinderTreeView.treeElement.view = unifinderTreeView; + + // When the unifinder is hidden, refreshEventTree is not called. Make sure + // the event tree is refreshed now. + if (!isUnifinderHidden() && gUnifinderNeedsRefresh) { + refreshEventTree(); + } + + // Make sure the selection is correct + if (unifinderTreeView.doingSelection) { + unifinderTreeView.resetAllowSelection(); + } + unifinderTreeView.setSelectedItems(); +} |