|
1 #ifdef 0 |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 #endif |
|
6 |
|
7 /** |
|
8 * This singleton provides a custom drop target detection. We need this because |
|
9 * the default DnD target detection relies on the cursor's position. We want |
|
10 * to pick a drop target based on the dragged site's position. |
|
11 */ |
|
12 let gDropTargetShim = { |
|
13 /** |
|
14 * Cache for the position of all cells, cleaned after drag finished. |
|
15 */ |
|
16 _cellPositions: null, |
|
17 |
|
18 /** |
|
19 * The last drop target that was hovered. |
|
20 */ |
|
21 _lastDropTarget: null, |
|
22 |
|
23 /** |
|
24 * Initializes the drop target shim. |
|
25 */ |
|
26 init: function () { |
|
27 gGrid.node.addEventListener("dragstart", this, true); |
|
28 }, |
|
29 |
|
30 /** |
|
31 * Add all event listeners needed during a drag operation. |
|
32 */ |
|
33 _addEventListeners: function () { |
|
34 gGrid.node.addEventListener("dragend", this); |
|
35 |
|
36 let docElement = document.documentElement; |
|
37 docElement.addEventListener("dragover", this); |
|
38 docElement.addEventListener("dragenter", this); |
|
39 docElement.addEventListener("drop", this); |
|
40 }, |
|
41 |
|
42 /** |
|
43 * Remove all event listeners that were needed during a drag operation. |
|
44 */ |
|
45 _removeEventListeners: function () { |
|
46 gGrid.node.removeEventListener("dragend", this); |
|
47 |
|
48 let docElement = document.documentElement; |
|
49 docElement.removeEventListener("dragover", this); |
|
50 docElement.removeEventListener("dragenter", this); |
|
51 docElement.removeEventListener("drop", this); |
|
52 }, |
|
53 |
|
54 /** |
|
55 * Handles all shim events. |
|
56 */ |
|
57 handleEvent: function (aEvent) { |
|
58 switch (aEvent.type) { |
|
59 case "dragstart": |
|
60 this._dragstart(aEvent); |
|
61 break; |
|
62 case "dragenter": |
|
63 aEvent.preventDefault(); |
|
64 break; |
|
65 case "dragover": |
|
66 this._dragover(aEvent); |
|
67 break; |
|
68 case "drop": |
|
69 this._drop(aEvent); |
|
70 break; |
|
71 case "dragend": |
|
72 this._dragend(aEvent); |
|
73 break; |
|
74 } |
|
75 }, |
|
76 |
|
77 /** |
|
78 * Handles the 'dragstart' event. |
|
79 * @param aEvent The 'dragstart' event. |
|
80 */ |
|
81 _dragstart: function (aEvent) { |
|
82 if (aEvent.target.classList.contains("newtab-link")) { |
|
83 gGrid.lock(); |
|
84 this._addEventListeners(); |
|
85 } |
|
86 }, |
|
87 |
|
88 /** |
|
89 * Handles the 'dragover' event. |
|
90 * @param aEvent The 'dragover' event. |
|
91 */ |
|
92 _dragover: function (aEvent) { |
|
93 // XXX bug 505521 - Use the dragover event to retrieve the |
|
94 // current mouse coordinates while dragging. |
|
95 let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode; |
|
96 gDrag.drag(sourceNode._newtabSite, aEvent); |
|
97 |
|
98 // Find the current drop target, if there's one. |
|
99 this._updateDropTarget(aEvent); |
|
100 |
|
101 // If we have a valid drop target, |
|
102 // let the drag-and-drop service know. |
|
103 if (this._lastDropTarget) { |
|
104 aEvent.preventDefault(); |
|
105 } |
|
106 }, |
|
107 |
|
108 /** |
|
109 * Handles the 'drop' event. |
|
110 * @param aEvent The 'drop' event. |
|
111 */ |
|
112 _drop: function (aEvent) { |
|
113 // We're accepting all drops. |
|
114 aEvent.preventDefault(); |
|
115 |
|
116 // Make sure to determine the current drop target |
|
117 // in case the dragover event hasn't been fired. |
|
118 this._updateDropTarget(aEvent); |
|
119 |
|
120 // A site was successfully dropped. |
|
121 this._dispatchEvent(aEvent, "drop", this._lastDropTarget); |
|
122 }, |
|
123 |
|
124 /** |
|
125 * Handles the 'dragend' event. |
|
126 * @param aEvent The 'dragend' event. |
|
127 */ |
|
128 _dragend: function (aEvent) { |
|
129 if (this._lastDropTarget) { |
|
130 if (aEvent.dataTransfer.mozUserCancelled) { |
|
131 // The drag operation was cancelled. |
|
132 this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget); |
|
133 this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget); |
|
134 } |
|
135 |
|
136 // Clean up. |
|
137 this._lastDropTarget = null; |
|
138 this._cellPositions = null; |
|
139 } |
|
140 |
|
141 gGrid.unlock(); |
|
142 this._removeEventListeners(); |
|
143 }, |
|
144 |
|
145 /** |
|
146 * Tries to find the current drop target and will fire |
|
147 * appropriate dragenter, dragexit, and dragleave events. |
|
148 * @param aEvent The current drag event. |
|
149 */ |
|
150 _updateDropTarget: function (aEvent) { |
|
151 // Let's see if we find a drop target. |
|
152 let target = this._findDropTarget(aEvent); |
|
153 |
|
154 if (target != this._lastDropTarget) { |
|
155 if (this._lastDropTarget) |
|
156 // We left the last drop target. |
|
157 this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget); |
|
158 |
|
159 if (target) |
|
160 // We're now hovering a (new) drop target. |
|
161 this._dispatchEvent(aEvent, "dragenter", target); |
|
162 |
|
163 if (this._lastDropTarget) |
|
164 // We left the last drop target. |
|
165 this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget); |
|
166 |
|
167 this._lastDropTarget = target; |
|
168 } |
|
169 }, |
|
170 |
|
171 /** |
|
172 * Determines the current drop target by matching the dragged site's position |
|
173 * against all cells in the grid. |
|
174 * @return The currently hovered drop target or null. |
|
175 */ |
|
176 _findDropTarget: function () { |
|
177 // These are the minimum intersection values - we want to use the cell if |
|
178 // the site is >= 50% hovering its position. |
|
179 let minWidth = gDrag.cellWidth / 2; |
|
180 let minHeight = gDrag.cellHeight / 2; |
|
181 |
|
182 let cellPositions = this._getCellPositions(); |
|
183 let rect = gTransformation.getNodePosition(gDrag.draggedSite.node); |
|
184 |
|
185 // Compare each cell's position to the dragged site's position. |
|
186 for (let i = 0; i < cellPositions.length; i++) { |
|
187 let inter = rect.intersect(cellPositions[i].rect); |
|
188 |
|
189 // If the intersection is big enough we found a drop target. |
|
190 if (inter.width >= minWidth && inter.height >= minHeight) |
|
191 return cellPositions[i].cell; |
|
192 } |
|
193 |
|
194 // No drop target found. |
|
195 return null; |
|
196 }, |
|
197 |
|
198 /** |
|
199 * Gets the positions of all cell nodes. |
|
200 * @return The (cached) cell positions. |
|
201 */ |
|
202 _getCellPositions: function DropTargetShim_getCellPositions() { |
|
203 if (this._cellPositions) |
|
204 return this._cellPositions; |
|
205 |
|
206 return this._cellPositions = gGrid.cells.map(function (cell) { |
|
207 return {cell: cell, rect: gTransformation.getNodePosition(cell.node)}; |
|
208 }); |
|
209 }, |
|
210 |
|
211 /** |
|
212 * Dispatches a custom DragEvent on the given target node. |
|
213 * @param aEvent The source event. |
|
214 * @param aType The event type. |
|
215 * @param aTarget The target node that receives the event. |
|
216 */ |
|
217 _dispatchEvent: function (aEvent, aType, aTarget) { |
|
218 let node = aTarget.node; |
|
219 let event = document.createEvent("DragEvents"); |
|
220 |
|
221 // The event should not bubble to prevent recursion. |
|
222 event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false, |
|
223 false, false, 0, node, aEvent.dataTransfer); |
|
224 |
|
225 node.dispatchEvent(event); |
|
226 } |
|
227 }; |