1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  */
 27 
 28 /**
 29  * Creates an overview container for a multi-account mailbox.
 30  * @class
 31  * Creates a header tree item for an account if mailbox has multiple accounts
 32  * configured. For each account header, a {@link ZmOverview} is added a child. If mailbox
 33  * only has one account configured, no account header is created and the
 34  * {@link ZmOverview} is added as a child of the container.
 35  *
 36  * @param	{Hash}	params		a hash of parameters
 37  * 
 38  * @author Parag Shah
 39  */
 40 ZmAccountOverviewContainer = function(params) {
 41 	if (arguments.length == 0) { return; }
 42 
 43 	ZmOverviewContainer.call(this, params);
 44 
 45 	this._vFolderTreeItemMap = {};
 46 	this._settingChangeListener = new AjxListener(this, this._handleSettingChange);
 47 
 48 	var mouseListener = new AjxListener(this, this._mouseListener);
 49 	this.addListener(DwtEvent.ONMOUSEDOWN, mouseListener);
 50 	this.addListener(DwtEvent.ONMOUSEUP, mouseListener);
 51 };
 52 
 53 ZmAccountOverviewContainer.prototype = new ZmOverviewContainer;
 54 ZmAccountOverviewContainer.prototype.constructor = ZmAccountOverviewContainer;
 55 
 56 
 57 // Consts
 58 ZmAccountOverviewContainer.VIRTUAL_FOLDERS = [
 59 	ZmFolder.ID_INBOX,
 60 	ZmFolder.ID_SENT,
 61 	ZmFolder.ID_DRAFTS,
 62 	ZmFolder.ID_SPAM,
 63 	ZmFolder.ID_OUTBOX,
 64 	ZmFolder.ID_TRASH
 65 ];
 66 
 67 
 68 // Public methods
 69 
 70 /**
 71  * Returns a string representation of the object.
 72  * 
 73  * @return		{String}		a string representation of the object
 74  */
 75 ZmAccountOverviewContainer.prototype.toString =
 76 function() {
 77 	return "ZmAccountOverviewContainer";
 78 };
 79 
 80 ZmAccountOverviewContainer.prototype.getHeaderItem =
 81 function(account) {
 82 	return account && this._headerItems[account.id];
 83 };
 84 
 85 /**
 86  * Expands the given account only (collapses all other accounts).
 87  *
 88  * @param {ZmAccount}	account		the account to expand
 89  */
 90 ZmAccountOverviewContainer.prototype.expandAccountOnly =
 91 function(account) {
 92 	if (!account) {
 93 		account = appCtxt.getActiveAccount();
 94 	}
 95 
 96 	for (var i in this._headerItems) {
 97 		this._headerItems[i].setExpanded((i == account.id), false, false);
 98 	}
 99 };
100 
101 ZmAccountOverviewContainer.prototype.getSelected =
102 function() {
103 	var selected = ZmOverviewContainer.prototype.getSelected.call(this);
104 
105 	if (!selected) {
106 		selected = this.getSelection()[0];
107 		var account = selected && appCtxt.accountList.getAccount(selected.getData(Dwt.KEY_ID));
108 		var tree = account && appCtxt.getFolderTree(account);
109 		return tree && tree.root;
110 	}
111 
112 	return selected;
113 };
114 
115 /**
116  * Initializes the account overview.
117  * 
118  * @param	{Hash}	params		a hash of parameters
119  * 
120  */
121 ZmAccountOverviewContainer.prototype.initialize =
122 function(params) {
123 
124 	var header, acct;
125 	var accounts = appCtxt.accountList.visibleAccounts;
126 	var showAllMailboxes = (appCtxt.isOffline && this._appName == ZmApp.MAIL && (accounts.length > 2));
127 	var showBackgroundColor = showAllMailboxes;
128 	var mainAcct = appCtxt.accountList.mainAccount;
129 	var origOmit = params.omit;
130 
131 	for (var i = 0; i < accounts.length; i++) {
132 		acct = accounts[i];
133 		// skip the main account in offline mode since we'll add it at the end
134 		if (appCtxt.isOffline && acct.isMain && this._appName != ZmApp.PREFERENCES) { continue; }
135 		if (!acct.active) { continue; }
136 
137 		 params.omit = {};
138 
139 		if (acct.type == ZmAccount.TYPE_POP) {
140 			params.omit[ZmFolder.ID_SPAM]   = true;
141 			params.omit[ZmFolder.ID_OUTBOX] = true;
142 		}
143 
144         if(params.overviewId && !params.isAppOverview) {
145             var overviewId = params.overviewId.split(":")[1];
146             if(overviewId && (overviewId == "ZmNewOrganizerDialog")){
147                 params.omit[ZmFolder.ID_DRAFTS] = true;
148             }
149         }
150 
151 		this._addAccount(params, acct, showBackgroundColor, null, "account" + i);
152 
153 		header = this.getHeaderItem(acct);
154 		if (header) {
155 			this._setupHeader(header, acct);
156 		}
157 
158 		this.updateAccountInfo(acct, true, true);
159 
160 		showBackgroundColor = !showBackgroundColor;
161 	}
162 
163 	// add "All Mailboxes"
164 	skip = origOmit && origOmit[ZmOrganizer.ID_ALL_MAILBOXES];
165 	if (showAllMailboxes && !skip) {
166 		var text = ZmMsg[ZmFolder.MSG_KEY[ZmOrganizer.ID_ALL_MAILBOXES]];
167 		var hdrText = appCtxt.get(ZmSetting.OFFLINE_ALL_MAILBOXES_TREE_OPEN)
168 			? text : this._getFolderLabel(ZmOrganizer.ID_INBOX, text);
169 		var params1 = {
170 			parent: this,
171 			text: hdrText,
172 			imageInfo: "AccountAll"
173 		};
174 		var showAllMboxes = appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES);
175 		var allTi = this._allMailboxesTreeHeader = new DwtTreeItem(params1);
176 		allTi.setData(Dwt.KEY_ID, ZmOrganizer.ID_ALL_MAILBOXES);
177 		allTi.addClassName("ZmOverviewGlobalInbox");
178 		allTi._initialize(0, true);
179 		allTi.setVisible(showAllMboxes);
180 		allTi.__origText = text;
181 		if (showAllMboxes) {
182 			this.highlightAllMboxes();
183 		}
184 
185 		var setting = appCtxt.getSettings(mainAcct).getSetting(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES);
186 		setting.addChangeListener(this._settingChangeListener);
187 
188 		var folders = ZmAccountOverviewContainer.VIRTUAL_FOLDERS;
189 		for (var i = 0; i < folders.length; i++) {
190 			var folderId = folders[i];
191 
192 			// add virtual system folders
193 			params1 = {
194 				parent: allTi,
195 				text: this._getFolderLabel(folderId, ZmMsg[ZmFolder.MSG_KEY[folderId]]),
196 				imageInfo: ZmFolder.ICON[folderId]
197 			};
198 			var ti = this._vFolderTreeItemMap[folderId] = new DwtTreeItem(params1);
199 			ti.setData(Dwt.KEY_ID, folderId);
200 			ti.addClassName("DwtTreeItemChildDiv");
201 			ti._initialize(null, true);
202 			ti.setToolTipContent(appCtxt.accountList.getTooltipForVirtualFolder(folderId));
203 		}
204 
205 		// add global searches
206 		params1 = {
207 			parent: allTi,
208 			text: ZmMsg.globalSearches,
209 			imageInfo: "SearchFolder"
210 		};
211 		var searchTi = this._searchTreeHeader = new DwtTreeItem(params1);
212 		searchTi.addClassName("DwtTreeItemChildDiv");
213 		searchTi._initialize(null, true);
214 		searchTi.__isSearch = true;
215 
216 		var root = appCtxt.getById(ZmOrganizer.ID_ROOT, mainAcct);
217 		var searchFolders = root.getByType(ZmOrganizer.SEARCH);
218 		for (var i = 0; i < searchFolders.length; i++) {
219 			var folder = searchFolders[i];
220 			if (folder.id != ZmOrganizer.ID_ALL_MAILBOXES &&
221 				folder.isOfflineGlobalSearch)
222 			{
223 				this.addSearchFolder(folder);
224 			}
225 		}
226 		searchTi.setVisible(searchTi.getItemCount() > 0);
227 
228 		// bug #43734 - set expand/collapse state based on user's pref
229 		if (appCtxt.get(ZmSetting.OFFLINE_SAVED_SEARCHES_TREE_OPEN)) {
230 			searchTi.setExpanded(true, null, true);
231 		}
232 		if (appCtxt.get(ZmSetting.OFFLINE_ALL_MAILBOXES_TREE_OPEN)) {
233 			allTi.setExpanded(true, null, true);
234 		}
235 	}
236 
237 	// add the "local" account last
238 	if (appCtxt.isOffline) {
239 		var params2 = AjxUtil.hashCopy(params);
240 		params2.omit = {};
241 
242 		if (this._appName != ZmApp.PREFERENCES) {
243 			this._addAccount(params2, mainAcct, showBackgroundColor, "ZmOverviewLocalHeader", "LocalFolders");
244 
245 			header = this.getHeaderItem(mainAcct);
246 			header.setExpanded(appCtxt.get(ZmSetting.ACCOUNT_TREE_OPEN, null, mainAcct));
247 
248 			this.updateAccountInfo(mainAcct, false, true);
249 		}
250 		else {
251 			var params3 = {
252 				parent: this,
253 				text: mainAcct.getDisplayName(),
254 				imageInfo: mainAcct.getIcon()
255 			};
256 			var localPrefTi = new DwtTreeItem(params3);
257 			localPrefTi._initialize(null, true);
258 
259 			var globalPrefOverviewId = appCtxt.getOverviewId(this.containerId, mainAcct);
260 			var tv = this._overview[globalPrefOverviewId].getTreeView(ZmOrganizer.PREF_PAGE);
261 			var importExportTi = tv.getTreeItemById("PREF_PAGE_IMPORT_EXPORT");
262 
263 			tv.getHeaderItem().removeChild(importExportTi);
264 			localPrefTi._addItem(importExportTi);
265 			importExportTi.addClassName("DwtTreeItemChildDiv");
266 			localPrefTi.setExpanded(true, null, true);
267 		}
268 	}
269 
270 	// add zimlets at the end of all overviews
271 	var skip = params.omit && params.omit[ZmOrganizer.ID_ZIMLET];
272 
273 	if (!appCtxt.inStartup && !skip && appCtxt.getZimletMgr().getPanelZimlets().length == 0) {
274 		skip = true;
275 	}
276 
277 	if (!skip) {
278 		AjxDispatcher.require("Zimlet");
279 	}
280 
281 	if (!skip && window[ZmOverviewController.CONTROLLER[ZmOrganizer.ZIMLET]] &&
282 		this._appName != ZmApp.PREFERENCES)
283 	{
284 		var headerLabel = ZmOrganizer.LABEL[ZmOrganizer.ZIMLET];
285 		var headerDataId = params.overviewId = appCtxt.getOverviewId([this.containerId, headerLabel], null);
286 		var headerParams = {
287 			label: ZmMsg[headerLabel],
288 			icon: "Zimlet",
289 			dataId: headerDataId,
290 			className: "ZmOverviewZimletHeader"
291 		};
292 		params.overviewTrees = [ZmOrganizer.ZIMLET];
293 
294 		this._addSection(headerParams, null, params);
295 
296 		var header = this._headerItems[headerDataId];
297 		if (header) {
298 			header.__isZimlet = true;
299 			header.setExpanded(appCtxt.get(ZmSetting.ZIMLET_TREE_OPEN, null, mainAcct));
300 		}
301 	}
302 
303 	this._initializeActionMenu();
304 };
305 
306 /**
307  * Adds a search folder.
308  * 
309  * @param	{ZmFolder}		folder		the folder
310  */
311 ZmAccountOverviewContainer.prototype.addSearchFolder =
312 function(folder) {
313 	if (!this._searchTreeHeader) { return; }
314 
315 	var params = {
316 		parent: this._searchTreeHeader,
317 		text: folder.getName(),
318 		imageInfo: folder.getIcon()
319 	};
320 	var ti = new DwtTreeItem(params);
321 	ti.setData(Dwt.KEY_ID, folder);
322 	ti._initialize(null, true);
323 
324 	if (!this._searchTreeHeader.getVisible()) {
325 		this._searchTreeHeader.setVisible(true);
326 	}
327 };
328 
329 /**
330  * Sets/updates the account-level status icon next to account name tree header.
331  * This only applies to app-based overview containers (i.e. not dialogs). Also resets the
332  * tooltip for the account header tree item.
333  *
334  * @param {ZmZimbraAccount}	account		the account to update status icon for
335  * @param	{Boolean}	updateStatus	if <code>true</code>, update the status
336  * @param	{Boolean}	updateTooltip	if <code>true</code>, update the tool tip
337  */
338 ZmAccountOverviewContainer.prototype.updateAccountInfo =
339 function(account, updateStatus, updateTooltip) {
340 	// check if appName is a real app (and not a dialog) before setting the account status
341 	var hi = appCtxt.getApp(this._appName) && this.getHeaderItem(account);
342 	if (hi) {
343 		if (updateStatus) {
344 			var html = (account.status == ZmZimbraAccount.STATUS_RUNNING)
345 				? ("<img src='/img/animated/ImgSpinner.gif' width=16 height=16 border=0>")
346 				: (AjxImg.getImageHtml(account.getStatusIcon()));
347 
348 			if (hi._extraCell) {
349 				hi._extraCell.innerHTML = (html || "");
350 			}
351             if (appCtxt.isOffline && account.status == ZmZimbraAccount.STATUS_AUTHFAIL) {
352                 var dialog = appCtxt.getPasswordChangeDialog();
353                 dialog.popup(account);
354             }
355 		}
356 
357 		if (updateTooltip || updateStatus) {
358 			hi.setToolTipContent(account.getToolTip());
359 		}
360 	}
361 };
362 
363 /**
364  * Updates the label.
365  * 
366  * @param	{ZmOrganizer}	organizer		the organizer
367  */
368 ZmAccountOverviewContainer.prototype.updateLabel =
369 function(organizer) {
370 	// update account header if necessary
371 	if (organizer.nId == ZmOrganizer.ID_INBOX) {
372 		var acct = organizer.getAccount();
373 		var header = this.getHeaderItem(acct);
374 		if (header && !header.getExpanded()) {
375 			header.setText(this._getAccountHeaderLabel(acct));
376 		}
377 	}
378 
379 	// update virtual folder label
380 	var ti = this._vFolderTreeItemMap[organizer.nId];
381 	if (ti) {
382 		ti.setText(this._getFolderLabel(organizer.nId, organizer.name));
383 		if (organizer.nId == ZmOrganizer.ID_INBOX &&
384 			!this._allMailboxesTreeHeader.getExpanded())
385 		{
386 			var text = this._getFolderLabel(organizer.nId, this._allMailboxesTreeHeader.__origText);
387 			this._allMailboxesTreeHeader.setText(text);
388 		}
389 	}
390 };
391 
392 /**
393  * Updates the tool tip.
394  * 
395  * @param	{String}	folderId		the folder id
396  * 
397  */
398 ZmAccountOverviewContainer.prototype.updateTooltip =
399 function(folderId) {
400 	var ti = this._allMailboxesTreeHeader && this._vFolderTreeItemMap[folderId];
401 	if (ti) {
402 		ti.setToolTipContent(appCtxt.accountList.getTooltipForVirtualFolder(folderId));
403 	}
404 };
405 
406 ZmAccountOverviewContainer.prototype.resetOperations =
407 function(parent, data) {
408 
409 	var emptyFolderOp = parent.getOp(ZmOperation.EMPTY_FOLDER);
410 
411 	if (data instanceof ZmSearchFolder) {
412 		parent.getOp(ZmOperation.MARK_ALL_READ).setVisible(false);
413 		emptyFolderOp.setVisible(false);
414 		parent.getOp(this._newOp).setVisible(false);
415 		if (appCtxt.isOffline) {
416 			parent.getOp(ZmOperation.SYNC).setVisible(false);
417 		}
418 		parent.getOp(ZmOperation.DELETE).setVisible(true);
419 		return;
420 	}
421 
422 	var acct = appCtxt.accountList.getAccount(data);
423 	var isAcctType = (acct || data == ZmOrganizer.ID_ALL_MAILBOXES);
424 
425 	parent.getOp(ZmOperation.MARK_ALL_READ).setVisible(!isAcctType);
426 	emptyFolderOp.setVisible(false);
427 	parent.getOp(this._newOp).setVisible(isAcctType && data != ZmOrganizer.ID_ALL_MAILBOXES);
428 	if (appCtxt.isOffline) {
429 		parent.getOp(ZmOperation.SYNC).setVisible(isAcctType && (!acct || (acct && !acct.isMain)));
430 	}
431 	parent.getOp(ZmOperation.DELETE).setVisible(false);
432 
433 	if (isAcctType) {
434 		parent.enable(this._newOp, true); 
435 		parent.enable(ZmOperation.SYNC, (!acct || (acct && !acct.isMain)));
436 	} else {
437 		// reset mark all based on a "friendly" hack ;)
438 		var markAllEnabled = false;
439 		if (data != ZmOrganizer.ID_OUTBOX && data != ZmFolder.ID_DRAFTS &&
440 			this._actionedHeaderItem.getText().indexOf("bold") != -1)
441 		{
442 			markAllEnabled = true;
443 		}
444 		parent.enable(ZmOperation.MARK_ALL_READ, markAllEnabled);
445 
446 		// reset empty "folder" based on Trash/Junk
447 		if (data == ZmOrganizer.ID_TRASH || data == ZmOrganizer.ID_SPAM) {
448 			var text = (data == ZmOrganizer.ID_TRASH) ? ZmMsg.emptyTrash : ZmMsg.emptyJunk;
449 			emptyFolderOp.setText(text);
450 			emptyFolderOp.setVisible(true);
451 			parent.enable(ZmOperation.EMPTY_FOLDER, !this._isFolderEmpty(data));
452 		}
453 	}
454 };
455 
456 // HACK - when the overview container for mail is initially created, zimlet
457 // data has yet to be parsed so we remove the zimlet section after zimlet load
458 // if there are no panel zimlets.
459 ZmAccountOverviewContainer.prototype.removeZimletSection =
460 function() {
461 	var headerLabel = ZmOrganizer.LABEL[ZmOrganizer.ZIMLET];
462 	var headerDataId = appCtxt.getOverviewId([this.containerId, headerLabel], null);
463 	var header = this._headerItems[headerDataId];
464 	if (header) {
465 		this.removeChild(header);
466 	}
467 };
468 
469 ZmAccountOverviewContainer.prototype.highlightAllMboxes =
470 function() {
471 	this.deselectAll();
472 	this.setSelection(this._allMailboxesTreeHeader, true);
473 };
474 
475 ZmAccountOverviewContainer.prototype._addAccount =
476 function(params, account, showBackgroundColor, headerClassName, predictableId) {
477 	params.overviewId = appCtxt.getOverviewId(this.containerId, account);
478 	params.account = account;	// tree controller might need reference to account
479 
480 	// only show sections for apps that are supported unless this is prefs app
481 	var app = appCtxt.getApp(this._appName);
482 	var isSupported = (!app || (app && appCtxt.get(ZmApp.SETTING[this._appName], null, account)));
483 
484 	if (this._appName == ZmApp.PREFERENCES || isSupported) {
485 		var omit = params.omitPerAcct
486 			? params.omitPerAcct[account.id] : params.omit;
487 
488 		var headerLabel, headerIcon;
489 		if (this._appName == ZmApp.PREFERENCES && account.isMain && appCtxt.isOffline) {
490 			headerLabel = ZmMsg.allAccounts;
491 			headerIcon = "AccountAll";
492 		} else {
493 			headerLabel = account.getDisplayName();
494 			if (!appCtxt.isFamilyMbox) {
495 				headerIcon = account.getIcon()
496 			}
497 		}
498 
499 		var headerParams = {
500 			label: headerLabel,
501 			icon: headerIcon,
502 			dataId: account.id,
503 			className: headerClassName,
504 			predictableId: predictableId
505 		};
506 
507 		this._addSection(headerParams, omit, params, showBackgroundColor);
508 	}
509 
510 	var setting = appCtxt.getSettings(account).getSetting(ZmSetting.QUOTA_USED);
511 	setting.addChangeListener(this._settingChangeListener);
512 };
513 
514 ZmAccountOverviewContainer.prototype._addSection =
515 function(headerParams, omit, overviewParams, showBackgroundColor) {
516 	// create a top-level section header
517 	var params = {
518 		parent: this,
519 		text:			headerParams.label,
520 		imageInfo:		headerParams.icon,
521 		selectable:		overviewParams.selectable,
522 		className:		headerParams.className,
523 		id:				this.getHTMLElId() + "__" + (headerParams.predictableId || headerParams.dataId) + "__SECTION"
524 	};
525 	var header = this._headerItems[headerParams.dataId] = new DwtTreeItem(params);
526 	header.setData(Dwt.KEY_ID, headerParams.dataId);
527 	header.setScrollStyle(Dwt.CLIP);
528 	header._initialize(null, true, true);
529 	header.addClassName(showBackgroundColor ? "ZmOverviewSectionHilite" : "ZmOverviewSection");
530 
531 	// reset some params for child overviews
532 	overviewParams.id = ZmId.getOverviewId(overviewParams.overviewId);
533 	overviewParams.parent = header;
534 	overviewParams.scroll = Dwt.CLIP;
535 	overviewParams.posStyle = Dwt.STATIC_STYLE;
536 
537 	// next, create an overview for this account and add it to the account header
538 	var ov = this._controller._overview[overviewParams.overviewId] = this._overview[overviewParams.overviewId] = new ZmOverview(overviewParams, this._controller);
539 	header._dndScrollCallback = this._overview._dndScrollCallback,
540 	header._dndScrollId = this._overview._scrollableContainerId,
541 	header._addItem(ov, null, true);
542 
543 	// finally set treeviews for this overview
544 	var treeIds = overviewParams.overviewTrees || overviewParams.treeIds;
545 	ov.set(treeIds, omit);
546 };
547 
548 ZmAccountOverviewContainer.prototype._setupHeader =
549 function(header, acct) {
550 	// always expand header in prefs app, otherwise follow implicit user pref
551 	if (this._appName == ZmApp.PREFERENCES) {
552 		header.setExpanded(true, false, true);
553 		header.enableSelection(false);
554 	} else {
555 		var isExpanded = appCtxt.get(ZmSetting.ACCOUNT_TREE_OPEN, null, acct);
556 		header.setExpanded(isExpanded);
557 		if (!isExpanded) {
558 			header.setText(this._getAccountHeaderLabel(acct));
559 		}
560 	}
561 
562 	// add onclick support
563 	if (header._extraCell) {
564 		header._extraCell.onclick = AjxCallback.simpleClosure(this._handleStatusClick, this, acct);
565 	}
566 
567 	// add DnD support
568 	var dropTgt = this._controller.getTreeController(ZmOrganizer.FOLDER).getDropTarget();
569 	var root = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT, acct);
570 	header.setDropTarget(dropTgt);
571 	header.setData(Dwt.KEY_OBJECT, appCtxt.getById(root));
572 };
573 
574 /**
575  * Iterates all visible account and checks whether folder for each account is
576  * empty. Used by "All Mailboxes" to determine whether a virtual folder needs
577  * to be disabled or not when right-clicked.
578  *
579  * @param folderId		[String]		Normalized folder ID (should not be fully qualified)
580  */
581 ZmAccountOverviewContainer.prototype._isFolderEmpty =
582 function(folderId) {
583 	var accounts = appCtxt.accountList.visibleAccounts;
584 	for (var i = 0; i < accounts.length; i++) {
585 		var folder = appCtxt.getById(ZmOrganizer.getSystemId(folderId, accounts[i]));
586 		if (folder && folder.numTotal > 0) {
587 			return false;
588 		}
589 	}
590 
591 	return true;
592 };
593 
594 ZmAccountOverviewContainer.prototype._syncAccount =
595 function(dialog, account) {
596 	dialog.popdown();
597 	account.sync();
598 };
599 
600 ZmAccountOverviewContainer.prototype._treeViewListener =
601 function(ev) {
602 	if (ev.detail != DwtTree.ITEM_ACTIONED &&
603 		ev.detail != DwtTree.ITEM_SELECTED &&
604 		ev.detail != DwtTree.ITEM_DBL_CLICKED)
605 	{
606 		return;
607 	}
608 
609 	var item = this._actionedHeaderItem = ev.item;
610 
611 	// do nothing if zimlet/search is clicked
612 	if (item && (item.__isZimlet || item.__isSearch)) { return; }
613 
614 	var data = item && item.getData(Dwt.KEY_ID);
615 
616 	if (ev.detail == DwtTree.ITEM_ACTIONED && appCtxt.getApp(this._appName)) {	// right click
617 		var actionMenu = this._getActionMenu(data);
618 		if (actionMenu) {
619 			this.resetOperations(actionMenu, data);
620 			actionMenu.popup(0, ev.docX, ev.docY);
621 		}
622 	}
623 	else if ((ev.detail == DwtTree.ITEM_SELECTED) && item) {					// left click
624 		// if calendar/prefs app, do nothing
625 		if (this._appName == ZmApp.CALENDAR ||
626 			this._appName == ZmApp.PREFERENCES)
627 		{
628 			return;
629 		}
630 
631 		this._deselectAllTreeViews();
632 
633 		// this avoids processing clicks in dialogs etc.
634 		if (!ZmApp.NAME[this._appName]) { return; }
635 
636 		// if an account header item was clicked, run the default search for it
637 		if (data) {
638 			var sc = appCtxt.getSearchController();
639 
640 			var account = appCtxt.accountList.getAccount(data);
641 			if (account) {
642 				// bug 41196 - turn off new mail notifier if inactive account header clicked
643 				if (appCtxt.isOffline && account.inNewMailMode) {
644 					account.inNewMailMode = false;
645 					var allContainers = appCtxt.getOverviewController()._overviewContainer;
646 					for (var i in allContainers) {
647 						allContainers[i].updateAccountInfo(account, true, true);
648 					}
649 				}
650 
651 				// don't process click if user clicked on error status icon
652 				if ((ev.target.parentNode == ev.item._extraCell) && account.isError()) {
653 					return;
654 				}
655 
656 				sc.searchAllAccounts = false;
657 				appCtxt.accountList.setActiveAccount(account);
658 
659 				if (appCtxt.isOffline && account.hasNotSynced() && !account.__syncAsked) {
660 					account.__syncAsked = true;
661 
662 					var dialog = appCtxt.getYesNoMsgDialog();
663 					dialog.registerCallback(DwtDialog.YES_BUTTON, this._syncAccount, this, [dialog, account]);
664 					dialog.setMessage(ZmMsg.neverSyncedAsk, DwtMessageDialog.INFO_STYLE);
665 					dialog.popup();
666 				}
667 
668 				var fid = ZmOrganizer.DEFAULT_FOLDER[ZmApp.ORGANIZER[this._appName]];
669 				var folder = appCtxt.getById(ZmOrganizer.getSystemId(fid, account));
670 
671 				// briefcase is not a ZmFolder so let's skip for now
672 				if (!(folder instanceof ZmFolder)) { return; }
673 
674 				var defaultSortBy = (this._appName == ZmApp.TASKS)
675 					? ZmSearch.DUE_DATE_DESC : ZmSearch.DATE_DESC;
676 
677 				var params = {
678 					query: folder.createQuery(),
679 					getHtml: appCtxt.get(ZmSetting.VIEW_AS_HTML),
680 					searchFor: (ZmApp.DEFAULT_SEARCH[this._appName]),
681 					sortBy: ((sc.currentSearch && folder.nId == sc.currentSearch.folderId) ? null : defaultSortBy),
682 					accountName: (account && account.name),
683 					noUpdateOverview: true
684 				};
685 			} else {
686 				var main = appCtxt.accountList.mainAccount;
687 				sc.resetSearchAllAccounts();
688 				sc.searchAllAccounts = true;
689 
690 				if (data instanceof ZmSearchFolder) {
691 					params = {
692 						searchAllAccounts: true,
693 						accountName: main.name,
694 						getHtml: appCtxt.get(ZmSetting.VIEW_AS_HTML),
695 						noUpdateOverview: true
696 					};
697 					sc.redoSearch(data.search, false, params);
698 					return;
699 				}
700 				if (data == ZmOrganizer.ID_ALL_MAILBOXES) {
701 					data = ZmFolder.ID_INBOX;
702 				}
703 
704 				params = {
705 					queryHint: appCtxt.accountList.generateQuery(data),
706 					folderId: null,
707 					getHtml: appCtxt.get(ZmSetting.VIEW_AS_HTML, null, main),
708 					searchFor: (ZmApp.DEFAULT_SEARCH[this._appName]),
709 					sortBy: ZmSearch.DATE_DESC,
710 					accountName: main.name,
711 					noUpdateOverview: true
712 				};
713 			}
714 
715 			sc.search(params);
716 		}
717 	} else {																	// double click
718 		// handle double click?
719 	}
720 };
721 
722 ZmAccountOverviewContainer.prototype._treeListener =
723 function(ev) {
724 	if (ev.detail != DwtTree.ITEM_COLLAPSED &&
725 		ev.detail != DwtTree.ITEM_EXPANDED)
726 	{
727 		return;
728 	}
729 
730 	var header = ev.item;
731 	var expanded = ev.detail == DwtTree.ITEM_EXPANDED;
732 
733 	var acct;
734 	if (header) {
735 		if (header.__isSearch) {
736 			appCtxt.set(ZmSetting.OFFLINE_SAVED_SEARCHES_TREE_OPEN, expanded);
737 			return;
738 		}
739 
740 		var data = header.getData(Dwt.KEY_ID);
741 		if (data == ZmOrganizer.ID_ALL_MAILBOXES) {
742 			var text = expanded
743 				? header.__origText
744 				: this._getFolderLabel(ZmOrganizer.ID_INBOX, header.__origText);
745 			header.setText(text);
746 
747 			// bug #43734 - remember expand/collapse state
748 			appCtxt.set(ZmSetting.OFFLINE_ALL_MAILBOXES_TREE_OPEN, expanded);
749 
750 			return;
751 		}
752 
753 		acct = header.__isZimlet
754 			? appCtxt.accountList.mainAccount
755 			: appCtxt.accountList.getAccount(data);
756 	}
757 
758 	if (acct && appCtxt.getCurrentAppName() != ZmApp.PREFERENCES) {
759 		if (!appCtxt.inStartup) {
760             // set account as active when account tree is opened/closed
761             appCtxt.accountList.setActiveAccount(acct);
762 			appCtxt.set(ZmSetting.ACCOUNT_TREE_OPEN, expanded, null, null, null, acct);
763 		}
764 
765 		if (!header.__isZimlet) {
766 			var text = expanded
767 				? acct.getDisplayName()
768 				: this._getAccountHeaderLabel(acct);
769 			header.setText(text);
770 		}
771 	}
772 };
773 
774 ZmAccountOverviewContainer.prototype._mouseListener =
775 function(ev) {
776 	return !Dwt.ffScrollbarCheck(ev);
777 };
778 
779 ZmAccountOverviewContainer.prototype._handleSettingChange =
780 function(ev) {
781 	if (ev.type != ZmEvent.S_SETTING) { return; }
782 
783 	var setting = ev.source;
784 
785 	if (setting.id == ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES) {
786 		var isVisible = setting.getValue();
787 		this._allMailboxesTreeHeader.setVisible(isVisible);
788 		if (!isVisible) {
789 			if (appCtxt.getActiveAccount().isMain) {
790 				appCtxt.accountList.setActiveAccount(appCtxt.accountList.defaultAccount);
791 			}
792 			appCtxt.getSearchController().searchAllAccounts = false;
793 			appCtxt.getApp(ZmApp.MAIL).mailSearch();
794 		} else {
795 			this._deselect(this._allMailboxesTreeHeader);
796 		}
797 	}
798 	else if (setting.id == ZmSetting.QUOTA_USED) {
799 		this.updateAccountInfo(ev.getDetails().account, false, true);
800 	}
801 };
802 
803 ZmAccountOverviewContainer.prototype._getAccountHeaderLabel =
804 function(acct, header) {
805 	var inboxId = (this._appName == ZmApp.MAIL)
806 		? ZmOrganizer.getSystemId(ZmOrganizer.ID_INBOX, acct, true) : null;
807 	var inbox = inboxId && appCtxt.getById(inboxId);
808 	if (inbox && inbox.numUnread > 0) {
809 		var name = AjxMessageFormat.format(ZmMsg.folderUnread, [acct.getDisplayName(), inbox.numUnread]);
810 		return (["<span style='font-weight:bold;'>", name, "</span>"].join(""));
811 	}
812 
813 	return acct.getDisplayName();
814 };
815 
816 ZmAccountOverviewContainer.prototype._getFolderLabel =
817 function(folderId, label) {
818 	var checkUnread = (folderId != ZmFolder.ID_DRAFTS && folderId != ZmFolder.ID_OUTBOX);
819 	var count = appCtxt.accountList.getItemCount(folderId, checkUnread);
820 	if (count > 0) {
821 		var name = AjxMessageFormat.format(ZmMsg.folderUnread, [label, count]);
822 		return (["<span style='font-weight:bold;'>", name, "</span>"].join(""));
823 	}
824 
825 	return label;
826 };
827 
828 ZmAccountOverviewContainer.prototype._initializeActionMenu =
829 function() {
830 	if (!this._actionMenu) {
831 		var orgType = ZmApp.ORGANIZER[this._appName];
832 		this._newOp = ZmOrganizer.NEW_OP[orgType];
833 
834 		var ops = [this._newOp];
835 		if (appCtxt.isOffline) {
836 			ops.push(ZmOperation.SYNC);
837 		}
838 		ops.push(ZmOperation.MARK_ALL_READ,
839 				ZmOperation.EMPTY_FOLDER,
840 				ZmOperation.DELETE);
841 
842 		this._actionMenu = new AjxCallback(this, this._createActionMenu, [ops]);
843 	}
844 };
845 
846 ZmAccountOverviewContainer.prototype._createActionMenu =
847 function(menuItems) {
848 	var listener = new AjxListener(this, this._actionMenuListener);
849 	var actionMenu = new ZmActionMenu({parent:appCtxt.getShell(), menuItems:menuItems});
850 	menuItems = actionMenu.opList;
851 	for (var i = 0; i < menuItems.length; i++) {
852 		var mi = actionMenu.getItem(i);
853 		var op = menuItems[i];
854 		if (op == ZmOperation.SYNC) {
855 			mi.setText(ZmMsg.sendReceive);
856 		}
857 		actionMenu.addSelectionListener(op, listener);
858 	}
859 
860 	actionMenu.addPopdownListener(new AjxListener(this, this._menuPopdownActionListener));
861 
862 	return actionMenu;
863 };
864 
865 ZmAccountOverviewContainer.prototype._menuPopdownActionListener =
866 function() {
867 	this._actionedHeaderItem._setActioned(false);
868 };
869 
870 ZmAccountOverviewContainer.prototype._actionMenuListener =
871 function(ev) {
872 	var opId = ev.item.getData(ZmOperation.KEY_ID);
873 	var data = this._actionedHeaderItem.getData(Dwt.KEY_ID);
874 
875 	if (opId == this._newOp) {
876 		var treeId = ZmApp.ORGANIZER[this._appName];
877 		var tc = this._controller.getTreeController(treeId, true);
878 		if (tc) {
879 			tc._actionedOrganizer = null;
880 			var account = appCtxt.accountList.getAccount(data);
881 			tc._actionedOrganizer = appCtxt.getFolderTree(account).root;
882 			tc._newListener(ev, account);
883 		}
884 	}
885 	else if (opId == ZmOperation.SYNC) {
886 		if (data == ZmOrganizer.ID_ALL_MAILBOXES) {
887 			appCtxt.accountList.syncAll();
888 		} else {
889 			var account = appCtxt.accountList.getAccount(data);
890 			if (account) {
891 				account.sync();
892 			}
893 		}
894 	}
895 	else if (opId == ZmOperation.MARK_ALL_READ) {
896 		this._doAction(data, opId);
897 	}
898 	else if (opId == ZmOperation.EMPTY_FOLDER) {
899 		this._confirmEmptyAction(data, opId);
900 	}
901 	else if (opId == ZmOperation.DELETE) {
902 		data._delete();
903 		var parent = this._actionedHeaderItem.parent;
904 		parent.removeChild(this._actionedHeaderItem); // HACK: just nuke it
905 		parent.setVisible(parent.getItemCount() > 0);
906 	}
907 };
908 
909 ZmAccountOverviewContainer.prototype._confirmEmptyAction =
910 function(data, opId) {
911 	var dialog = appCtxt.getOkCancelMsgDialog();
912 	dialog.reset();
913 	dialog.registerCallback(DwtDialog.OK_BUTTON, this._emptyCallback, this, [dialog, data, opId]);
914 
915 	var msg = (data == ZmFolder.ID_TRASH)
916 		? ZmMsg.confirmEmptyTrashFolder
917 		: (AjxMessageFormat.format(ZmMsg.confirmEmptyFolder, ZmMsg[ZmFolder.MSG_KEY[data]]));
918 
919 	dialog.setMessage(msg, DwtMessageDialog.WARNING_STYLE);
920 	dialog.popup();
921 };
922 
923 ZmAccountOverviewContainer.prototype._emptyCallback =
924 function(dialog, folderId, opId) {
925 	dialog.popdown();
926 	this._doAction(folderId, opId);
927 };
928 
929 ZmAccountOverviewContainer.prototype._doAction =
930 function(folderId, opId) {
931 	var bc = new ZmBatchCommand(true, appCtxt.accountList.mainAccount.name);
932 
933 	var accounts = appCtxt.accountList.visibleAccounts;
934 	for (var i = 0; i < accounts.length; i++) {
935 		var account = accounts[i];
936 		if (account.isMain) { continue; }
937 
938 		var fid = ZmOrganizer.getSystemId(folderId, account);
939 		var folder = appCtxt.getById(fid);
940 		if (folder) {
941 			if (opId == ZmOperation.MARK_ALL_READ) {
942 				folder.markAllRead(bc);
943 			} else {
944 				folder.empty(null, bc);
945 			}
946 			bc.curId++;
947 		}
948 	}
949 
950 	bc.run();
951 	if (appCtxt.isOffline) {
952 		appCtxt.getApp(ZmApp.MAIL).clearNewMailBadge();
953 	}
954 };
955 
956 ZmAccountOverviewContainer.prototype._handleStatusClick =
957 function(account, ev) {
958 
959     if(!account.isError()) {
960         return;
961     } else if (appCtxt.isOffline && (account.status == ZmZimbraAccount.STATUS_AUTHFAIL)) {
962         var dialog = appCtxt.getPasswordChangeDialog();
963         dialog.popup(account);
964     } else {
965         account.showErrorMessage();
966     }
967 };
968