summaryrefslogtreecommitdiff
path: root/calendar/base/content/calendar-management.js
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/base/content/calendar-management.js')
-rw-r--r--calendar/base/content/calendar-management.js444
1 files changed, 444 insertions, 0 deletions
diff --git a/calendar/base/content/calendar-management.js b/calendar/base/content/calendar-management.js
new file mode 100644
index 000000000..873fc29ab
--- /dev/null
+++ b/calendar/base/content/calendar-management.js
@@ -0,0 +1,444 @@
+/* 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 promptDeleteCalendar, loadCalendarManager, unloadCalendarManager,
+ * updateSortOrderPref, calendarListTooltipShowing,
+ * calendarListSetupContextMenu, ensureCalendarVisible, toggleCalendarVisible,
+ * showAllCalendars, showOnlyCalendar, openCalendarSubscriptionsDialog,
+ * calendarOfflineManager
+ */
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Preferences.jsm");
+
+/**
+ * Get this window's currently selected calendar.
+ *
+ * @return The currently selected calendar.
+ */
+function getSelectedCalendar() {
+ return getCompositeCalendar().defaultCalendar;
+}
+
+/**
+ * Deletes the passed calendar, prompting the user if he really wants to do
+ * this. If there is only one calendar left, no calendar is removed and the user
+ * is not prompted.
+ *
+ * @param aCalendar The calendar to delete.
+ */
+function promptDeleteCalendar(aCalendar) {
+ const nIPS = Components.interfaces.nsIPromptService;
+ const cICM = Components.interfaces.calICalendarManager;
+
+ let calMgr = cal.getCalendarManager();
+ let calendars = calMgr.getCalendars({});
+ if (calendars.length <= 1) {
+ // If this is the last calendar, don't delete it.
+ return;
+ }
+
+ let modes = new Set(aCalendar.getProperty("capabilities.removeModes") || ["unsubscribe"]);
+ let title = cal.calGetString("calendar", "removeCalendarTitle");
+
+ let textKey, b0text, b2text;
+ let removeFlags = 0;
+ let promptFlags = (nIPS.BUTTON_POS_0 * nIPS.BUTTON_TITLE_IS_STRING) +
+ (nIPS.BUTTON_POS_1 * nIPS.BUTTON_TITLE_CANCEL);
+
+ if (modes.has("delete") && !modes.has("unsubscribe")) {
+ textKey = "removeCalendarMessageDelete";
+ promptFlags += nIPS.BUTTON_DELAY_ENABLE;
+ b0text = cal.calGetString("calendar", "removeCalendarButtonDelete");
+ } else if (modes.has("delete")) {
+ textKey = "removeCalendarMessageDeleteOrUnsubscribe";
+ promptFlags += (nIPS.BUTTON_POS_2 * nIPS.BUTTON_TITLE_IS_STRING);
+ b0text = cal.calGetString("calendar", "removeCalendarButtonUnsubscribe");
+ b2text = cal.calGetString("calendar", "removeCalendarButtonDelete");
+ } else if (modes.has("unsubscribe")) {
+ textKey = "removeCalendarMessageUnsubscribe";
+ removeFlags |= cICM.REMOVE_NO_DELETE;
+ b0text = cal.calGetString("calendar", "removeCalendarButtonUnsubscribe");
+ } else {
+ return;
+ }
+
+ let text = cal.calGetString("calendar", textKey, [aCalendar.name]);
+ let res = Services.prompt.confirmEx(window, title, text, promptFlags,
+ b0text, null, b2text, null, {});
+
+ if (res != 1) { // Not canceled
+ if (textKey == "removeCalendarMessageDeleteOrUnsubscribe" && res == 0) {
+ // Both unsubscribing and deleting is possible, but unsubscribing was
+ // requested. Make sure no delete is executed.
+ removeFlags |= cICM.REMOVE_NO_DELETE;
+ }
+
+ calMgr.removeCalendar(aCalendar, removeFlags);
+ }
+}
+
+/**
+ * Called to initialize the calendar manager for a window.
+ */
+function loadCalendarManager() {
+ // Set up the composite calendar in the calendar list widget.
+ let tree = document.getElementById("calendar-list-tree-widget");
+ let compositeCalendar = getCompositeCalendar();
+ tree.compositeCalendar = compositeCalendar;
+
+ // Initialize our composite observer
+ compositeCalendar.addObserver(compositeObserver);
+
+ // Create the home calendar if no calendar exists.
+ let calendars = cal.getCalendarManager().getCalendars({});
+ if (calendars.length) {
+ // migration code to make sure calendars, which do not support caching have cache enabled
+ // required to further clean up on top of bug 1182264
+ for (let calendar of calendars) {
+ if (calendar.getProperty("cache.supported") === false &&
+ calendar.getProperty("cache.enabled") === true) {
+ calendar.deleteProperty("cache.enabled");
+ }
+ }
+ } else {
+ initHomeCalendar();
+ }
+}
+
+/**
+ * Creates the initial "Home" calendar if no calendar exists.
+ */
+function initHomeCalendar() {
+ let calMgr = cal.getCalendarManager();
+ let composite = getCompositeCalendar();
+ let url = cal.makeURL("moz-storage-calendar://");
+ let homeCalendar = calMgr.createCalendar("storage", url);
+ homeCalendar.name = calGetString("calendar", "homeCalendarName");
+ calMgr.registerCalendar(homeCalendar);
+ Preferences.set("calendar.list.sortOrder", homeCalendar.id);
+ composite.addCalendar(homeCalendar);
+
+ // Wrapping this in a try/catch block, as if any of the migration code
+ // fails, the app may not load.
+ if (Preferences.get("calendar.migrator.enabled", true)) {
+ try {
+ gDataMigrator.checkAndMigrate();
+ } catch (e) {
+ Components.utils.reportError("Migrator error: " + e);
+ }
+ }
+
+ return homeCalendar;
+}
+
+/**
+ * Called to clean up the calendar manager for a window.
+ */
+function unloadCalendarManager() {
+ let compositeCalendar = getCompositeCalendar();
+ compositeCalendar.setStatusObserver(null, null);
+ compositeCalendar.removeObserver(compositeObserver);
+}
+
+/**
+ * Updates the sort order preference based on the given event. The event is a
+ * "SortOrderChanged" event, emitted from the calendar-list-tree binding. You
+ * can also pass in an object like { sortOrder: "Space separated calendar ids" }
+ *
+ * @param event The SortOrderChanged event described above.
+ */
+function updateSortOrderPref(event) {
+ let sortOrderString = event.sortOrder.join(" ");
+ Preferences.set("calendar.list.sortOrder", sortOrderString);
+ try {
+ Services.prefs.savePrefFile(null);
+ } catch (e) {
+ cal.ERROR(e);
+ }
+}
+
+/**
+ * Handler function to call when the tooltip is showing on the calendar list.
+ *
+ * @param event The DOM event provoked by the tooltip showing.
+ */
+function calendarListTooltipShowing(event) {
+ let tree = document.getElementById("calendar-list-tree-widget");
+ let calendar = tree.getCalendarFromEvent(event);
+ let tooltipText = false;
+ if (calendar) {
+ let currentStatus = calendar.getProperty("currentStatus");
+ if (!Components.isSuccessCode(currentStatus)) {
+ tooltipText = calGetString("calendar", "tooltipCalendarDisabled", [calendar.name]);
+ } else if (calendar.readOnly) {
+ tooltipText = calGetString("calendar", "tooltipCalendarReadOnly", [calendar.name]);
+ }
+ }
+ setElementValue("calendar-list-tooltip", tooltipText, "label");
+ return (tooltipText != false);
+}
+
+/**
+ * A handler called to set up the context menu on the calendar list.
+ *
+ * @param event The DOM event that caused the context menu to open.
+ * @return Returns true if the context menu should be shown.
+ */
+function calendarListSetupContextMenu(event) {
+ let col = {};
+ let row = {};
+ let calendar;
+ let calendars = getCalendarManager().getCalendars({});
+ let treeNode = document.getElementById("calendar-list-tree-widget");
+ let composite = getCompositeCalendar();
+
+ if (document.popupNode.localName == "tree") {
+ // Using VK_APPS to open the context menu will target the tree
+ // itself. In that case we won't have a client point even for
+ // opening the context menu. The "target" element should then be the
+ // selected calendar.
+ row.value = treeNode.tree.currentIndex;
+ col.value = treeNode.getColumn("calendarname-treecol");
+ calendar = treeNode.getCalendar(row.value);
+ } else {
+ // Using the mouse, the context menu will open on the treechildren
+ // element. Here we can use client points.
+ calendar = treeNode.getCalendarFromEvent(event, col, row);
+ }
+
+ if (col.value &&
+ col.value.element.getAttribute("anonid") == "checkbox-treecol") {
+ // Don't show the context menu if the checkbox was clicked.
+ return false;
+ }
+
+ document.getElementById("list-calendars-context-menu").contextCalendar = calendar;
+
+ // Only enable calendar search if there's actually the chance of finding something:
+ let hasProviders = getCalendarSearchService().getProviders({}).length < 1 && "true";
+ setElementValue("list-calendars-context-find", hasProviders, "collapsed");
+
+ if (calendar) {
+ enableElement("list-calendars-context-edit");
+ enableElement("list-calendars-context-publish");
+
+ enableElement("list-calendars-context-togglevisible");
+ setElementValue("list-calendars-context-togglevisible", false, "collapsed");
+ let stringName = composite.getCalendarById(calendar.id) ? "hideCalendar" : "showCalendar";
+ setElementValue("list-calendars-context-togglevisible",
+ cal.calGetString("calendar", stringName, [calendar.name]),
+ "label");
+ let accessKey = document.getElementById("list-calendars-context-togglevisible")
+ .getAttribute(composite.getCalendarById(calendar.id) ?
+ "accesskeyhide" : "accesskeyshow");
+ setElementValue("list-calendars-context-togglevisible", accessKey, "accesskey");
+
+ enableElement("list-calendars-context-showonly");
+ setElementValue("list-calendars-context-showonly", false, "collapsed");
+ setElementValue("list-calendars-context-showonly",
+ cal.calGetString("calendar", "showOnlyCalendar", [calendar.name]),
+ "label");
+
+ setupDeleteMenuitem("list-calendars-context-delete", calendar);
+ // Only enable the delete calendars item if there is more than one
+ // calendar. We don't want to have the last calendar deleted.
+ setElementValue("list-calendars-context-delete", calendars.length < 2 && "true", "disabled");
+ } else {
+ disableElement("list-calendars-context-edit");
+ disableElement("list-calendars-context-publish");
+ disableElement("list-calendars-context-delete");
+ disableElement("list-calendars-context-togglevisible");
+ setElementValue("list-calendars-context-togglevisible", true, "collapsed");
+ disableElement("list-calendars-context-showonly");
+ setElementValue("list-calendars-context-showonly", true, "collapsed");
+ setupDeleteMenuitem("list-calendars-context-delete", null);
+ }
+ return true;
+}
+
+/**
+ * Changes the "delete calendar" menuitem to have the right label based on the
+ * removeModes. The menuitem must have the attributes "labelremove",
+ * "labeldelete" and "labelunsubscribe".
+ *
+ * @param aDeleteId The id of the menuitem to delete the calendar
+ */
+function setupDeleteMenuitem(aDeleteId, aCalendar) {
+ let calendar = (aCalendar === undefined ? getSelectedCalendar() : aCalendar);
+ let modes = new Set(calendar ? calendar.getProperty("capabilities.removeModes") || ["unsubscribe"] : []);
+
+ let type = "remove";
+ if (modes.has("delete") && !modes.has("unsubscribe")) {
+ type = "delete";
+ } else if (modes.has("unsubscribe") && !modes.has("delete")) {
+ type = "unsubscribe";
+ }
+
+ let deleteItem = document.getElementById(aDeleteId);
+ setElementValue(deleteItem, deleteItem.getAttribute("label" + type), "label");
+ setElementValue(deleteItem, deleteItem.getAttribute("accesskey" + type), "accesskey");
+ setElementValue(deleteItem, modes.size == 0 && "true", "disabled");
+}
+
+/**
+ * Makes sure the passed calendar is visible to the user
+ *
+ * @param aCalendar The calendar to make visible.
+ */
+function ensureCalendarVisible(aCalendar) {
+ // We use the main window's calendar list to ensure that the calendar is visible.
+ // If the main window has been closed this function may still be called,
+ // like when an event/task window is still open and the user clicks 'save',
+ // thus we have the extra checks.
+ let list = document.getElementById("calendar-list-tree-widget");
+ if (list && list.ensureCalendarVisible) {
+ list.ensureCalendarVisible(aCalendar);
+ }
+}
+
+/**
+ * Hides the specified calendar if it is visible, or shows it if it is hidden.
+ *
+ * @param aCalendar The calendar to show or hide
+ */
+function toggleCalendarVisible(aCalendar) {
+ let composite = getCompositeCalendar();
+ if (composite.getCalendarById(aCalendar.id)) {
+ composite.removeCalendar(aCalendar);
+ } else {
+ composite.addCalendar(aCalendar);
+ }
+}
+
+/**
+ * Shows all hidden calendars.
+ */
+function showAllCalendars() {
+ let composite = getCompositeCalendar();
+ let cals = cal.getCalendarManager().getCalendars({});
+
+ composite.startBatch();
+ for (let calendar of cals) {
+ if (!composite.getCalendarById(calendar.id)) {
+ composite.addCalendar(calendar);
+ }
+ }
+ composite.endBatch();
+}
+
+/**
+ * Shows only the specified calendar, and hides all others.
+ *
+ * @param aCalendar The calendar to show as the only visible calendar
+ */
+function showOnlyCalendar(aCalendar) {
+ let composite = getCompositeCalendar();
+ let cals = composite.getCalendars({}) || [];
+
+ composite.startBatch();
+ for (let calendar of cals) {
+ if (calendar.id != aCalendar.id) {
+ composite.removeCalendar(calendar);
+ }
+ }
+ composite.addCalendar(aCalendar);
+ composite.endBatch();
+}
+
+var compositeObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calIObserver,
+ Components.interfaces.calICompositeObserver]),
+
+ onStartBatch: function() {},
+ onEndBatch: function() {},
+ onAddItem: function() {},
+ onModifyItem: function() {},
+ onDeleteItem: function() {},
+ onError: function() {},
+ onPropertyChanged: function() {},
+ onPropertyDeleting: function() {},
+
+ onLoad: function() {
+ calendarUpdateNewItemsCommand();
+ document.commandDispatcher.updateCommands("calendar_commands");
+ },
+
+ onCalendarAdded: function(aCalendar) {
+ // Update the calendar commands for number of remote calendars and for
+ // more than one calendar
+ document.commandDispatcher.updateCommands("calendar_commands");
+ },
+
+ onCalendarRemoved: function(aCalendar) {
+ // Update commands to disallow deleting the last calendar and only
+ // allowing reload remote calendars when there are remote calendars.
+ document.commandDispatcher.updateCommands("calendar_commands");
+ },
+
+ onDefaultCalendarChanged: function(aNewCalendar) {
+ // A new default calendar may mean that the new calendar has different
+ // ACLs. Make sure the commands are updated.
+ calendarUpdateNewItemsCommand();
+ document.commandDispatcher.updateCommands("calendar_commands");
+ }
+};
+
+/**
+ * Opens the subscriptions dialog modally.
+ */
+function openCalendarSubscriptionsDialog() {
+ // the dialog will reset this to auto when it is done loading
+ window.setCursor("wait");
+
+ // open the dialog modally
+ window.openDialog("chrome://calendar/content/calendar-subscriptions-dialog.xul",
+ "_blank",
+ "chrome,titlebar,modal,resizable");
+}
+
+/**
+ * Calendar Offline Manager
+ */
+var calendarOfflineManager = {
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
+
+ init: function() {
+ if (this.initialized) {
+ throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
+ }
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+ this.updateOfflineUI(!this.isOnline());
+ this.initialized = true;
+ },
+
+ uninit: function() {
+ if (!this.initialized) {
+ throw Components.results.NS_ERROR_NOT_INITIALIZED;
+ }
+ Services.obs.removeObserver(this, "network:offline-status-changed", false);
+ this.initialized = false;
+ },
+
+ isOnline: function() {
+ return !Services.io.offline;
+ },
+
+ updateOfflineUI: function(aIsOffline) {
+ // Refresh the current view
+ currentView().goToDay(currentView().selectedDay);
+
+ // Set up disabled locks for offline
+ document.commandDispatcher.updateCommands("calendar_commands");
+ },
+
+ observe: function(aSubject, aTopic, aState) {
+ if (aTopic == "network:offline-status-changed") {
+ this.updateOfflineUI(aState == "offline");
+ }
+ }
+};