Fix bug 475715 - Create a binding for the calendar list. r=berend
authorPhilipp Kewisch <mozilla@kewis.ch>
Thu, 26 Feb 2009 14:53:15 +0100
changeset 2080 43ba72c39c9f3b078a5de72ea02b508904db176a
parent 2079 132e294403b01f046607ad3548735ab1074acac5
child 2081 90197594f3b610c3fc80acc7e9ef9c9ad7251ae7
push idunknown
push userunknown
push dateunknown
reviewersberend
bugs475715
Fix bug 475715 - Create a binding for the calendar list. r=berend
calendar/base/content/calendar-base-view.xml
calendar/base/content/calendar-calendars-list.xul
calendar/base/content/calendar-chrome-startup.js
calendar/base/content/calendar-creation.js
calendar/base/content/calendar-management.js
calendar/base/content/calendar-month-view.xml
calendar/base/content/calendar-multiday-view.xml
calendar/base/content/calendar-task-tree.xml
calendar/base/content/calendar-ui-utils.js
calendar/base/content/calendar-unifinder-todo.js
calendar/base/content/calendar-unifinder-todo.xul
calendar/base/content/calendar-unifinder.js
calendar/base/content/calendar-view-core.xml
calendar/base/content/calendar-views.js
calendar/base/content/widgets/calendar-list-tree.xml
calendar/base/content/widgets/calendar-widget-bindings.css
calendar/base/content/widgets/minimonth.xml
calendar/base/jar.mn
calendar/base/themes/pinstripe/calendar-management.css
calendar/base/themes/winstripe/calendar-management.css
calendar/lightning/content/messenger-overlay-sidebar.xul
--- a/calendar/base/content/calendar-base-view.xml
+++ b/calendar/base/content/calendar-base-view.xml
@@ -626,23 +626,27 @@
                case "calendar.week.d3wednesdaysoff":
                case "calendar.week.d4thursdaysoff":
                case "calendar.week.d5fridaysoff":
                case "calendar.week.d6saturdaysoff":
                    this.updateDaysOffPrefs();
                    break;
                case "calendar.timezone.local":
                    this.timezone = calendarDefaultTimezone();
+                   this.refreshView();
                    break;
                case "calendar.alarms.indicator.show":
                    //Break here to ensure the view is refreshed
                    break;
                case "calendar.week.start":
                    this.weekStartOffset = aSubject.getIntPref(aPreference);
                    break;
+               case "calendar.date.format":
+                   this.refreshView();
+                   break;
                default:
                    return;
             }
             this.refreshView();
         ]]></body>
       </method>
 
       <method name="updateDaysOffPrefs">
--- a/calendar/base/content/calendar-calendars-list.xul
+++ b/calendar/base/content/calendar-calendars-list.xul
@@ -32,93 +32,63 @@
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the LGPL or the GPL. 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 ***** -->
 
 <?xml-stylesheet href="chrome://calendar/skin/calendar-management.css" type="text/css"?>
+<?xml-stylesheet href="chrome://calendar/content/widgets/calendar-widget-bindings.css" type="text/css"?>
 
 <!DOCTYPE overlay SYSTEM "chrome://calendar/locale/calendar.dtd">
 
 <overlay id="calendar-list-overlay"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <popupset id="calendar-popupset">
     <popup id="list-calendars-context-menu"
-           onpopupshowing="return calendarListTreeView.setupContextMenu(event);">
+           onpopupshowing="return calendarListSetupContextMenu(event);">
       <menuitem id="list-calendars-context-new"
                 label="&calendar.context.newserver.label;"
                 accesskey="&calendar.context.newserver.accesskey;"
                 observes="calendar_new_calendar_command"/>
       <menuitem id="list-calendars-context-find"
                 label="&calendar.context.findcalendar.label;"
                 accesskey="&calendar.context.findcalendar.accesskey;"
                 oncommand="openCalendarSubscriptionsDialog();"/>
       <menuitem id="list-calendars-context-delete"
                 label="&calendar.context.deleteserver.label;"
                 accesskey="&calendar.context.deleteserver.accesskey;"
                 observes="calendar_delete_calendar_command"/>
       <menuseparator id="list-calendars-context-itemops-menuseparator"/>
       <menuitem id="list-calendars-context-export"
                 label="&calendar.context.export.label;"
                 accesskey="&calendar.context.export.accesskey;"
-                oncommand="exportEntireCalendar(calendarListTreeView.getCalendarFromEvent(event));"/>
+                oncommand="exportEntireCalendar(document.getElementById('list-calendars-context-menu').contextCalendar);"/>
       <menuitem id="list-calendars-context-publish"
                 label="&calendar.context.publish.label;"
                 accesskey="&calendar.context.publish.accesskey;"
                 observes="calendar_publish_selected_calendar_command"/>
       <menuseparator id="list-calendars-context-export-menuseparator"/>
       <menuitem id="list-calendars-context-reload"
                 label="&calendar.context.reloadserver.label;"
                 accesskey="&calendar.context.reloadserver.accesskey;"
                 observes="calendar_reload_remote_calendars"/>
       <menuseparator id="list-calendars-context-reload-menuseparator"/>
       <menuitem id="list-calendars-context-edit"
                 label="&calendar.context.properties.label;"
                 accesskey="&calendar.context.properties.accesskey;"
                 observes="calendar_edit_calendar_command"/>
     </popup>
     <tooltip id="calendar-list-tooltip"
-             onpopupshowing="return calendarListTreeView.onTooltipShowing(event)"/>
+             onpopupshowing="return calendarListTooltipShowing(event)"/>
   </popupset>
 
-  <tree id="calendar-list-tree-widget"
-        hidecolumnpicker="true"
-        seltype="single"
-        onkeypress="calendarListTreeView.onKeyPress(event);"
-        ondblclick="calendarListTreeView.onDoubleClick(event);"
-        onselect="calendarListTreeView.onSelect(event);"
-        context="list-calendars-context-menu">
-      <treecols id="calendar-treecols" hideheader="true">
-          <treecol id="calendar-list-tree-checkbox"
-                   cycler="true"
-                   type="checkbox"
-                   hideheader="true"
-                   width="17"/>
-          <treecol id="calendar-list-tree-color"
-                   hideheader="true"
-                   width="16"/>
-          <treecol id="calendar-list-tree-calendar"
-                   hideheader="true"
-                   label="&calendar.unifinder.tree.calendarname.label;"
-                   flex="1"/>
-          <treecol id="calendar-list-tree-status"
-                   hideheader="true"
-                   width="18"/>
-          <treecol id="calendar-list-tree-scrollbar-spacer"
-                   fixed="true"
-                   hideheader="true">
-            <!-- This is a very elegant workaround to make sure the last column
-                 is not covered by the scrollbar in case of an overflow. This
-                 treecol needs to be here last, so if you decide to insert
-                 columns please do so before this treecol -->
-            <slider id="calendar-list-tree-scrollbar-slider" orient="vertical"/>
-          </treecol>
-        </treecols>
-     <treechildren id="calendar-treechildren"
-                   tooltip="calendar-list-tooltip"
-                   ondragstart="calendarListTreeView.onDragStart(event);"
-                   onoverflow="document.getElementById('calendar-list-tree-scrollbar-spacer').collapsed = false;"
-                   onunderflow="document.getElementById('calendar-list-tree-scrollbar-spacer').collapsed = true;"/>
-   </tree>
+  <calendar-list-tree id="calendar-list-tree-widget"
+                      type="full"
+                      writable="true"
+                      allowdrag="true"
+                      onSortOrderChanged="updateSortOrderPref(event)"
+                      onselect="document.commandDispatcher.updateCommands('calendar_commands')"
+                      childtooltip="calendar-list-tooltip"
+                      childcontext="list-calendars-context-menu"/>
 </overlay>
--- a/calendar/base/content/calendar-chrome-startup.js
+++ b/calendar/base/content/calendar-chrome-startup.js
@@ -59,30 +59,36 @@ function commonInitCalendar() {
     getViewDeck().addEventListener("itemselect", calendarController.onSelectionChanged, true);
 
     // Start alarm service
     Components.classes["@mozilla.org/calendar/alarm-service;1"]
               .getService(Components.interfaces.calIAlarmService)
               .startup();
     document.getElementById("calsidebar_splitter").addEventListener("command", onCalendarViewResize, false);
     window.addEventListener("resize", onCalendarViewResize, true);
+
+    // Set up the category colors
+    categoryManagement.initCategories();
 }
 
 /**
  * Common unload steps for calendar chrome windows.
  */
 function commonFinishCalendar() {
     // Unload the calendar manager
     unloadCalendarManager();
 
     // Remove the command controller
     removeCalendarCommandController();
 
     document.getElementById("calsidebar_splitter").removeEventListener("command", onCalendarViewResize, false);
     window.removeEventListener("resize", onCalendarViewResize, true);
+
+    // Clean up the category colors
+    categoryManagement.cleanupCategories();
 }
 
 /**
  * Handler function to create |viewtype + "viewresized"| events that are
  * dispatched through the calendarviewBroadcaster.
  *
  * XXX this has nothing to do with startup, needs to go somewhere else.
  */
--- a/calendar/base/content/calendar-creation.js
+++ b/calendar/base/content/calendar-creation.js
@@ -35,43 +35,44 @@
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Shows the filepicker and creates a new calendar with a local file using the ICS
  * provider.
  */
 function openLocalCalendar() {
     const nsIFilePicker = Components.interfaces.nsIFilePicker;
-    var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+    let fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
     fp.init(window, calGetString("calendar", "Open"), nsIFilePicker.modeOpen);
-    var wildmat = "*.ics";
-    var description = calGetString("calendar", "filterIcs", [wildmat]);
+    let wildmat = "*.ics";
+    let description = calGetString("calendar", "filterIcs", [wildmat]);
     fp.appendFilter(description, wildmat);
     fp.appendFilters(nsIFilePicker.filterAll);
 
     if (fp.show() != nsIFilePicker.returnOK) {
         return;
     }
 
-    var calMgr = getCalendarManager();
-    var index = calendarListTreeView.findIndexByUri(fp.fileURL);
-    if (index >= 0) {
+    let calMgr = getCalendarManager();
+    let calendars = calMgr.getCalendars({});
+    if (calendars.some(function(x) x.uri == fp.fileURL)) {
         // The calendar already exists, select it and return.
-        calendarListTreeView.tree.view.selection.select(index);
+        document.getElementById("calendar-list-tree-widget")
+                .tree.view.selection.select(index);
         return;
     }
 
-    var openCalendar = calMgr.createCalendar("ics", fp.fileURL);
+    let openCalendar = calMgr.createCalendar("ics", fp.fileURL);
     calMgr.registerCalendar(openCalendar);
 
     // Strip ".ics" from filename for use as calendar name, taken from
     // calendarCreation.js
-    var fullPathRegex = new RegExp("([^/:]+)[.]ics$");
-    var prettyName = fp.fileURL.spec.match(fullPathRegex);
-    var name;
+    let fullPathRegex = new RegExp("([^/:]+)[.]ics$");
+    let prettyName = fp.fileURL.spec.match(fullPathRegex);
+    let name;
 
     if (prettyName && prettyName.length >= 1) {
         name = decodeURIComponent(prettyName[1]);
     } else {
         name = calGetString("calendar", "untitledCalendarName");
     }
 
     openCalendar.name = name;
--- a/calendar/base/content/calendar-management.js
+++ b/calendar/base/content/calendar-management.js
@@ -38,22 +38,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Get this window's currently selected calendar.
  * 
  * @return      The currently selected calendar.
  */
 function getSelectedCalendar() {
-    var tree = document.getElementById("calendar-list-tree-widget");
-    if (tree) {
-        return calendarListTreeView.getCalendar(tree.currentIndex);
-    } else { // make robust in startup scenarios when calendar list is not yet loaded:
-        return getCompositeCalendar().defaultCalendar;
-    }
+    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.
@@ -78,1031 +73,171 @@ function promptDeleteCalendar(aCalendar)
     if (ok) {
         var calMgr = getCalendarManager();
         calMgr.unregisterCalendar(aCalendar);
         calMgr.deleteCalendar(aCalendar);
     }
 }
 
 /**
- * Ensure that the passed calendar is visible to the user in the current window.
- */
-function ensureCalendarVisible(aCalendar) {
-    getCompositeCalendar().addCalendar(aCalendar);
-}
-
-/**
  * Called to initialize the calendar manager for a window.
  */
 function loadCalendarManager() {
-    var calMgr = getCalendarManager();
-    var composite = getCompositeCalendar();
-    var calendars = calMgr.getCalendars({});
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                      .getService(Components.interfaces.nsIPrefService);
-    var branch = prefService.getBranch("").QueryInterface(Components.interfaces.nsIPrefBranch2);
-
-    if (calendars.length == 0) {
-        var url = makeURL("moz-profile-calendar://");
-        var homeCalendar = calMgr.createCalendar("storage", url);
-
-        calMgr.registerCalendar(homeCalendar);
-        var name = calGetString("calendar", "homeCalendarName");
-
-        homeCalendar.name = name;
-        cal.setPref("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 (getPrefSafe("calendar.migrator.enabled", true)) {
-            try {
-                gDataMigrator.checkAndMigrate();
-            } catch (e) {
-                Components.utils.reportError("Migrator error: " + e);
-            }
-        }
-
-        calendars = [homeCalendar];
+    // Create the home calendar if no calendar exists.
+    let calendars = getCalendarManager().getCalendars({});
+    if (!calendars.length) {
+        initHomeCalendar();
     }
 
-    calendarListInitCategoryColors();
+    // Set up the composite calendar in the calendar list widget.
+    let tree = document.getElementById("calendar-list-tree-widget");
+    tree.compositeCalendar = getCompositeCalendar();
+}
 
-    // Set up the tree view
-    var tree = document.getElementById("calendar-list-tree-widget");
-    calendarListTreeView.tree = tree;
-    tree.view = calendarListTreeView;
+/**
+ * Creates the initial "Home" calendar if no calendar exists.
+ */
+function initHomeCalendar() {
+    let url = makeURL("moz-profile-calendar://");
+    let homeCalendar = calMgr.createCalendar("storage", url);
+
+    calMgr.registerCalendar(homeCalendar);
+    let name = calGetString("calendar", "homeCalendarName");
 
-    calMgr.addObserver(calendarManagerObserver);
-    composite.addObserver(calendarManagerCompositeObserver);
-    branch.addObserver("calendar.", calendarManagerObserver, false);
+    homeCalendar.name = name;
+    cal.setPref("calendar.list.sortOrder", homeCalendar.id);
+    composite.addCalendar(homeCalendar);
 
-    // The calendar manager will not notify for existing calendars. Go through
-    // them all and set up manually.
-    for each (let calendar in sortCalendarArray(calendars)) {
-        calendarManagerObserver.initializeCalendar(calendar);
+    // Wrapping this in a try/catch block, as if any of the migration code
+    // fails, the app may not load.
+    if (getPrefSafe("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() {
-    calendarManagerObserver.unload();
-    var calMgr = getCalendarManager();
     var composite = getCompositeCalendar();
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                      .getService(Components.interfaces.nsIPrefService);
-    var branch = prefService.getBranch("").QueryInterface(Components.interfaces.nsIPrefBranch2);
-
-    branch.removeObserver("calendar.", calendarManagerObserver);
-    composite.removeObserver(calendarManagerCompositeObserver);
     composite.setStatusObserver(null, null);
-    calMgr.removeObserver(calendarManagerObserver);
 }
 
 /**
- * Color specific functions
- */
-/**
- * Update the calendar-view-bindings.css stylesheet to provide rules for
- * category colors.
+ * 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" }
  *
- * XXX This doesn't really fit into the calendar manager and is here for
- * historical reasons.
+ * @param event     The SortOrderChanged event described above.
  */
-var gCachedStyleSheet;
-function calendarListInitCategoryColors() {
-    var calendars = getCalendarManager().getCalendars({});
-    if (!gCachedStyleSheet) {
-        var cssUri = "chrome://calendar/content/calendar-view-bindings.css";
-        gCachedStyleSheet = getStyleSheet(cssUri);
-    }
-
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                      .getService(Components.interfaces.nsIPrefService);
-    var categoryPrefBranch = prefService.getBranch("calendar.category.color.");
-    var categories = categoryPrefBranch.getChildList("", {});
-
-    // check category preference name syntax
-    categories = calendarConvertObsoleteColorPrefs(categoryPrefBranch, categories);
-
-    // Update all categories
-    for each (var category in categories) {
-        updateStyleSheetForObject(category, gCachedStyleSheet);
-    }
+function updateSortOrderPref(event) {
+    let sortOrderString = event.sortOrder.join(" ");
+    cal.setPref("calendar.list.sortOrder", sortOrderString);
 }
 
 /**
- * Remove illegally formatted category names from the array coloredCategories
- * so they don't cause CSS errors.  For each illegal colored category c, if
- * its color preference has not yet been replaced with a converted preference
- * with key formatStringForCSSRule(c), create the preference with the
- * converted key and with the previous preference value, and clear the old
- * preference.  (For most users who upgrade and do not later add colors with a
- * downgrade version, this should convert any illegal preferences once, so
- * future runs have no illegal preferences.)
+ * Handler function to call when the tooltip is showing on the calendar list.
  *
- * @param categoryPrefBranch        PrefBranch for "calendar.category.color."
- * @param coloredCategories         Array of preference name suffixes under the
- *                                    prefBranch.
- * @return                          Same array with each illegal name replaced
- *                                    with formatted name if it doesn't already
- *                                    exist, or simply removed from array if it
- *                                    does.
- *
+ * @param event     The DOM event provoked by the tooltip showing.
  */
-function calendarConvertObsoleteColorPrefs(categoryPrefBranch, coloredCategories) {
-    for (var i in coloredCategories) {
-        var category = coloredCategories[i];
-        if (category.search(/[^_0-9a-z-]/) != -1) {
-            var categoryFix = formatStringForCSSRule(category);
-            if (!categoryPrefBranch.prefHasUserValue(categoryFix)) {
-                var color = categoryPrefBranch.getCharPref(category);
-                categoryPrefBranch.setCharPref(categoryFix, color);
-                categoryPrefBranch.clearUserPref(category); // not usable
-                coloredCategories[i] = categoryFix;  // replace illegal name
-            } else {
-                coloredCategories.splice(i, 1); // remove illegal name
-            }
+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]);
         }
     }
-    return coloredCategories;
-}
-
-/**
- * Update the cached stylesheet to provide rules for the calendar list's
- * calendar colors.
- *
- * @param aCalendar         The calendar to update rules for.
- */
-function calendarListUpdateColor(aCalendar) {
-    var selectorPrefix = "treechildren::-moz-tree-cell";
-    var color = aCalendar.getProperty("color");
-    if (!color) {
-        return;
-    }
-    var selector = selectorPrefix + "color-"  + color.substr(1);
-
-    for (var i = 0; i < gCachedStyleSheet.cssRules.length; i++) {
-        var thisrule = gCachedStyleSheet.cssRules[i];
-        if (thisrule.selectorText && thisrule.selectorText == selector) {
-            return;
-        }
-    }
-
-    var ruleString = selectorPrefix + "(calendar-list-tree-color, color-" + color.substr(1) + ") { }";
-
-    var rule = gCachedStyleSheet
-               .insertRule(ruleString, gCachedStyleSheet.cssRules.length);
-
-    gCachedStyleSheet.cssRules[rule].style.backgroundColor = color;
-    return;
+    setElementValue("calendar-list-tooltip", tooltipText, "label");
+    return (tooltipText != false);
 }
 
 /**
- * Calendar Tree View
+ * 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.
  */
-var calendarListTreeView = {
-    mCalendarList: [],
-    tree: null,
-    treebox: null,
-    mContextElement: null,
-
-    QueryInterface: function cLTV_QueryInterface(aIID) {
-        return doQueryInterface(this, calendarListTreeView.__proto__, aIID,
-                                [Components.interfaces.nsISupports,
-                                 Components.interfaces.nsITreeView]);
-    },
-
-    /**
-     * High-level calendar tree manipulation
-     */
-
-    /**
-     * Find the array index of the passed calendar
-     *
-     * @param aCalendar     The calendar to find an index for.
-     * @return              The array index, or -1 if not found.
-     */
-    findIndex: function cLTV_findIndex(aCalendar) {
-        for (var i = 0; i < this.mCalendarList.length; i++) {
-            if (this.mCalendarList[i].id == aCalendar.id) {
-                return i;
-            }
-        }
-        return -1;
-    },
-
-    /**
-     * Find the array index of a calendar by its uri.
-     *
-     * @param aUri          The uri to find an index for.
-     * @return              The array index, or -1 if not found.
-     */
-    findIndexByUri: function cLTV_findIndexByUri(aUri) {
-        for (var i = 0; i < this.mCalendarList.length; i++) {
-            if (this.mCalendarList[i].uri.equals(aUri)) {
-                return i;
-            }
-        }
-        return -1;
-    },
-
-    /**
-     * Add a calendar to the calendar list
-     * 
-     * @param aCalendar     The calendar to add.
-     */
-    addCalendar: function cLTV_addCalendar(aCalendar) {
-        var composite = getCompositeCalendar();
-        this.mCalendarList.push(aCalendar);
-        calendarListUpdateColor(aCalendar);
-        this.treebox.rowCountChanged(this.mCalendarList.length - 1, 1);
-
-        if (!composite.defaultCalendar ||
-            aCalendar.id == composite.defaultCalendar.id) {
-            this.tree.view.selection.select(this.mCalendarList.length - 1);
-        }
-    },
-
-    /**
-     * Remove a calendar from the calendar list
-     * 
-     * @param aCalendar     The calendar to remove.
-     */
-    removeCalendar: function cLTV_removeCalendar(aCalendar) {
-        var index = this.findIndex(aCalendar);
-        if (index < 0) {
-            return;
-        }
+function calendarListSetupContextMenu(event) {
+    let col = {};
+    let row = {};
+    let calendar;
+    let calendars = getCalendarManager().getCalendars({});
+    let treeNode = document.getElementById("calendar-list-tree-widget");
 
-        this.mCalendarList.splice(index, 1);
-        if (index == this.rowCount) {
-            index--;
-        }
-
-        this.tree.view.selection.select(index + 1);
-        this.treebox.rowCountChanged(index, -1);
-    },
-
-    /**
-     * Update a calendar's tree row (to refresh the color and such)
-     * 
-     * @param aCalendar     The calendar to update.
-     */
-    updateCalendar: function cLTV_updateCalendar(aCalendar) {
-        var index = this.findIndex(aCalendar);
-        this.treebox.invalidateRow(index);
-    },
-
-    /**
-     * Get the calendar from the given DOM event. This can be a Mouse event or a
-     * keyboard event.
-     *
-     * @param event     The DOM event to check
-     * @param aCol      An out-object for the column id.
-     * @param aRow      An out-object for the row index.
-     */
-    getCalendarFromEvent: function cLTV_getCalendarFromEvent(event,
-                                                             aCol,
-                                                             aRow) {
-        if (event.clientX && event.clientY) {
-            // If we have a client point, get the row directly from the client
-            // point.
-            aRow = aRow || {};
-            this.treebox.getCellAt(event.clientX,
-                                   event.clientY,
-                                   aRow,
-                                   aCol || {},
-                                   {});
-
-        } else {
-            // The event is probably coming from a context menu oncommand
-            // handler. We saved the row and column where the context menu
-            // showed up in setupContextMenu().
-            aCol = { value: this.mContextElement.column };
-            aRow = { value: this.mContextElement.row };
-        }
-        return aRow && aRow.value > -1 && this.mCalendarList[aRow.value];
-    },
-
-
-    /**
-     * Get the calendar from a certain index.
-     * 
-     * @param index     The index to get the calendar for.
-     */
-    getCalendar: function cLTV_getCalendar(index) {
-        if (index < 0) {
-            index = 0;
-        } else if (index >= this.mCalendarList.length) {
-            index = (this.mCalendarList.length - 1);
-        }
-        return this.mCalendarList[index];
-    },
-
-    /**
-     * nsITreeView methods and properties
-     */
-    get rowCount() {
-        return this.mCalendarList.length;
-    },
-
-    getCellProperties: function cLTV_getCellProperties(aRow, aCol, aProps) {
-        this.getRowProperties(aRow, aProps);
-        this.getColumnProperties(aCol, aProps);
-    },
-
-    getRowProperties: function cLTV_getRowProperties(aRow, aProps) {
-        var calendar = this.getCalendar(aRow);
-        var 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);
+    }
 
-        // Set up the composite calendar status
-        if (composite.getCalendarById(calendar.id)) {
-            aProps.AppendElement(getAtomFromService("checked"));
-        } else {
-            aProps.AppendElement(getAtomFromService("unchecked"));
-        }
-
-        // Get the calendar color
-        var color = calendar.getProperty("color");
-        color = color && color.substr(1);
-
-        // Set up the calendar color (background)
-        var bgColorProp = "color-" + (color || "default");
-        aProps.AppendElement(getAtomFromService(bgColorProp));
-
-        // Set a property to get the contrasting text color (foreground)
-        var fgColorProp = getContrastingTextColor(color || "a8c2e1");
-        aProps.AppendElement(getAtomFromService(fgColorProp));
-
-        var currentStatus = calendar.getProperty("currentStatus");
-        if (!Components.isSuccessCode(currentStatus)){
-            aProps.AppendElement(getAtomFromService("readfailed"));
-        // 'readfailed' is supposed to "win" over 'readonly', meaning that 
-        // if reading from a calendar fails there is no further need to also display
-        // information about 'readonly' status
-        } else if (calendar.readOnly) {
-            aProps.AppendElement(getAtomFromService("readonly"));
-        }
-
-        // Set up the disabled state
-        if (calendar.getProperty("disabled")) {
-            aProps.AppendElement(getAtomFromService("disabled"));
-        } else {
-            aProps.AppendElement(getAtomFromService("enabled"));
-        }
-    },
-
-    getColumnProperties: function cLTV_getColumnProperties(aCol, aProps) {},
-
-    isContainer: function cLTV_isContainer(aRow) {
+    if (col.value &&
+        col.value.element.getAttribute("anonid") == "checkbox-treecol") {
+        // Don't show the context menu if the checkbox was clicked.
         return false;
-    },
-
-    isContainerOpen: function cLTV_isContainerOpen(aRow) {
-        return false;
-    },
+    }
 
-    isContainerEmpty: function cLTV_isContainerEmpty(aRow) {
-        return false;
-    },
-
-    isSeparator: function cLTV_isSeparator(aRow) {
-        return false;
-    },
-
-    isSorted: function cLTV_isSorted(aRow) {
-        return false;
-    },
+    document.getElementById("list-calendars-context-menu").contextCalendar = calendar;
 
-    /**
-     * Initiate a drag operation for the calendar list. Can be used in the
-     * dragstart handler.
-     *
-     * @param event     The DOM event containing drag information.
-     */
-    onDragStart: function cLTV_onDragStart(event) {
-        let calendar = this.getCalendarFromEvent(event);
-        if (event.dataTransfer) {
-            // Setting data starts a drag session
-            event.dataTransfer.setData("application/x-moz-calendarID", calendar.id);
-            event.dataTransfer.effectAllowed = "move";
-        }
-    },
-
-    canDrop: function cLTV_canDrop(aRow, aOrientation) {
-        let dragSession = cal.getDragService().getCurrentSession();
-        let dataTransfer = dragSession.dataTransfer;
-        if (!dataTransfer) {
-            // If there is no data transfer then we can't drop (i.e dropping a
-            // file on the calendar list.
-            return false;
-        }
+    // 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");
 
-        let dragCalId = dataTransfer.getData("application/x-moz-calendarID");
-
-        return (aOrientation != Components.interfaces.nsITreeView.DROP_ON &&
-                dragCalId != null);
-    },
-
-    drop: function cLTV_drop(aRow, aOrientation) {
-        let dragSession = cal.getDragService().getCurrentSession();
-        let dataTransfer = dragSession.dataTransfer;
-        let dragCalId = dataTransfer &&
-                        dataTransfer.getData("application/x-moz-calendarID");
-        if (!dataTransfer || !dragCalId) {
-            return false;
-        }
-
-        let sortOrder = cal.getPrefSafe("calendar.list.sortOrder", "").split(" ");
-        let oldIndex = sortOrder.indexOf(dragCalId);
-
-        // If no row is specified (-1), then assume append.
-        let row = (aRow < 0 ? sortOrder.length - 1 : aRow);
-        let targetIndex = row + Math.max(0, aOrientation);
-
-        // We don't need to move if the target row has the same index as the old
-        // row. The same goes for dropping after the row before the old row or
-        // before the row after the old row. Think about it :-)
-        if (aRow != oldIndex && row + aOrientation != oldIndex) {
-            // Add the new one, remove the old one.
-            sortOrder.splice(targetIndex, 0, dragCalId);
-            sortOrder.splice(oldIndex + (oldIndex > targetIndex ? 1 : 0), 1);
-
-            cal.setPref("calendar.list.sortOrder", sortOrder.join(" "));
-            this.mCalendarList = sortCalendarArray(this.mCalendarList);
-
-            // Invalidate the tree rows between the old item and the new one.
-            if (oldIndex < targetIndex) {
-                this.treebox.invalidateRange(oldIndex, targetIndex);
-            } else {
-                this.treebox.invalidateRange(targetIndex, oldIndex);
-            }
+    if (calendar) {
+        enableElement("list-calendars-context-edit");
+        enableElement("list-calendars-context-publish");
+        // Only enable the delete calendars item if there is more than one
+        // calendar. We don't want to have the last calendar deleted.
+        if (calendars.length > 1) {
+            enableElement("list-calendars-context-delete");
         }
-        return true;
-    },
+    } else {
+        disableElement("list-calendars-context-edit");
+        disableElement("list-calendars-context-publish");
+        disableElement("list-calendars-context-delete");
+    }
+    return true;
+}
 
-    /**
-     * This function can be used by other nodes to simulate dropping on the
-     * tree. This can be used for example on the tree header so that the row
-     * will be inserted before the first visible row. The event client
-     * coordinate are used to determine if the row should be dropped before the
-     * first row (above treechildren) or below the last visible row (below top
-     * of treechildren).
-     *
-     * @param event     The DOM drop event.
-     * @return          Boolean indicating if the drop succeeded.
-     */
-    foreignDrop: function cLTV_foreignDrop(event) {
-        let treechildren = document.getElementById("calendar-treechildren");
-        if (event.clientY < this.tree.boxObject.y) {
-            return this.drop(this.treebox.getFirstVisibleRow(), -1);
-        } else {
-            return this.drop(this.treebox.getLastVisibleRow(), 1);
-        }
-    },
-
-    /**
-     * Similar function to foreignCanDrop but for the dragenter event
-     * @see calendarListTreeView::foreignDrop
-     */
-    foreignCanDrop: function cLTV_foreignCanDrop(event) {
-        let treechildren = document.getElementById("calendar-treechildren");
-        if (event.clientY < this.tree.boxObject.y) {
-            return this.canDrop(this.treebox.getFirstVisibleRow(), -1);
-        } else {
-            return this.canDrop(this.treebox.getLastVisibleRow(), 1);
-        }
-    },
-
-    getParentIndex: function cLTV_getParentIndex(aRow) {
-        return -1;
-    },
-
-    hasNextSibling: function cLTV_hasNextSibling(aRow, aAfterIndex) {},
-
-    getLevel: function cLTV_getLevel(aRow) {
-        return 0;
+var compositeObserver = {
+    QueryInterface: function cO_QueryInterface(aIID) {
+        return cal.doQueryInterface(this,
+                                    calendarManagementCompositeObserver.prototype,
+                                    aIID,
+                                    [Components.interfaces.calICompositeObserver]);
     },
 
-    getImageSrc: function cLTV_getImageSrc(aRow, aOrientation) {},
-
-    getProgressMode: function cLTV_getProgressMode(aRow, aCol) {},
-
-    getCellValue: function cLTV_getCellValue(aRow, aCol) {
-        var calendar = this.getCalendar(aRow);
-        var composite = getCompositeCalendar();
-
-        switch (aCol.id) {
-            case "calendar-list-tree-checkbox":
-                return composite.getCalendarById(calendar.id) ? "true" : "false";
-            case "calendar-list-tree-status":
-                // The value of this cell shows the calendar readonly state
-                return (calendar.readOnly ? "true" : "false");
-        }
-        return null;
-    },
-
-    getCellText: function cLTV_getCellText(aRow, aCol) {
-        switch (aCol.id) {
-            case "calendar-list-tree-calendar":
-                return this.getCalendar(aRow).name;
-        }
-        return "";
-    },
-
-    setTree: function cLTV_setTree(aTreeBox) {
-        this.treebox = aTreeBox;
-    },
-
-    toggleOpenState: function cLTV_toggleOpenState(aRow) {},
-
-    cycleHeader: function cLTV_cycleHeader(aCol) { },
-
-    cycleCell: function cLTV_cycleCell(aRow, aCol) {
-        var calendar = this.getCalendar(aRow);
-        var composite = getCompositeCalendar();
-
-        switch (aCol.id) {
-            case "calendar-list-tree-checkbox":
-                composite.startBatch();
-                try {
-                    if (composite.getCalendarById(calendar.id)) {
-                        composite.removeCalendar(calendar);
-                    } else {
-                        composite.addCalendar(calendar);
-                    }
-                } finally {
-                    composite.endBatch();
-                }
-                break;
-        }
-        this.treebox.invalidateRow(aRow);
-    },
-
-    isEditable: function cLTV_isEditable(aRow, aCol) {
-        return false;
-    },
-
-    setCellValue: function cLTV_setCellValue(aRow, aCol, aValue) {
-        var calendar = this.getCalendar(aRow);
-        var composite = getCompositeCalendar();
-
-        switch (aCol.id) {
-            case "calendar-list-tree-checkbox":
-                if (aValue == "true") {
-                    composite.addCalendar(calendar);
-                } else {
-                    composite.removeCalendar(calendar);
-                }
-                break;
-            case "calendar-list-tree-status":
-                calendar.readOnly = (aValue == "true");
-                break;
-            default:
-                return null;
-        }
-        return aValue;
-    },
-
-    setCellText: function cLTV_setCellText(aRow, aCol, aValue) {},
-
-    performAction: function cLTV_performAction(aAction) {},
-
-    performActionOnRow: function cLTV_performActionOnRow(aAction, aRow) {},
-
-    performActionOnCell: function cLTV_performActionOnCell(aAction, aRow, aCol) {},
-
-    /**
-     * Calendar Tree Events
-     */
-    onKeyPress: function cLTV_onKeyPress(event) {
-        const kKE = Components.interfaces.nsIDOMKeyEvent;
-        switch (event.keyCode || event.which) {
-            case kKE.DOM_VK_DELETE:
-                promptDeleteCalendar(getSelectedCalendar());
-                break;
-            case kKE.DOM_VK_SPACE:
-                if (this.tree.currentIndex > -1 ) {
-                    var cbCol = this.treebox.columns.getNamedColumn("calendar-list-tree-checkbox");
-                    this.cycleCell(this.tree.currentIndex, cbCol);
-                }
-                break;
-            case kKE.DOM_VK_DOWN:
-                if (event.ctrlKey) {
-                    let ci = this.tree.currentIndex
-                    let sortOrder = cal.getPrefSafe("calendar.list.sortOrder", "").split(" ");
-                    if (ci < sortOrder.length - 1) {
-                        sortOrder.splice(ci + 1, 0, sortOrder.splice(ci, 1));
-                        cal.setPref("calendar.list.sortOrder", sortOrder.join(" "));
-                        this.mCalendarList = sortCalendarArray(this.mCalendarList);
-                        this.treebox.invalidateRange(ci, ci + 1);
-
-                        if (this.tree.view.selection.isSelected(ci)) {
-                            this.tree.view.selection.toggleSelect(ci);
-                            this.tree.view.selection.toggleSelect(ci + 1);
-                        }
-                        if (this.tree.view.selection.currentIndex == ci) {
-                            this.tree.view.selection.currentIndex = ci + 1;
-                        }
-                    }
-                    // Don't call the default <key> handler.
-                    event.preventDefault();
-                }
-                break;
-            case kKE.DOM_VK_UP:
-                if (event.ctrlKey) {
-                    let ci = this.tree.currentIndex
-                    let sortOrder = cal.getPrefSafe("calendar.list.sortOrder", "").split(" ");
-                    if (ci > 0) {
-                        sortOrder.splice(ci - 1, 0, sortOrder.splice(ci, 1));
-                        cal.setPref("calendar.list.sortOrder", sortOrder.join(" "));
-                        this.mCalendarList = sortCalendarArray(this.mCalendarList);
-                        this.treebox.invalidateRange(ci - 1, ci);
-
-                        if (this.tree.view.selection.isSelected(ci)) {
-                            this.tree.view.selection.toggleSelect(ci);
-                            this.tree.view.selection.toggleSelect(ci - 1);
-                        }
-                        if (this.tree.view.selection.currentIndex == ci) {
-                            this.tree.view.selection.currentIndex = ci - 1;
-                        }
-                    }
-                    // Don't call the default <key> handler.
-                    event.preventDefault();
-                }
-                break;
-        }
-    },
-
-    onDoubleClick: function cLTV_onDoubleClick(event) {
-        var col = {};
-        var calendar = this.getCalendarFromEvent(event, col);
-        if (event.button != 0 ||
-            (col.value && col.value.id == "calendar-list-tree-checkbox")) {
-            // Only left clicks that are not on the checkbox column
-            return;
-        }
-        if (calendar) {
-            openCalendarProperties(calendar);
-        } else {
-            openCalendarWizard();
-        }
-    },
-
-    onSelect: function cLTV_onSelect(event) {
-        // The select event should only fire when an item is actually selected,
-        // therefore we can assume that getSelectedCalendar() returns a
-        // calendar.
-        var composite = getCompositeCalendar();
-        composite.defaultCalendar = getSelectedCalendar();
-        document.commandDispatcher.updateCommands("calendar_commands");
-    },
-
-    onTooltipShowing: function cLTV_onTooltipShowing(event) {
-        var calendar = this.getCalendarFromEvent(event);
-        var tooltipText = false;
-        if (calendar) {
-            var 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.
-     */
-    setupContextMenu: function cLTV_setupContextMenu(event) {
-        var col = {};
-        var row = {};
-        var calendar;
-        var calendars = getCalendarManager().getCalendars({});
-
-        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 element.
-            row.value =  this.tree.currentIndex;
-            col.value = this.treebox.columns
-                            .getNamedColumn("calendar-list-tree-calendar");
-            calendar = this.getCalendar(row.value);
-        } else {
-            // Using the mouse, the context menu will open on the treechildren
-            // element. Here we can use client points.
-            calendar = this.getCalendarFromEvent(event, col, row);
-        }
-
-        if (col.value && col.value.id == "calendar-list-tree-checkbox") {
-            // Don't show the context menu if the checkbox was clicked.
-            return false;
-        }
-
-        // We need to save the row to return the correct calendar in
-        // getCalendarFromEvent()
-        this.mContextElement = {
-            row: row && row.value,
-            column: col && col.value
-        };
-
-        // Only enable calendar search if there's actually the chance of finding something:
-        document.getElementById("list-calendars-context-find").setAttribute(
-            "collapsed", (getCalendarSearchService().getProviders({}).length > 0 ? "false" : "true"));
-
-        if (calendar) {
-            document.getElementById("list-calendars-context-edit")
-                    .removeAttribute("disabled");
-            document.getElementById("list-calendars-context-publish")
-                    .removeAttribute("disabled");
-            // Only enable the delete calendars item if there is more than one
-            // calendar. We don't want to have the last calendar deleted.
-            if (calendars.length > 1) {
-                document.getElementById("list-calendars-context-delete")
-                        .removeAttribute("disabled");
-            }
-        } else {
-            document.getElementById("list-calendars-context-edit")
-                    .setAttribute("disabled", "true");
-            document.getElementById("list-calendars-context-publish")
-                    .setAttribute("disabled", "true");
-            document.getElementById("list-calendars-context-delete")
-                    .setAttribute("disabled", "true");
-        }
-        return true;
-    }
-};
-
-/**
- * An observer of the composite calendar to keep the calendar list in sync.
- * Implements calICompositeObserver and calIObserver.
- */
-var calendarManagerCompositeObserver = {
-    QueryInterface: function cMCO_QueryInterface(aIID) {
-        if (!aIID.equals(Components.interfaces.calICompositeObserver) &&
-            !aIID.equals(Components.interfaces.calIObserver) &&
-            !aIID.equals(Components.interfaces.nsISupports)) {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-        return this;
-    },
-
-    onCalendarAdded: function cMO_onCalendarAdded(aCalendar) {
-        // Make sure the checkbox state is updated
-        var index = calendarListTreeView.findIndex(aCalendar);
-        calendarListTreeView.treebox.invalidateRow(index);
-    },
-
-    onCalendarRemoved: function cMO_onCalendarRemoved(aCalendar) {
-        // Make sure the checkbox state is updated
-        var index = calendarListTreeView.findIndex(aCalendar);
-        calendarListTreeView.treebox.invalidateRow(index);
-    },
-
-    onDefaultCalendarChanged: function cMO_onDefaultCalendarChanged(aCalendar) {
-    },
-
-    // calIObserver. Note that each registered calendar uses this observer, not
-    // only the composite calendar.
-    onStartBatch: function cMO_onStartBatch() { },
-    onEndBatch: function cMO_onEndBatch() { },
-    onLoad: function cMO_onLoad() { },
-
-    // TODO: remove these temporary caldav exclusions when it is safe to do so
-    // needed to allow cadav refresh() to update w/o forcing visibility
-    onAddItem: function cMO_onAddItem(aItem) {
-        if (aItem.calendar.type != "caldav") {
-            ensureCalendarVisible(aItem.calendar);
-        }
-    },
-
-    onModifyItem: function cMO_onModifyItem(aNewItem, aOldItem) {
-        if (aNewItem.calendar.type != "caldav") {
-            ensureCalendarVisible(aNewItem.calendar);
-        }
-    },
-
-    onDeleteItem: function cMO_onDeleteItem(aDeletedItem) { },
-    onError: function cMO_onError(aCalendar, aErrNo, aMessage) { },
-
-    onPropertyChanged: function cMO_onPropertyChanged(aCalendar,
-                                                      aName,
-                                                      aValue,
-                                                      aOldValue) {
-    },
-    
-    onPropertyDeleting: function cMO_onPropertyDeleting(aCalendar,
-                                                        aName) {}
-}
-
-/**
- * An observer for the calendar manager xpcom component, to keep the calendar
- * list in sync.
- */
-var calendarManagerObserver = {
-    mDefaultCalendarItem: null,
-
-    QueryInterface: function cMO_QueryInterface(aIID) {
-        if (!aIID.equals(Components.interfaces.calICalendarManagerObserver) &&
-            !aIID.equals(Components.interfaces.calIObserver) &&
-            !aIID.equals(Components.interfaces.nsIObserver) &&
-            !aIID.equals(Components.interfaces.nsISupports)) {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-        return this;
-    },
-
-    /**
-     * Set up the UI for a new calendar.
-     *
-     * @param aCalendar     The calendar to add.
-     */
-    initializeCalendar: function cMO_initializeCalendar(aCalendar) {
-        calendarListTreeView.addCalendar(aCalendar);
-
-        updateStyleSheetForObject(aCalendar, gCachedStyleSheet);
-        calendarListUpdateColor(aCalendar);
-
-        // Watch the calendar for changes, to ensure its visibility when adding
-        // or changing items.
-        aCalendar.addObserver(this);
-
+    onCalendarAdded: function cO_onCalendarAdded(aCalendar) {
         // Update the calendar commands for number of remote calendars and for
         // more than one calendar
         document.commandDispatcher.updateCommands("calendar_commands");
     },
 
-    /**
-     * Clean up function to remove observers when closing the window
-     */
-    unload: function cMO_unload() {
-        var calendars = getCalendarManager().getCalendars({});
-        for each (var calendar in calendars) {
-            calendar.removeObserver(this);
-        }
-    },
-
-    /**
-     * Disables all elements with the attribute
-     * 'disable-when-no-writable-calendars' set to 'true'.
-     */
-    setupWritableCalendars: function cMO_setupWritableCalendars() {
-        var nodes = document.getElementsByAttribute("disable-when-no-writable-calendars", "true");
-        for (var i = 0; i < nodes.length; i++) {
-            if (this.mWritableCalendars < 1) {
-                nodes[i].setAttribute("disabled", "true");
-            } else {
-                nodes[i].removeAttribute("disabled");
-            }
-        }
-    },
-
-    // calICalendarManagerObserver
-    onCalendarRegistered: function cMO_onCalendarRegistered(aCalendar) {
-        // append by default:
-        let sortOrder = cal.getPrefSafe("calendar.list.sortOrder", "").split(" ");
-        sortOrder.push(aCalendar.id);
-        cal.setPref("calendar.list.sortOrder", sortOrder.join(" "));
-
-        this.initializeCalendar(aCalendar);
-        var composite = getCompositeCalendar();
-        var inComposite = aCalendar.getProperty(composite.prefPrefix +
-                                                "-in-composite");
-        if ((inComposite === null) || inComposite) {
-            composite.addCalendar(aCalendar);
-        }
-    },
-
-    onCalendarUnregistering: function cMO_onCalendarUnregistering(aCalendar) {
-        var calendars = getCalendarManager().getCalendars({});
-
-        calendarListTreeView.removeCalendar(aCalendar);
-        aCalendar.removeObserver(this);
-
-        // Make sure the calendar is removed from the composite calendar
-        getCompositeCalendar().removeCalendar(aCalendar);
-
+    onCalendarRemoved: function cO_onCalendarRemoved(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");
-    },
-
-    onCalendarDeleting: function cMO_onCalendarDeleting(aCalendar) {
-    },
-
-    // calIObserver. Note that each registered calendar uses this observer, not
-    // only the composite calendar.
-    onStartBatch: function cMO_onStartBatch() { },
-    onEndBatch: function cMO_onEndBatch() { },
-    onLoad: function cMO_onLoad() { },
-
-    // TODO: remove these temporary caldav exclusions when it is safe to do so
-    // needed to allow cadav refresh() to update w/o forcing visibility
-    onAddItem: function cMO_onAddItem(aItem) {
-        if (aItem.calendar.type != "caldav") {
-            ensureCalendarVisible(aItem.calendar);
-        }
-    },
-
-    onModifyItem: function cMO_onModifyItem(aNewItem, aOldItem) {
-        if (aNewItem.calendar.type != "caldav") {
-            ensureCalendarVisible(aNewItem.calendar);
-        }
-    },
-
-    onDeleteItem: function cMO_onDeleteItem(aDeletedItem) { },
-    onError: function cMO_onError(aCalendar, aErrNo, aMessage) { },
-
-    onPropertyChanged: function cMO_onPropertyChanged(aCalendar,
-                                                      aName,
-                                                      aValue,
-                                                      aOldValue) {
-        switch (aName) {
-            case "color":
-                updateStyleSheetForObject(aCalendar, gCachedStyleSheet);
-                calendarListUpdateColor(aCalendar);
-                // Fall through, update item in any case
-            case "name":
-            case "currentStatus":
-            case "readOnly":
-            case "disabled":
-                calendarListTreeView.updateCalendar(aCalendar);
-                // Fall through, update commands in any cases.
-            case "requiresNetwork":
-                document.commandDispatcher.updateCommands("calendar_commands");
-                break;
-        }
-    },
-
-    onPropertyDeleting: function cMO_onPropertyDeleting(aCalendar,
-                                                        aName) {
-        // Since the old value is not used directly in onPropertyChanged,
-        // but should not be the same as the value, set it to a different
-        // value.
-        this.onPropertyChanged(aCalendar, aName, null, null);
-    },
-
-    // nsIObserver
-    observe: function cMO_observe(aSubject, aTopic, aPrefName) {
-
-        switch (aPrefName) {
-            case "calendar.week.start":
-                getMinimonth().refreshDisplay(true);
-                break;
-            case "calendar.date.format":
-                var view = currentView();
-                var day = view.selectedDay;
-                if (day) {
-                    // The view may not be initialized, only refresh if there is
-                    // a selected day.
-                    view.goToDay(day);
-                }
-
-                if (isSunbird()) {
-                    refreshEventTree();
-                }
-                toDoUnifinderRefresh();
-                break;
-            case "calendar.timezone.local":
-                var subject = aSubject.QueryInterface(Components.interfaces.nsIPrefBranch2);
-                gDefaultTimezone = subject.getCharPref(aPrefName);
-
-                var view = currentView();
-                var day = view.selectedDay;
-                if (day) {
-                    // The view may not be initialized, only refresh if there is
-                    // a selected day.
-                    view.goToDay(day);
-                }
-
-                if (isSunbird()) {
-                    refreshEventTree();
-                }
-                toDoUnifinderRefresh();
-                break;
-            default :
-                break;
-        }
-
-        // Since we want to take care of all categories, this must be done
-        // extra.
-        if (aPrefName.substring(0, 24) == "calendar.category.color.") {
-            var categoryName = aPrefName.substring(24);
-            updateStyleSheetForObject(categoryName, gCachedStyleSheet);
-        }
     }
 };
 
 /**
  * Opens the subscriptions dialog modally.
  */
 function openCalendarSubscriptionsDialog() {
     // the dialog will reset this to auto when it is done loading
--- a/calendar/base/content/calendar-month-view.xml
+++ b/calendar/base/content/calendar-month-view.xml
@@ -53,17 +53,17 @@
   xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="calendar-month-day-box-item" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
     <content tooltip="itemTooltip">
       <xul:vbox flex="1">
         <xul:hbox>
           <xul:box anonid="event-container"
                    class="calendar-color-box"
-                   xbl:inherits="item-calendar"
+                   xbl:inherits="calendar-uri,calendar-id"
                    flex="1">
             <xul:box class="calendar-event-selection" orient="horizontal" flex="1">
               <xul:stack anonid="eventbox"
                          class="calendar-event-box-container"
                          xbl:inherits="readonly,flashing,alarm,allday,priority,progress,status,calendar,categories"
                          flex="1">
                 <xul:hbox class="calendar-event-details">
                   <xul:image anonid="item-icon"
@@ -374,17 +374,18 @@
             if (!itd.box) {
               // find what element to insert before
               var before = null;
               for (var j = i+1; !before && this.mItemData[j]; j++)
                 before = this.mItemData[j].box;
 
               var box = createXULElement("calendar-month-day-box-item");
               box.setAttribute("context", this.getAttribute("item-context") || this.getAttribute("context"));
-              box.setAttribute("item-calendar", itd.item.calendar.uri.spec);
+              box.setAttribute("calendar-uri", itd.item.calendar.uri.spec);
+              box.setAttribute("calendar-id", itd.item.calendar.id);
 
               this.insertBefore(box, before);
 
               box.calendarView = this.calendarView;
               box.item = itd.item;
               box.parentBox = this;
               box.occurrence = itd.item;
               itd.box = box;
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -1888,17 +1888,17 @@
   <!--
      -  An individual event box, to be inserted into a column.
     -->
   <binding id="calendar-event-box" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
     <content tooltip="itemTooltip" mousethrough="never">
         <xul:box xbl:inherits="orient,width,height" flex="1">
           <xul:box anonid="event-container"
                    class="calendar-color-box"
-                   xbl:inherits="orient,readonly,flashing,alarm,allday,priority,progress,status,calendar,categories,item-calendar"
+                   xbl:inherits="orient,readonly,flashing,alarm,allday,priority,progress,status,calendar,categories,calendar-uri,calendar-id"
                    flex="1">
             <xul:box class="calendar-event-selection" orient="horizontal" flex="1">
               <xul:stack anonid="eventbox"
                          align="stretch"
                          class="calendar-event-box-container"
                          flex="1"
                          xbl:inherits="context,parentorient=orient,readonly,flashing,alarm,allday,priority,progress,status,calendar,categories">
                 <xul:image flex="1" class="calendar-event-box-gradient"/>
--- a/calendar/base/content/calendar-task-tree.xml
+++ b/calendar/base/content/calendar-task-tree.xml
@@ -132,33 +132,41 @@
                        flex="1"
                        itemproperty="calendar"
                        label="&calendar.unifinder.tree.calendarname.label;"/>
         </xul:treecols>
         <xul:treechildren tooltip="taskTreeTooltip"/>
       </xul:tree>
     </content>
 
-    <implementation>
+    <implementation implements="nsIObserver">
       <constructor><![CDATA[
         Components.utils.import("resource://gre/modules/PluralForm.jsm");
         let self = this;
 
         // set up the tree filter
         this.mFilter = new calFilter();
 
         // set up the custom tree view
         let tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
         this.mTreeView.tree = tree;
         tree.view = this.mTreeView;
 
         // set up our calendar event observer
         let composite = getCompositeCalendar();
         composite.addObserver(this.mTaskTreeObserver);
 
+        // set up the preference observer
+        let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                    .getService(Components.interfaces.nsIPrefService);
+        let branch = prefService.getBranch("")
+                                .QueryInterface(Components.interfaces.nsIPrefBranch2);
+        branch.addObserver("calendar.", this, false);
+
+
         // we want to make several attributes on the column
         // elements persistent, but unfortunately there's no
         // relyable way with the 'persist' feature.
         // that's why we need to store the necessary bits and
         // pieces at the element this binding is attached to.
         let names = this.getAttribute("visible-columns").split(' ');
         let ordinals = this.getAttribute("ordinals").split(' ');
         let widths = this.getAttribute("widths").split(' ');
@@ -186,20 +194,27 @@
                 if (sorted == content) {
                     this.mTreeView.sortDirection = sortDirection;
                     this.mTreeView.selectedColumn = treecols[i];
                 }
             }
         }
       ]]></constructor>
       <destructor><![CDATA[
-          // remove out calendar event observer
+          // remove composite calendar observer
           let composite = getCompositeCalendar();
           composite.removeObserver(this.mTaskTreeObserver);
 
+          // remove the preference observer
+          let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                      .getService(Components.interfaces.nsIPrefService);
+          let branch = prefService.getBranch("")
+                                  .QueryInterface(Components.interfaces.nsIPrefBranch2);
+          branch.removeObserver("calendar.", this, false);
+
           let widths = "";
           let ordinals = "";
           let visible = "";
           let sorted = this.mTreeView.selectedColumn;
           let tree = document.getAnonymousNodes(this)[0];
           let treecols = tree.getElementsByTagNameNS(tree.namespaceURI, "treecol");
           for (let i = 0; i < treecols.length; i++) {
               if (treecols[i].getAttribute("hidden") != "true") {
@@ -875,16 +890,31 @@
                   this.binding.onCalendarRemoved(aCalendar);
               }
           },
 
           onDefaultCalendarChanged: function tTO_onDefaultCalendarChanged(aNewDefaultCalendar) {}
       })
       ]]></field>
 
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body><![CDATA[
+          switch (aPrefName) {
+              case "calendar.date.format":
+              case "calendar.timezone.local":
+                  this.refresh();
+                  break;
+          }
+
+        ]]></body>
+      </method>
+
       <!-- Called by event observers to update the display -->
       <method name="refresh">
         <parameter name="aFilter"/>
         <body><![CDATA[
           // XXX: why do I need this ?
           if(!this.mFilter) {
             this.mFilter = new calFilter();
           }
--- a/calendar/base/content/calendar-ui-utils.js
+++ b/calendar/base/content/calendar-ui-utils.js
@@ -98,19 +98,21 @@ function getElementValue(aElement, aProp
 
 /**
  * Sets the value of a boolean attribute by either setting the value or
  * removing the attribute
  *
  * @param aXulElement     The XUL element/string ID the attribute is applied to.
  * @param aAttribute      The name of the attribute
  * @param aValue          The boolean value
+ * @return                Returns aValue (for chaining)
  */
 function setBooleanAttribute(aXulElement, aAttribute, aValue) {
     setElementValue(aXulElement, (aValue ? "true" : false), aAttribute);
+    return aValue;
 }
 
 /**
  * Unconditionally show the element (hidden attribute)
  *
  * @param aElement      ID of XUL element to set, or the element node itself
  */
 function showElement(aElement) {
--- a/calendar/base/content/calendar-unifinder-todo.js
+++ b/calendar/base/content/calendar-unifinder-todo.js
@@ -45,21 +45,21 @@
 
 /**
  * Called when the window is loaded to set up the unifinder-todo.
  */
 function prepareCalendarToDoUnifinder() {
     if (isSunbird()) {
         document.getElementById("todo-label").removeAttribute("collapsed");
     }
-    toDoUnifinderRefresh();
+    updateShowCompleted();
 }
 
 /**
- * Called by event observers to update the task display
+ * Handler function to update the show completed view in the unifinder todo.
  */
-function toDoUnifinderRefresh() {
+function updateShowCompleted() {
     // Set up hiding completed tasks for the unifinder-todo tree
     var showCompleted = document.getElementById("show-completed-checkbox").checked;
     var tree = document.getElementById("unifinder-todo-tree");
     tree.showCompleted = showCompleted;
     tree.refresh();
 }
--- a/calendar/base/content/calendar-unifinder-todo.xul
+++ b/calendar/base/content/calendar-unifinder-todo.xul
@@ -58,17 +58,17 @@
     <box id="todo-label" align="left" collapsed="true">
       <label flex="1" crop="end" style="font-weight: bold" value="&calendar.unifinder.todoitems.label;" control="unifinder-todo-tree"/>
     </box>
     <box align="center">
       <checkbox id="show-completed-checkbox"
                 label="&calendar.unifinder.showcompletedtodos.label;"
                 flex="1"
                 crop="end"
-                oncommand="toDoUnifinderRefresh();"
+                oncommand="updateShowCompleted()"
                 persist="checked"/>
     </box>
     <vbox id="calendar-task-tree-detail" flex="1">
       <calendar-task-tree id="unifinder-todo-tree" flex="1"
                           visible-columns="completed priority title"
                           persist="visible-columns ordinals widths sort-active sort-direction"
                           context="taskitem-context-menu"/>
        <textbox id="unifinder-task-edit-field"
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -93,23 +93,22 @@ function getCurrentUnifinderFilter() {
  *
  * @see calIObserver
  * @see calICompositeObserver
  */
 var unifinderObserver = {
     mInBatch: false,
 
     QueryInterface: function uO_QueryInterface (aIID) {
-        if (!aIID.equals(Components.interfaces.nsISupports) &&
-            !aIID.equals(Components.interfaces.calICompositeObserver) &&
-            !aIID.equals(Components.interfaces.calIObserver)) {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-
-        return this;
+        return cal.doQueryInterface(this,
+                                    unifinderObserver.prototype,
+                                    aIID,
+                                    [Components.interfaces.calICompositeObserver,
+                                     Components.interfaces.nsIObserver,
+                                     Components.interfaces.calIObserver]);
     },
 
     // calIObserver:
     onStartBatch: function uO_onStartBatch() {
         this.mInBatch = true;
     },
 
     onEndBatch: function uO_onEndBatch() {
@@ -213,28 +212,44 @@ var unifinderObserver = {
         var 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 uO_observe(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);
     var unifinderTree = document.getElementById("unifinder-search-results-tree");
 
+    // Add pref observer
+    let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                .getService(Components.interfaces.nsIPrefService);
+    let branch = prefService.getBranch("")
+                            .QueryInterface(Components.interfaces.nsIPrefBranch2);
+    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
         var ccalendar = getCompositeCalendar();
         ccalendar.addObserver(unifinderObserver);
 
         kDefaultTimezone = calendarDefaultTimezone();
 
@@ -275,20 +290,27 @@ function prepareCalendarUnifinder() {
     }
 }
 
 /**
  * Called when the window is unloaded to clean up any observers and listeners
  * added.
  */
 function finishCalendarUnifinder() {
-    var ccalendar = getCompositeCalendar();
+    let ccalendar = getCompositeCalendar();
     ccalendar.removeObserver(unifinderObserver);
 
-    var viewDeck = getViewDeck();
+    // Remove pref observer
+    let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                .getService(Components.interfaces.nsIPrefService);
+    let branch = prefService.getBranch("")
+                            .QueryInterface(Components.interfaces.nsIPrefBranch2);
+    branch.removeObserver("calendar.", unifinderObserver, false);
+
+    let viewDeck = getViewDeck();
     if (viewDeck) {
         viewDeck.removeEventListener("dayselect", unifinderDaySelect, false);
         viewDeck.removeEventListener("itemselect", unifinderItemSelect, true);
     }
 
     //persist the sort
     var unifinderTree = document.getElementById("unifinder-search-results-tree");
     var sorted = unifinderTreeView.selectedColumn;
--- a/calendar/base/content/calendar-view-core.xml
+++ b/calendar/base/content/calendar-view-core.xml
@@ -48,17 +48,17 @@
     xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="calendar-editable-item">
     <content tooltip="itemTooltip">
       <xul:vbox flex="1">
         <xul:hbox>
           <xul:box anonid="event-container"
                    class="calendar-color-box"
-                   xbl:inherits="item-calendar"                   
+                   xbl:inherits="calendar-uri,calendar-id"
                    flex="1">
             <xul:box class="calendar-event-selection" orient="horizontal" flex="1">
               <xul:stack anonid="eventbox"
                          class="calendar-event-box-container"
                          flex="1"
                          xbl:inherits="readonly,flashing,alarm,allday,priority,progress,status,calendar,categories">
                 <xul:hbox class="calendar-event-details">
                   <xul:vbox align="left"
@@ -179,17 +179,18 @@
             evl.value = calGetString("calendar", "eventUntitled")
           }
         ]]></body>
       </method>
 
       <method name="setCSSClasses">
         <body><![CDATA[
           var item = this.mOccurrence;          
-          this.setAttribute("item-calendar", item.calendar.uri.spec);
+          this.setAttribute("calendar-uri", item.calendar.uri.spec);
+          this.setAttribute("calendar-id", item.calendar.id);
           var categoriesArray = item.getCategories({});
           if (categoriesArray.length > 0) {
             var cssClassesArray = categoriesArray.map(formatStringForCSSRule);
             this.setAttribute("categories", cssClassesArray.join(" "));
           }
 
           // Add alarm icons as needed.
           let alarms = item.getAlarms({});
--- a/calendar/base/content/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -37,16 +37,17 @@
  * 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 ***** */
 
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
 
 /**
  * Controller for the views
  * @see calIcalendarViewController
  */
 var calendarViewController = {
     QueryInterface: function(aIID) {
@@ -456,99 +457,137 @@ function scheduleMidnightUpdate(aRefresh
                                    .createInstance(Components.interfaces.nsITimer);
     } else {
         gMidnightTimer.cancel();
     }
     gMidnightTimer.initWithCallback(udCallback, msUntilTomorrow, gMidnightTimer.TYPE_ONE_SHOT);
 }
 
 /**
- * Returns the actual style sheet object with the specified path.  Callers are
- * responsible for any caching they may want to do.
+ * Retuns a cached copy of the view stylesheet.
  *
- * @param aStyleSheetPath       The chrome:// uri of the stylesheet to retrieve.
- * @return                      The stylesheet object from document.styleSheets.
+ * @return      The view stylesheet object.
  */
-function getStyleSheet(aStyleSheetPath) {
-    for each (var sheet in document.styleSheets) {
-        if (sheet.href == aStyleSheetPath) {
-            return sheet;
-        }
+function getViewStyleSheet() {
+  if (!getViewStyleSheet.sheet) {
+      const cssUri = "chrome://calendar/content/calendar-view-bindings.css";
+      for each (let sheet in document.styleSheets) {
+          if (sheet.href == cssUri) {
+              getViewStyleSheet.sheet = sheet;
+              break;
+          }
+      }
+  }
+  return getViewStyleSheet.sheet;
+}
+
+/**
+ * Updates the view stylesheet to contain rules that give all boxes with class
+ * .calendar-color-box and an attribute calendar-id="<id of the calendar>" the
+ * background color of the specified calendar.
+ *
+ * @param aCalendar     The calendar to update the stylesheet for.
+ */
+function updateStyleSheetForViews(aCalendar) {
+    if (!updateStyleSheetForViews.ruleCache) {
+        updateStyleSheetForViews.ruleCache = {};
     }
-    return null;
+    let ruleCache = updateStyleSheetForViews.ruleCache;
+
+    if (!(aCalendar.id in ruleCache)) {
+        // We haven't create a rule for this calendar yet, do so now.
+        let sheet = getViewStyleSheet();
+        let ruleString = '.calendar-color-box[calendar-id="' + aCalendar.id + '"] {} ';
+        let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
+
+        ruleCache[aCalendar.id] = sheet.cssRules[ruleIndex];
+    }
+
+    let color = aCalendar.getProperty("color") || "#A8C2E1";
+    ruleCache[aCalendar.id].style.backgroundColor = color;
+    ruleCache[aCalendar.id].style.color = cal.getContrastingTextColor(color);
 }
 
 /**
- * Updates the style rules for a particular object.  If the object is a
- * category (and hence doesn't have a uri), we set the category bar color.
- * If it's a calendar, we set the background color and contrasting text color.
- *
- * XXX This function is quite specific, needs some generalization and caller
- * specific code for calendars and categories.
- *
- * TODO This function still uses the .uri, we are moving to using ids.
- *
- * @param aObject       Either a calendar (with a .uri), or the category color
- *                        pref key suffix [the non-unicode part after
- *                        "calendar.category.color.", equivalent to
- *                        formatStringForCSSRule(categoryNameInUnicode)].
- * @param aSheet         The stylesheet to update.
- *
+ * Category preferences observer. Used to update the stylesheets for category
+ * colors.
  */
-function updateStyleSheetForObject(aObject, aSheet) {
-    var selectorPrefix, name, ruleUpdaterFunc, classPrefix;
-    if (aObject.uri) {
-        // For a calendar, set background and contrasting text colors
-        name = aObject.uri.spec;
-        classPrefix = ".calendar-color-box";
-        selectorPrefix = "item-calendar=";
-        ruleUpdaterFunc = function calendarRuleFunc(aRule, aIndex) {
-            var color = aObject.getProperty('color');
-            if (!color) {
-                color = "#A8C2E1";
-            }
-            aRule.style.backgroundColor = color;
-            aRule.style.color = getContrastingTextColor(color);
-        };
-    } else {
-        // For a category, set the category bar color.  Also note that
-        // it uses the ~= selector, since there could be multiple categories.
-        name = aObject;
-        selectorPrefix = "categories~=";
-        classPrefix = ".category-color-box"
-        ruleUpdaterFunc = function categoryRuleFunc(aRule, aIndex) {
-            var color = getPrefSafe("calendar.category.color."+name, null);
-            if (color) {
-                aRule.style.backgroundColor = color;
-            } else {
-                aSheet.deleteRule(aIndex);
-            }
-        };
+var categoryManagement = {
+    QueryInterface: function cM_QueryInterface(aIID) {
+        return cal.doQueryInterface(this,
+                                    categoryPrefObserver.prototype,
+                                    aIID,
+                                    [Components.interfaces.nsIObserver]);
+    },
+
+    initCategories: function cM_initCategories() {
+      let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                  .getService(Components.interfaces.nsIPrefService);
+      let categoryPrefBranch = prefService.getBranch("calendar.category.color.")
+                                          .QueryInterface(Components.interfaces.nsIPrefBranch2);
+      let categories = categoryPrefBranch.getChildList("", {});
+
+      // Fix illegally formatted category prefs.
+      for (let i in categories) {
+          let category = categories[i];
+          if (category.search(/[^_0-9a-z-]/) != -1) {
+              let categoryFix = formatStringForCSSRule(category);
+              if (!categoryPrefBranch.prefHasUserValue(categoryFix)) {
+                  let color = categoryPrefBranch.getCharPref(category);
+                  categoryPrefBranch.setCharPref(categoryFix, color);
+                  categoryPrefBranch.clearUserPref(category); // not usable
+                  categories[i] = categoryFix;  // replace illegal name
+              } else {
+                  categories.splice(i, 1); // remove illegal name
+              }
+          }
+      }
+
+      // Add color information to the stylesheets.
+      categories.forEach(categoryManagement.updateStyleSheetForCategory,
+                         categoryManagement);
+
+      categoryPrefBranch.addObserver("", this, false);
+    },
+
+    cleanupCategories: function cM_cleanupCategories() {
+      let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                  .getService(Components.interfaces.nsIPrefService);
+      let categoryPrefBranch = prefService.getBranch("calendar.category.color.");
+      categoryPrefBranch.removeObserver("", this, false);
+    },
+
+    observe: function cM_observe(aSubject, aTopic, aPrefName) {
+        if (aPrefName.substring(0, 24) == "calendar.category.color.") {
+            this.updateStyleSheetForCateogry(aPrefName.substring(24));
+        }
+        // TODO Currently, the only way to find out if categories are removed is
+        // to initially grab the calendar.categories.names preference and then
+        // observe changes to it. it would be better if we had hooks for this,
+        // so we could delete the rule from our style cache and also remove its
+        // color preference.
+        
+    },
+
+    categoryStyleCache: {},
+
+    updateStyleSheetForCategory: function cM_updateStyleSheetForCategory(aCatName) {
+        if (!(aCatName in this.categoryStyleCache)) {
+            // We haven't created a rule for this category yet, do so now.
+            let sheet = getViewStyleSheet();
+            let ruleString = '.category-color-box[categories~="' + aCalendar.id + '"] {} ';
+            let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
+
+            this.categoryStyleCache[aCatName] = sheet.cssRules[ruleIndex];
+        }
+
+        let color = cal.getPrefSafe("calendar.category.color." + aCatName) || "";
+        this.categoryStyleCache[aCatName].style.backgroundColor = color;
     }
-
-    var selector = classPrefix + '[' + selectorPrefix + '"' + name + '"]';
-
-    // Now go find our rule
-    var rule, ruleIndex;
-    for (var i = 0; i < aSheet.cssRules.length; i++) {
-        var maybeRule = aSheet.cssRules[i];
-        if (maybeRule.selectorText && (maybeRule.selectorText == selector)) {
-            rule = maybeRule;
-            ruleIndex = i;
-            break;
-        }
-    }
-
-    if (!rule) {
-        aSheet.insertRule(selector + ' { }', aSheet.cssRules.length);
-        rule = aSheet.cssRules[aSheet.cssRules.length-1];
-    }
-
-    ruleUpdaterFunc(rule, ruleIndex);
-}
+};
 
 /**
  * Handler function to set the selected day in the minimonth to the currently
  * selected day in the current view.
  *
  * @param event     The "dayselect" event emitted from the views.
  *
  */
new file mode 100644
--- /dev/null
+++ b/calendar/base/content/widgets/calendar-list-tree.xml
@@ -0,0 +1,1110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ***** 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
+   -   Philipp Kewisch <mozilla@kewis.ch>
+   - Portions created by the Initial Developer are Copyright (C) 2008
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -
+   - 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 LGPL or the GPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+
+<!DOCTYPE overlay SYSTEM "chrome://calendar/locale/calendar.dtd">
+
+<bindings id="calendar-list-tree-bindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <binding id="full-calendar-list-tree" extends="#calendar-list-tree">
+    <!--
+      - This binding implements a full calendar list, that automatically adds
+      - and removes calendars when a calendar is registered or unregistered.
+      -->
+    <implementation>
+      <constructor><![CDATA[
+        Components.utils.import("resource://calendar/modules/calUtils.jsm");
+        let calMgr = cal.getCalendarManager();
+        calMgr.addObserver(this.calMgrObserver);
+      ]]></constructor>
+      <destructor><![CDATA[
+        let calMgr = cal.getCalendarManager();
+        calMgr.removeObserver(this.calMgrObserver);
+        this.calMgrObserver.listTree = null;
+      ]]></destructor>
+
+      <property name="compositeCalendar">
+        <getter><![CDATA[
+          if (!this.mCompositeCalendar) {
+              throw Components.Exception("Calendar list has no composite calendar yet",
+                                         Components.results.NS_ERROR_NOT_INITIALIZED);
+          }
+          return this.mCompositeCalendar;
+        ]]></getter>
+        <setter><![CDATA[
+          this.mCompositeCalendar = val;
+          this.mCompositeCalendar.addObserver(this.compositeObserver);
+
+          // Now that we have a composite calendar, we can get all calendars
+          // from the calendar manager.
+          let calendars = cal.getCalendarManager().getCalendars({});
+          calendars.forEach(this.addCalendar, this);
+
+          return val;
+        ]]></setter>
+      </property>
+
+      <property name="calendars">
+        <getter><![CDATA[
+          return this.mCalendarList;
+        ]]></getter>
+        <setter><![CDATA[
+          // Setting calendars externally is not wanted. This is done internally
+          // in the compositeCalendar setter.
+          throw Components.Exception("Seting calendars on type='full' is not supported",
+                                     Components.results.NS_ERROR_NOT_IMPLEMENTED);
+        ]]></setter>
+      </property>
+
+      <field name="calMgrObserver"><![CDATA[
+      ({ listTree: this,
+
+        // calICalendarManagerObserver
+        onCalendarRegistered: function cMO_onCalendarRegistered(aCalendar) {
+            this.listTree.addCalendar(aCalendar);
+            let composite = this.listTree.compositeCalendar;
+            let inComposite = aCalendar.getProperty(composite.prefPrefix +
+                                                    "-in-composite");
+            if ((inComposite === null) || inComposite) {
+                composite.addCalendar(aCalendar);
+            }
+        },
+
+        onCalendarUnregistering: function cMO_onCalendarUnregistering(aCalendar) {
+            this.listTree.removeCalendar(aCalendar);
+        },
+
+        onCalendarDeleting: function cMO_onCalendarDeleting(aCalendar) { },
+      })
+      ]]></field>
+      <field name="compositeObserver"><![CDATA[
+      ({ listTree: this,
+
+        QueryInterface: function cO_QueryInterface(aIID) {
+            if (!aIID.equals(Components.interfaces.calICompositeObserver) &&
+                !aIID.equals(Components.interfaces.calIObserver) &&
+                !aIID.equals(Components.interfaces.nsISupports)) {
+                throw Components.results.NS_ERROR_NO_INTERFACE;
+            }
+            return this;
+        },
+
+        // calICompositeObserver
+        onCalendarAdded: function onCalendarAdded(aCalendar) {
+            // Make sure the checkbox state is updated
+            this.listTree.updateCalendar(aCalendar);
+        },
+
+        onCalendarRemoved: function onCalendarRemoved(aCalendar) {
+            // Make sure the checkbox state is updated
+            this.listTree.updateCalendar(aCalendar);
+        },
+
+        onDefaultCalendarChanged: function cMO_onDefaultCalendarChanged(aCalendar) {
+        },
+
+        // calIObserver
+        onStartBatch: function cO_onStartBatch() { },
+        onEndBatch: function cO_onEndBatch() { },
+        onLoad: function cO_onLoad() { },
+
+        onAddItem: function cO_onAddItem(aItem) {
+            if (aItem.calendar.type != "caldav") {
+                this.listTree.ensureCalendarVisible(aItem.calendar);
+            }
+        },
+        onModifyItem: function cO_onModifyItem(aNewItem, aOldItem) { 
+            if (aNewItem.calendar.type != "caldav") {
+                this.listTree.ensureCalendarVisible(aNewItem.calendar);
+            }
+        },
+        onDeleteItem: function cO_onDeleteItem(aDeletedItem) { },
+        onError: function cO_onError(aCalendar, aErrNo, aMessage) { },
+
+        onPropertyChanged: function cO_onPropertyChanged(aCalendar,
+                                                          aName,
+                                                          aValue,
+                                                          aOldValue) {
+        },
+
+        onPropertyDeleting: function cO_onPropertyDeleting(aCalendar,
+                                                            aName) {
+        }
+      })
+      ]]></field>
+    </implementation>
+    <handlers>
+      <handler event="dblclick"><![CDATA[
+        var col = {};
+        var calendar = this.getCalendarFromEvent(event, col);
+        if (event.button != 0 ||
+            (col.value && col.value.id == "calendar-list-tree-checkbox")) {
+            // Only left clicks that are not on the checkbox column
+            return;
+        }
+        if (calendar) {
+            openCalendarProperties(calendar);
+        } else {
+            openCalendarWizard();
+        }
+      ]]></handler>
+    </handlers>
+  </binding>
+
+  <binding id="calendar-list-tree">
+    <content>
+      <xul:tree anonid="tree"
+                xbl:inherits="hidecolumnpicker"
+                hidecolumnpicker="true"
+                seltype="single"
+                flex="1">
+        <xul:treecols anonid="treecols"
+                      xbl:inherits="hideheader"
+                      hideheader="true">
+          <xul:treecol anonid="checkbox-treecol"
+                       xbl:inherits="cycler,hideheader"
+                       cycler="true"
+                       hideheader="true"
+                       width="17"/>
+          <xul:treecol anonid="color-treecol"
+                       xbl:inherits="cycler,hideheader"
+                       hideheader="true"
+                       width="16"/>
+          <xul:treecol anonid="calendarname-treecol"
+                       xbl:inherits="cycler,hideheader"
+                       hideheader="true"
+                       label="&calendar.unifinder.tree.calendarname.label;"
+                       flex="1"/>
+          <xul:treecol anonid="status-treecol"
+                       xbl:inherits="cycler,hideheader"
+                       hideheader="true"
+                       width="18"/>
+          <children includes="treecol"/>
+          <xul:treecol anonid="scrollbar-spacer"
+                       xbl:inherits="cycler,hideheader"
+                       fixed="true"
+                       hideheader="true">
+            <!-- This is a very elegant workaround to make sure the last column
+                 is not covered by the scrollbar in case of an overflow. This
+                 treecol needs to be here last -->
+            <xul:slider anonid="scrollbar-slider" orient="vertical"/>
+          </xul:treecol>
+        </xul:treecols>
+        <xul:treechildren anonid="treechildren"
+                          xbl:inherits="tooltip=childtooltip,context=childcontext"
+                          tooltip="_child"
+                          context="_child"
+                          ondragstart="onDragStart(event);"
+                          onoverflow="displayScrollbarSpacer(true)"
+                          onunderflow="displayScrollbarSpacer(false)">
+          <children includes="tooltip|menupopup"/>
+        </xul:treechildren>
+      </xul:tree>
+    </content>
+    <implementation implements="nsITreeView">
+
+      <field name="mCalendarList">[]</field>
+      <field name="mCompositeCalendar">null</field>
+      <field name="tree">null</field>
+      <field name="treebox">null</field>
+      <field name="ruleCache">new Object()</field>
+      <field name="mCachedSheet">null</field>
+
+
+      <constructor><![CDATA[
+        Components.utils.import("resource://calendar/modules/calUtils.jsm");
+        this.tree.view = this;
+      ]]></constructor>
+      <destructor><![CDATA[
+        // Clean up the calendar manager observers. Do not use removeCalendar
+        // here since that will remove the calendar from the composite calendar.
+        for each (let calendar in this.mCalendarList) {
+            calendar.removeObserver(this.calObserver);
+        }
+
+        this.tree.view = null;
+        this.calObserver.listTree = null;
+
+        if (this.mCompositeCalendar) {
+            this.mCompositeCalendar.removeObserver(this.compositeObserver);
+        }
+
+      ]]></destructor>
+
+      <field name="calObserver"><![CDATA[
+      ({ listTree: this,
+
+        // calIObserver. Note that each registered calendar uses this observer
+        onStartBatch: function cMO_onStartBatch() { },
+        onEndBatch: function cMO_onEndBatch() { },
+        onLoad: function cMO_onLoad() { },
+
+        onAddItem: function cMO_onAddItem(aItem) { },
+        onModifyItem: function cMO_onModifyItem(aNewItem, aOldItem) { },
+        onDeleteItem: function cMO_onDeleteItem(aDeletedItem) { },
+        onError: function cMO_onError(aCalendar, aErrNo, aMessage) { },
+
+        onPropertyChanged: function cMO_onPropertyChanged(aCalendar,
+                                                          aName,
+                                                          aValue,
+                                                          aOldValue) {
+            switch (aName) {
+                case "color":
+                    // TODO See other TODO in this file about updateStyleSheetForViews
+                    if (updateStyleSheetForViews) {
+                        updateStyleSheetForViews(aCalendar);
+                    }
+                    this.listTree.updateCalendarColor(aCalendar);
+                    // Fall through, update item in any case
+                case "name":
+                case "currentStatus":
+                case "readOnly":
+                case "disabled":
+                    this.listTree.updateCalendar(aCalendar);
+                    // Fall through, update commands in any cases.
+            }
+        },
+
+        onPropertyDeleting: function cMO_onPropertyDeleting(aCalendar,
+                                                            aName) {
+            // Since the old value is not used directly in onPropertyChanged,
+            // but should not be the same as the value, set it to a different
+            // value.
+            this.onPropertyChanged(aCalendar, aName, null, null);
+        }
+      })
+      ]]></field>
+
+      <field name="compositeObserver"><![CDATA[
+      ({ listTree: this,
+
+        QueryInterface: function cO_QueryInterface(aIID) {
+            if (!aIID.equals(Components.interfaces.calICompositeObserver) &&
+                !aIID.equals(Components.interfaces.nsISupports)) {
+                throw Components.results.NS_ERROR_NO_INTERFACE;
+            }
+            return this;
+        },
+
+        // calICompositeObserver
+        onCalendarAdded: function onCalendarAdded(aCalendar) {
+            // Make sure the checkbox state is updated
+            this.listTree.updateCalendar(aCalendar);
+        },
+
+        onCalendarRemoved: function onCalendarRemoved(aCalendar) {
+            // Make sure the checkbox state is updated
+            this.listTree.updateCalendar(aCalendar);
+        },
+
+        onDefaultCalendarChanged: function cMO_onDefaultCalendarChanged(aCalendar) {
+        }
+      })
+      ]]></field>
+
+      <property name="treechildren"
+                readonly="true"
+                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'treechildren')"/>
+      <property name="tree"
+                readonly="true"
+                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'tree')"/>
+
+
+      <property name="sheet" readonly="true">
+        <getter><![CDATA[
+          if (!this.mCachedSheet) {
+              for each (let sheet in document.styleSheets) {
+                  if (sheet.href == "chrome://calendar/skin/calendar-management.css") {
+                      this.mCachedSheet = sheet;
+                      break;
+                  }
+              }
+              if (!this.mCachedSheet) {
+                cal.ERROR("Could not find calendar-management.css, needs to be added to " +
+                          window.document.title + "'s stylesheets");
+              }
+          }
+
+          return this.mCachedSheet;
+        ]]></getter>
+      </property>
+
+      <property name="calendars">
+        <getter><![CDATA[
+          return this.mCalendarList;
+        ]]></getter>
+        <setter><![CDATA[
+          this.mCalendarList = val;
+          this.mCalendarList.forEach(this.addCalendar, this);
+          return this.mCalendarList;
+        ]]></setter>
+      </property>
+
+      <property name="compositeCalendar">
+        <getter><![CDATA[
+          if (!this.mCompositeCalendar) {
+              this.mCompositeCalendar = 
+                  Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
+                            .createInstance(Components.interfaces.calICompositeCalendar);
+          }
+
+          return this.mCompositeCalendar;
+        ]]></getter>
+        <setter><![CDATA[
+          if (this.mCompositeCalendar) {
+              throw Components.Exception("A composite calendar has already been set",
+                                         Components.results.NS_ERROR_ALREADY_INITIALIZED);
+          }
+          this.mCompositeCalendar = val;
+          this.mCompositeCalendar.addObserver(this.compositeObserver);
+          return val;
+        ]]></setter>
+      </property>
+
+      <property name="sortOrder"
+                readonly="true"
+                onget="return this.mCalendarList.map(function(x) x.id);"/>
+      <property name="selectedCalendars"
+                readonly="true"
+                onget="return this.compositeCalendar.getCalendars({});"/>
+      <property name="allowDrag"
+                onget="return (this.getAttribute('allowdrag') == 'true');"
+                onset="return setBooleanAttribute(this, 'allowdrag', val);"/>
+      <property name="writable"
+                onget="return (this.getAttribute('writable') == 'true');"
+                onset="return setBooleanAttribute(this, 'writable', val);"/>
+      <property name="seltype"
+                onget="return (this.getAttribute('seltype') || 'checkbox');"
+                onset="this.setAttribute('seltype', val); return val;"/>
+      <property name="ignoreDisabledState"
+                onget="return (this.getAttribute('ignoredisabledstate') == 'true');"
+                onset="return setBooleanAttribute(this, 'ignoredisabledstate', val);"/>
+
+      <method name="sortOrderChanged">
+        <parameter name=""/>
+        <body><![CDATA[
+          let event = document.createEvent('Events');
+          event.initEvent("SortOrderChanged", true, false);
+          event.sortOrder = this.sortOrder
+          this.dispatchEvent(event);
+        ]]></body>
+      </method>
+      <method name="displayScrollbarSpacer">
+        <parameter name="aShouldDisplay"/>
+        <body><![CDATA[
+            let spacer = document.getAnonymousElementByAttribute(this, "anonid", "scrollbar-spacer");
+            spacer.collapsed = !aShouldDisplay;
+        ]]></body>
+      </method>
+
+      <method name="ensureCalendarVisible">
+        <parameter name="aCalendar"/>
+        <body><![CDATA[
+          this.compositeCalendar.addCalendar(aCalendar);
+        ]]></body>
+      </method>
+
+      <method name="getColumn">
+        <parameter name="aAnonId"/>
+        <body><![CDATA[
+          let colElem = document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
+          return this.treebox.columns.getColumnFor(colElem);
+        ]]></body>
+      </method>
+
+      <method name="findIndexById">
+        <!--
+          - Find the array index of the calendar with the passed id.
+          -
+          - @param aId           The calendar id to find an index for.
+          - @return              The array index, or -1 if not found.
+          -->
+        <parameter name="aId"/>
+        <body><![CDATA[
+          for (var i = 0; i < this.mCalendarList.length; i++) {
+              if (this.mCalendarList[i].id == aId) {
+                  return i;
+              }
+          }
+          return -1;
+        ]]></body>
+      </method>
+
+      <method name="addCalendar">
+        <!--
+          - Add a calendar to the calendar list
+          -
+          - @param aCalendar     The calendar to add.
+          -->
+        <parameter name="aCalendar"/>
+        <body><![CDATA[
+          let composite = this.compositeCalendar;
+          this.mCalendarList.push(aCalendar);
+          this.treebox.rowCountChanged(this.mCalendarList.length - 1, 1);
+
+          if (!composite.defaultCalendar ||
+              aCalendar.id == composite.defaultCalendar.id) {
+              this.tree.view.selection.select(this.mCalendarList.length - 1);
+          }
+
+          this.updateCalendarColor(aCalendar);
+
+          // TODO This should be done only once outside of this binding, but to
+          // do that right, we need to have an easy way to register an observer
+          // all calendar properties. This could be the calendar manager that
+          // holds an observer on every calendar anyway, which would then use the
+          // global observer service which clients can register with.
+          if (updateStyleSheetForViews) {
+              updateStyleSheetForViews(aCalendar);
+          }
+
+          // Watch the calendar for changes, i.e color.
+          aCalendar.addObserver(this.calObserver);
+
+          // Adding a calendar causes the sortorder to be changed.
+          this.sortOrderChanged();
+        ]]></body>
+      </method>
+
+      <method name="removeCalendar">
+        <!--
+          - Remove a calendar from the calendar list
+          - 
+          - @param aCalendar     The calendar to remove.
+          -->
+        <parameter name="aCalendar"/>
+        <body><![CDATA[
+          let index = this.findIndexById(aCalendar.id);
+          if (index < 0) {
+              return;
+          }
+
+          this.mCalendarList.splice(index, 1);
+          if (index == this.rowCount) {
+              index--;
+          }
+
+          this.tree.view.selection.select(index + 1);
+          this.treebox.rowCountChanged(index, -1);
+
+          aCalendar.removeObserver(this.calObserver);
+
+          // Make sure the calendar is removed from the composite calendar
+          this.compositeCalendar.removeCalendar(aCalendar);
+
+          // Remove the css style rule from the sheet.
+          let sheet = this.sheet;
+          for (let i = 0; i < sheet.cssRules.length; i++) {
+              if (sheet.cssRules[i] == this.ruleCache[aCalendar.id]) {
+                  sheet.deleteRule(i);
+                  delete this.ruleCache[aCalendar.id];
+                  break;
+              }
+          } 
+
+          this.sortOrderChanged();
+        ]]></body>
+      </method>
+
+      <method name="updateCalendar">
+        <!--
+          - Update a calendar's tree row (to refresh the color and such)
+          - 
+          - @param aCalendar     The calendar to update.
+          -->
+        <parameter name="aCalendar"/>
+        <body><![CDATA[
+          this.treebox.invalidateRow(this.findIndexById(aCalendar.id));
+        ]]></body>
+      </method>
+
+      <method name="updateCalendarColor">
+        <!--
+          - Update a calendar's color rules.
+          - 
+          - @param aCalendar     The calendar to update.
+          -->
+        <parameter name="aCalendar"/>
+        <body><![CDATA[
+          if (!(aCalendar.id in this.ruleCache)) {
+              let color = aCalendar.getProperty("color");
+              if (color) {
+                  let sheet = this.sheet;
+                  if (!(aCalendar.id in this.ruleCache)) {
+                      let ruleString = "calendar-list-tree > tree > treechildren" +
+                                       "::-moz-tree-cell(color-treecol, id-"  +
+                                       aCalendar.id + ") {}";
+                                     
+                      let ruleIndex = sheet.insertRule(ruleString, sheet.cssRules.length);
+                      this.ruleCache[aCalendar.id] = sheet.cssRules[ruleIndex];
+                  }
+                  this.ruleCache[aCalendar.id].style.backgroundColor = color;
+              }
+          }
+        ]]></body>
+      </method>
+
+      <method name="getCalendarFromEvent">
+        <!--
+          - Get the calendar from the given DOM event. This can be a Mouse event or a
+          - keyboard event.
+          -
+          - @param event     The DOM event to check
+          - @param aCol      An out-object for the column id.
+          - @param aRow      An out-object for the row index.
+          -->
+        <parameter name="event"/>
+        <parameter name="aCol"/>
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          if (event.clientX && event.clientY) {
+              // If we have a client point, get the row directly from the client
+              // point.
+              aRow = aRow || {};
+              this.treebox.getCellAt(event.clientX,
+                                     event.clientY,
+                                     aRow,
+                                     aCol || {},
+                                     {});
+
+          } else {
+              // Otherwise, we can try to get the context calendar from the popupNode.
+              if (document.popupNode && document.popupNode.contextCalendar) {
+                  return document.popupNode.contextCalendar;
+              }
+          }
+          return aRow && aRow.value > -1 && this.mCalendarList[aRow.value];
+        ]]></body>
+      </method>
+
+      <method name="getCalendar">
+        <!--
+          - Get the calendar from a certain index.
+          - 
+          - @param aIndex     The index to get the calendar for.
+          -->
+        <parameter name="aIndex"/>
+        <body><![CDATA[
+          let index = Math.max(0, Math.min(this.mCalendarList.length - 1, aIndex));
+          return this.mCalendarList[index];
+        ]]></body>
+      </method>
+
+      <!-- Implement nsITreeView -->
+      <property name="rowCount"
+                readonly="true"
+                onget="return this.mCalendarList.length"/>
+
+      <method name="getCellProperties">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <parameter name="aProps"/>
+        <body><![CDATA[
+          try { 
+              this.getRowProperties(aRow, aProps);
+              this.getColumnProperties(aCol, aProps);
+          } catch (e) {
+              // It seems errors in these functions are not shown, do this
+              // explicitly.
+              cal.ERROR("Error getting cell props: " + e);
+          }
+        ]]></body>
+      </method>
+
+      <method name="getRowProperties">
+        <parameter name="aRow"/>
+        <parameter name="aProps"/>
+        <body><![CDATA[
+          let calendar = this.getCalendar(aRow);
+          let composite = this.compositeCalendar;
+
+          // Set up the composite calendar status
+          if (composite.getCalendarById(calendar.id)) {
+              aProps.AppendElement(cal.getAtomFromService("checked"));
+          } else {
+              aProps.AppendElement(cal.getAtomFromService("unchecked"));
+          }
+
+          // Set up the calendar id
+          aProps.AppendElement(cal.getAtomFromService("id-" + calendar.id));
+
+          // Get the calendar color
+          let color = (calendar.getProperty("color") || "").substr(1);
+
+          // Set up the calendar color (background)
+          let bgColorProp = "color-" + (color || "default");
+          aProps.AppendElement(cal.getAtomFromService(bgColorProp));
+
+          // Set a property to get the contrasting text color (foreground)
+          let fgColorProp = cal.getContrastingTextColor(color || "a8c2e1");
+          aProps.AppendElement(cal.getAtomFromService(fgColorProp));
+
+          let currentStatus = calendar.getProperty("currentStatus");
+          if (!Components.isSuccessCode(currentStatus)) {
+              // 'readfailed' is supposed to "win" over 'readonly', meaning that 
+              // if reading from a calendar fails there is no further need to also display
+              // information about 'readonly' status
+              aProps.AppendElement(cal.getAtomFromService("readfailed"));
+          } else if (calendar.readOnly) {
+              aProps.AppendElement(cal.getAtomFromService("readonly"));
+          }
+
+          // Set up the disabled state
+          if (!this.ignoreDisabledState && calendar.getProperty("disabled")) {
+              aProps.AppendElement(cal.getAtomFromService("disabled"));
+          } else {
+              aProps.AppendElement(cal.getAtomFromService("enabled"));
+          }
+        ]]></body>
+      </method>
+
+      <method name="getColumnProperties">
+        <parameter name="aCol"/>
+        <parameter name="aProps"/>
+        <body><![CDATA[
+          // Workaround for anonymous treecols
+          let colAtom = cal.getAtomFromService(aCol.element.getAttribute("anonid"));
+          aProps.AppendElement(colAtom);
+        ]]></body>
+      </method>
+
+      <method name="isContainer">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return false;
+        ]]></body>
+      </method>
+
+      <method name="isContainerOpen">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return false;
+        ]]></body>
+      </method>
+
+      <method name="isContainerEmpty">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return false;
+        ]]></body>
+      </method>
+
+      <method name="isSeparator">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return false;
+        ]]></body>
+      </method>
+
+      <method name="isSorted">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return false;
+        ]]></body>
+      </method>
+
+      <method name="onDragStart">
+        <!--
+          - Initiate a drag operation for the calendar list. Can be used in the
+          - dragstart handler.
+          -
+          - @param event     The DOM event containing drag information.
+          -->
+        <parameter name="event"/>
+        <body><![CDATA[
+          let calendar = this.getCalendarFromEvent(event);
+          if (this.allowDrag && event.dataTransfer) {
+              // Setting data starts a drag session, do this only if dragging
+              // is enabled for this binding.
+              event.dataTransfer.setData("application/x-moz-calendarID", calendar.id);
+              event.dataTransfer.effectAllowed = "move";
+          }
+        ]]></body>
+      </method>
+
+      <method name="canDrop">
+        <parameter name="aRow"/>
+        <parameter name="aOrientation"/>
+        <body><![CDATA[
+          let dragSession = cal.getDragService().getCurrentSession();
+          let dataTransfer = dragSession && dragSession.dataTransfer;
+          if (!this.allowDrag || !dataTransfer) {
+              // If dragging is not allowed or there is no data transfer then 
+              // we can't drop (i.e dropping a file on the calendar list).
+              return false;
+          }
+
+          let dragCalId = dataTransfer.getData("application/x-moz-calendarID");
+
+          return (aOrientation != Components.interfaces.nsITreeView.DROP_ON &&
+                  dragCalId != null);
+        ]]></body>
+      </method>
+
+      <method name="drop">
+        <parameter name="aRow"/>
+        <parameter name="aOrientation"/>
+        <body><![CDATA[
+          let dragSession = cal.getDragService().getCurrentSession();
+          let dataTransfer = dragSession.dataTransfer;
+          let dragCalId = dataTransfer &&
+                          dataTransfer.getData("application/x-moz-calendarID");
+          if (!this.allowDrag || !dataTransfer || !dragCalId) {
+              return false;
+          }
+
+          let oldIndex = -1;
+          for (let i = 0; i < this.mCalendarList.length; i++) {
+              if (this.mCalendarList[i].id == dragCalId) {
+                  oldIndex = i;
+                  break;
+              }
+          }
+          if (oldIndex < 0) {
+              return;
+          }
+
+          // If no row is specified (-1), then assume append.
+          let row = (aRow < 0 ? this.mCalendarList.length - 1 : aRow);
+          let targetIndex = row + Math.max(0, aOrientation);
+
+          // We don't need to move if the target row has the same index as the old
+          // row. The same goes for dropping after the row before the old row or
+          // before the row after the old row. Think about it :-)
+          if (aRow != oldIndex && row + aOrientation != oldIndex) {
+              // Add the new one, remove the old one.
+              this.mCalendarList.splice(targetIndex, 0, this.mCalendarList[oldIndex]);
+              this.mCalendarList.splice(oldIndex + (oldIndex > targetIndex ? 1 : 0), 1);
+
+              // Invalidate the tree rows between the old item and the new one.
+              if (oldIndex < targetIndex) {
+                  this.treebox.invalidateRange(oldIndex, targetIndex);
+              } else {
+                  this.treebox.invalidateRange(targetIndex, oldIndex);
+              }
+
+              // Fire event
+              this.sortOrderChanged();
+          }
+          return true;
+        ]]></body>
+      </method>
+
+      <method name="foreignDrop">
+        <!--
+          - This function can be used by other nodes to simulate dropping on the
+          - tree. This can be used for example on the tree header so that the row
+          - will be inserted before the first visible row. The event client
+          - coordinate are used to determine if the row should be dropped before the
+          - first row (above treechildren) or below the last visible row (below top
+          - of treechildren).
+          -
+          - @param event     The DOM drop event.
+          - @return          Boolean indicating if the drop succeeded.
+          -
+          -->
+        <parameter name="event"/>
+        <body><![CDATA[
+          let hasDropped;
+          if (event.clientY < this.tree.boxObject.y) {
+              hasDropped = this.drop(this.treebox.getFirstVisibleRow(), -1);
+          } else {
+              hasDropped = this.drop(this.treebox.getLastVisibleRow(), 1);
+          }
+          if (hasDropped) {
+              event.preventDefault();
+          }
+          return hasDropped;
+        ]]></body>
+      </method>
+
+      <method name="foreignCanDrop">
+        <!--
+          - Similar function to foreignCanDrop but for the dragenter event
+          - @see ::foreignDrop
+          -->
+        <parameter name="event"/>
+        <body><![CDATA[
+          // The dragenter/dragover events expect false to be returned when
+          // dropping is allowed, therefore we return !canDrop.
+          if (event.clientY < this.tree.boxObject.y) {
+              return !this.canDrop(this.treebox.getFirstVisibleRow(), -1);
+          } else {
+              return !this.canDrop(this.treebox.getLastVisibleRow(), 1);
+          }
+        ]]></body>
+      </method>
+
+      <method name="getParentIndex">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return -1;
+        ]]></body>
+      </method>
+
+      <method name="hasNextSibling">
+        <parameter name="aRow"/>
+        <parameter name="aAfterIndex"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="getLevel">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+          return 0;
+        ]]></body>
+      </method>
+
+      <method name="getImageSrc">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="getProgressMode">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="getCellValue">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <body><![CDATA[
+          let calendar = this.getCalendar(aRow);
+          let composite = this.compositeCalendar;
+
+          switch (aCol.element.getAttribute("anonid")) {
+              case "checkbox-treecol":
+                  return composite.getCalendarById(calendar.id) ? "true" : "false";
+              case "status-treecol":
+                  // The value of this cell shows the calendar readonly state
+                  return (calendar.readOnly ? "true" : "false");
+          }
+          return null;
+        ]]></body>
+      </method>
+        
+      <method name="getCellText">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <body><![CDATA[
+          let calendar = this.getCalendar(aRow);
+
+          switch (aCol.element.getAttribute("anonid")) {
+              case "calendarname-treecol":
+                  return this.getCalendar(aRow).name;
+          }
+          return "";
+        ]]></body>
+      </method>
+      
+      <method name="setTree">
+        <parameter name="aTreeBox"/>
+        <body><![CDATA[
+          this.treebox = aTreeBox;
+        ]]></body>
+      </method>
+
+      <method name="toggleOpenState">
+        <parameter name="aRow"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="cycleHeader">
+        <parameter name="aCol"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="cycleCell">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <body><![CDATA[
+          let calendar = this.getCalendar(aRow);
+          let composite = this.compositeCalendar;
+
+          switch (aCol.element.getAttribute("anonid")) {
+              case "checkbox-treecol":
+                try {
+                    composite.startBatch();
+                    if (composite.getCalendarById(calendar.id)) {
+                        composite.removeCalendar(calendar);
+                    } else {
+                        composite.addCalendar(calendar);
+                    }
+                } finally {
+                    composite.endBatch();
+                }
+                break;
+          }
+          this.treebox.invalidateRow(aRow);
+        ]]></body>
+      </method>
+
+      <method name="isEditable">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <body><![CDATA[
+          return false;
+        ]]></body>
+      </method>
+
+      <method name="setCellValue">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <parameter name="aValue"/>
+        <body><![CDATA[
+          let calendar = this.getCalendar(aRow);
+          let composite = this.compositeCalendar;
+
+          switch (aCol.element.getAttribute("anonid")) {
+              case "checkbox-treecol":
+                  if (aValue == "true") {
+                      composite.addCalendar(calendar);
+                  } else {
+                      composite.removeCalendar(calendar);
+                  }
+                  break;
+              default:
+                  return null;
+          }
+          return aValue;
+        ]]></body>
+      </method>
+
+      <method name="setCellText">
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <parameter name="aValue"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="performAction">
+        <parameter name="aAction"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="performActionOnRow">
+        <parameter name="aAction"/>
+        <parameter name="aRow"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+
+      <method name="performActionOnCell">
+        <parameter name="aAction"/>
+        <parameter name="aRow"/>
+        <parameter name="aCol"/>
+        <body><![CDATA[
+        ]]></body>
+      </method>
+    </implementation>
+    <handlers>
+      <handler event="select"><![CDATA[
+        this.compositeCalendar.defaultCalendar = this.getCalendar(this.tree.currentIndex);
+      ]]></handler>
+      <handler event="keypress" keycode="VK_DELETE"><![CDATA[
+        if (this.writable) {
+            promptDeleteCalendar(this.compositeCalendar.defaultCalendar);
+            event.preventDefault();
+        }
+      ]]></handler>
+      <handler event="keypress" keycode="VK_SPACE"><![CDATA[
+        if (this.tree.currentIndex > -1 && this.seltype == "checkbox") {
+            this.cycleCell(this.tree.currentIndex, this.getColumn("checkbox-treecol"));
+            event.preventDefault();
+        }
+      ]]></handler>
+      <handler event="keypress" keycode="VK_DOWN" modifiers="control"><![CDATA[
+        if (!this.allowDrag) {
+          return;
+        }
+
+        let ci = this.tree.currentIndex;
+
+        if (ci < this.mCalendarList.length - 1) {
+            this.mCalendarList.splice(ci + 1, 0, this.mCalendarList.splice(ci, 1)[0]);
+            this.treebox.invalidateRange(ci, ci + 1);
+
+            if (this.tree.view.selection.isSelected(ci)) {
+                this.tree.view.selection.toggleSelect(ci);
+                this.tree.view.selection.toggleSelect(ci + 1);
+            }
+            if (this.tree.view.selection.currentIndex == ci) {
+                this.tree.view.selection.currentIndex = ci + 1;
+            }
+
+            // Fire event
+            this.sortOrderChanged();
+        }
+        // Don't call the default <key> handler.
+        event.preventDefault();
+      ]]></handler>
+      <handler event="keypress" keycode="VK_UP" modifiers="control"><![CDATA[
+        if (!this.allowDrag) {
+          return;
+        }
+
+        let ci = this.tree.currentIndex;
+        if (ci > 0) {
+            this.mCalendarList.splice(ci - 1, 0, this.mCalendarList.splice(ci, 1)[0]);
+            this.treebox.invalidateRange(ci - 1, ci);
+
+            if (this.tree.view.selection.isSelected(ci)) {
+                this.tree.view.selection.toggleSelect(ci);
+                this.tree.view.selection.toggleSelect(ci - 1);
+            }
+            if (this.tree.view.selection.currentIndex == ci) {
+                this.tree.view.selection.currentIndex = ci - 1;
+            }
+
+            // Fire event
+            this.sortOrderChanged();
+        }
+        // Don't call the default <key> handler.
+        event.preventDefault();
+      ]]></handler>
+    </handlers>
+  </binding>
+</bindings>
--- a/calendar/base/content/widgets/calendar-widget-bindings.css
+++ b/calendar/base/content/widgets/calendar-widget-bindings.css
@@ -75,8 +75,16 @@ minimonth-header[readonly="true"] {
 
 dragndropContainer {
    -moz-binding: url(chrome://calendar/content/widgets/calendar-widgets.xml#dragndropContainer);
 }
 
 tab[calview] {
    -moz-binding: url(chrome://calendar/content/widgets/calendar-widgets.xml#view-tab);
 }
+
+calendar-list-tree {
+   -moz-binding: url(chrome://calendar/content/widgets/calendar-list-tree.xml#calendar-list-tree);
+}
+
+calendar-list-tree[type="full"] {
+   -moz-binding: url(chrome://calendar/content/widgets/calendar-list-tree.xml#full-calendar-list-tree);
+}
--- a/calendar/base/content/widgets/minimonth.xml
+++ b/calendar/base/content/widgets/minimonth.xml
@@ -418,17 +418,17 @@
             <xul:text class="minimonth-day" flex="1"/>
             <xul:text class="minimonth-day" flex="1"/>
             <xul:text class="minimonth-day" flex="1"/>
           </xul:hbox>
         </xul:vbox>
     </content>
 
     <!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
-    <implementation implements="calICompositeObserver calIOperationListener" >
+    <implementation implements="calICompositeObserver calIOperationListener nsIObserver" >
       <property name="value"
                 onget="return this.mValue"
                 onset="this.update(val)"/>
 
        <!--returns the first (inclusive) date of the minimonth as a calIDateTime object-->
       <property name="firstDate" readonly="true">
         <getter><![CDATA[
             var calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
@@ -459,22 +459,36 @@
           // save references for convenience
           if (this.hasAttribute("readonly")) {
               this.mIsReadOnly = this.getAttribute("readonly") == "true";
           }
           this.refreshDisplay( );
           if (this.hasAttribute("freebusy")) {
               this._setFreeBusy(this.getAttribute("freebusy") == "true");
           }
+
+          // Add pref observer for calendar.week.start
+          let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                      .getService(Components.interfaces.nsIPrefService);
+          let branch = prefService.getBranch("")
+                                  .QueryInterface(Components.interfaces.nsIPrefBranch2);
+          branch.addObserver("calendar.week.start", this, false);
       ]]></constructor>
       <destructor><![CDATA[
           var composite = getCompositeCalendar();
           if (composite && this.mObservesComposite == true) {
               getCompositeCalendar().removeObserver(this);
           }
+
+          // Remove pref observer for calendar.week.start
+          let prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                      .getService(Components.interfaces.nsIPrefService);
+          let branch = prefService.getBranch("")
+                                  .QueryInterface(Components.interfaces.nsIPrefBranch2);
+          branch.removeObserver("calendar.week.start", this, false);
       ]]></destructor>
 
      <!-- calIOperationListener methods -->
       <method name="onOperationComplete">
         <parameter name="aCalendar"/>
         <parameter name="aStatus"/>
         <parameter name="aOperationType"/>
         <parameter name="aId"/>
@@ -655,16 +669,33 @@
       </method>
 
       <method name="onDefaultCalendarChanged">
         <parameter name="aCalendar"/>
         <body><![CDATA[
         ]]></body>
       </method>
 
+      <!-- nsIObserver methods -->
+      <method name="observe">
+        <parameter name="aSubject"/>
+        <parameter name="aTopic"/>
+        <parameter name="aData"/>
+        <body><![CDATA[
+          // WARNING: In the constructor, only calendar.week.start is observed.
+          // If you decide to add further prefs to this observer, you'll need to
+          // modify constructor and destructor.
+          switch (aData) {
+              case "calendar.week.start":
+                  this.refreshDisplay(true);
+                  break;
+          }
+        ]]></body>
+      </method>
+
       <method name="refreshDisplay">
         <body><![CDATA[
           // Find out which should be the first day of the week
           this.weekStart = getPrefSafe("calendar.week.start", 0);
           if (!this.mValue) {
               this.mValue = new Date();
           }
           this.setHeader();
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -85,16 +85,17 @@ calendar.jar:
     content/calendar/preferences/general.js                (content/preferences/general.js)
     content/calendar/preferences/general.xul               (content/preferences/general.xul)
     content/calendar/preferences/timezones.js              (content/preferences/timezones.js)
     content/calendar/preferences/timezones.xul             (content/preferences/timezones.xul)
     content/calendar/preferences/views.js                  (content/preferences/views.js)
     content/calendar/preferences/views.xul                 (content/preferences/views.xul)
     content/calendar/widgets/minimonth.xml                 (content/widgets/minimonth.xml)
     content/calendar/widgets/calendar-widgets.xml          (content/widgets/calendar-widgets.xml)
+    content/calendar/widgets/calendar-list-tree.xml        (content/widgets/calendar-list-tree.xml)
     content/calendar/widgets/calendar-widget-bindings.css  (content/widgets/calendar-widget-bindings.css)
 *   content/calendar/calApplicationUtils.js                (src/calApplicationUtils.js)
     content/calendar/calUtils.js                           (src/calUtils.js)
     content/calendar/calFilter.js                          (src/calFilter.js)
     content/calendar/Windows98ToZoneInfoTZId.properties    (src/Windows98ToZoneInfoTZId.properties)
     content/calendar/WindowsNTToZoneInfoTZId.properties    (src/WindowsNTToZoneInfoTZId.properties)
 % skin calendar classic/1.0 %skin/calendar/
     skin/calendar/abcard.png                               (themes/common/abcard.png)
--- a/calendar/base/themes/pinstripe/calendar-management.css
+++ b/calendar/base/themes/pinstripe/calendar-management.css
@@ -29,66 +29,66 @@
  * 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 ***** */
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-checkbox) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(checkbox-treecol) {
     list-style-image: url(chrome://calendar/skin/unifinder-images.png);
     -moz-image-region: rect(0 13px 13px 0);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-checkbox, checked) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(checkbox-treecol, checked) {
     -moz-image-region: rect(0 26px 13px 13px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-checkbox, disabled) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(checkbox-treecol, disabled) {
     -moz-image-region: rect(0 39px 13px 26px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-color, color-default) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(color-treecol, color-default) {
     background-color: #a8c2e1;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-checkbox) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(checkbox-treecol) {
     padding: 0;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-color) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(color-treecol) {
     margin: 1px;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-calendar) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(calendarname-treecol) {
     -moz-margin-start: 1px;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-status, readonly) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(status-treecol, readonly) {
     list-style-image: url(chrome://calendar/skin/calendar-status.png);
     -moz-image-region: rect(0px, 14px, 14px, 0px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-status, readfailed) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(status-treecol, readfailed) {
     list-style-image: url(chrome://calendar/skin/calendar-status.png);
     -moz-image-region: rect(0px, 28px, 14px, 14px);
 }
 
-#calendar-list-tree-widget {
+calendar-list-tree > tree {
   border: none;
   padding: 0;
   margin: 4px;
   -moz-border-top-colors: none;
   -moz-border-right-colors: none;
   -moz-border-bottom-colors: none;
   -moz-border-left-colors: none;
   -moz-appearance: none;
 }
 
-treecol[hideheader="true"],
-treecols[hideheader="true"] {
+calendar-list-tree > tree > treecols > treecol[hideheader="true"],
+calendar-list-tree > tree > treecols > treecol[hideheader="true"] {
   font-size: 0px;
   border: none;
   padding: 0;
   max-height: 0px;
   height: 0px;
 }
--- a/calendar/base/themes/winstripe/calendar-management.css
+++ b/calendar/base/themes/winstripe/calendar-management.css
@@ -29,66 +29,66 @@
  * 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 ***** */
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-checkbox) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(checkbox-treecol) {
     list-style-image: url(chrome://calendar/skin/unifinder-images.png);
     -moz-image-region: rect(0 13px 13px 0);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-checkbox, checked) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(checkbox-treecol, checked) {
     -moz-image-region: rect(0 26px 13px 13px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-checkbox, disabled) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(checkbox-treecol, disabled) {
     -moz-image-region: rect(0 39px 13px 26px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-color, color-default) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(color-treecol, color-default) {
     background-color: #a8c2e1;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-color) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(color-treecol) {
     margin: 1px;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell(calendar-list-tree-calendar) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell(calendarname-treecol) {
     -moz-margin-start: 1px;
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-status, readonly) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(status-treecol, readonly) {
     list-style-image: url(chrome://calendar/skin/calendar-status.png);
     -moz-image-region: rect(0px, 14px, 14px, 0px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-image(calendar-list-tree-status, readfailed) {
+calendar-list-tree > tree > treechildren::-moz-tree-image(status-treecol, readfailed) {
     list-style-image: url(chrome://calendar/skin/calendar-status.png);
     -moz-image-region: rect(0px, 28px, 14px, 14px);
 }
 
-#calendar-list-tree-widget > treechildren::-moz-tree-cell-text(disabled) {
+calendar-list-tree > tree > treechildren::-moz-tree-cell-text(disabled) {
     color: GrayText;
 }
 
-#calendar-list-tree-widget {
+calendar-list-tree > tree {
   border: none;
   padding: 0;
   margin: 4px;
   -moz-border-top-colors: none;
   -moz-border-right-colors: none;
   -moz-border-bottom-colors: none;
   -moz-border-left-colors: none;
   -moz-appearance: none;
 }
 
-treecol[hideheader="true"],
-treecols[hideheader="true"] {
+calendar-list-tree > tree > treecols > treecol[hideheader="true"],
+calendar-list-tree > tree > treecols > treecol[hideheader="true"] {
   font-size: 0px;
   border: none;
   padding: 0;
   max-height: 0px;
   height: 0px;
 }
--- a/calendar/lightning/content/messenger-overlay-sidebar.xul
+++ b/calendar/lightning/content/messenger-overlay-sidebar.xul
@@ -481,24 +481,26 @@
                  </radiogroup>
               </modevbox>
             </modevbox>
             <modevbox id="calendar-list-pane" flex="1" mode="calendar,task" broadcaster="modeBroadcaster"
                       refcontrol="calendar_toggle_calendarlist_command">
               <treenode-checkbox id="calendar-list-header"
                                checked="true"
                                class="treenode-checkbox"
-                               ondrop="return calendarListTreeView.foreignDrop(event)"
-                               ondragenter="return calendarListTreeView.foreignCanDrop(event)"
+                               ondrop="return document.getElementById('calendar-list-tree-widget').foreignDrop(event)"
+                               ondragenter="return document.getElementById('calendar-list-tree-widget').foreignCanDrop(event)"
+                               ondragover="return document.getElementById('calendar-list-tree-widget').foreignCanDrop(event)"
                                label="&calendar.list.header.label;"/>
               <modevbox id="calendar-listtree-pane" flex="1" mode="calendar,task" broadcaster="modeBroadcaster"
                         refcontrol="calendar-list-header">
                 
-                <tree id="calendar-list-tree-widget"  class="task-tree-subpane"
-                  flex="1"/>
+                <calendar-list-tree id="calendar-list-tree-widget"
+                                    class="task-tree-subpane"
+                                    flex="1"/>
               </modevbox>
           </modevbox>
           </vbox>
         </vbox>
 
         <splitter id="calsidebar_splitter" collapse="before" persist="state"/>
 
         <deck id="calendarDisplayDeck" flex="1">