treeView.js
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is Mozilla History System
16  *
17  * The Initial Developer of the Original Code is
18  * Google Inc.
19  * Portions created by the Initial Developer are Copyright (C) 2005
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * Brett Wilson <brettw@gmail.com> (original author)
24  * Asaf Romano <mano@mozilla.com> (Javascript version)
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either the GNU General Public License Version 2 or later (the "GPL"), or
28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
39 
40 PlacesTreeView.prototype = {
41  _makeAtom: function PTV__makeAtom(aString) {
42  return Cc["@mozilla.org/atom-service;1"].
43  getService(Ci.nsIAtomService).
44  getAtom(aString);
45  },
46 
47  _atoms: [],
48  _getAtomFor: function PTV__getAtomFor(aName) {
49  if (!this._atoms[aName])
50  this._atoms[aName] = this._makeAtom(aName);
51 
52  return this._atoms[aName];
53  },
54 
55  _ensureValidRow: function PTV__ensureValidRow(aRow) {
56  if (aRow < 0 || aRow >= this._visibleElements.length)
57  throw Cr.NS_ERROR_INVALID_ARG;
58  },
59 
60  __dateService: null,
61  get _dateService() {
62  if (!this.__dateService) {
63  this.__dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
64  getService(Ci.nsIScriptableDateFormat);
65  }
66  return this.__dateService;
67  },
68 
69  QueryInterface: function PTV_QueryInterface(aIID) {
70  if (aIID.equals(Ci.nsITreeView) ||
71  aIID.equals(Ci.nsINavHistoryResultViewer) ||
72  aIID.equals(Ci.nsINavHistoryResultTreeViewer) ||
73  aIID.equals(Ci.nsISupports))
74  return this;
75 
76  throw Cr.NS_ERROR_NO_INTERFACE;
77  },
78 
82  _finishInit: function PTV__finishInit() {
83  var selection = this.selection;
84  if (selection)
85  selection.selectEventsSuppressed = true;
86 
87  this._rootNode._viewIndex = -1;
88  if (!this._rootNode.containerOpen) {
89  // This triggers containerOpened which then builds the visible section.
90  this._rootNode.containerOpen = true;
91  }
92  else
93  this.invalidateContainer(this._rootNode);
94 
95  // "Activate" the sorting column and update commands.
96  this.sortingChanged(this._result.sortingMode);
97 
98  if (selection)
99  selection.selectEventsSuppressed = false;
100  },
101 
102  _rootNode: null,
103 
116  _buildVisibleSection:
117  function PTV__buildVisibleSection(aContainer, aVisible, aToOpen, aVisibleStartIndex)
118  {
119  if (!aContainer.containerOpen)
120  return; // nothing to do
121 
122  const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
123  const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
124 
125  var cc = aContainer.childCount;
126  var sortingMode = this._result.sortingMode;
127  for (var i=0; i < cc; i++) {
128  var curChild = aContainer.getChild(i);
129  var curChildType = curChild.type;
130 
131  // Don't display separators when sorted.
132  if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
133  if (sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
134  curChild._viewIndex = -1;
135  continue;
136  }
137  }
138 
139  // Add the node to the visible-nodes array and set its viewIndex.
140  curChild._viewIndex = aVisibleStartIndex + aVisible.length;
141  aVisible.push(curChild);
142 
143  // Recursively do containers.
144  if (!this._flatList &&
145  curChild instanceof Ci.nsINavHistoryContainerResultNode) {
146  var resource = this._getResourceForNode(curChild);
147  var isopen = resource != null &&
148  PlacesUIUtils.localStore.HasAssertion(resource, openLiteral,
149  trueLiteral, true);
150  if (isopen != curChild.containerOpen)
151  aToOpen.push(curChild);
152  else if (curChild.containerOpen && curChild.childCount > 0)
153  this._buildVisibleSection(curChild, aVisible, aToOpen, aVisibleStartIndex);
154  }
155  }
156  },
157 
162  _countVisibleRowsForNode: function PTV__countVisibleRowsForNode(aNode) {
163  if (aNode == this._rootNode)
164  return this._visibleElements.length;
165 
166  var viewIndex = aNode._viewIndex;
167  NS_ASSERT(viewIndex >= 0, "Node is not visible, no rows to count");
168  var outerLevel = aNode.indentLevel;
169  for (var i = viewIndex + 1; i < this._visibleElements.length; i++) {
170  if (this._visibleElements[i].indentLevel <= outerLevel)
171  return i - viewIndex;
172  }
173  // this node plus its children occupy the bottom of the list
174  return this._visibleElements.length - viewIndex;
175  },
176 
185  _refreshVisibleSection: function PTV__refreshVisibleSection(aContainer) {
186  NS_ASSERT(this._result, "Need to have a result to update");
187  if (!this._tree)
188  return;
189 
190  // The root node is invisible.
191  if (aContainer != this._rootNode) {
192  if (aContainer._viewIndex < 0 ||
193  aContainer._viewIndex > this._visibleElements.length)
194  throw "Trying to expand a node that is not visible";
195 
196  NS_ASSERT(this._visibleElements[aContainer._viewIndex] == aContainer,
197  "Visible index is out of sync!");
198  }
199 
200  var startReplacement = aContainer._viewIndex + 1;
201  var replaceCount = this._countVisibleRowsForNode(aContainer);
202 
203  // We don't replace the container node itself so we should decrease the
204  // replaceCount by 1, unless the container is our root node, which isn't
205  // visible.
206  if (aContainer != this._rootNode)
207  replaceCount -= 1;
208 
209  // Persist selection state.
210  var previouslySelectedNodes = [];
211  var selection = this.selection;
212  var rc = selection.getRangeCount();
213  for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
214  var min = { }, max = { };
215  selection.getRangeAt(rangeIndex, min, max);
216  var lastIndex = Math.min(max.value, startReplacement + replaceCount -1);
217  // If this range does not overlap the replaced chunk, we don't need to
218  // persist the selection.
219  if (max.value < startReplacement || min.value > lastIndex)
220  continue;
221 
222  // If this range starts before the replaced chunk, we should persist from
223  // startReplacement to lastIndex.
224  var firstIndex = Math.max(min.value, startReplacement);
225  for (var nodeIndex = firstIndex; nodeIndex <= lastIndex; nodeIndex++) {
226  // Mark the node invisible if we're about to remove it,
227  // otherwise we'll try to select it later.
228  var node = this._visibleElements[nodeIndex];
229  if (nodeIndex >= startReplacement &&
230  nodeIndex < startReplacement + replaceCount)
231  node._viewIndex = -1;
232 
233  previouslySelectedNodes.push(
234  { node: node, oldIndex: nodeIndex });
235  }
236  }
237 
238  // Building the new list will set the new elements' visible indices.
239  var newElements = [];
240  var toOpenElements = [];
241  this._buildVisibleSection(aContainer,
242  newElements, toOpenElements, startReplacement);
243 
244  // Actually update the visible list.
245  // XXX: We can probably make this more efficient using splice through
246  // Function.apply.
247  this._visibleElements =
248  this._visibleElements.slice(0, startReplacement).concat(newElements)
249  .concat(this._visibleElements.slice(startReplacement + replaceCount,
250  this._visibleElements.length));
251 
252  // If the new area has a different size, we'll have to renumber the
253  // elements following the area.
254  if (replaceCount != newElements.length) {
255  for (var i = startReplacement + newElements.length;
256  i < this._visibleElements.length; i++) {
257  this._visibleElements[i]._viewIndex = i;
258  }
259  }
260 
261  // now update the number of elements
262  selection.selectEventsSuppressed = true;
263  this._tree.beginUpdateBatch();
264 
265  if (replaceCount)
266  this._tree.rowCountChanged(startReplacement, -replaceCount);
267  if (newElements.length)
268  this._tree.rowCountChanged(startReplacement, newElements.length);
269 
270  if (!this._flatList) {
271  // now, open any containers that were persisted
272  for (var i = 0; i < toOpenElements.length; i++) {
273  var item = toOpenElements[i];
274  var parent = item.parent;
275  // avoid recursively opening containers
276  while (parent) {
277  if (parent.uri == item.uri)
278  break;
279  parent = parent.parent;
280  }
281  // if we don't have a parent, we made it all the way to the root
282  // and didn't find a match, so we can open our item
283  if (!parent && !item.containerOpen)
284  item.containerOpen = true;
285  }
286  }
287 
288  this._tree.endUpdateBatch();
289 
290  // restore selection
291  if (previouslySelectedNodes.length > 0) {
292  for (var i = 0; i < previouslySelectedNodes.length; i++) {
293  var nodeInfo = previouslySelectedNodes[i];
294  var index = nodeInfo.node._viewIndex;
295 
296  // If the nodes under the invalidated container were preserved, we can
297  // just use viewIndex.
298  if (index == -1) {
299  // Otherwise, try to find an equal node.
300  var itemId = PlacesUtils.getConcreteItemId(nodeInfo.node);
301  if (itemId != 1) {
302  // Search by itemId.
303  for (var j = 0; j < newElements.length && index == -1; j++) {
304  if (PlacesUtils.getConcreteItemId(newElements[j]) == itemId)
305  index = newElements[j]._viewIndex;
306  }
307  }
308  else {
309  // Search by uri.
310  var uri = nodeInfo.node.uri;
311  if (uri) {
312  for (var j = 0; j < newElements.length && index == -1; j++) {
313  if (newElements[j].uri == uri)
314  index = newElements[j]._viewIndex;
315  }
316  }
317  }
318  }
319 
320  // Select the found node, if any.
321  if (index != -1)
322  selection.rangedSelect(index, index, true);
323  }
324 
325  // If only one node was previously selected and there's no selection now,
326  // select the node at its old viewIndex, if any.
327  if (previouslySelectedNodes.length == 1 &&
328  selection.getRangeCount() == 0 &&
329  this._visibleElements.length > previouslySelectedNodes[0].oldIndex) {
330  selection.rangedSelect(previouslySelectedNodes[0].oldIndex,
331  previouslySelectedNodes[0].oldIndex, true);
332  }
333  }
334  selection.selectEventsSuppressed = false;
335  },
336 
337  _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
338  var timeInMilliseconds = aTime / 1000; // PRTime is in microseconds
339 
340  // Date is calculated starting from midnight, so the modulo with a day are
341  // milliseconds from today's midnight.
342  // getTimezoneOffset corrects that based on local time.
343  // 86400000 = 24 * 60 * 60 * 1000 = 1 day
344  // 60000 = 60 * 1000 = 1 minute
345  var dateObj = new Date();
346  var timeZoneOffsetInMs = dateObj.getTimezoneOffset() * 60000;
347  var now = dateObj.getTime() - timeZoneOffsetInMs;
348  var midnight = now - (now % (86400000));
349 
350  var dateFormat = timeInMilliseconds - timeZoneOffsetInMs >= midnight ?
351  Ci.nsIScriptableDateFormat.dateFormatNone :
352  Ci.nsIScriptableDateFormat.dateFormatShort;
353 
354  var timeObj = new Date(timeInMilliseconds);
355  return (this._dateService.FormatDateTime("", dateFormat,
356  Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
357  timeObj.getFullYear(), timeObj.getMonth() + 1,
358  timeObj.getDate(), timeObj.getHours(),
359  timeObj.getMinutes(), timeObj.getSeconds()));
360  },
361 
362  COLUMN_TYPE_UNKNOWN: 0,
363  COLUMN_TYPE_TITLE: 1,
364  COLUMN_TYPE_URI: 2,
365  COLUMN_TYPE_DATE: 3,
366  COLUMN_TYPE_VISITCOUNT: 4,
367  COLUMN_TYPE_KEYWORD: 5,
368  COLUMN_TYPE_DESCRIPTION: 6,
369  COLUMN_TYPE_DATEADDED: 7,
370  COLUMN_TYPE_LASTMODIFIED: 8,
371  COLUMN_TYPE_TAGS: 9,
372 
373  _getColumnType: function PTV__getColumnType(aColumn) {
374  var columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
375 
376  switch (columnType) {
377  case "title":
378  return this.COLUMN_TYPE_TITLE;
379  case "url":
380  return this.COLUMN_TYPE_URI;
381  case "date":
382  return this.COLUMN_TYPE_DATE;
383  case "visitCount":
384  return this.COLUMN_TYPE_VISITCOUNT;
385  case "keyword":
386  return this.COLUMN_TYPE_KEYWORD;
387  case "description":
388  return this.COLUMN_TYPE_DESCRIPTION;
389  case "dateAdded":
390  return this.COLUMN_TYPE_DATEADDED;
391  case "lastModified":
392  return this.COLUMN_TYPE_LASTMODIFIED;
393  case "tags":
394  return this.COLUMN_TYPE_TAGS;
395  }
396  return this.COLUMN_TYPE_UNKNOWN;
397  },
398 
399  _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) {
400  switch (aSortType) {
401  case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING:
402  return [this.COLUMN_TYPE_TITLE, false];
403  case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
404  return [this.COLUMN_TYPE_TITLE, true];
405  case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
406  return [this.COLUMN_TYPE_DATE, false];
407  case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
408  return [this.COLUMN_TYPE_DATE, true];
409  case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
410  return [this.COLUMN_TYPE_URI, false];
411  case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
412  return [this.COLUMN_TYPE_URI, true];
413  case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
414  return [this.COLUMN_TYPE_VISITCOUNT, false];
415  case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
416  return [this.COLUMN_TYPE_VISITCOUNT, true];
417  case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_ASCENDING:
418  return [this.COLUMN_TYPE_KEYWORD, false];
419  case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_DESCENDING:
420  return [this.COLUMN_TYPE_KEYWORD, true];
421  case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING:
422  if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
423  return [this.COLUMN_TYPE_DESCRIPTION, false];
424  break;
425  case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
426  if (this._result.sortingAnnotation == DESCRIPTION_ANNO)
427  return [this.COLUMN_TYPE_DESCRIPTION, true];
428  case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
429  return [this.COLUMN_TYPE_DATEADDED, false];
430  case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING:
431  return [this.COLUMN_TYPE_DATEADDED, true];
432  case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING:
433  return [this.COLUMN_TYPE_LASTMODIFIED, false];
434  case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING:
435  return [this.COLUMN_TYPE_LASTMODIFIED, true];
436  case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING:
437  return [this.COLUMN_TYPE_TAGS, false];
438  case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING:
439  return [this.COLUMN_TYPE_TAGS, true];
440  }
441  return [this.COLUMN_TYPE_UNKNOWN, false];
442  },
443 
444  // nsINavHistoryResultViewer
445  nodeInserted: function PTV_nodeInserted(aParentNode, aNode, aNewIndex) {
446  if (!this._tree)
447  return;
448  if (!this._result)
449  throw Cr.NS_ERROR_UNEXPECTED;
450 
451  if (PlacesUtils.nodeIsSeparator(aNode) &&
452  this._result.sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
453  aNode._viewIndex = -1;
454  return;
455  }
456 
457  // Update parent when inserting the first item, since twisty may
458  // have changed.
459  if (aParentNode.childCount == 1)
460  this._tree.invalidateRow(aParentNode._viewIndex);
461 
462  // compute the new view index of the item
463  var newViewIndex = -1;
464  if (aNewIndex == 0) {
465  // item is the first thing in our child list, it takes our index +1. Note
466  // that this computation still works if the parent is an invisible root
467  // node, because root_index + 1 = -1 + 1 = 0
468  newViewIndex = aParentNode._viewIndex + 1;
469  }
470  else {
471  // Here, we try to find the next visible element in the child list so we
472  // can set the new visible index to be right before that. Note that we
473  // have to search DOWN instead of up, because some siblings could have
474  // children themselves that would be in the way.
475  for (var i = aNewIndex + 1; i < aParentNode.childCount; i++) {
476  var viewIndex = aParentNode.getChild(i)._viewIndex;
477  if (viewIndex >= 0) {
478  // the view indices of subsequent children have not been shifted so
479  // the next item will have what should be our index
480  newViewIndex = viewIndex;
481  break;
482  }
483  }
484  if (newViewIndex < 0) {
485  // At the end of the child list without finding a visible sibling: This
486  // is a little harder because we don't know how many rows the last item
487  // in our list takes up (it could be a container with many children).
488  var prevChild = aParentNode.getChild(aNewIndex - 1);
489  newViewIndex = prevChild._viewIndex + this._countVisibleRowsForNode(prevChild);
490  }
491  }
492 
493  aNode._viewIndex = newViewIndex;
494  this._visibleElements.splice(newViewIndex, 0, aNode);
495  for (var i = newViewIndex + 1;
496  i < this._visibleElements.length; i++) {
497  this._visibleElements[i]._viewIndex = i;
498  }
499  this._tree.rowCountChanged(newViewIndex, 1);
500 
501  if (PlacesUtils.nodeIsContainer(aNode) && asContainer(aNode).containerOpen)
502  this._refreshVisibleSection(aNode);
503  },
504 
505  // This is used in nodeRemoved and nodeMoved to fix viewIndex values.
506  // Throws if the node has an invalid viewIndex.
507  _fixViewIndexOnRemove: function PTV_fixViewIndexOnRemove(aNode,
508  aParentNode) {
509  var oldViewIndex = aNode._viewIndex;
510  // this may have been a container, in which case it has a lot of rows
511  var count = this._countVisibleRowsForNode(aNode);
512 
513  if (oldViewIndex > this._visibleElements.length)
514  throw("Trying to remove a node with an invalid viewIndex");
515 
516  this._visibleElements.splice(oldViewIndex, count);
517  for (var i = oldViewIndex; i < this._visibleElements.length; i++)
518  this._visibleElements[i]._viewIndex = i;
519 
520  this._tree.rowCountChanged(oldViewIndex, -count);
521 
522  // redraw parent because twisty may have changed
523  if (!aParentNode.hasChildren)
524  this._tree.invalidateRow(aParentNode._viewIndex);
525  },
526 
536  nodeRemoved: function PTV_nodeRemoved(aParentNode, aNode, aOldIndex) {
537  NS_ASSERT(this._result, "Got a notification but have no result!");
538  if (!this._tree)
539  return; // nothing to do
540 
541  var oldViewIndex = aNode._viewIndex;
542  if (oldViewIndex < 0) {
543  // There's nothing to do if the node was already invisible.
544  return;
545  }
546 
547  // If the node was exclusively selected, the node next to it will be
548  // selected.
549  var selectNext = false;
550  var selection = this.selection;
551  if (selection.getRangeCount() == 1) {
552  var min = { }, max = { };
553  selection.getRangeAt(0, min, max);
554  if (min.value == max.value &&
555  this.nodeForTreeIndex(min.value) == aNode)
556  selectNext = true;
557  }
558 
559  // Remove the node and fix viewIndex values.
560  this._fixViewIndexOnRemove(aNode, aParentNode);
561 
562  // Restore selection if the node was exclusively selected.
563  if (!selectNext)
564  return;
565 
566  // Restore selection
567  if (this._visibleElements.length > oldViewIndex)
568  selection.rangedSelect(oldViewIndex, oldViewIndex, true);
569  else if (this._visibleElements.length > 0) {
570  // if we removed the last child, we select the new last child if exists
571  selection.rangedSelect(this._visibleElements.length - 1,
572  this._visibleElements.length - 1, true);
573  }
574  },
575 
580  nodeMoved:
581  function PTV_nodeMoved(aNode, aOldParent, aOldIndex, aNewParent, aNewIndex) {
582  NS_ASSERT(this._result, "Got a notification but have no result!");
583  if (!this._tree)
584  return; // nothing to do
585 
586  var oldViewIndex = aNode._viewIndex;
587  if (oldViewIndex < 0) {
588  // There's nothing to do if the node was already invisible.
589  return;
590  }
591 
592  // This may have been a container, in which case it has a lot of rows.
593  var count = this._countVisibleRowsForNode(aNode);
594 
595  // Persist selection state.
596  var nodesToSelect = [];
597  var selection = this.selection;
598  var rc = selection.getRangeCount();
599  for (var rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
600  var min = { }, max = { };
601  selection.getRangeAt(rangeIndex, min, max);
602  var lastIndex = Math.min(max.value, oldViewIndex + count -1);
603  if (min.value < oldViewIndex || min.value > lastIndex)
604  continue;
605 
606  for (var nodeIndex = min.value; nodeIndex <= lastIndex; nodeIndex++)
607  nodesToSelect.push(this._visibleElements[nodeIndex]);
608  }
609  if (nodesToSelect.length > 0)
610  selection.selectEventsSuppressed = true;
611 
612  // Remove node from the old position.
613  this._fixViewIndexOnRemove(aNode, aOldParent);
614 
615  // Insert the node into the new position.
616  this.nodeInserted(aNewParent, aNode, aNewIndex);
617 
618  // Restore selection.
619  if (nodesToSelect.length > 0) {
620  for (var i = 0; i < nodesToSelect.length; i++) {
621  var node = nodesToSelect[i];
622  var index = node._viewIndex;
623  selection.rangedSelect(index, index, true);
624  }
625  selection.selectEventsSuppressed = false;
626  }
627  },
628 
633  nodeReplaced:
634  function PTV_nodeReplaced(aParentNode, aOldNode, aNewNode, aIndexDoNotUse) {
635  if (!this._tree)
636  return;
637 
638  var viewIndex = aOldNode._viewIndex;
639  aNewNode._viewIndex = viewIndex;
640  if (viewIndex >= 0 &&
641  viewIndex < this._visibleElements.length) {
642  this._visibleElements[viewIndex] = aNewNode;
643  }
644  aOldNode._viewIndex = -1;
645  this._tree.invalidateRow(viewIndex);
646  },
647 
648  _invalidateCellValue: function PTV__invalidateCellValue(aNode,
649  aColumnType) {
650  NS_ASSERT(this._result, "Got a notification but have no result!");
651  let viewIndex = aNode._viewIndex;
652  if (viewIndex == -1) // invisible
653  return;
654 
655  if (this._tree) {
656  let column = this._findColumnByType(aColumnType);
657  if (column && !column.element.hidden)
658  this._tree.invalidateCell(viewIndex, column);
659 
660  // Last modified time is altered for almost all node changes.
661  if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {
662  let lastModifiedColumn =
663  this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED);
664  if (lastModifiedColumn && !lastModifiedColumn.hidden)
665  this._tree.invalidateCell(viewIndex, lastModifiedColumn);
666  }
667  }
668  },
669 
670  nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
671  this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
672  },
673 
674  nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) {
675  this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI);
676  },
677 
678  nodeIconChanged: function PTV_nodeIconChanged(aNode) {
679  this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
680  },
681 
682  nodeHistoryDetailsChanged:
683  function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate,
684  aUpdatedVisitCount) {
685  this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
686  this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
687  },
688 
689  nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
690  this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
691  },
692 
693  nodeKeywordChanged: function PTV_nodeKeywordChanged(aNode, aNewKeyword) {
694  this._invalidateCellValue(aNode, this.COLUMN_TYPE_KEYWORD);
695  },
696 
697  nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
698  if (aAnno == DESCRIPTION_ANNO)
699  this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
700  },
701 
702  nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
703  this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
704  },
705 
706  nodeLastModifiedChanged:
707  function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
708  this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
709  },
710 
711  containerOpened: function PTV_containerOpened(aNode) {
712  this.invalidateContainer(aNode);
713  },
714 
715  containerClosed: function PTV_containerClosed(aNode) {
716  this.invalidateContainer(aNode);
717  },
718 
719  invalidateContainer: function PTV_invalidateContainer(aNode) {
720  NS_ASSERT(this._result, "Got a notification but have no result!");
721  if (!this._tree)
722  return; // nothing to do, container is not visible
723 
724  if (aNode._viewIndex >= this._visibleElements.length) {
725  // be paranoid about visible indices since others can change it
726  throw Cr.NS_ERROR_UNEXPECTED;
727  }
728  this._refreshVisibleSection(aNode);
729  },
730 
731  _columns: [],
732  _findColumnByType: function PTV__findColumnByType(aColumnType) {
733  if (this._columns[aColumnType])
734  return this._columns[aColumnType];
735 
736  var columns = this._tree.columns;
737  var colCount = columns.count;
738  for (var i = 0; i < colCount; i++) {
739  let column = columns.getColumnAt(i);
740  let columnType = this._getColumnType(column);
741  this._columns[columnType] = column;
742  if (columnType == aColumnType)
743  return column;
744  }
745 
746  // That's completely valid. Most of our trees actually include just the
747  // title column.
748  return null;
749  },
750 
751  sortingChanged: function PTV__sortingChanged(aSortingMode) {
752  if (!this._tree || !this._result)
753  return;
754 
755  // depending on the sort mode, certain commands may be disabled
756  window.updateCommands("sort");
757 
758  var columns = this._tree.columns;
759 
760  // clear old sorting indicator
761  var sortedColumn = columns.getSortedColumn();
762  if (sortedColumn)
763  sortedColumn.element.removeAttribute("sortDirection");
764 
765  // set new sorting indicator by looking through all columns for ours
766  if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE)
767  return;
768 
769  var [desiredColumn, desiredIsDescending] =
770  this._sortTypeToColumnType(aSortingMode);
771  var colCount = columns.count;
772  var column = this._findColumnByType(desiredColumn);
773  if (column) {
774  let sortDir = desiredIsDescending ? "descending" : "ascending";
775  column.element.setAttribute("sortDirection", sortDir);
776  }
777  },
778 
779  get result() {
780  return this._result;
781  },
782 
783  set result(val) {
784  // Some methods (e.g. getURLsFromContainer) temporarily null out the
785  // viewer when they do temporary changes to the view, this does _not_
786  // call setResult(null), but then, we're called again with the result
787  // object which is already set for this viewer. At that point,
788  // we should do nothing.
789  if (this._result != val) {
790  if (this._result)
791  this._rootNode.containerOpen = false;
792 
793  this._result = val;
794  this._rootNode = val ? val.root : null;
795 
796  // If the tree is not set yet, setTree will call finishInit.
797  if (this._tree && val)
798  this._finishInit();
799  }
800  return val;
801  },
802 
803  nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) {
804  if (aIndex > this._visibleElements.length)
805  throw Cr.NS_ERROR_INVALID_ARG;
806 
807  return this._visibleElements[aIndex];
808  },
809 
810  treeIndexForNode: function PTV_treeNodeForIndex(aNode) {
811  var viewIndex = aNode._viewIndex;
812  if (viewIndex < 0)
813  return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
814 
815  NS_ASSERT(this._visibleElements[viewIndex] == aNode,
816  "Node's visible index and array out of sync");
817  return viewIndex;
818  },
819 
820  _getResourceForNode: function PTV_getResourceForNode(aNode)
821  {
822  var uri = aNode.uri;
823  NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
824  return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
825  },
826 
827  // nsITreeView
828  get rowCount() {
829  return this._visibleElements.length;
830  },
831 
832  get selection() {
833  return this._selection;
834  },
835 
836  set selection(val) {
837  return this._selection = val;
838  },
839 
840  getRowProperties: function PTV_getRowProperties(aRow, aProperties) { },
841 
842  getCellProperties: function PTV_getCellProperties(aRow, aColumn, aProperties) {
843  this._ensureValidRow(aRow);
844 
845  // for anonid-trees, we need to add the column-type manually
846  var columnType = aColumn.element.getAttribute("anonid");
847  if (columnType)
848  aProperties.AppendElement(this._getAtomFor(columnType));
849  else
850  var columnType = aColumn.id;
851 
852  // Set the "ltr" property on url cells
853  if (columnType == "url")
854  aProperties.AppendElement(this._getAtomFor("ltr"));
855 
856  if (columnType != "title")
857  return;
858 
859  var node = this._visibleElements[aRow];
860  if (!node._cellProperties) {
861  let properties = new Array();
862  var itemId = node.itemId;
863  var nodeType = node.type;
864  if (PlacesUtils.containerTypes.indexOf(nodeType) != -1) {
865  if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
866  properties.push(this._getAtomFor("query"));
867  if (PlacesUtils.nodeIsTagQuery(node))
868  properties.push(this._getAtomFor("tagContainer"));
869  else if (PlacesUtils.nodeIsDay(node))
870  properties.push(this._getAtomFor("dayContainer"));
871  else if (PlacesUtils.nodeIsHost(node))
872  properties.push(this._getAtomFor("hostContainer"));
873  }
874  else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
875  nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
876  if (PlacesUtils.nodeIsLivemarkContainer(node))
877  properties.push(this._getAtomFor("livemark"));
878  }
879 
880  if (itemId != -1) {
881  var queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
882  if (queryName)
883  properties.push(this._getAtomFor("OrganizerQuery_" + queryName));
884  }
885  }
886  else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
887  properties.push(this._getAtomFor("separator"));
888  else if (PlacesUtils.nodeIsURI(node)) {
889  properties.push(this._getAtomFor(PlacesUIUtils.guessUrlSchemeForUI(node.uri)));
890  if (itemId != -1) {
891  if (PlacesUtils.nodeIsLivemarkContainer(node.parent))
892  properties.push(this._getAtomFor("livemarkItem"));
893  }
894  }
895 
896  node._cellProperties = properties;
897  }
898  for (var i = 0; i < node._cellProperties.length; i++)
899  aProperties.AppendElement(node._cellProperties[i]);
900  },
901 
902  getColumnProperties: function(aColumn, aProperties) { },
903 
904  isContainer: function PTV_isContainer(aRow) {
905  this._ensureValidRow(aRow);
906 
907  var node = this._visibleElements[aRow];
908  if (PlacesUtils.nodeIsContainer(node)) {
909  // Flat-lists may ignore expandQueries and other query options when
910  // they are asked to open a container.
911  if (this._flatList)
912  return true;
913 
914  // treat non-expandable childless queries as non-containers
915  if (PlacesUtils.nodeIsQuery(node)) {
916  var parent = node.parent;
917  if ((PlacesUtils.nodeIsQuery(parent) ||
918  PlacesUtils.nodeIsFolder(parent)) &&
919  !node.hasChildren)
920  return asQuery(parent).queryOptions.expandQueries;
921  }
922  return true;
923  }
924  return false;
925  },
926 
927  isContainerOpen: function PTV_isContainerOpen(aRow) {
928  if (this._flatList)
929  return false;
930 
931  this._ensureValidRow(aRow);
932  return this._visibleElements[aRow].containerOpen;
933  },
934 
935  isContainerEmpty: function PTV_isContainerEmpty(aRow) {
936  if (this._flatList)
937  return true;
938 
939  this._ensureValidRow(aRow);
940  return !this._visibleElements[aRow].hasChildren;
941  },
942 
943  isSeparator: function PTV_isSeparator(aRow) {
944  this._ensureValidRow(aRow);
945  return PlacesUtils.nodeIsSeparator(this._visibleElements[aRow]);
946  },
947 
948  isSorted: function PTV_isSorted() {
949  return this._result.sortingMode !=
950  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
951  },
952 
953  canDrop: function PTV_canDrop(aRow, aOrientation) {
954  if (!this._result)
955  throw Cr.NS_ERROR_UNEXPECTED;
956 
957  // drop position into a sorted treeview would be wrong
958  if (this.isSorted())
959  return false;
960 
961  var ip = this._getInsertionPoint(aRow, aOrientation);
962  return ip && PlacesControllerDragHelper.canDrop(ip);
963  },
964 
965  _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) {
966  var container = this._result.root;
967  var dropNearItemId = -1;
968  // When there's no selection, assume the container is the container
969  // the view is populated from (i.e. the result's itemId).
970  if (index != -1) {
971  var lastSelected = this.nodeForTreeIndex(index);
972  if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
973  // If the last selected item is an open container, append _into_
974  // it, rather than insert adjacent to it.
975  container = lastSelected;
976  index = -1;
977  }
978  else if (lastSelected.containerOpen &&
979  orientation == Ci.nsITreeView.DROP_AFTER &&
980  lastSelected.hasChildren) {
981  // If the last selected node is an open container and the user is
982  // trying to drag into it as a first node, really insert into it.
983  container = lastSelected;
984  orientation = Ci.nsITreeView.DROP_ON;
985  index = 0;
986  }
987  else {
988  // Use the last-selected node's container unless the root node
989  // is selected, in which case we use the root node itself as the
990  // insertion point.
991  container = lastSelected.parent || container;
992 
993  // avoid the potentially expensive call to getIndexOfNode()
994  // if we know this container doesn't allow insertion
995  if (PlacesControllerDragHelper.disallowInsertion(container))
996  return null;
997 
998  var queryOptions = asQuery(this._result.root).queryOptions;
999  if (queryOptions.sortingMode !=
1000  Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
1001  // If we are within a sorted view, insert at the ends
1002  index = -1;
1003  }
1004  else if (queryOptions.excludeItems ||
1005  queryOptions.excludeQueries ||
1006  queryOptions.excludeReadOnlyFolders) {
1007  // Some item may be invisible, insert near last selected one.
1008  // We don't replace index here to avoid requests to the db,
1009  // instead it will be calculated later by the controller.
1010  index = -1;
1011  dropNearItemId = lastSelected.itemId;
1012  }
1013  else {
1014  var lsi = PlacesUtils.getIndexOfNode(lastSelected);
1015  index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
1016  }
1017  }
1018  }
1019 
1020  if (PlacesControllerDragHelper.disallowInsertion(container))
1021  return null;
1022 
1023  return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
1024  index, orientation,
1025  PlacesUtils.nodeIsTagQuery(container),
1026  dropNearItemId);
1027  },
1028 
1029  drop: function PTV_drop(aRow, aOrientation) {
1030  // We are responsible for translating the |index| and |orientation|
1031  // parameters into a container id and index within the container,
1032  // since this information is specific to the tree view.
1033  var ip = this._getInsertionPoint(aRow, aOrientation);
1034  if (!ip)
1035  return;
1036  PlacesControllerDragHelper.onDrop(ip);
1037  },
1038 
1039  getParentIndex: function PTV_getParentIndex(aRow) {
1040  this._ensureValidRow(aRow);
1041  var parent = this._visibleElements[aRow].parent;
1042  if (!parent || parent._viewIndex < 0)
1043  return -1;
1044 
1045  return parent._viewIndex;
1046  },
1047 
1048  hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) {
1049  this._ensureValidRow(aRow);
1050  if (aRow == this._visibleElements.length -1) {
1051  // this is the last thing in the list -> no next sibling
1052  return false;
1053  }
1054 
1055  var thisLevel = this._visibleElements[aRow].indentLevel;
1056  for (var i = aAfterIndex + 1; i < this._visibleElements.length; ++i) {
1057  var nextLevel = this._visibleElements[i].indentLevel;
1058  if (nextLevel == thisLevel)
1059  return true;
1060  if (nextLevel < thisLevel)
1061  break;
1062  }
1063  return false;
1064  },
1065 
1066  getLevel: function PTV_getLevel(aRow) {
1067  this._ensureValidRow(aRow);
1068 
1069  // Level is 0 for nodes at the root level, 1 for its children and so on.
1070  return this._visibleElements[aRow].indentLevel;
1071  },
1072 
1073  getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
1074  this._ensureValidRow(aRow);
1075 
1076  // only the title column has an image
1077  if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
1078  return "";
1079 
1080  return this._visibleElements[aRow].icon;
1081  },
1082 
1083  getProgressMode: function(aRow, aColumn) { },
1084  getCellValue: function(aRow, aColumn) { },
1085 
1086  getCellText: function PTV_getCellText(aRow, aColumn) {
1087  this._ensureValidRow(aRow);
1088 
1089  var node = this._visibleElements[aRow];
1090  var columnType = this._getColumnType(aColumn);
1091  switch (columnType) {
1092  case this.COLUMN_TYPE_TITLE:
1093  // normally, this is just the title, but we don't want empty items in
1094  // the tree view so return a special string if the title is empty.
1095  // Do it here so that callers can still get at the 0 length title
1096  // if they go through the "result" API.
1097  if (PlacesUtils.nodeIsSeparator(node))
1098  return "";
1099  return PlacesUIUtils.getBestTitle(node);
1100  case this.COLUMN_TYPE_TAGS:
1101  return node.tags;
1102  case this.COLUMN_TYPE_URI:
1103  if (PlacesUtils.nodeIsURI(node))
1104  return node.uri;
1105  return "";
1106  case this.COLUMN_TYPE_DATE:
1107  let nodeTime = node.time;
1108  if (nodeTime == 0 || !PlacesUtils.nodeIsURI(node)) {
1109  // hosts and days shouldn't have a value for the date column.
1110  // Actually, you could argue this point, but looking at the
1111  // results, seeing the most recently visited date is not what
1112  // I expect, and gives me no information I know how to use.
1113  // Only show this for URI-based items.
1114  return "";
1115  }
1116 
1117  return this._convertPRTimeToString(nodeTime);
1118  case this.COLUMN_TYPE_VISITCOUNT:
1119  return node.accessCount;
1120  case this.COLUMN_TYPE_KEYWORD:
1121  if (PlacesUtils.nodeIsBookmark(node))
1122  return PlacesUtils.bookmarks.getKeywordForBookmark(node.itemId);
1123  return "";
1124  case this.COLUMN_TYPE_DESCRIPTION:
1125  if (node.itemId != -1) {
1126  try {
1127  return PlacesUtils.annotations.
1128  getItemAnnotation(node.itemId, DESCRIPTION_ANNO);
1129  }
1130  catch (ex) { /* has no description */ }
1131  }
1132  return "";
1133  case this.COLUMN_TYPE_DATEADDED:
1134  if (node.dateAdded)
1135  return this._convertPRTimeToString(node.dateAdded);
1136  return "";
1137  case this.COLUMN_TYPE_LASTMODIFIED:
1138  if (node.lastModified)
1139  return this._convertPRTimeToString(node.lastModified);
1140  return "";
1141  }
1142  return "";
1143  },
1144 
1145  setTree: function PTV_setTree(aTree) {
1146  var hasOldTree = this._tree != null;
1147  this._tree = aTree;
1148 
1149  if (this._result) {
1150  if (hasOldTree) {
1151  // detach from result when we are detaching from the tree.
1152  // This breaks the reference cycle between us and the result.
1153  if (!aTree)
1154  this._result.viewer = null;
1155  }
1156  if (aTree)
1157  this._finishInit();
1158  }
1159  },
1160 
1161  toggleOpenState: function PTV_toggleOpenState(aRow) {
1162  if (!this._result)
1163  throw Cr.NS_ERROR_UNEXPECTED;
1164  this._ensureValidRow(aRow);
1165 
1166  var node = this._visibleElements[aRow];
1167  if (this._flatList && this._openContainerCallback) {
1168  this._openContainerCallback(node);
1169  return;
1170  }
1171 
1172  var resource = this._getResourceForNode(node);
1173  if (resource) {
1174  const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
1175  const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
1176 
1177  if (node.containerOpen)
1178  PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
1179  else
1180  PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
1181  }
1182 
1183  node.containerOpen = !node.containerOpen;
1184  },
1185 
1186  cycleHeader: function PTV_cycleHeader(aColumn) {
1187  if (!this._result)
1188  throw Cr.NS_ERROR_UNEXPECTED;
1189 
1190  // Sometimes you want a tri-state sorting, and sometimes you don't. This
1191  // rule allows tri-state sorting when the root node is a folder. This will
1192  // catch the most common cases. When you are looking at folders, you want
1193  // the third state to reset the sorting to the natural bookmark order. When
1194  // you are looking at history, that third state has no meaning so we try
1195  // to disallow it.
1196  //
1197  // The problem occurs when you have a query that results in bookmark
1198  // folders. One example of this is the subscriptions view. In these cases,
1199  // this rule doesn't allow you to sort those sub-folders by their natural
1200  // order.
1201  var allowTriState = PlacesUtils.nodeIsFolder(this._result.root);
1202 
1203  var oldSort = this._result.sortingMode;
1204  var oldSortingAnnotation = this._result.sortingAnnotation;
1205  var newSort;
1206  var newSortingAnnotation = "";
1207  const NHQO = Ci.nsINavHistoryQueryOptions;
1208  var columnType = this._getColumnType(aColumn);
1209  switch (columnType) {
1210  case this.COLUMN_TYPE_TITLE:
1211  if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING)
1212  newSort = NHQO.SORT_BY_TITLE_DESCENDING;
1213  else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING)
1214  newSort = NHQO.SORT_BY_NONE;
1215  else
1216  newSort = NHQO.SORT_BY_TITLE_ASCENDING;
1217 
1218  break;
1219  case this.COLUMN_TYPE_URI:
1220  if (oldSort == NHQO.SORT_BY_URI_ASCENDING)
1221  newSort = NHQO.SORT_BY_URI_DESCENDING;
1222  else if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING)
1223  newSort = NHQO.SORT_BY_NONE;
1224  else
1225  newSort = NHQO.SORT_BY_URI_ASCENDING;
1226 
1227  break;
1228  case this.COLUMN_TYPE_DATE:
1229  if (oldSort == NHQO.SORT_BY_DATE_ASCENDING)
1230  newSort = NHQO.SORT_BY_DATE_DESCENDING;
1231  else if (allowTriState &&
1232  oldSort == NHQO.SORT_BY_DATE_DESCENDING)
1233  newSort = NHQO.SORT_BY_NONE;
1234  else
1235  newSort = NHQO.SORT_BY_DATE_ASCENDING;
1236 
1237  break;
1238  case this.COLUMN_TYPE_VISITCOUNT:
1239  // visit count default is unusual because we sort by descending
1240  // by default because you are most likely to be looking for
1241  // highly visited sites when you click it
1242  if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
1243  newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
1244  else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING)
1245  newSort = NHQO.SORT_BY_NONE;
1246  else
1247  newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
1248 
1249  break;
1250  case this.COLUMN_TYPE_KEYWORD:
1251  if (oldSort == NHQO.SORT_BY_KEYWORD_ASCENDING)
1252  newSort = NHQO.SORT_BY_KEYWORD_DESCENDING;
1253  else if (allowTriState && oldSort == NHQO.SORT_BY_KEYWORD_DESCENDING)
1254  newSort = NHQO.SORT_BY_NONE;
1255  else
1256  newSort = NHQO.SORT_BY_KEYWORD_ASCENDING;
1257 
1258  break;
1259  case this.COLUMN_TYPE_DESCRIPTION:
1260  if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING &&
1261  oldSortingAnnotation == DESCRIPTION_ANNO) {
1262  newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING;
1263  newSortingAnnotation = DESCRIPTION_ANNO;
1264  }
1265  else if (allowTriState &&
1266  oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING &&
1267  oldSortingAnnotation == DESCRIPTION_ANNO)
1268  newSort = NHQO.SORT_BY_NONE;
1269  else {
1270  newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING;
1271  newSortingAnnotation = DESCRIPTION_ANNO;
1272  }
1273 
1274  break;
1275  case this.COLUMN_TYPE_DATEADDED:
1276  if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING)
1277  newSort = NHQO.SORT_BY_DATEADDED_DESCENDING;
1278  else if (allowTriState &&
1279  oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING)
1280  newSort = NHQO.SORT_BY_NONE;
1281  else
1282  newSort = NHQO.SORT_BY_DATEADDED_ASCENDING;
1283 
1284  break;
1285  case this.COLUMN_TYPE_LASTMODIFIED:
1286  if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING)
1287  newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING;
1288  else if (allowTriState &&
1289  oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING)
1290  newSort = NHQO.SORT_BY_NONE;
1291  else
1292  newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
1293 
1294  break;
1295  case this.COLUMN_TYPE_TAGS:
1296  if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING)
1297  newSort = NHQO.SORT_BY_TAGS_DESCENDING;
1298  else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING)
1299  newSort = NHQO.SORT_BY_NONE;
1300  else
1301  newSort = NHQO.SORT_BY_TAGS_ASCENDING;
1302 
1303  break;
1304  default:
1305  throw Cr.NS_ERROR_INVALID_ARG;
1306  }
1307  this._result.sortingAnnotation = newSortingAnnotation;
1308  this._result.sortingMode = newSort;
1309  },
1310 
1311  isEditable: function PTV_isEditable(aRow, aColumn) {
1312  // At this point we only support editing the title field.
1313  if (aColumn.index != 0)
1314  return false;
1315 
1316  var node = this.nodeForTreeIndex(aRow);
1317  if (!PlacesUtils.nodeIsReadOnly(node) &&
1318  (PlacesUtils.nodeIsFolder(node) ||
1319  (PlacesUtils.nodeIsBookmark(node) &&
1320  !PlacesUtils.nodeIsLivemarkItem(node))))
1321  return true;
1322 
1323  return false;
1324  },
1325 
1326  setCellText: function PTV_setCellText(aRow, aColumn, aText) {
1327  // we may only get here if the cell is editable
1328  var node = this.nodeForTreeIndex(aRow);
1329  if (node.title != aText) {
1330  var txn = PlacesUIUtils.ptm.editItemTitle(node.itemId, aText);
1331  PlacesUIUtils.ptm.doTransaction(txn);
1332  }
1333  },
1334 
1335  selectionChanged: function() { },
1336  cycleCell: function PTV_cycleCell(aRow, aColumn) { },
1337  isSelectable: function(aRow, aColumn) { return false; },
1338  performAction: function(aAction) { },
1339  performActionOnRow: function(aAction, aRow) { },
1340  performActionOnCell: function(aAction, aRow, aColumn) { }
1341 };
1342 
1343 function PlacesTreeView(aFlatList, aOnOpenFlatContainer) {
1344  this._tree = null;
1345  this._result = null;
1346  this._selection = null;
1347  this._visibleElements = [];
1348  this._flatList = aFlatList;
1349  this._openContainerCallback = aOnOpenFlatContainer;
1350 }
var PlacesUIUtils
Definition: utils.js:85
this _columns
const Cc
#define DESCRIPTION_ANNO
sbOSDControlService prototype QueryInterface
function asQuery(aNode)
Definition: utils.js:83
getService(Ci.sbIFaceplateManager)
let window
gImageView getCellProperties
Definition: pageInfo.js:171
var count
Definition: test_bug7406.js:32
this _dialogInput val(dateText)
var columns
return null
Definition: FeedWriter.js:1143
let node
function NS_ASSERT(cond, msg)
Definition: httpd.js:70
_updateCookies aName
var uri
Definition: FeedWriter.js:1135
const Cr
var PlacesControllerDragHelper
Definition: controller.js:1333
if(DEBUG_DATAREMOTES)
const Ci
function now()
#define min(a, b)
sbDeviceServicePane prototype nodeInserted
function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag, aDropNearItemId)
Definition: controller.js:84
function PlacesTreeView(aFlatList, aOnOpenFlatContainer)
Definition: treeView.js:1343
_getSelectedPageStyle s i
function asContainer(aNode)
Definition: utils.js:82
sbDeviceServicePane prototype nodeRemoved