Fix bug 266249 - can't re-order or sort Calendars list. r=berend
authorPhilipp Kewisch <mozilla@kewis.ch>
Thu, 15 Jan 2009 20:53:44 +0100
changeset 1654 33289583f87ee0f47078264b3bc279b5c8d8a0f4
parent 1653 66d414c9616eff733d4aaae63639e2149a247df3
child 1655 25d5967e1ce642b78dc26357408d8e5b4360ba65
push id1322
push usermozilla@kewis.ch
push dateThu, 15 Jan 2009 19:56:25 +0000
treeherdercomm-central@33289583f87e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersberend
bugs266249
Fix bug 266249 - can't re-order or sort Calendars list. r=berend
calendar/base/content/calendar-calendars-list.xul
calendar/base/content/calendar-management.js
calendar/lightning/content/messenger-overlay-sidebar.xul
--- a/calendar/base/content/calendar-calendars-list.xul
+++ b/calendar/base/content/calendar-calendars-list.xul
@@ -83,16 +83,17 @@
   </popupset>
 
   <tree id="calendar-list-tree-widget"
         hidecolumnpicker="true"
         seltype="single"
         onkeypress="calendarListTreeView.onKeyPress(event);"
         ondblclick="calendarListTreeView.onDoubleClick(event);"
         onselect="calendarListTreeView.onSelect(event);"
+        ondragstart="calendarListTreeView.onDragStart(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"
--- a/calendar/base/content/calendar-management.js
+++ b/calendar/base/content/calendar-management.js
@@ -473,21 +473,114 @@ var calendarListTreeView = {
     isSeparator: function cLTV_isSeparator(aRow) {
         return false;
     },
 
     isSorted: function cLTV_isSorted(aRow) {
         return false;
     },
 
+    /**
+     * 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);
+
+        // 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) {
-        return false;
+        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;
+        }
+
+        let dragCalId = dataTransfer.getData("application/x-moz-calendarID");
+
+        return (aOrientation != Components.interfaces.nsITreeView.DROP_ON &&
+                dragCalId != null);
     },
 
-    drop: function cLTV_drop(aRow, aOrientation) {},
+    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);
+            }
+        }
+        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) {
@@ -595,16 +688,60 @@ var calendarListTreeView = {
                 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")) {
--- a/calendar/lightning/content/messenger-overlay-sidebar.xul
+++ b/calendar/lightning/content/messenger-overlay-sidebar.xul
@@ -484,16 +484,18 @@
                  </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)"
                                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"/>
               </modevbox>
           </modevbox>