summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/components/addrbook/content/abTrees.js
blob: ac52d3464d645215d58d5d35a5249d4e90614b9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains our implementation for various addressbook trees.  It
 * depends on jsTreeView.js being loaded before this script is loaded.
 */

const {IOUtils} = ChromeUtils.import("resource:///modules/IOUtils.js");

// Tree Sort helper methods.
var AB_ORDER = ["aab", "pab", "mork", "ldap", "mapi+other", "anyab", "cab"];

function getDirectoryValue(aDir, aKey) {
  if (aKey == "ab_type") {
    if (aDir._directory.URI == kAllDirectoryRoot + "?")
      return "aab";
    if (aDir._directory.URI == kPersonalAddressbookURI)
      return "pab";
    if (aDir._directory.URI == kCollectedAddressbookURI)
      return "cab";
    if (aDir._directory instanceof Ci.nsIAbMDBDirectory)
      return "mork";
    if (aDir._directory instanceof Ci.nsIAbLDAPDirectory)
      return "ldap";

    // If there is any other AB type.
    return "mapi+other";
  } else if (aKey == "ab_name") {
    return aDir._directory.dirName;
  }

  // This should never happen.
  return null;
}

function abNameCompare(a, b) {
  return a.localeCompare(b);
}

function abTypeCompare(a, b) {
  return (AB_ORDER.indexOf(a) - AB_ORDER.indexOf(b));
}

var SORT_PRIORITY = ["ab_type", "ab_name"];
var SORT_FUNCS = [abTypeCompare, abNameCompare];

function abSort(a, b) {
  for (let i = 0; i < SORT_FUNCS.length; i++) {
    let sortBy = SORT_PRIORITY[i];
    let aValue = getDirectoryValue(a, sortBy);
    let bValue = getDirectoryValue(b, sortBy);

    if (!aValue && !bValue)
      return 0;
    if (!aValue)
      return -1;
    if (!bValue)
      return 1;
    if (aValue != bValue) {
      let result = SORT_FUNCS[i](aValue, bValue);

      if (result != 0)
        return result;
    }
  }
  return 0;
}

/**
 * Each abDirTreeItem corresponds to one row in the tree view.
 */
function abDirTreeItem(aDirectory)
{
  this._directory = aDirectory;
}

abDirTreeItem.prototype =
{
  getText: function atv_getText()
  {
    return this._directory.dirName;
  },

  get id()
  {
    return this._directory.URI;
  },

  _open: false,
  get open()
  {
    return this._open;
  },

  _level: 0,
  get level()
  {
    return this._level;
  },

  _children: null,
  get children()
  {
    if (!this._children)
    {
      this._children = [];
      let myEnum;
      if (this._directory.URI == (kAllDirectoryRoot + "?"))
        myEnum = MailServices.ab.directories;
      else
        myEnum = this._directory.childNodes;

      while (myEnum.hasMoreElements())
      {
        var abItem = new abDirTreeItem(
          myEnum.getNext().QueryInterface(Ci.nsIAbDirectory));
        if (gDirectoryTreeView&&
            this.id == kAllDirectoryRoot + "?" &&
            getDirectoryValue(abItem, "ab_type") == "ldap")
          gDirectoryTreeView.hasRemoteAB = true;

        abItem._level = this._level + 1;
        abItem._parent = this;
        this._children.push(abItem);
      }

      this._children.sort(abSort);
    }
    return this._children;
  },

  getProperties: function atv_getProps()
  {
    var properties = []
    if (this._directory.isMailList)
      properties.push("IsMailList-true");
    if (this._directory.isRemote)
      properties.push("IsRemote-true");
    if (this._directory.isSecure)
      properties.push("IsSecure-true");
    return properties.join(" ");
  }
};

/**
 * Our actual implementation of nsITreeView.
 */
function directoryTreeView() {}
directoryTreeView.prototype =
{
  __proto__: new PROTO_TREE_VIEW(),

  hasRemoteAB: false,

  init: function dtv_init(aTree, aJSONFile)
  {
    if (aJSONFile) {
      // Parse our persistent-open-state json file
      let data = IOUtils.loadFileToString(aJSONFile);
      if (data) {
        this._persistOpenMap = JSON.parse(data);
      }
    }

    this._rebuild();
    aTree.view = this;
  },

  shutdown: function dtv_shutdown(aJSONFile)
  {
    // Write out the persistOpenMap to our JSON file.
    if (aJSONFile)
    {
      // Write out our json file...
      let data = JSON.stringify(this._persistOpenMap);
      IOUtils.saveStringToFile(aJSONFile, data);
    }
  },

  // Override the dnd methods for those functions in abDragDrop.js
  canDrop: function dtv_canDrop(aIndex, aOrientation, dataTransfer)
  {
    return abDirTreeObserver.canDrop(aIndex, aOrientation, dataTransfer);
  },

  drop: function dtv_drop(aRow, aOrientation, dataTransfer)
  {
    abDirTreeObserver.onDrop(aRow, aOrientation, dataTransfer);
  },

  getDirectoryAtIndex: function dtv_getDirForIndex(aIndex)
  {
    return this._rowMap[aIndex]._directory;
  },

  getIndexOfDirectory: function dtv_getIndexOfDir(aItem)
  {
    for (var i = 0; i < this._rowMap.length; i++)
      if (this._rowMap[i]._directory == aItem)
        return i;

    return -1;
  },

  // Override jsTreeView's isContainer, since we want to be able
  // to react to drag-drop events for all items in the directory
  // tree.
  isContainer: function dtv_isContainer(aIndex)
  {
    return true;
  },

  /**
   * NOTE: This function will result in indeterminate rows being selected.
   *       Callers should take care to re-select a desired row after calling
   *       this function.
   */
  _rebuild: function dtv__rebuild() {
    this._rowMap = [];

    // Make an entry for All Address Books.
    let rootAB = MailServices.ab.getDirectory(kAllDirectoryRoot + "?");
    rootAB.dirName = gAddressBookBundle.getString("allAddressBooks");
    this._rowMap.push(new abDirTreeItem(rootAB));

    // Sort our addressbooks now.
    this._rowMap.sort(abSort);

    this._restoreOpenStates();
  },

  getIndexForId: function(aId) {
    for (let i = 0; i < this._rowMap.length; i++) {
      if (this._rowMap[i].id == aId)
        return i;
    }

    return -1;
  },

  // nsIAbListener interfaces
  onItemAdded: function dtv_onItemAdded(aParent, aItem)
  {
    if (!(aItem instanceof Ci.nsIAbDirectory))
      return;

    var oldCount = this._rowMap.length;
    var tree = this._tree;
    this._tree = null;
    this._rebuild();
    if (!tree)
      return;

    this._tree = tree;
    var itemIndex = this.getIndexOfDirectory(aItem);
    tree.rowCountChanged(itemIndex, this._rowMap.length - oldCount);
    var parentIndex = this.getIndexOfDirectory(aParent);
    if (parentIndex > -1)
      tree.invalidateRow(parentIndex);
  },

  onItemRemoved: function dtv_onItemRemoved(aParent, aItem)
  {
    if (!(aItem instanceof Ci.nsIAbDirectory))
      return;

    var itemIndex = this.getIndexOfDirectory(aItem);
    var oldCount = this._rowMap.length;
    var tree = this._tree;
    this._tree = null;
    this._rebuild();
    if (!tree)
      return;

    this._tree = tree;
    tree.rowCountChanged(itemIndex, this._rowMap.length - oldCount);

    // This does not currently work, see Bug 1323563.
    // If we're deleting a top-level address-book, just select the first book.
    // if (aParent.URI == kAllDirectoryRoot ||
    //     aParent.URI == kAllDirectoryRoot + "?") {
    //   this.selection.select(0);
    //   return;
    // }

    var parentIndex = this.getIndexOfDirectory(aParent);
    if (parentIndex > -1)
      tree.invalidateRow(parentIndex);

    if (!this.selection.count)
    {
      // The previously selected item was a member of the deleted subtree.
      // Select the parent of the subtree.
      // If there is no parent, select the next item.
      // If there is no next item, select the first item.
      var newIndex = parentIndex;
      if (newIndex < 0)
        newIndex = itemIndex;
      if (newIndex >= this._rowMap.length)
        newIndex = 0;

      this.selection.select(newIndex);
    }
  },

  onItemPropertyChanged: function dtv_onItemProp(aItem, aProp, aOld, aNew)
  {
    if (!(aItem instanceof Ci.nsIAbDirectory))
      return;

    var index = this.getIndexOfDirectory(aItem);
    var current = this.getDirectoryAtIndex(this.selection.currentIndex);
    var tree = this._tree;
    this._tree = null;
    this._rebuild();
    this._tree = tree;
    this.selection.select(this.getIndexOfDirectory(current));

    if (index > -1) {
      var newIndex = this.getIndexOfDirectory(aItem);
      if (newIndex >= index)
        this._tree.invalidateRange(index, newIndex);
      else
        this._tree.invalidateRange(newIndex, index);
    }
  }
};

var gDirectoryTreeView = new directoryTreeView();