|
1 <?xml version="1.0"?> |
|
2 |
|
3 <!-- This Source Code Form is subject to the terms of the Mozilla Public |
|
4 - License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> |
|
6 |
|
7 <bindings id="placesTreeBindings" |
|
8 xmlns="http://www.mozilla.org/xbl" |
|
9 xmlns:xbl="http://www.mozilla.org/xbl" |
|
10 xmlns:html="http://www.w3.org/1999/xhtml" |
|
11 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> |
|
12 |
|
13 <binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree"> |
|
14 <implementation> |
|
15 <constructor><![CDATA[ |
|
16 // Force an initial build. |
|
17 if (this.place) |
|
18 this.place = this.place; |
|
19 ]]></constructor> |
|
20 |
|
21 <destructor><![CDATA[ |
|
22 // Break the treeviewer->result->treeviewer cycle. |
|
23 // Note: unsetting the result's viewer also unsets |
|
24 // the viewer's reference to our treeBoxObject. |
|
25 var result = this.result; |
|
26 if (result) { |
|
27 result.root.containerOpen = false; |
|
28 } |
|
29 |
|
30 // Unregister the controllber before unlinking the view, otherwise it |
|
31 // may still try to update commands on a view with a null result. |
|
32 if (this._controller) { |
|
33 this._controller.terminate(); |
|
34 this.controllers.removeController(this._controller); |
|
35 } |
|
36 |
|
37 this.view = null; |
|
38 ]]></destructor> |
|
39 |
|
40 <property name="controller" |
|
41 readonly="true" |
|
42 onget="return this._controller"/> |
|
43 |
|
44 <!-- overriding --> |
|
45 <property name="view"> |
|
46 <getter><![CDATA[ |
|
47 try { |
|
48 return this.treeBoxObject.view.wrappedJSObject || null; |
|
49 } |
|
50 catch(e) { |
|
51 return null; |
|
52 } |
|
53 ]]></getter> |
|
54 <setter><![CDATA[ |
|
55 return this.treeBoxObject.view = val; |
|
56 ]]></setter> |
|
57 </property> |
|
58 |
|
59 <property name="associatedElement" |
|
60 readonly="true" |
|
61 onget="return this"/> |
|
62 |
|
63 <method name="applyFilter"> |
|
64 <parameter name="filterString"/> |
|
65 <parameter name="folderRestrict"/> |
|
66 <parameter name="includeHidden"/> |
|
67 <body><![CDATA[ |
|
68 // preserve grouping |
|
69 var queryNode = PlacesUtils.asQuery(this.result.root); |
|
70 var options = queryNode.queryOptions.clone(); |
|
71 |
|
72 // Make sure we're getting uri results. |
|
73 // We do not yet support searching into grouped queries or into |
|
74 // tag containers, so we must fall to the default case. |
|
75 if (PlacesUtils.nodeIsHistoryContainer(queryNode) || |
|
76 options.resultType == options.RESULTS_AS_TAG_QUERY || |
|
77 options.resultType == options.RESULTS_AS_TAG_CONTENTS) |
|
78 options.resultType = options.RESULTS_AS_URI; |
|
79 |
|
80 var query = PlacesUtils.history.getNewQuery(); |
|
81 query.searchTerms = filterString; |
|
82 |
|
83 if (folderRestrict) { |
|
84 query.setFolders(folderRestrict, folderRestrict.length); |
|
85 options.queryType = options.QUERY_TYPE_BOOKMARKS; |
|
86 } |
|
87 |
|
88 options.includeHidden = !!includeHidden; |
|
89 |
|
90 this.load([query], options); |
|
91 ]]></body> |
|
92 </method> |
|
93 |
|
94 <method name="load"> |
|
95 <parameter name="queries"/> |
|
96 <parameter name="options"/> |
|
97 <body><![CDATA[ |
|
98 let result = PlacesUtils.history |
|
99 .executeQueries(queries, queries.length, |
|
100 options); |
|
101 let callback; |
|
102 if (this.flatList) { |
|
103 let onOpenFlatContainer = this.onOpenFlatContainer; |
|
104 if (onOpenFlatContainer) |
|
105 callback = new Function("aContainer", onOpenFlatContainer); |
|
106 } |
|
107 |
|
108 if (!this._controller) { |
|
109 this._controller = new PlacesController(this); |
|
110 this.controllers.appendController(this._controller); |
|
111 } |
|
112 |
|
113 let treeView = new PlacesTreeView(this.flatList, callback, this._controller); |
|
114 |
|
115 // Observer removal is done within the view itself. When the tree |
|
116 // goes away, treeboxobject calls view.setTree(null), which then |
|
117 // calls removeObserver. |
|
118 result.addObserver(treeView, false); |
|
119 this.view = treeView; |
|
120 |
|
121 if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) { |
|
122 treeView.selection.select(0); |
|
123 } |
|
124 |
|
125 this._cachedInsertionPoint = undefined; |
|
126 ]]></body> |
|
127 </method> |
|
128 |
|
129 <property name="flatList"> |
|
130 <getter><![CDATA[ |
|
131 return this.getAttribute("flatList") == "true"; |
|
132 ]]></getter> |
|
133 <setter><![CDATA[ |
|
134 if (this.flatList != val) { |
|
135 this.setAttribute("flatList", val); |
|
136 // reload with the last place set |
|
137 if (this.place) |
|
138 this.place = this.place; |
|
139 } |
|
140 return val; |
|
141 ]]></setter> |
|
142 </property> |
|
143 |
|
144 <property name="onOpenFlatContainer"> |
|
145 <getter><![CDATA[ |
|
146 return this.getAttribute("onopenflatcontainer"); |
|
147 ]]></getter> |
|
148 <setter><![CDATA[ |
|
149 if (this.onOpenFlatContainer != val) { |
|
150 this.setAttribute("onopenflatcontainer", val); |
|
151 // reload with the last place set |
|
152 if (this.place) |
|
153 this.place = this.place; |
|
154 } |
|
155 return val; |
|
156 ]]></setter> |
|
157 </property> |
|
158 |
|
159 <!-- |
|
160 Causes a particular node represented by the specified placeURI to be |
|
161 selected in the tree. All containers above the node in the hierarchy |
|
162 will be opened, so that the node is visible. |
|
163 --> |
|
164 <method name="selectPlaceURI"> |
|
165 <parameter name="placeURI"/> |
|
166 <body><![CDATA[ |
|
167 // Do nothing if a node matching the given uri is already selected |
|
168 if (this.hasSelection && this.selectedNode.uri == placeURI) |
|
169 return; |
|
170 |
|
171 function findNode(container, placeURI, nodesURIChecked) { |
|
172 var containerURI = container.uri; |
|
173 if (containerURI == placeURI) |
|
174 return container; |
|
175 if (nodesURIChecked.indexOf(containerURI) != -1) |
|
176 return null; |
|
177 |
|
178 // never check the contents of the same query |
|
179 nodesURIChecked.push(containerURI); |
|
180 |
|
181 var wasOpen = container.containerOpen; |
|
182 if (!wasOpen) |
|
183 container.containerOpen = true; |
|
184 for (var i = 0; i < container.childCount; ++i) { |
|
185 var child = container.getChild(i); |
|
186 var childURI = child.uri; |
|
187 if (childURI == placeURI) |
|
188 return child; |
|
189 else if (PlacesUtils.nodeIsContainer(child)) { |
|
190 var nested = findNode(PlacesUtils.asContainer(child), placeURI, nodesURIChecked); |
|
191 if (nested) |
|
192 return nested; |
|
193 } |
|
194 } |
|
195 |
|
196 if (!wasOpen) |
|
197 container.containerOpen = false; |
|
198 |
|
199 return null; |
|
200 } |
|
201 |
|
202 var container = this.result.root; |
|
203 NS_ASSERT(container, "No result, cannot select place URI!"); |
|
204 if (!container) |
|
205 return; |
|
206 |
|
207 var child = findNode(container, placeURI, []); |
|
208 if (child) |
|
209 this.selectNode(child); |
|
210 else { |
|
211 // If the specified child could not be located, clear the selection |
|
212 var selection = this.view.selection; |
|
213 selection.clearSelection(); |
|
214 } |
|
215 ]]></body> |
|
216 </method> |
|
217 |
|
218 <!-- |
|
219 Causes a particular node to be selected in the tree, resulting in all |
|
220 containers above the node in the hierarchy to be opened, so that the |
|
221 node is visible. |
|
222 --> |
|
223 <method name="selectNode"> |
|
224 <parameter name="node"/> |
|
225 <body><![CDATA[ |
|
226 var view = this.view; |
|
227 |
|
228 var parent = node.parent; |
|
229 if (parent && !parent.containerOpen) { |
|
230 // Build a list of all of the nodes that are the parent of this one |
|
231 // in the result. |
|
232 var parents = []; |
|
233 var root = this.result.root; |
|
234 while (parent && parent != root) { |
|
235 parents.push(parent); |
|
236 parent = parent.parent; |
|
237 } |
|
238 |
|
239 // Walk the list backwards (opening from the root of the hierarchy) |
|
240 // opening each folder as we go. |
|
241 for (var i = parents.length - 1; i >= 0; --i) { |
|
242 var index = view.treeIndexForNode(parents[i]); |
|
243 if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE && |
|
244 view.isContainer(index) && !view.isContainerOpen(index)) |
|
245 view.toggleOpenState(index); |
|
246 } |
|
247 // Select the specified node... |
|
248 } |
|
249 |
|
250 var index = view.treeIndexForNode(node); |
|
251 if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE) |
|
252 return; |
|
253 |
|
254 view.selection.select(index); |
|
255 // ... and ensure it's visible, not scrolled off somewhere. |
|
256 this.treeBoxObject.ensureRowIsVisible(index); |
|
257 ]]></body> |
|
258 </method> |
|
259 |
|
260 <!-- nsIPlacesView --> |
|
261 <property name="result"> |
|
262 <getter><![CDATA[ |
|
263 try { |
|
264 return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result; |
|
265 } |
|
266 catch (e) { |
|
267 return null; |
|
268 } |
|
269 ]]></getter> |
|
270 </property> |
|
271 |
|
272 <!-- nsIPlacesView --> |
|
273 <property name="place"> |
|
274 <getter><![CDATA[ |
|
275 return this.getAttribute("place"); |
|
276 ]]></getter> |
|
277 <setter><![CDATA[ |
|
278 this.setAttribute("place", val); |
|
279 |
|
280 var queriesRef = { }; |
|
281 var queryCountRef = { }; |
|
282 var optionsRef = { }; |
|
283 PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef); |
|
284 if (queryCountRef.value == 0) |
|
285 queriesRef.value = [PlacesUtils.history.getNewQuery()]; |
|
286 if (!optionsRef.value) |
|
287 optionsRef.value = PlacesUtils.history.getNewQueryOptions(); |
|
288 |
|
289 this.load(queriesRef.value, optionsRef.value); |
|
290 |
|
291 return val; |
|
292 ]]></setter> |
|
293 </property> |
|
294 |
|
295 <!-- nsIPlacesView --> |
|
296 <property name="hasSelection"> |
|
297 <getter><![CDATA[ |
|
298 return this.view && this.view.selection.count >= 1; |
|
299 ]]></getter> |
|
300 </property> |
|
301 |
|
302 <!-- nsIPlacesView --> |
|
303 <property name="selectedNodes"> |
|
304 <getter><![CDATA[ |
|
305 let nodes = []; |
|
306 if (!this.hasSelection) |
|
307 return nodes; |
|
308 |
|
309 let selection = this.view.selection; |
|
310 let rc = selection.getRangeCount(); |
|
311 let resultview = this.view; |
|
312 for (let i = 0; i < rc; ++i) { |
|
313 let min = { }, max = { }; |
|
314 selection.getRangeAt(i, min, max); |
|
315 for (let j = min.value; j <= max.value; ++j) { |
|
316 nodes.push(resultview.nodeForTreeIndex(j)); |
|
317 } |
|
318 } |
|
319 return nodes; |
|
320 ]]></getter> |
|
321 </property> |
|
322 |
|
323 <method name="toggleCutNode"> |
|
324 <parameter name="aNode"/> |
|
325 <parameter name="aValue"/> |
|
326 <body><![CDATA[ |
|
327 this.view.toggleCutNode(aNode, aValue); |
|
328 ]]></body> |
|
329 </method> |
|
330 |
|
331 <!-- nsIPlacesView --> |
|
332 <property name="removableSelectionRanges"> |
|
333 <getter><![CDATA[ |
|
334 // This property exists in addition to selectedNodes because it |
|
335 // encodes selection ranges (which only occur in list views) into |
|
336 // the return value. For each removed range, the index at which items |
|
337 // will be re-inserted upon the remove transaction being performed is |
|
338 // the first index of the range, so that the view updates correctly. |
|
339 // |
|
340 // For example, if we remove rows 2,3,4 and 7,8 from a list, when we |
|
341 // undo that operation, if we insert what was at row 3 at row 3 again, |
|
342 // it will show up _after_ the item that was at row 5. So we need to |
|
343 // insert all items at row 2, and the tree view will update correctly. |
|
344 // |
|
345 // Also, this function collapses the selection to remove redundant |
|
346 // data, e.g. when deleting this selection: |
|
347 // |
|
348 // http://www.foo.com/ |
|
349 // (-) Some Folder |
|
350 // http://www.bar.com/ |
|
351 // |
|
352 // ... returning http://www.bar.com/ as part of the selection is |
|
353 // redundant because it is implied by removing "Some Folder". We |
|
354 // filter out all such redundancies since some partial amount of |
|
355 // the folder's children may be selected. |
|
356 // |
|
357 let nodes = []; |
|
358 if (!this.hasSelection) |
|
359 return nodes; |
|
360 |
|
361 var selection = this.view.selection; |
|
362 var rc = selection.getRangeCount(); |
|
363 var resultview = this.view; |
|
364 // This list is kept independently of the range selected (i.e. OUTSIDE |
|
365 // the for loop) since the row index of a container is unique for the |
|
366 // entire view, and we could have some really wacky selection and we |
|
367 // don't want to blow up. |
|
368 var containers = { }; |
|
369 for (var i = 0; i < rc; ++i) { |
|
370 var range = []; |
|
371 var min = { }, max = { }; |
|
372 selection.getRangeAt(i, min, max); |
|
373 |
|
374 for (var j = min.value; j <= max.value; ++j) { |
|
375 if (this.view.isContainer(j)) |
|
376 containers[j] = true; |
|
377 if (!(this.view.getParentIndex(j) in containers)) |
|
378 range.push(resultview.nodeForTreeIndex(j)); |
|
379 } |
|
380 nodes.push(range); |
|
381 } |
|
382 return nodes; |
|
383 ]]></getter> |
|
384 </property> |
|
385 |
|
386 <!-- nsIPlacesView --> |
|
387 <property name="draggableSelection" |
|
388 onget="return this.selectedNodes"/> |
|
389 |
|
390 <!-- nsIPlacesView --> |
|
391 <property name="selectedNode"> |
|
392 <getter><![CDATA[ |
|
393 var view = this.view; |
|
394 if (!view || view.selection.count != 1) |
|
395 return null; |
|
396 |
|
397 var selection = view.selection; |
|
398 var min = { }, max = { }; |
|
399 selection.getRangeAt(0, min, max); |
|
400 |
|
401 return this.view.nodeForTreeIndex(min.value); |
|
402 ]]></getter> |
|
403 </property> |
|
404 |
|
405 <!-- nsIPlacesView --> |
|
406 <property name="insertionPoint"> |
|
407 <getter><![CDATA[ |
|
408 // invalidated on selection and focus changes |
|
409 if (this._cachedInsertionPoint !== undefined) |
|
410 return this._cachedInsertionPoint; |
|
411 |
|
412 // there is no insertion point for history queries |
|
413 // so bail out now and save a lot of work when updating commands |
|
414 var resultNode = this.result.root; |
|
415 if (PlacesUtils.nodeIsQuery(resultNode) && |
|
416 PlacesUtils.asQuery(resultNode).queryOptions.queryType == |
|
417 Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) |
|
418 return this._cachedInsertionPoint = null; |
|
419 |
|
420 var orientation = Ci.nsITreeView.DROP_BEFORE; |
|
421 // If there is no selection, insert at the end of the container. |
|
422 if (!this.hasSelection) { |
|
423 var index = this.view.rowCount - 1; |
|
424 this._cachedInsertionPoint = |
|
425 this._getInsertionPoint(index, orientation); |
|
426 return this._cachedInsertionPoint; |
|
427 } |
|
428 |
|
429 // This is a two-part process. The first part is determining the drop |
|
430 // orientation. |
|
431 // * The default orientation is to drop _before_ the selected item. |
|
432 // * If the selected item is a container, the default orientation |
|
433 // is to drop _into_ that container. |
|
434 // |
|
435 // Warning: It may be tempting to use tree indexes in this code, but |
|
436 // you must not, since the tree is nested and as your tree |
|
437 // index may change when folders before you are opened and |
|
438 // closed. You must convert your tree index to a node, and |
|
439 // then use getChildIndex to find your absolute index in |
|
440 // the parent container instead. |
|
441 // |
|
442 var resultView = this.view; |
|
443 var selection = resultView.selection; |
|
444 var rc = selection.getRangeCount(); |
|
445 var min = { }, max = { }; |
|
446 selection.getRangeAt(rc - 1, min, max); |
|
447 |
|
448 // If the sole selection is a container, and we are not in |
|
449 // a flatlist, insert into it. |
|
450 // Note that this only applies to _single_ selections, |
|
451 // if the last element within a multi-selection is a |
|
452 // container, insert _adjacent_ to the selection. |
|
453 // |
|
454 // If the sole selection is the bookmarks toolbar folder, we insert |
|
455 // into it even if it is not opened |
|
456 if (selection.count == 1 && resultView.isContainer(max.value) && |
|
457 !this.flatList) |
|
458 orientation = Ci.nsITreeView.DROP_ON; |
|
459 |
|
460 this._cachedInsertionPoint = |
|
461 this._getInsertionPoint(max.value, orientation); |
|
462 return this._cachedInsertionPoint; |
|
463 ]]></getter> |
|
464 </property> |
|
465 |
|
466 <method name="_getInsertionPoint"> |
|
467 <parameter name="index"/> |
|
468 <parameter name="orientation"/> |
|
469 <body><![CDATA[ |
|
470 var result = this.result; |
|
471 var resultview = this.view; |
|
472 var container = result.root; |
|
473 var dropNearItemId = -1; |
|
474 NS_ASSERT(container, "null container"); |
|
475 // When there's no selection, assume the container is the container |
|
476 // the view is populated from (i.e. the result's itemId). |
|
477 if (index != -1) { |
|
478 var lastSelected = resultview.nodeForTreeIndex(index); |
|
479 if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) { |
|
480 // If the last selected item is an open container, append _into_ |
|
481 // it, rather than insert adjacent to it. |
|
482 container = lastSelected; |
|
483 index = -1; |
|
484 } |
|
485 else if (lastSelected.containerOpen && |
|
486 orientation == Ci.nsITreeView.DROP_AFTER && |
|
487 lastSelected.hasChildren) { |
|
488 // If the last selected item is an open container and the user is |
|
489 // trying to drag into it as a first item, really insert into it. |
|
490 container = lastSelected; |
|
491 orientation = Ci.nsITreeView.DROP_ON; |
|
492 index = 0; |
|
493 } |
|
494 else { |
|
495 // Use the last-selected node's container. |
|
496 container = lastSelected.parent; |
|
497 |
|
498 // See comment in the treeView.js's copy of this method |
|
499 if (!container || !container.containerOpen) |
|
500 return null; |
|
501 |
|
502 // Avoid the potentially expensive call to getChildIndex |
|
503 // if we know this container doesn't allow insertion |
|
504 if (PlacesControllerDragHelper.disallowInsertion(container)) |
|
505 return null; |
|
506 |
|
507 var queryOptions = PlacesUtils.asQuery(result.root).queryOptions; |
|
508 if (queryOptions.sortingMode != |
|
509 Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) { |
|
510 // If we are within a sorted view, insert at the end |
|
511 index = -1; |
|
512 } |
|
513 else if (queryOptions.excludeItems || |
|
514 queryOptions.excludeQueries || |
|
515 queryOptions.excludeReadOnlyFolders) { |
|
516 // Some item may be invisible, insert near last selected one. |
|
517 // We don't replace index here to avoid requests to the db, |
|
518 // instead it will be calculated later by the controller. |
|
519 index = -1; |
|
520 dropNearItemId = lastSelected.itemId; |
|
521 } |
|
522 else { |
|
523 var lsi = container.getChildIndex(lastSelected); |
|
524 index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1; |
|
525 } |
|
526 } |
|
527 } |
|
528 |
|
529 if (PlacesControllerDragHelper.disallowInsertion(container)) |
|
530 return null; |
|
531 |
|
532 return new InsertionPoint(PlacesUtils.getConcreteItemId(container), |
|
533 index, orientation, |
|
534 PlacesUtils.nodeIsTagQuery(container), |
|
535 dropNearItemId); |
|
536 ]]></body> |
|
537 </method> |
|
538 |
|
539 <!-- nsIPlacesView --> |
|
540 <method name="selectAll"> |
|
541 <body><![CDATA[ |
|
542 this.view.selection.selectAll(); |
|
543 ]]></body> |
|
544 </method> |
|
545 |
|
546 <!-- This method will select the first node in the tree that matches |
|
547 each given item id. It will open any folder nodes that it needs |
|
548 to in order to show the selected items. |
|
549 --> |
|
550 <method name="selectItems"> |
|
551 <parameter name="aIDs"/> |
|
552 <parameter name="aOpenContainers"/> |
|
553 <body><![CDATA[ |
|
554 // Never open containers in flat lists. |
|
555 if (this.flatList) |
|
556 aOpenContainers = false; |
|
557 // By default, we do search and select within containers which were |
|
558 // closed (note that containers in which nodes were not found are |
|
559 // closed). |
|
560 if (aOpenContainers === undefined) |
|
561 aOpenContainers = true; |
|
562 |
|
563 var ids = aIDs; // don't manipulate the caller's array |
|
564 |
|
565 // Array of nodes found by findNodes which are to be selected |
|
566 var nodes = []; |
|
567 |
|
568 // Array of nodes found by findNodes which should be opened |
|
569 var nodesToOpen = []; |
|
570 |
|
571 // A set of URIs of container-nodes that were previously searched, |
|
572 // and thus shouldn't be searched again. This is empty at the initial |
|
573 // start of the recursion and gets filled in as the recursion |
|
574 // progresses. |
|
575 var nodesURIChecked = []; |
|
576 |
|
577 /** |
|
578 * Recursively search through a node's children for items |
|
579 * with the given IDs. When a matching item is found, remove its ID |
|
580 * from the IDs array, and add the found node to the nodes dictionary. |
|
581 * |
|
582 * NOTE: This method will leave open any node that had matching items |
|
583 * in its subtree. |
|
584 */ |
|
585 function findNodes(node) { |
|
586 var foundOne = false; |
|
587 // See if node matches an ID we wanted; add to results. |
|
588 // For simple folder queries, check both itemId and the concrete |
|
589 // item id. |
|
590 var index = ids.indexOf(node.itemId); |
|
591 if (index == -1 && |
|
592 node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) |
|
593 index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId); |
|
594 |
|
595 if (index != -1) { |
|
596 nodes.push(node); |
|
597 foundOne = true; |
|
598 ids.splice(index, 1); |
|
599 } |
|
600 |
|
601 if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) || |
|
602 nodesURIChecked.indexOf(node.uri) != -1) |
|
603 return foundOne; |
|
604 |
|
605 // Don't try to open a query or a shurtcut, since it may return |
|
606 // any duplicate data and be infinitely nested. Though, if it has |
|
607 // been explicitly opened by the caller, search into it. |
|
608 let shouldOpen = aOpenContainers && |
|
609 node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER; |
|
610 PlacesUtils.asContainer(node); |
|
611 if (!node.containerOpen && !shouldOpen) |
|
612 return foundOne; |
|
613 |
|
614 nodesURIChecked.push(node.uri); |
|
615 |
|
616 // Remember the beginning state so that we can re-close |
|
617 // this node if we don't find any additional results here. |
|
618 var previousOpenness = node.containerOpen; |
|
619 node.containerOpen = true; |
|
620 for (var child = 0; child < node.childCount && ids.length > 0; |
|
621 child++) { |
|
622 var childNode = node.getChild(child); |
|
623 var found = findNodes(childNode); |
|
624 if (!foundOne) |
|
625 foundOne = found; |
|
626 } |
|
627 |
|
628 // If we didn't find any additional matches in this node's |
|
629 // subtree, revert the node to its previous openness. |
|
630 if (foundOne) |
|
631 nodesToOpen.unshift(node); |
|
632 node.containerOpen = previousOpenness; |
|
633 return foundOne; |
|
634 } |
|
635 |
|
636 // Disable notifications while looking for nodes. |
|
637 let result = this.result; |
|
638 let didSuppressNotifications = result.suppressNotifications; |
|
639 if (!didSuppressNotifications) |
|
640 result.suppressNotifications = true |
|
641 try { |
|
642 findNodes(this.result.root); |
|
643 } |
|
644 finally { |
|
645 if (!didSuppressNotifications) |
|
646 result.suppressNotifications = false; |
|
647 } |
|
648 |
|
649 // For all the nodes we've found, highlight the corresponding |
|
650 // index in the tree. |
|
651 var resultview = this.view; |
|
652 var selection = this.view.selection; |
|
653 selection.selectEventsSuppressed = true; |
|
654 selection.clearSelection(); |
|
655 // Open nodes containing found items |
|
656 for (var i = 0; i < nodesToOpen.length; i++) { |
|
657 nodesToOpen[i].containerOpen = true; |
|
658 } |
|
659 for (var i = 0; i < nodes.length; i++) { |
|
660 var index = resultview.treeIndexForNode(nodes[i]); |
|
661 if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE) |
|
662 continue; |
|
663 selection.rangedSelect(index, index, true); |
|
664 } |
|
665 selection.selectEventsSuppressed = false; |
|
666 ]]></body> |
|
667 </method> |
|
668 |
|
669 <field name="_contextMenuShown">false</field> |
|
670 |
|
671 <method name="buildContextMenu"> |
|
672 <parameter name="aPopup"/> |
|
673 <body><![CDATA[ |
|
674 this._contextMenuShown = true; |
|
675 return this.controller.buildContextMenu(aPopup); |
|
676 ]]></body> |
|
677 </method> |
|
678 |
|
679 <method name="destroyContextMenu"> |
|
680 <parameter name="aPopup"/> |
|
681 this._contextMenuShown = false; |
|
682 <body/> |
|
683 </method> |
|
684 |
|
685 <property name="ownerWindow" |
|
686 readonly="true" |
|
687 onget="return window;"/> |
|
688 |
|
689 <field name="_active">true</field> |
|
690 <property name="active" |
|
691 onget="return this._active" |
|
692 onset="return this._active = val"/> |
|
693 |
|
694 </implementation> |
|
695 <handlers> |
|
696 <handler event="focus"><![CDATA[ |
|
697 this._cachedInsertionPoint = undefined; |
|
698 |
|
699 // See select handler. We need the sidebar's places commandset to be |
|
700 // updated as well |
|
701 document.commandDispatcher.updateCommands("focus"); |
|
702 ]]></handler> |
|
703 <handler event="select"><![CDATA[ |
|
704 this._cachedInsertionPoint = undefined; |
|
705 |
|
706 // This additional complexity is here for the sidebars |
|
707 var win = window; |
|
708 while (true) { |
|
709 win.document.commandDispatcher.updateCommands("focus"); |
|
710 if (win == window.top) |
|
711 break; |
|
712 |
|
713 win = win.parent; |
|
714 } |
|
715 ]]></handler> |
|
716 |
|
717 <handler event="dragstart"><![CDATA[ |
|
718 if (event.target.localName != "treechildren") |
|
719 return; |
|
720 |
|
721 let nodes = this.selectedNodes; |
|
722 for (let i = 0; i < nodes.length; i++) { |
|
723 let node = nodes[i]; |
|
724 |
|
725 // Disallow dragging the root node of a tree. |
|
726 if (!node.parent) { |
|
727 event.preventDefault(); |
|
728 event.stopPropagation(); |
|
729 return; |
|
730 } |
|
731 |
|
732 // If this node is child of a readonly container (e.g. a livemark) |
|
733 // or cannot be moved, we must force a copy. |
|
734 if (!PlacesControllerDragHelper.canMoveNode(node)) { |
|
735 event.dataTransfer.effectAllowed = "copyLink"; |
|
736 break; |
|
737 } |
|
738 } |
|
739 |
|
740 this._controller.setDataTransfer(event); |
|
741 event.stopPropagation(); |
|
742 ]]></handler> |
|
743 |
|
744 <handler event="dragover"><![CDATA[ |
|
745 if (event.target.localName != "treechildren") |
|
746 return; |
|
747 |
|
748 let row = { }, col = { }, child = { }; |
|
749 this.treeBoxObject.getCellAt(event.clientX, event.clientY, |
|
750 row, col, child); |
|
751 let node = row.value != -1 ? |
|
752 this.view.nodeForTreeIndex(row.value) : |
|
753 this.result.root; |
|
754 // cache the dropTarget for the view |
|
755 PlacesControllerDragHelper.currentDropTarget = node; |
|
756 |
|
757 // We have to calculate the orientation since view.canDrop will use |
|
758 // it and we want to be consistent with the dropfeedback. |
|
759 let tbo = this.treeBoxObject; |
|
760 let rowHeight = tbo.rowHeight; |
|
761 let eventY = event.clientY - tbo.treeBody.boxObject.y - |
|
762 rowHeight * (row.value - tbo.getFirstVisibleRow()); |
|
763 |
|
764 let orientation = Ci.nsITreeView.DROP_BEFORE; |
|
765 |
|
766 if (row.value == -1) { |
|
767 // If the row is not valid we try to insert inside the resultNode. |
|
768 orientation = Ci.nsITreeView.DROP_ON; |
|
769 } |
|
770 else if (PlacesUtils.nodeIsContainer(node) && |
|
771 eventY > rowHeight * 0.75) { |
|
772 // If we are below the 75% of a container the treeview we try |
|
773 // to drop after the node. |
|
774 orientation = Ci.nsITreeView.DROP_AFTER; |
|
775 } |
|
776 else if (PlacesUtils.nodeIsContainer(node) && |
|
777 eventY > rowHeight * 0.25) { |
|
778 // If we are below the 25% of a container the treeview we try |
|
779 // to drop inside the node. |
|
780 orientation = Ci.nsITreeView.DROP_ON; |
|
781 } |
|
782 |
|
783 if (!this.view.canDrop(row.value, orientation, event.dataTransfer)) |
|
784 return; |
|
785 |
|
786 event.preventDefault(); |
|
787 event.stopPropagation(); |
|
788 ]]></handler> |
|
789 |
|
790 <handler event="dragend"><![CDATA[ |
|
791 PlacesControllerDragHelper.currentDropTarget = null; |
|
792 ]]></handler> |
|
793 |
|
794 </handlers> |
|
795 </binding> |
|
796 |
|
797 </bindings> |