1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a new compose controller to manage message composition.
 26  * @constructor
 27  * @class
 28  * This class manages message composition.
 29  *
 30  * @author Conrad Damon
 31  *
 32  * @param {DwtShell}	container	the containing shell
 33  * @param {ZmApp}		mailApp		the containing app
 34  * @param {constant}	type		controller type
 35  * @param {string}		sessionId	the session id
 36  * 
 37  * @extends		ZmController
 38  */
 39 ZmComposeController = function(container, mailApp, type, sessionId) {
 40 
 41 	ZmController.apply(this, arguments);
 42 
 43 	this._action = null;
 44 
 45 	ZmComposeController._setStatics();
 46 
 47 	this._listeners = {};
 48 	this._listeners[ZmOperation.SEND]				= this._sendListener.bind(this);
 49 	this._listeners[ZmOperation.SEND_MENU]			= this._sendListener.bind(this);
 50 	this._listeners[ZmOperation.SEND_LATER]			= this._sendLaterListener.bind(this);
 51 	this._listeners[ZmOperation.CANCEL]				= this._cancelListener.bind(this);
 52 	this._listeners[ZmOperation.ATTACHMENT]			= this._attachmentListener.bind(this);
 53 	this._listeners[ZmOperation.DETACH_COMPOSE]		= this._detachListener.bind(this);
 54 	this._listeners[ZmOperation.SAVE_DRAFT]			= this._saveDraftListener.bind(this);
 55 	this._listeners[ZmOperation.SPELL_CHECK]		= this._spellCheckListener.bind(this);
 56 	this._listeners[ZmOperation.COMPOSE_OPTIONS]	= this._optionsListener.bind(this);
 57 
 58 	this._dialogPopdownListener = this._dialogPopdownActionListener.bind(this);
 59 
 60 	this._autoSaveTimer = null;
 61 	this._draftType = ZmComposeController.DRAFT_TYPE_NONE;
 62 	this._elementsToHide = ZmAppViewMgr.LEFT_NAV;
 63 };
 64 
 65 ZmComposeController.prototype = new ZmController();
 66 ZmComposeController.prototype.constructor = ZmComposeController;
 67 
 68 ZmComposeController.prototype.isZmComposeController = true;
 69 ZmComposeController.prototype.toString = function() { return "ZmComposeController"; };
 70 
 71 //
 72 // Constants
 73 //
 74 
 75 ZmComposeController.SIGNATURE_KEY = "sigKeyId";
 76 
 77 // Constants for defining the reason for saving a draft message.
 78 /**
 79  * Defines the "none" draft type reason.
 80  */
 81 ZmComposeController.DRAFT_TYPE_NONE		= "none";
 82 /**
 83  * Defines the "manual" draft type reason.
 84  */
 85 ZmComposeController.DRAFT_TYPE_MANUAL	= "manual";
 86 /**
 87  * Defines the "auto" draft type reason.
 88  */
 89 ZmComposeController.DRAFT_TYPE_AUTO		= "auto";
 90 /**
 91  * Defines the "delaysend" draft type reason.
 92  */
 93 ZmComposeController.DRAFT_TYPE_DELAYSEND	= "delaysend";
 94 
 95 ZmComposeController.DEFAULT_TAB_TEXT = ZmMsg.compose;
 96 
 97 ZmComposeController.NEW_WINDOW_WIDTH = 975;
 98 ZmComposeController.NEW_WINDOW_HEIGHT = 475;
 99 
100 // Message dialogs
101 ZmComposeController.MSG_DIALOG_1	= 1;	// OK
102 ZmComposeController.MSG_DIALOG_2	= 2;	// OK Cancel
103 
104 ZmComposeController._setStatics =
105 function() {
106 
107 	if (ZmComposeController.RADIO_GROUP) {
108 		return;
109 	}
110 
111 	// radio groups for options items
112 	ZmComposeController.RADIO_GROUP = {};
113 	ZmComposeController.RADIO_GROUP[ZmOperation.REPLY]				= 1;
114 	ZmComposeController.RADIO_GROUP[ZmOperation.REPLY_ALL]			= 1;
115     ZmComposeController.RADIO_GROUP[ZmOperation.CAL_REPLY]			= 1;
116 	ZmComposeController.RADIO_GROUP[ZmOperation.CAL_REPLY_ALL]		= 1;
117 	ZmComposeController.RADIO_GROUP[ZmOperation.FORMAT_HTML]		= 2;
118 	ZmComposeController.RADIO_GROUP[ZmOperation.FORMAT_TEXT]		= 2;
119 	ZmComposeController.RADIO_GROUP[ZmOperation.INC_ATTACHMENT]		= 3;
120     ZmComposeController.RADIO_GROUP[ZmOperation.INC_BODY]	    	= 3;
121 	ZmComposeController.RADIO_GROUP[ZmOperation.INC_NONE]			= 3;
122 	ZmComposeController.RADIO_GROUP[ZmOperation.INC_SMART]			= 3;
123 
124 	// translate between include settings and operations
125 	ZmComposeController.INC_OP = {};
126 	ZmComposeController.INC_OP[ZmSetting.INC_ATTACH]		= ZmOperation.INC_ATTACHMENT;
127 	ZmComposeController.INC_OP[ZmSetting.INC_BODY]			= ZmOperation.INC_BODY;
128 	ZmComposeController.INC_OP[ZmSetting.INC_NONE]			= ZmOperation.INC_NONE;
129 	ZmComposeController.INC_OP[ZmSetting.INC_SMART]			= ZmOperation.INC_SMART;
130 	ZmComposeController.INC_MAP = {};
131 	for (var i in ZmComposeController.INC_OP) {
132 		ZmComposeController.INC_MAP[ZmComposeController.INC_OP[i]] = i;
133 	}
134 
135 	ZmComposeController.OPTIONS_TT = {};
136 	ZmComposeController.OPTIONS_TT[ZmOperation.NEW_MESSAGE]		= "composeOptions";
137 	ZmComposeController.OPTIONS_TT[ZmOperation.REPLY]			= "replyOptions";
138 	ZmComposeController.OPTIONS_TT[ZmOperation.REPLY_ALL]		= "replyOptions";
139     ZmComposeController.OPTIONS_TT[ZmOperation.CAL_REPLY]		= "replyOptions";
140 	ZmComposeController.OPTIONS_TT[ZmOperation.CAL_REPLY_ALL]	= "replyOptions";
141 	ZmComposeController.OPTIONS_TT[ZmOperation.FORWARD_ATT]		= "forwardOptions";
142 	ZmComposeController.OPTIONS_TT[ZmOperation.FORWARD_INLINE]	= "forwardOptions";
143 
144 	ZmComposeController.OP_CHECK = {};
145 	ZmComposeController.OP_CHECK[ZmOperation.SHOW_BCC] 	            = true;
146 	ZmComposeController.OP_CHECK[ZmOperation.REQUEST_READ_RECEIPT] 	= true;
147 	ZmComposeController.OP_CHECK[ZmOperation.USE_PREFIX] 			= true;
148 	ZmComposeController.OP_CHECK[ZmOperation.INCLUDE_HEADERS] 		= true;
149 
150 	// Classification hashes for a compose action
151 	ZmComposeController.IS_INVITE_REPLY = {};
152 	ZmComposeController.IS_INVITE_REPLY[ZmOperation.REPLY_ACCEPT]		= true;
153 	ZmComposeController.IS_INVITE_REPLY[ZmOperation.REPLY_CANCEL]		= true;
154 	ZmComposeController.IS_INVITE_REPLY[ZmOperation.REPLY_DECLINE]		= true;
155 	ZmComposeController.IS_INVITE_REPLY[ZmOperation.REPLY_TENTATIVE]	= true;
156 	ZmComposeController.IS_INVITE_REPLY[ZmOperation.REPLY_MODIFY]		= true;
157 	ZmComposeController.IS_INVITE_REPLY[ZmOperation.REPLY_NEW_TIME]		= true;
158 
159 	ZmComposeController.IS_CAL_REPLY = AjxUtil.hashCopy(ZmComposeController.IS_INVITE_REPLY);
160 	ZmComposeController.IS_CAL_REPLY[ZmOperation.CAL_REPLY]		= true;
161 	ZmComposeController.IS_CAL_REPLY[ZmOperation.CAL_REPLY_ALL]	= true;
162 	
163 	ZmComposeController.IS_REPLY = AjxUtil.hashCopy(ZmComposeController.IS_CAL_REPLY);
164 	ZmComposeController.IS_REPLY[ZmOperation.REPLY]		 = true;
165 	ZmComposeController.IS_REPLY[ZmOperation.REPLY_ALL]	 = true;
166 	
167 	ZmComposeController.IS_FORWARD = {};
168 	ZmComposeController.IS_FORWARD[ZmOperation.FORWARD_INLINE]	= true;
169 	ZmComposeController.IS_FORWARD[ZmOperation.FORWARD_ATT]	 	= true;
170 
171 	ZmComposeController.PRIORITY_FLAG_TO_OP = {};
172 	ZmComposeController.PRIORITY_FLAG_TO_OP[ZmItem.FLAG_LOW_PRIORITY]   = ZmOperation.PRIORITY_LOW;
173 	ZmComposeController.PRIORITY_FLAG_TO_OP[ZmItem.FLAG_HIGH_PRIORITY]  = ZmOperation.PRIORITY_HIGH;
174 };
175 
176 //
177 // Public methods
178 //
179 
180 ZmComposeController.getDefaultViewType =
181 function() {
182 	return ZmId.VIEW_COMPOSE;
183 };
184 ZmComposeController.prototype.getDefaultViewType = ZmComposeController.getDefaultViewType;
185 
186 /**
187 * Called by ZmNewWindow.unload to remove ZmSettings listeners (which reside in
188 * the parent window). Otherwise, after the child window is closed, the parent
189 * window is still referencing the child window's compose controller, which has
190 * been unloaded!!
191 * 
192 * @private
193 */
194 ZmComposeController.prototype.dispose =
195 function() {
196 	var settings = appCtxt.getSettings();
197 	if (ZmComposeController.SETTINGS) { //no SETTINGS in child window
198 		for (var i = 0; i < ZmComposeController.SETTINGS.length; i++) {
199 			settings.getSetting(ZmComposeController.SETTINGS[i]).removeChangeListener(this._settingChangeListener);
200 		}
201 	}
202 	this._composeView.dispose();
203 
204 	var app = this.getApp();
205 	app.disposeTreeControllers();
206 	appCtxt.notifyZimlets("onDisposeComposeController", [this]);
207 
208 };
209 
210 /**
211  * Begins a compose session by presenting a form to the user.
212  *
213  * @param {Hash}		params			a hash of parameters:
214  * @param {constant}	action			the new message, reply, forward, or an invite action
215  * @param {Boolean}		inNewWindow		if <code>true</code>, we are in detached window
216  * @param {ZmMailMsg}	msg				the original message (reply/forward), or address (new message)
217  * @param {String}		toOverride 		the initial value for To: field
218  * @param {String}		ccOverride		Cc: addresses (optional)
219  * @param {String}		subjOverride 	the initial value for Subject: field
220  * @param {String}		extraBodyText	the canned text to prepend to body (invites)
221  * @param {AjxCallback}	callback		the callback to run after view has been set
222  * @param {String}		accountName		the on-behalf-of From address
223  * @param {String}		accountName		on-behalf-of From address
224  * @param {boolean}		hideView		if true, don't show compose view
225  */
226 ZmComposeController.prototype.doAction =
227 function(params) {
228 
229 	params = params || {};
230 	var ac = window.parentAppCtxt || window.appCtxt;
231 	
232 	// in zdesktop, it's possible there are no accounts that support smtp
233 	if (ac.isOffline && !ac.get(ZmSetting.OFFLINE_COMPOSE_ENABLED)) {
234 		this._showMsgDialog(ZmComposeController.MSG_DIALOG_1, ZmMsg.composeDisabled, DwtMessageDialog.CRITICAL_STYLE);
235 		return;
236 	}
237 
238 	params.action = params.action || ZmOperation.NEW_MESSAGE;
239 	params.inNewWindow = !appCtxt.isWebClientOffline() && !this.isHidden && (params.inNewWindow || this._app._inNewWindow(params.ev));
240 	this._msgSent = false;
241 	if (params.inNewWindow) {
242         var msgId = (params.msg && params.msg.nId) || Dwt.getNextId();
243 		var newWinObj = ac.getNewWindow(false, ZmComposeController.NEW_WINDOW_WIDTH, ZmComposeController.NEW_WINDOW_HEIGHT, ZmId.VIEW_COMPOSE + "_" + msgId.replace(/\s|\-/g, '_'));
244 		if (newWinObj) {
245 			// this is how child window knows what to do once loading:
246 			newWinObj.command = "compose";
247 			newWinObj.params = params;
248 	        if (newWinObj.win) {
249 	            newWinObj.win.focus();
250 	        }
251 		}
252 	} else {
253 		this._setView(params);
254 		this._listController = params.listController;
255 	}
256 };
257 
258 /**
259  * Toggles the spell check button.
260  * 
261  * @param	{Boolean}	selected		if <code>true</code>, toggle the spell check to "selected"
262  * 
263  */
264 ZmComposeController.prototype.toggleSpellCheckButton =
265 function(selected) {
266 	var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK);
267 	if (spellCheckButton) {
268 		spellCheckButton.setSelected((selected || false));
269 		spellCheckButton.setAttribute('aria-pressed', selected);
270 	}
271 };
272 
273 /**
274  * Detaches compose view to child window.
275  * 
276  */
277 ZmComposeController.prototype.detach =
278 function() {
279 	// bug fix #7192 - disable detach toolbar button
280 	this._toolbar.enable(ZmOperation.DETACH_COMPOSE, false);
281 
282 	var view = this._composeView;
283 	var msg = this._msg || view._origMsg || view._msg;
284 	var subj = view._subjectField.value;
285 	var msgAttId = view._msgAttId; //include original as attachment
286 	var body = this._getBodyContent();
287 	var composeMode = view.getComposeMode();
288 	var backupForm = view.backupForm;
289 	var sendUID = view.sendUID;
290 	var action = view._action || this._action;
291 	var identity = view.getIdentity();
292     var requestReadReceipt = this.isRequestReadReceipt();
293     var selectedIdentityIndex = view.identitySelect && view.identitySelect.getSelectedIndex();
294 
295 	var addrList = {};
296 	for (var i = 0; i < ZmMailMsg.COMPOSE_ADDRS.length; i++) {
297 		var type = ZmMailMsg.COMPOSE_ADDRS[i];
298 		addrList[type] = view.getAddrInputField(type).getAddresses(true);
299 	}
300 
301     var partToAttachmentMap = AjxUtil.map(view._partToAttachmentMap, function(member) {
302        return AjxUtil.hashCopy(member);
303     });
304 
305 	// this is how child window knows what to do once loading:
306     var msgId = (msg && msg.nId) || Dwt.getNextId();
307 	var newWinObj = appCtxt.getNewWindow(false, ZmComposeController.NEW_WINDOW_WIDTH, ZmComposeController.NEW_WINDOW_HEIGHT, ZmId.VIEW_COMPOSE + "_" + msgId.replace(/\s|\-/g, '_'));
308     if (newWinObj && newWinObj.win) {
309         newWinObj.win.focus();
310     }
311 	newWinObj.command = "composeDetach";
312 	newWinObj.params = {
313 		action:			action,
314 		msg:			msg,
315 		addrs:			addrList,
316 		subj:			subj,
317 		priority:		this._getPriority(),
318         attHtml:        view._attcDiv.innerHTML,
319 		msgAttId:		msgAttId,
320         msgIds:         msg && msg.isDraft ? null : this._msgIds,
321 		draftType: 		this._draftType,
322 		draftMsg:		this._draftMsg,
323 		body:			body,
324 		composeMode:	composeMode,
325 		identityId:		selectedIdentityIndex,
326 		accountName:	this._accountName,
327 		backupForm:		backupForm,
328 		sendUID:		sendUID,
329 		sessionId:		this.getSessionId(),
330         readReceipt:	requestReadReceipt,
331 		sigId:			this.getSelectedSignature(),
332         incOptions:     this._curIncOptions,
333         partMap:        partToAttachmentMap,
334         origMsgAtt:     view._origMsgAtt ? AjxUtil.hashCopy(view._origMsgAtt) : null,
335         origAction:     this._origAction
336 	};
337 };
338 
339 ZmComposeController.prototype.popShield =
340 function() {
341 	var dirty = this._composeView.isDirty(true, true);
342 	if (!dirty && (this._draftType != ZmComposeController.DRAFT_TYPE_AUTO)) {
343 		return true;
344 	}
345 
346 	var ps = this._popShield = appCtxt.getYesNoCancelMsgDialog();
347 	if (this._draftType == ZmComposeController.DRAFT_TYPE_AUTO) {
348 		// Message has been saved, but never explicitly by the user.
349 		// Ask if he wants to keep the autosaved draft.
350 		ps.reset();
351 		ps.setMessage(ZmMsg.askSaveAutosavedDraft, DwtMessageDialog.WARNING_STYLE);
352 		if (dirty) {
353 			ps.registerCallback(DwtDialog.YES_BUTTON, this._popShieldYesCallback, this);
354 		} else {
355 			ps.registerCallback(DwtDialog.YES_BUTTON, this._popShieldNoCallback, this);
356 		}
357 		ps.registerCallback(DwtDialog.NO_BUTTON, this._popShieldDiscardCallback, this);
358 		ps.registerCallback(DwtDialog.CANCEL_BUTTON, this._popShieldDismissCallback, this);
359 	} else if (this._canSaveDraft()) {
360 		ps.reset();
361 		ps.setMessage(ZmMsg.askSaveDraft, DwtMessageDialog.WARNING_STYLE);
362 		ps.registerCallback(DwtDialog.YES_BUTTON, this._popShieldYesCallback, this);
363 		ps.registerCallback(DwtDialog.NO_BUTTON, this._popShieldNoCallback, this);
364 		ps.registerCallback(DwtDialog.CANCEL_BUTTON, this._popShieldDismissCallback, this);
365 	} else {
366 		ps = this._popShield = appCtxt.getYesNoMsgDialog();
367 		ps.setMessage(ZmMsg.askLeaveCompose, DwtMessageDialog.WARNING_STYLE);
368 		ps.registerCallback(DwtDialog.YES_BUTTON, this._popShieldYesCallback, this);
369 		ps.registerCallback(DwtDialog.NO_BUTTON, this._popShieldDismissCallback, this);
370 	}
371 	ps.addPopdownListener(this._dialogPopdownListener);
372 	ps.popup();
373 
374 	return false;
375 };
376 
377 // We don't call ZmController._preHideCallback here because it saves the current
378 // focus member, and we want to start over each time
379 ZmComposeController.prototype._preHideCallback = function(view, force) {
380 
381     DBG.println('draft', 'ZmComposeController._preHideCallback for ' + view + ': force = ' + force + ', _dontSavePreHide = ' + this._dontSavePreHide);
382 	if (this._autoSaveTimer) {
383 		//the following is a bit suspicious to me. I assume maybe this method might be called with force == true
384 		//in a way that is not after the popShield was activated? That would be the only explanation to have this.
385 		//I wonder if that's the case that leaves orphan drafts
386 		if (force) {
387 			// auto-save if we leave this compose tab and the message has not yet been sent
388 			// this is a refactoring/fix of code initially from bug 72106 (since it's confusing I mention this bug to keep this knowledge)
389             if (this._dontSavePreHide) {
390                 this._dontSavePreHide = false;
391             }
392             else {
393                 this._autoSaveCallback(true);
394             }
395 		}
396 	}
397 
398 	return force ? true : this.popShield();
399 };
400 
401 ZmComposeController.prototype._preUnloadCallback =
402 function(view) {
403 	return !this._composeView.isDirty(true, true);
404 };
405 
406 
407 ZmComposeController.prototype._preShowCallback = function() {
408 
409     this._composeView.enableInputs(true);
410 
411 	return true;
412 };
413 
414 ZmComposeController.prototype._postShowCallback =
415 function() {
416 	// always reset auto save every time this view is shown. This covers the
417 	// case where a compose tab is inactive and becomes active when user clicks
418 	// on compose tab.
419 	this._initAutoSave();
420 
421 	if (!appCtxt.isChildWindow) {
422 		// no need to "restore" focus between windows
423 		ZmController.prototype._postShowCallback.call(this);
424 	}
425     var view = this._composeView;
426 	var composeMode = view.getComposeMode();
427 	if (this._action != ZmOperation.NEW_MESSAGE &&
428 		this._action != ZmOperation.FORWARD_INLINE &&
429 		this._action != ZmOperation.FORWARD_ATT)
430 	{
431 		if (composeMode == Dwt.HTML) {
432  			setTimeout(view._focusHtmlEditor.bind(view), 100);
433 		}
434 		this._composeView._setBodyFieldCursor();
435 	}
436 };
437 
438 ZmComposeController.prototype._postHideCallback = function() {
439 
440     DBG.println('draft', 'ZmComposeController._postHideCallback for ' + this._currentViewId);
441     if (this._autoSaveTimer) {
442         this._autoSaveTimer.kill();
443     }
444 
445 	// hack to kill the child window when replying to an invite
446 	if (appCtxt.isChildWindow && ZmComposeController.IS_INVITE_REPLY[this._action]) {
447 		window.close();
448 	}
449 };
450 
451 /**
452  * This method gets called if user clicks on mailto link while compose view is
453  * already being used.
454  * 
455  * @private
456  */
457 ZmComposeController.prototype.resetComposeForMailto =
458 function(params) {
459 	if (this._popShield && this._popShield.isPoppedUp()) {
460 		return false;
461 	}
462 
463 	var ps = this._popShield = appCtxt.getYesNoCancelMsgDialog();
464 	ps.reset();
465 	ps.setMessage(ZmMsg.askSaveDraft, DwtMessageDialog.WARNING_STYLE);
466 	ps.registerCallback(DwtDialog.YES_BUTTON, this._popShieldYesCallback, this, params);
467 	ps.registerCallback(DwtDialog.NO_BUTTON, this._popShieldNoCallback, this, params);
468 	ps.registerCallback(DwtDialog.CANCEL_BUTTON, this._popShieldDismissCallback, this);
469 	ps.addPopdownListener(this._dialogPopdownListener);
470 	ps.popup();
471 
472 	return true;
473 };
474 
475 /**
476  * Sends the message represented by the content of the compose view.
477  *
478  * @param	{String}		attId					the id
479  * @param	{constant}		draftType				the draft type (see <code>ZmComposeController.DRAFT_TYPE_</code> constants)
480  * @param	{AjxCallback}	callback				the callback
481  * @param	{Boolean}		processImages           remove webkit-fake-url images and upload data uri images
482  */
483 ZmComposeController.prototype.sendMsg =
484 function(attId, draftType, callback, contactId, processImages) {
485 
486     if (processImages !== false && this._composeView) {
487         //Dont use bind as its arguments cannot be modified before its execution.
488         var processImagesCallback = new AjxCallback(this, this._sendMsg, [attId, null, draftType, callback, contactId]);
489         var result = this._processImages(processImagesCallback);
490         if (result) {
491             return;
492         }
493     }
494 	return this._sendMsg(attId, null, draftType, callback, contactId);
495 };
496 
497 /**
498  * Sends the message represented by the content of the compose view with specified docIds as attachment.
499  * 
500  * @param	{Array}	docIds		the document Ids
501  * @param	{constant}	draftType		the draft type (see <code>ZmComposeController.DRAFT_TYPE_</code> constants)
502  * @param	{AjxCallback}	callback		the callback
503  */
504 ZmComposeController.prototype.sendDocs =
505 function(docIds, draftType, callback, contactId) {
506 	return this._sendMsg(null, docIds, draftType, callback, contactId);
507 };
508 
509 /**
510  * Sends the message represented by the content of the compose view.
511  * 
512  * @private
513  */
514 ZmComposeController.prototype._sendMsg =
515 function(attId, docIds, draftType, callback, contactId) {
516 
517 	var isTimed = Boolean(this._sendTime);
518 	draftType = draftType || (isTimed ? ZmComposeController.DRAFT_TYPE_DELAYSEND : ZmComposeController.DRAFT_TYPE_NONE);
519 	var isDraft = draftType != ZmComposeController.DRAFT_TYPE_NONE;
520 	var isAutoSave = draftType == ZmComposeController.DRAFT_TYPE_AUTO;
521 	// bug fix #38408 - briefcase attachments need to be set *before* calling
522 	// getMsg() but we cannot do that without having a ZmMailMsg to store it in.
523 	// File this one under WTF.
524 	var tempMsg;
525 	if (docIds) {
526 		tempMsg = new ZmMailMsg();
527 		this._composeView.setDocAttachments(tempMsg, docIds);
528 	}
529 	var msg = this._composeView.getMsg(attId, isDraft, tempMsg, isTimed, contactId);
530 
531 	if (!msg) {
532 		return;
533 	}
534 
535 	if (this._autoSaveTimer) {
536         //If this._autoSaveTimer._timer is null then this._autoSaveTimer.kill(); is already called.
537         //Due to browsers cleartimeout taking some time, we are again checking this._autoSaveTimer._timer to prevent unnecessary autosave call
538         //Bug:74148
539         if (isAutoSave && isDraft && !this._autoSaveTimer._timer) {
540             return;
541         }
542 		//kill the timer, no save is attempted while message is pending
543         this._autoSaveTimer.kill();
544 	}
545 
546 	var origMsg = msg._origMsg;
547 	var isCancel = (msg.inviteMode == ZmOperation.REPLY_CANCEL);
548 	var isModify = (msg.inviteMode == ZmOperation.REPLY_MODIFY);
549 
550 	if (isCancel || isModify) {
551 		var appt = origMsg._appt;
552 		var respCallback = this._handleResponseCancelOrModifyAppt.bind(this);
553 		if (isCancel) {
554 			appt.cancel(origMsg._mode, msg, respCallback);
555 		} else {
556 			appt.save();
557 		}
558 		return;
559 	}
560 
561 	var ac = window.parentAppCtxt || window.appCtxt;
562 	var acctName = appCtxt.multiAccounts
563 		? this._composeView.getFromAccount().name : this._accountName;
564 	if (msg.delegatedSenderAddr && !msg.delegatedSenderAddrIsDL) {
565 		acctName = msg.delegatedSenderAddr;
566 	}
567 
568 	if (isDraft) {
569 		if (appCtxt.multiAccounts) {
570 			// for offline, save drafts based on account owner of From: dropdown
571 			acctName = ac.accountList.getAccount(msg.fromSelectValue.accountId).name;
572 		} else {
573 			acctName = ac.getActiveAccount().name;
574 		}
575 		if (msg._origMsg && msg._origMsg.isDraft) {
576 			// if shared folder, make sure we save the draft under the owner account name
577 			var folder = msg.folderId ? ac.getById(msg.folderId) : null;
578 			if (folder && folder.isRemote()) {
579 				acctName = folder.getOwner();
580 			}
581 		}
582 	} else {
583 		// if shared folder, make sure we send the email on-behalf-of
584 		var folder = msg.folderId ? ac.getById(msg.folderId) : null;
585 		if (folder && folder.isRemote() && this._composeView.sendMsgOboIsOK()) {
586 			acctName = folder.getOwner();            
587 		}
588 	}
589 
590 	if (origMsg) {
591 		origMsg.sendAsMe = !this._composeView.sendMsgOboIsOK();
592 	}
593 
594 	// If this message had been saved from draft and it has a sender (meaning
595 	// it's a reply from someone else's account) then get the account name from
596 	// the from field.
597 	if (!acctName && !isDraft && origMsg && origMsg.isDraft) {
598 		if (this._composeView.sendMsgOboIsOK()) {
599 			if (origMsg._addrs[ZmMailMsg.HDR_FROM] &&
600 				origMsg._addrs[ZmMailMsg.HDR_SENDER] &&
601 				origMsg._addrs[ZmMailMsg.HDR_SENDER].size())
602 			{
603 				acctName = origMsg._addrs[ZmMailMsg.HDR_FROM].get(0).address;
604 			}
605 		} else {
606 			origMsg.sendAsMe = true; // hack.
607 		}
608 	}
609 
610 	// check for read receipt
611 	var requestReadReceipt = !this.isHidden && this.isRequestReadReceipt();
612 
613 	var respCallback = this._handleResponseSendMsg.bind(this, draftType, msg, callback);
614 	var errorCallback = this._handleErrorSendMsg.bind(this, draftType, msg);
615 	msg.send(isDraft, respCallback, errorCallback, acctName, null, requestReadReceipt, null, this._sendTime, isAutoSave);
616 	this._resetDelayTime();
617 };
618 
619 ZmComposeController.prototype._handleResponseSendMsg =
620 function(draftType, msg, callback, result) {
621 	var resp = result.getResponse();
622 	// Reset the autosave interval to the default
623 	delete(this._autoSaveInterval);
624 	// Re-enable autosave
625 	if (draftType !== ZmComposeController.DRAFT_TYPE_NONE) {
626 		//only re-init autosave if it's a draft, NOT if it's an actual message send. (user clicked "send").
627 		//In order to avoid a potential race condition, and there's no reason to init auto save anyway.
628 		this._initAutoSave();
629 	}
630 	var needToPop = this._processSendMsg(draftType, msg, resp);
631 
632 //	this._msg = msg;
633 
634 	if (callback) {
635 		callback.run(result);
636 	}
637 
638     if(this.sendMsgCallback) {
639         this.sendMsgCallback.run(result);
640     }
641 
642 	appCtxt.notifyZimlets("onSendMsgSuccess", [this, msg, draftType]);//notify Zimlets on success
643 
644 	if (needToPop) {
645 		this._dontSavePreHide = true;
646 		this._app.popView(true, this._currentView);
647 	}
648 	
649 };
650 
651 ZmComposeController.prototype._handleResponseCancelOrModifyAppt =
652 function() {
653 	this._app.popView(true);
654     appCtxt.setStatusMsg(ZmMsg.messageSent);
655 };
656 
657 ZmComposeController.prototype._handleErrorSendMsg = function(draftType, msg, ex, params) {
658 
659 	if (draftType !== ZmComposeController.DRAFT_TYPE_NONE && !AjxUtil.isUndefined(this._wasDirty)) {
660 		this._composeView._isDirty = this._wasDirty;
661 		delete this._wasDirty;
662 	}
663 
664     var retVal = false;
665 	if (!this.isHidden) {
666 		this.resetToolbarOperations();
667 		this._composeView.enableInputs(true);
668 	}
669 
670     appCtxt.notifyZimlets("onSendMsgFailure", [this, ex, msg]);//notify Zimlets on failure
671     if (ex && ex.code) {
672 	
673         var errorMsg = null;
674         var showMsg = false;
675 		var style = null;
676         if (ex.code === ZmCsfeException.MAIL_SEND_ABORTED_ADDRESS_FAILURE) {
677             var invalid = ex.getData ? ex.getData(ZmCsfeException.MAIL_SEND_ADDRESS_FAILURE_INVALID) : null;
678             var invalidMsg = invalid && invalid.length ? AjxMessageFormat.format(ZmMsg.sendErrorInvalidAddresses, invalid.join(", ")) : null;
679             errorMsg = ZmMsg.sendErrorAbort + "<br/>" +  AjxStringUtil.htmlEncode(invalidMsg);
680             this.popupErrorDialog(errorMsg, ex, true, true, false, true);
681             retVal = true;
682         }
683         else if (ex.code === ZmCsfeException.MAIL_SEND_PARTIAL_ADDRESS_FAILURE) {
684             var invalid = ex.getData ? ex.getData(ZmCsfeException.MAIL_SEND_ADDRESS_FAILURE_INVALID) : null;
685             errorMsg = invalid && invalid.length ? AjxMessageFormat.format(ZmMsg.sendErrorPartial, AjxStringUtil.htmlEncode(invalid.join(", "))) : ZmMsg.sendErrorAbort;
686             showMsg = true;
687         }
688         else if (ex.code == AjxException.CANCELED) {
689             if (draftType === ZmComposeController.DRAFT_TYPE_AUTO) {
690 				//note - this interval is not really used anymore. Only the idle timer is used. This is only used as a boolean checkbox now. I'm pretty sure.
691                 if (!this._autoSaveInterval) {
692                     // Request was cancelled due to a ZmRequestMgr send timeout.
693                     // The server can either be hung or this particular message is taking
694                     // too long to process. Backoff the send interval - restored to
695                     // default upon first successful save
696                     this._autoSaveInterval = appCtxt.get(ZmSetting.AUTO_SAVE_DRAFT_INTERVAL);
697                 }
698                 if (this._autoSaveInterval) {
699                     // Cap the save attempt interval at 5 minutes
700                     this._autoSaveInterval *= 2;
701                     if (this._autoSaveInterval > 300) {
702                         this._autoSaveInterval = 300;
703                     }
704                 }
705             }
706             errorMsg = ZmMsg.cancelSendMsgWarning;
707             this._composeView.setBackupForm();
708             retVal = true;
709         }
710         else if (ex.code === ZmCsfeException.MAIL_QUOTA_EXCEEDED) {
711             errorMsg = ZmMsg.errorQuotaExceeded;
712         }
713         else if (ex.code === ZmCsfeException.MAIL_NO_SUCH_CONTACT) {
714             errorMsg = ZmMsg.vcardContactGone;
715             showMsg = true;
716         }
717 
718         if (this._uploadingProgress){
719             this._initAutoSave();
720 		    this._composeView._resetUpload(true);
721 			if (ex.code === ZmCsfeException.MAIL_MESSAGE_TOO_BIG) {
722 				errorMsg = AjxMessageFormat.format(ZmMsg.attachmentSizeError, AjxUtil.formatSize(appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT)));
723 				style = DwtMessageDialog.WARNING_STYLE;
724                 showMsg = true;
725 			}
726 			else if (ex.code === ZmCsfeException.MAIL_NO_SUCH_MSG) {
727                 // The message was deleted while upload was in progress (likely a discarded draft). Ignore the error.
728                 DBG.println(AjxDebug.DBG1, "Message was deleted while uploading a file; ignore the SaveDraft 'No Such Message' error." );
729                 retVal  = true;
730                 showMsg = false;
731             } else {
732 				errorMsg = errorMsg || ZmMsg.attachingFilesError + "<br>" + (ex.msg || "");
733                 showMsg = true;
734 			}
735         }
736 
737         if (errorMsg && showMsg) {
738 			this._showMsgDialog(ZmComposeController.MSG_DIALOG_1, errorMsg, style || DwtMessageDialog.CRITICAL_STYLE, null, true);
739             retVal = true;
740         }
741     }
742 
743     // Assume the user stays on the compose view, so we need the timer.
744     // (it was canceled when send was called)
745     this._initAutoSave();
746     return retVal;
747 };
748 
749 
750 /**
751  * Creates a new ZmComposeView if one does not already exist
752  */
753 ZmComposeController.prototype.initComposeView =
754 function() {
755 
756 	if (this._composeView) { return; }
757 
758 	if (!this.isHidden) {
759 		this._composeView = new ZmComposeView(this._container, this, this._composeMode, this._action);
760 		var callbacks = {};
761 		callbacks[ZmAppViewMgr.CB_PRE_HIDE]		= this._preHideCallback.bind(this);
762 		callbacks[ZmAppViewMgr.CB_PRE_UNLOAD]	= this._preUnloadCallback.bind(this);
763 		callbacks[ZmAppViewMgr.CB_POST_SHOW]	= this._postShowCallback.bind(this);
764 		callbacks[ZmAppViewMgr.CB_PRE_SHOW]		= this._preShowCallback.bind(this);
765 		callbacks[ZmAppViewMgr.CB_POST_HIDE]	= this._postHideCallback.bind(this);
766 		this._initializeToolBar();
767 		var elements = this.getViewElements(null, this._composeView, this._toolbar);
768 	
769 		this._app.createView({	viewId:		this._currentViewId,
770 								viewType:	this._currentViewType,
771 								elements:	elements,
772 								hide:		this._elementsToHide,
773 								controller:	this,
774 								callbacks:	callbacks,
775 								tabParams:	this._getTabParams()});
776 
777 		if (this._composeView.identitySelect) {
778 			this._composeView.identitySelect.addChangeListener(this._identityChangeListener.bind(this));
779 		}
780 	}
781 	else {
782 		this._composeView = new ZmHiddenComposeView(this, this._composeMode);
783 	}
784 };
785 
786 ZmComposeController.prototype._getTabParams =
787 function() {
788 	return {id:this.tabId, image:"CloseGray", hoverImage:"Close", text:ZmComposeController.DEFAULT_TAB_TEXT, textPrecedence:75,
789 			tooltip:ZmComposeController.DEFAULT_TAB_TEXT, style: DwtLabel.IMAGE_RIGHT};
790 };
791 
792 ZmComposeController.prototype.isTransient =
793 function(oldView, newView) {
794 	return (appCtxt.getViewTypeFromId(newView) == ZmId.VIEW_MAIL_CONFIRM);
795 };
796 
797 ZmComposeController.prototype._identityChangeListener =
798 function(event) {
799 
800 	var cv = this._composeView;
801 	var signatureId = cv._getSignatureIdForAction(null, this._action);
802 
803 	// don't do anything if signature is same
804 	if (signatureId == this._currentSignatureId) { return; }
805 
806 	var okCallback = this._switchIdentityOkCallback.bind(this);
807 	var cancelCallback = this._switchIdentityCancelCallback.bind(this, cv.identitySelect.getValue());
808 	if (!this._warnUserAboutChanges(ZmId.OP_ADD_SIGNATURE, okCallback, cancelCallback)) {
809 		this._switchIdentityOkCallback();
810 	}
811 };
812 
813 ZmComposeController.prototype._switchIdentityOkCallback =
814 function() {
815     if(this._currentDlg) {
816 	    this._currentDlg.popdown();
817     }
818 	this._switchIdentity();
819 };
820 
821 ZmComposeController.prototype._switchIdentityCancelCallback =
822 function(identityId) {
823 	this._currentDlg.popdown();
824 	this._composeView.identitySelect.setSelectedValue(this._currentIdentityId);
825 };
826 
827 ZmComposeController.prototype._switchIdentity =
828 function() {
829 	var identity = this._composeView.getIdentity();
830 	var sigId = this._composeView._getSignatureIdForAction(identity);
831 	this.setSelectedSignature(sigId);
832 	var params = {
833 		action:			this._action,
834 		msg:			this._msg,
835 		extraBodyText:	this._composeView.getUserText(),
836 		keepAttachments: true,
837 		op:				ZmId.OP_ADD_SIGNATURE
838  	};
839 	this._composeView.resetBody(params);
840 	this._setAddSignatureVisibility();
841 	if (identity) {
842 		this.resetIdentity(identity.id);
843 	}
844 	this.resetSignature(sigId);
845 };
846 
847 ZmComposeController.prototype._handleSelectSignature =
848 function(ev) {
849 
850 	var sigId = ev.item.getData(ZmComposeController.SIGNATURE_KEY);
851 	var okCallback = this._switchSignatureOkCallback.bind(this, sigId);
852 	var cancelCallback = this._switchSignatureCancelCallback.bind(this);
853 	//TODO: There shouldn't be a need to warn the user now that we're preserving quoted text and headers.
854 	//(see bugs 91743 and 92086
855 	//However since it's the release time, it's safer to keep warning the user.
856 	//Revisit this after the release.
857 	if (!this._warnUserAboutChanges(ZmId.OP_ADD_SIGNATURE, okCallback, cancelCallback)) {
858 		this._switchSignature(sigId);
859 	}
860 };
861 
862 ZmComposeController.prototype._switchSignatureOkCallback =
863 function(sigId) {
864 	this._currentDlg.popdown();
865 	this._switchSignature(sigId);
866 };
867 
868 ZmComposeController.prototype._switchSignatureCancelCallback =
869 function() {
870 	this._currentDlg.popdown();
871 	this.setSelectedSignature(this._currentSignatureId);
872 };
873 
874 ZmComposeController.prototype._switchSignature =
875 function(sigId) {
876 	this.setSelectedSignature(sigId);
877 	var params = {
878 		keepAttachments: true,
879 		action:			this._action,
880 		msg:			this._msg,
881 		extraBodyText:	this._composeView.getUserText(),
882 		op:				ZmId.OP_ADD_SIGNATURE
883 	};
884 	this._composeView._updateSignatureVcard(this._currentSignatureId, sigId);
885 	this._composeView.resetBody(params);
886 	this.resetSignature(sigId);
887 };
888 
889 /**
890  * Sets the tab stops for the compose form. All address fields are added; they're
891  * not actual tab stops unless they're visible. The textarea for plain text and
892  * the HTML editor for HTML compose are swapped in and out depending on the mode.
893  * 
894  * @private
895  */
896 ZmComposeController.prototype._setComposeTabGroup =
897 function() {
898 	var tg = this._createTabGroup();
899 	var rootTg = appCtxt.getRootTabGroup();
900 	tg.newParent(rootTg);
901 	tg.addMember(this._toolbar);
902 	tg.addMember(this._composeView.getTabGroupMember());
903 };
904 
905 ZmComposeController.prototype.getKeyMapName =
906 function() {
907 	return ZmKeyMap.MAP_COMPOSE;
908 };
909 
910 ZmComposeController.prototype.handleKeyAction =
911 function(actionCode) {
912 	switch (actionCode) {
913 		case ZmKeyMap.CANCEL:
914 			this._cancelCompose();
915 			break;
916 
917 		case ZmKeyMap.SAVE: // Save to draft
918 			if (this._uploadingProgress) {
919 				break;
920 			}
921 			if (this._canSaveDraft()) {
922 				this.saveDraft();
923 			}
924 			break;
925 
926 		case ZmKeyMap.SEND: // Send message
927 			if (!appCtxt.get(ZmSetting.USE_SEND_MSG_SHORTCUT) || this._uploadingProgress) {
928 				break;
929 			}
930 			this._sendListener();
931 			break;
932 
933 		case ZmKeyMap.ATTACHMENT:
934 			this._attachmentListener();
935 			break;
936 
937 		case ZmKeyMap.SPELLCHECK:
938             if (!appCtxt.isSpellCheckerAvailable()) {
939                 break;
940             }
941 			this.toggleSpellCheckButton(true);
942 			this._spellCheckListener();
943 			break;
944 
945 		case ZmKeyMap.HTML_FORMAT:
946 			if (appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED)) {
947 				var mode = this._composeView.getComposeMode();
948 				var newMode = (mode == Dwt.TEXT) ? Dwt.HTML : Dwt.TEXT;
949 				this._setFormat(newMode);
950 				this._setOptionsMenu(newMode);
951 			}
952 			break;
953 
954 		case ZmKeyMap.ADDRESS_PICKER:
955 			this._composeView.getAddressButtonListener(null, AjxEmailAddress.TO);
956 			break;
957 
958 		case ZmKeyMap.NEW_WINDOW:
959 			if (!appCtxt.isChildWindow) {
960 				this._detachListener();
961 			}
962 			break;
963 
964 		default:
965 			return ZmMailListController.prototype.handleKeyAction.call(this, actionCode);
966 			break;
967 	}
968 	return true;
969 };
970 
971 ZmComposeController.prototype.mapSupported =
972 function(map) {
973 	return (map == "editor");
974 };
975 
976 /**
977  * Gets the selected signature.
978  * 
979  * @return	{String}	the selected signature key or <code>null</code> if none selected
980  */
981 ZmComposeController.prototype.getSelectedSignature =
982 function() {
983 	if (!this.isHidden) {
984 		var button = this._getSignatureButton();
985 		var menu = button ? button.getMenu() : null;
986 		if (menu) {
987 			var menuitem = menu.getSelectedItem(DwtMenuItem.RADIO_STYLE);
988 			return menuitem ? menuitem.getData(ZmComposeController.SIGNATURE_KEY) : null;
989 		}
990 	}
991 	else {
992 		// for hidden compose, return the default signature
993 		var ac = window.parentAppCtxt || window.appCtxt;
994 		var collection = ac.getIdentityCollection();
995 		return this._composeView._getSignatureIdForAction(collection.defaultIdentity);
996 	}
997 };
998 
999 /**
1000  * Gets the selected signature.
1001  * 
1002  * @param	{String}	value 	the selected signature key
1003  */
1004 ZmComposeController.prototype.setSelectedSignature =
1005 function(value) {
1006 	var button = this._getSignatureButton();
1007 	var menu = button ? button.getMenu() : null;
1008 	if (menu) {
1009         if (value === ZmIdentity.SIG_ID_NONE) {
1010             value = "";
1011         }
1012 		menu.checkItem(ZmComposeController.SIGNATURE_KEY, value, true);
1013 	}
1014 };
1015 
1016 ZmComposeController.prototype.resetSignature =
1017 function(sigId) {
1018 	this._currentSignatureId = sigId;
1019 };
1020 
1021 ZmComposeController.prototype.resetIdentity =
1022 function(identityId) {
1023 	this._currentIdentityId = identityId;
1024 };
1025 
1026 //
1027 // Protected methods
1028 //
1029 
1030 ZmComposeController.prototype._deleteDraft =
1031 function(delMsg) {
1032 
1033 	if (!delMsg) { return; }
1034     var ac = window.parentAppCtxt || window.appCtxt;
1035     if (delMsg && delMsg.isSent) {
1036       var folder = delMsg.folderId ? ac.getById(delMsg.folderId) : null;
1037 	  if (folder && folder.isRemote() && !folder.isPermAllowed(ZmOrganizer.PERM_DELETE)) {
1038          return;   //remote folder no permission to delete, exit
1039 	  }
1040     }
1041 
1042 	delMsg.doDelete();
1043 };
1044 
1045 /**
1046  * Creates the compose view based on the mode we're in. Lazily creates the
1047  * compose toolbar, a contact picker, and the compose view itself.
1048  *
1049  * @param action		[constant]		new message, reply, forward, or an invite action
1050  * @param msg			[ZmMailMsg]*	the original message (reply/forward), or address (new message)
1051  * @param toOverride 	[string]*		initial value for To: field
1052  * @param subjOverride 	[string]*		initial value for Subject: field
1053  * @param extraBodyText [string]*		canned text to prepend to body (invites)
1054  * @param composeMode	[constant]*		HTML or text compose
1055  * @param accountName	[string]*		on-behalf-of From address
1056  * @param msgIds		[Array]*		list of msg Id's to be added as attachments
1057  * @param readReceipt   [boolean]       true/false read receipt setting
1058  */
1059 ZmComposeController.prototype._setView =
1060 function(params) {
1061 
1062 	if (this._autoSaveTimer) {
1063 		this._autoSaveTimer.kill();
1064 	}
1065 
1066 	// msg is the original msg for a reply or when editing a draft (not a newly saved draft or sent msg)
1067 	var msg = this._msg = params.msg;
1068 	if (msg && msg.isInvite() && ZmComposeController.IS_FORWARD[params.action]) {
1069 		params.action = ZmOperation.FORWARD_INLINE;
1070 	}
1071 	var action = this._action = params.action;
1072     this._origAction = params.origAction;
1073 	
1074 	this._toOverride = params.toOverride;
1075 	this._ccOverride = params.ccOverride;
1076 	this._subjOverride = params.subjOverride;
1077 	this._extraBodyText = params.extraBodyText;
1078 	this._msgIds = params.msgIds;
1079 	this._accountName = params.accountName;
1080 	var identity = params.identity = this._getIdentity(msg);
1081 
1082 	this._composeMode = params.composeMode || this._getComposeMode(msg, identity, params);
1083 
1084     var cv = this._composeView;
1085 	if (!cv) {
1086 		this.initComposeView();
1087 		cv = this._composeView;
1088 	} else {
1089 		cv.setComposeMode(this._composeMode, true);
1090 	}
1091 
1092 	if (identity) {
1093 		this.resetSignature(cv._getSignatureIdForAction(identity, action));
1094 	}
1095 
1096 	if (!this.isHidden) {
1097 		this._initializeToolBar();
1098 		this.resetToolbarOperations();
1099 		this._setOptionsMenu(this._composeMode, params.incOptions);
1100 	}
1101 	cv.set(params);
1102 
1103 	if (!this.isHidden) {
1104 		this._setOptionsMenu();	// reset now that compose view has figured out the inc options
1105 		appCtxt.notifyZimlets("initializeToolbar", [this._app, this._toolbar, this, this._currentViewId], {waitUntilLoaded:true});
1106 		this._setAddSignatureVisibility();
1107 		if (params.sigId) {
1108 			this.setSelectedSignature(params.sigId);
1109 			this.resetSignature(params.sigId);
1110 		}
1111 
1112 		// preserve priority for drafts
1113 		if (appCtxt.get(ZmSetting.MAIL_PRIORITY_ENABLED)) {
1114 			if (msg && action === ZmOperation.DRAFT) {
1115 				var priority = msg.isHighPriority ? ZmItem.FLAG_HIGH_PRIORITY : msg.isLowPriority ? ZmItem.FLAG_LOW_PRIORITY : "";
1116 				if (priority) {
1117 					this._setPriority(priority);
1118 				}
1119 			}
1120 			else {
1121 				this._setPriority();
1122 			}
1123 		}
1124 
1125 		if (params.readReceipt) {
1126 			var menu = this._optionsMenu[action];
1127 			var mi = menu && menu.getOp(ZmOperation.REQUEST_READ_RECEIPT);
1128 			if (mi && this.isReadReceiptEnabled()) {
1129 				mi.setChecked(true, true);
1130 			}
1131 		}
1132 	
1133 		this._setComposeTabGroup();
1134 		if (!params.hideView) {
1135 			this._app.pushView(this._currentViewId);
1136 		}
1137 		if (!appCtxt.isChildWindow) {
1138 			cv.updateTabTitle();
1139 		}
1140 		cv.reEnableDesignMode();
1141 
1142 		this._draftMsg = params.draftMsg;
1143 		this._draftType = params.draftType || ZmComposeController.DRAFT_TYPE_NONE;
1144 		if ((this._msgIds || cv._msgAttId) && !appCtxt.isChildWindow) {
1145 			this.saveDraft(ZmComposeController.DRAFT_TYPE_AUTO);
1146 		}
1147 		else if (msg && (action == ZmOperation.DRAFT)) {
1148 			this._draftType = ZmComposeController.DRAFT_TYPE_MANUAL;
1149 			if (msg.autoSendTime) {
1150 				this.saveDraft(ZmComposeController.DRAFT_TYPE_MANUAL, null, null, msg.setAutoSendTime.bind(msg));
1151 				this._showMsgDialog(ZmComposeController.MSG_DIALOG_1, ZmMsg.messageAutoSaveAborted);
1152 			}
1153 		}
1154 	}
1155 
1156     cv.checkAttachments();
1157     this.sendMsgCallback = params.sendMsgCallback;
1158 
1159 	if (params.callback) {
1160 		params.callback.run(this);
1161 	}
1162 };
1163 
1164 ZmComposeController.prototype._getIdentity =
1165 function(msg) {
1166 	var account = (appCtxt.multiAccounts && appCtxt.getActiveAccount().isMain)
1167 		? appCtxt.accountList.defaultAccount : null;
1168 	var identityCollection = appCtxt.getIdentityCollection(account);
1169 	if (!msg) {
1170 		var ac = window.parentAppCtxt || window.appCtxt;
1171 		var curSearch = ac.getApp(ZmApp.MAIL).currentSearch;
1172 		var folderId = curSearch && curSearch.folderId;
1173 		if (folderId) {
1174 			return identityCollection.selectIdentityFromFolder(folderId);
1175 		}
1176 	} else {
1177 		msg = this._getInboundMsg(msg);
1178 	}
1179 	return (msg && msg.identity) ? msg.identity : identityCollection.selectIdentity(msg);
1180 };
1181 
1182 /**
1183  * find the first message after msg (or msg itself) in the conv, that's inbound (not outbound). This is since inbound ones are
1184  * relevant to the rules to select identify (such as folder rules, outbound could be in "sent" for example, or "to" address rules)
1185  *
1186  * Also, just return the message if it does not have an associated folder - this occurs when a blank message is created in
1187  * Briefcase, for the 'Send as Attachment' command.
1188  * @param msg
1189  * @returns {ZmMailMsg}
1190  */
1191 ZmComposeController.prototype._getInboundMsg =
1192 function(msg) {
1193 	var folder = appCtxt.getById(msg.folderId);
1194 	if (!folder || !folder.isOutbound()) {
1195 		return msg;
1196 	}
1197 	var conv = appCtxt.getById(msg.cid);
1198 	if (!conv || !conv.msgs) {
1199 		return msg;
1200 	}
1201 	//first find the message in the conv.
1202 	var msgs = conv.msgs.getArray();
1203 	for (var i = 0; i < msgs.length; i++) {
1204 		if (msgs[i].id === msg.id) {
1205 			break;
1206 		}
1207 	}
1208 	//now find the first msg after it that's not outbound
1209 	for (i = i + 1; i < msgs.length; i++) {
1210 		var nextMsg = msgs[i];
1211 		folder = appCtxt.getById(nextMsg.folderId);
1212 		if (!folder.isOutbound()) {
1213 			return nextMsg;
1214 		}
1215 	}
1216 	return msg;
1217 };
1218 
1219 ZmComposeController.prototype._initializeToolBar =
1220 function() {
1221 
1222 	if (this._toolbar) { return; }
1223 	
1224 	var buttons = [];
1225 	if (this._canSaveDraft() && appCtxt.get(ZmSetting.MAIL_SEND_LATER_ENABLED)) {
1226 		buttons.push(ZmOperation.SEND_MENU);
1227 	} else {
1228 		buttons.push(ZmOperation.SEND);
1229 	}
1230 
1231 	buttons.push(ZmOperation.CANCEL, ZmOperation.SEP, ZmOperation.SAVE_DRAFT);
1232 
1233 	if (appCtxt.isSpellCheckerAvailable()) {
1234 		buttons.push(ZmOperation.SEP, ZmOperation.SPELL_CHECK);
1235 	}
1236 	buttons.push(ZmOperation.SEP, ZmOperation.COMPOSE_OPTIONS, ZmOperation.FILLER);
1237 
1238 	if (appCtxt.get(ZmSetting.DETACH_COMPOSE_ENABLED) && !appCtxt.isChildWindow && !appCtxt.isWebClientOffline()) {
1239 		buttons.push(ZmOperation.DETACH_COMPOSE);
1240 	}
1241 
1242 	var tb = this._toolbar = new ZmButtonToolBar({
1243 		parent: this._container,
1244 		buttons: buttons,
1245 		className: (appCtxt.isChildWindow ? "ZmAppToolBar_cw" : "ZmAppToolBar") + " ImgSkin_Toolbar itemToolbar",
1246 		context: this._currentViewId
1247 	});
1248 
1249 	for (var i = 0; i < tb.opList.length; i++) {
1250 		var button = tb.opList[i];
1251 		if (this._listeners[button]) {
1252 			tb.addSelectionListener(button, this._listeners[button]);
1253 		}
1254 	}
1255 
1256 	if (appCtxt.get(ZmSetting.SIGNATURES_ENABLED) || appCtxt.multiAccounts) {
1257 		var sc = appCtxt.getSignatureCollection();
1258 		sc.addChangeListener(this._signatureChangeListener.bind(this));
1259 
1260 		var button = tb.getButton(ZmOperation.ADD_SIGNATURE);
1261 		if (button) {
1262 			button.setMenu(new AjxCallback(this, this._createSignatureMenu));
1263 		}
1264 	}
1265 
1266 	var actions = [ZmOperation.NEW_MESSAGE, ZmOperation.REPLY, ZmOperation.FORWARD_ATT, ZmOperation.DECLINE_PROPOSAL, ZmOperation.CAL_REPLY];
1267 	this._optionsMenu = {};
1268 	for (var i = 0; i < actions.length; i++) {
1269 		this._optionsMenu[actions[i]] = this._createOptionsMenu(actions[i]);
1270 	}
1271 	this._optionsMenu[ZmOperation.REPLY_ALL] = this._optionsMenu[ZmOperation.REPLY];
1272     this._optionsMenu[ZmOperation.CAL_REPLY_ALL] = this._optionsMenu[ZmOperation.CAL_REPLY];
1273 	this._optionsMenu[ZmOperation.FORWARD_INLINE] = this._optionsMenu[ZmOperation.FORWARD_ATT];
1274 	this._optionsMenu[ZmOperation.REPLY_CANCEL] = this._optionsMenu[ZmOperation.REPLY_ACCEPT] =
1275 		this._optionsMenu[ZmOperation.DECLINE_PROPOSAL] = this._optionsMenu[ZmOperation.REPLY_DECLINE] = this._optionsMenu[ZmOperation.REPLY_TENTATIVE] =
1276 		this._optionsMenu[ZmOperation.SHARE] = this._optionsMenu[ZmOperation.DRAFT] =
1277 		this._optionsMenu[ZmOperation.NEW_MESSAGE];
1278 
1279 	// change default button style to select for spell check button
1280 	var spellCheckButton = tb.getButton(ZmOperation.SPELL_CHECK);
1281 	if (spellCheckButton) {
1282 		spellCheckButton.setAlign(DwtLabel.IMAGE_LEFT | DwtButton.TOGGLE_STYLE);
1283 	}
1284 
1285 	var button = tb.getButton(ZmOperation.SEND_MENU);
1286 	if (button) {
1287 		var menu = new ZmPopupMenu(button, null, null, this);
1288 		var sendItem = menu.createMenuItem(ZmOperation.SEND, ZmOperation.defineOperation(ZmOperation.SEND));
1289 		sendItem.addSelectionListener(this._listeners[ZmOperation.SEND]);
1290 		var sendLaterItem = menu.createMenuItem(ZmOperation.SEND_LATER, ZmOperation.defineOperation(ZmOperation.SEND_LATER));
1291 		sendLaterItem.addSelectionListener(this._listeners[ZmOperation.SEND_LATER]);
1292 		button.setMenu(menu);
1293 	}
1294 };
1295 
1296 ZmComposeController.prototype._initAutoSave =
1297 function() {
1298 	if (!this._canSaveDraft()) { return; }
1299     if (appCtxt.get(ZmSetting.AUTO_SAVE_DRAFT_INTERVAL)) {
1300         if (!this._autoSaveTimer) {
1301             this._autoSaveTimer = new DwtIdleTimer(ZmMailApp.AUTO_SAVE_IDLE_TIME * 1000, new AjxCallback(this, this._autoSaveCallback));
1302         }
1303         else{
1304             this._autoSaveTimer.resurrect(ZmMailApp.AUTO_SAVE_IDLE_TIME * 1000);
1305         }
1306     }
1307 };
1308 
1309 ZmComposeController.prototype._getOptionsMenu =
1310 function() {
1311 	return this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS).getMenu();
1312 };
1313 
1314 
1315 /**
1316  * returns the signature button - not exactly a button but a menu item in the options menu, that has a sub-menu attached to it.
1317  */
1318 ZmComposeController.prototype._getSignatureButton =
1319 function() {
1320 	var menu = this._getOptionsMenu();
1321 	return menu && menu.getItemById(ZmOperation.MENUITEM_ID, ZmOperation.ADD_SIGNATURE);
1322 };
1323 
1324 // only show signature submenu if the account has at least one signature
1325 ZmComposeController.prototype._setAddSignatureVisibility =
1326 function(account) {
1327 	var ac = window.parentAppCtxt || window.appCtxt;
1328 	if (!ac.get(ZmSetting.SIGNATURES_ENABLED, null, account)) {
1329 		return;
1330 	}
1331 	
1332 	var button = this._getSignatureButton();
1333 	if (button) {
1334 		var visible = ac.getSignatureCollection(account).getSize() > 0;
1335 		button.setVisible(visible);
1336 		button.parent.cleanupSeparators();
1337 	}
1338 	this._setOptionsVisibility();
1339 };
1340 
1341 ZmComposeController.prototype._setOptionsVisibility =
1342 function() {
1343 
1344 	var button = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
1345 	var menu = button.getMenu(),
1346 		opList = menu && menu.opList,
1347 		optionsEmpty = !opList || opList.length === 0;
1348 
1349 	if (opList && opList.length === 1 && opList[0] === ZmOperation.ADD_SIGNATURE) {
1350 		//this is kinda ugly, special case for the signature menu that is empty. It gets hidden instead of removed so it's still here.
1351 		var sigButton = this._getSignatureButton();
1352 		optionsEmpty = !sigButton.getVisible();
1353 	}
1354 	button.setVisible(!optionsEmpty);
1355 };
1356 
1357 ZmComposeController.prototype._createOptionsMenu =
1358 function(action) {
1359 
1360 	var isReply = ZmComposeController.IS_REPLY[action];
1361 	var isCalReply = ZmComposeController.IS_CAL_REPLY[action];
1362 	var isInviteReply = ZmComposeController.IS_INVITE_REPLY[action];
1363 	var isForward = ZmComposeController.IS_FORWARD[action];
1364 	var list = [];
1365     var ac = window.parentAppCtxt || window.appCtxt;
1366 	if (isReply || isCalReply) {
1367 		list.push(ZmOperation.REPLY, ZmOperation.REPLY_ALL, ZmOperation.SEP);
1368 	}
1369 	if (ac.get(ZmSetting.HTML_COMPOSE_ENABLED)) {
1370 		list.push(ZmOperation.FORMAT_HTML, ZmOperation.FORMAT_TEXT, ZmOperation.SEP);
1371 	}
1372 	if (isInviteReply) { // Accept/decline/etc... an appointment invitation
1373 		list.push(ZmOperation.SEP, ZmOperation.INC_NONE, ZmOperation.INC_BODY, ZmOperation.INC_SMART);
1374 	}
1375 	else if (isCalReply) { // Regular reply to an appointment
1376 		list.push(ZmOperation.SEP, ZmOperation.INC_NONE, ZmOperation.INC_BODY, ZmOperation.INC_SMART);
1377 	}
1378 	else if (isReply) { // Message reply
1379         list.push(ZmOperation.SEP, ZmOperation.INC_NONE, ZmOperation.INC_BODY, ZmOperation.INC_SMART, ZmOperation.INC_ATTACHMENT);
1380 	}
1381 	else if (isForward) { // Message forwarding
1382         list.push(ZmOperation.SEP, ZmOperation.INC_BODY, ZmOperation.INC_ATTACHMENT);
1383 	}
1384 
1385     if (isReply || isForward || isCalReply) {
1386         list.push(ZmOperation.SEP, ZmOperation.USE_PREFIX, ZmOperation.INCLUDE_HEADERS);
1387     }
1388 
1389 	if (appCtxt.get(ZmSetting.SIGNATURES_ENABLED)) {
1390 		list.push(ZmOperation.SEP, ZmOperation.ADD_SIGNATURE);
1391 	}
1392 
1393 	list.push(ZmOperation.SEP, ZmOperation.SHOW_BCC);
1394 
1395 	if (appCtxt.get(ZmSetting.MAIL_PRIORITY_ENABLED)) {
1396 		list.push(ZmOperation.SEP);
1397 		list.push(ZmOperation.PRIORITY_HIGH);
1398 		list.push(ZmOperation.PRIORITY_NORMAL);
1399 		list.push(ZmOperation.PRIORITY_LOW);
1400 	}
1401 
1402 	if (ac.get(ZmSetting.MAIL_READ_RECEIPT_ENABLED, null, ac.getActiveAccount())) {
1403 		list.push(ZmOperation.SEP, ZmOperation.REQUEST_READ_RECEIPT);
1404 	}
1405 
1406 	var button = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
1407 
1408 	var overrides = {};
1409 	for (var i = 0; i < list.length; i++) {
1410 		var op = list[i];
1411 		if (op == ZmOperation.SEP) { continue; }
1412 		overrides[op] = {};
1413 		if (ZmComposeController.OP_CHECK[op]) {
1414 			overrides[op].style = DwtMenuItem.CHECK_STYLE;
1415 		} else {
1416 			overrides[op].style = DwtMenuItem.RADIO_STYLE;
1417 			overrides[op].radioGroupId = ZmComposeController.RADIO_GROUP[op];
1418 		}
1419 		if (op == ZmOperation.REPLY || op == ZmOperation.CAL_REPLY) {
1420 			overrides[op].text = ZmMsg.replySender;
1421 		}
1422 	}
1423 
1424 	var menu = new ZmActionMenu({parent:button, menuItems:list, overrides:overrides,
1425 								 context:[this._currentViewId, action].join("_")});
1426 
1427 	for (var i = 0; i < list.length; i++) {
1428 		var op = list[i];
1429 		var mi = menu.getOp(op);
1430 		if (!mi) { continue; }
1431 		if (op == ZmOperation.FORMAT_HTML) {
1432 			mi.setData(ZmHtmlEditor.VALUE, Dwt.HTML);
1433 		} else if (op == ZmOperation.FORMAT_TEXT) {
1434 			mi.setData(ZmHtmlEditor.VALUE, Dwt.TEXT);
1435 		}
1436 		mi.setData(ZmOperation.KEY_ID, op);
1437 		mi.addSelectionListener(this._listeners[ZmOperation.COMPOSE_OPTIONS]);
1438 	}
1439 
1440 	return menu;
1441 };
1442 
1443 ZmComposeController.prototype._setOptionsMenu =
1444 function(composeMode, incOptions) {
1445 
1446 	composeMode = composeMode || this._composeMode;
1447 	incOptions = incOptions || this._curIncOptions || {};
1448     var ac = window.parentAppCtxt || window.appCtxt;
1449 
1450 	var button = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
1451 	button.noMenuBar = true;
1452 	button.setToolTipContent(ZmMsg[ZmComposeController.OPTIONS_TT[this._action]], true);
1453 	var menu = this._optionsMenu[this._action];
1454 	if (!menu) { return; }
1455 
1456 	if (ac.get(ZmSetting.HTML_COMPOSE_ENABLED)) {
1457 		menu.checkItem(ZmHtmlEditor.VALUE, composeMode, true);
1458 	}
1459 
1460 	if (ac.get(ZmSetting.MAIL_READ_RECEIPT_ENABLED) || ac.multiAccounts) {
1461 		var mi = menu.getOp(ZmOperation.REQUEST_READ_RECEIPT);
1462 		if (mi) {
1463 			// did this draft have "request read receipt" option set?
1464 			if (this._msg && this._msg.isDraft) {
1465 				mi.setChecked(this._msg.readReceiptRequested);
1466 			} else {
1467 				// bug: 41329 - always re-init read-receipt option to be off
1468                 //read receipt default state will be based on the preference configured
1469 				mi.setChecked(appCtxt.get(ZmSetting.AUTO_READ_RECEIPT_ENABLED), true);
1470 			}
1471 
1472 			if (ac.multiAccounts) {
1473                 mi.setEnabled(ac.get(ZmSetting.MAIL_READ_RECEIPT_ENABLED, null, this._composeView.getFromAccount()));
1474 			}
1475 		}
1476 	}
1477 
1478 	if (this._action == ZmOperation.REPLY || this._action == ZmOperation.REPLY_ALL  ||
1479         this._action == ZmOperation.CAL_REPLY || this._action == ZmOperation.CAL_REPLY_ALL) {
1480 		menu.checkItem(ZmOperation.KEY_ID, this._action, true);
1481 	}
1482 
1483 	this._setDependentOptions(incOptions);
1484 
1485 	var showBcc = appCtxt.get(ZmSetting.SHOW_BCC);
1486 	var mi = menu.getOp(ZmOperation.SHOW_BCC);
1487 	if (mi) {
1488 		mi.setChecked(showBcc);
1489 		this._composeView._recipients._toggleBccField(showBcc);
1490 	}
1491 
1492 	button.setMenu(menu);
1493 	this._setOptionsVisibility();
1494 };
1495 
1496 ZmComposeController.prototype._setDependentOptions =
1497 function(incOptions) {
1498 
1499 	incOptions = incOptions || this._curIncOptions || {};
1500 
1501 	var menu = this._optionsMenu[this._action];
1502 	if (!menu) { return; }
1503 
1504 	// handle options for what's included
1505 	var what = incOptions.what;
1506 	menu.checkItem(ZmOperation.KEY_ID, ZmComposeController.INC_OP[what], true);
1507 	var allowOptions = (what == ZmSetting.INC_BODY || what == ZmSetting.INC_SMART);
1508 	var mi = menu.getOp(ZmOperation.USE_PREFIX);
1509 	if (mi) {
1510 		mi.setEnabled(allowOptions);
1511 		mi.setChecked(incOptions.prefix, true);
1512 	}
1513 	mi = menu.getOp(ZmOperation.INCLUDE_HEADERS);
1514 	if (mi) {
1515 		mi.setEnabled(allowOptions);
1516 		mi.setChecked(incOptions.headers, true);
1517 	}
1518 	//If we attach multiple messages, disable changing from "include as attachment" to "include original" (inc_body, a.k.a. include inline). Bug 74467
1519 	var incOptionsDisabled = (this._msgIds && this._msgIds.length > 1) || this._origAction === ZmOperation.FORWARD_CONV;
1520 	mi = menu.getOp(ZmOperation.INC_BODY);
1521 	if (mi) {
1522 		mi.setEnabled(!incOptionsDisabled);
1523 	}
1524 	mi = menu.getOp(ZmOperation.INC_ATTACHMENT);
1525 	if (mi) {
1526 		mi.setEnabled(!incOptionsDisabled);
1527 	}
1528 	mi = menu.getOp(ZmOperation.REQUEST_READ_RECEIPT);
1529 	if (mi) {
1530 		var fid = this._msg && this._msg.folderId;
1531 		var ac = window.parentAppCtxt || window.appCtxt;
1532 		var folder = fid ? ac.getById(fid) : null;
1533 		mi.setEnabled(!folder || (folder && !folder.isRemote()));
1534 	}
1535 };
1536 
1537 /**
1538  * Called in multi-account mode, when an account has been changed
1539  */
1540 ZmComposeController.prototype._resetReadReceipt =
1541 function(newAccount) {
1542 	var menu = this._optionsMenu[this._action];
1543 	var mi = menu && menu.getOp(ZmOperation.REQUEST_READ_RECEIPT);
1544 	if (mi) {
1545 		var isEnabled = appCtxt.get(ZmSetting.MAIL_READ_RECEIPT_ENABLED, null, newAccount);
1546 		if (!isEnabled) {
1547 			mi.setChecked(false, true);
1548 		}
1549 		mi.setEnabled(isEnabled);
1550 	}
1551 };
1552 
1553 ZmComposeController.prototype._getComposeMode =
1554 function(msg, identity, params) {
1555 
1556 	// depending on COS/user preference set compose format
1557 	var composeMode = Dwt.TEXT;
1558     var ac = window.parentAppCtxt || window.appCtxt;
1559 	if (ac.get(ZmSetting.HTML_COMPOSE_ENABLED)) {
1560         if (this._action == ZmOperation.NEW_MESSAGE) {
1561             if (ac.get(ZmSetting.COMPOSE_AS_FORMAT) == ZmSetting.COMPOSE_HTML) {
1562                 composeMode = Dwt.HTML;
1563             }
1564         } 
1565 		else if (this._action == ZmOperation.DRAFT) {
1566             if (params && params.isEditAsNew) { //For Edit As New option Bug:73479
1567                 if (ac.get(ZmSetting.COMPOSE_AS_FORMAT) === ZmSetting.COMPOSE_HTML) {
1568                     composeMode = Dwt.HTML;
1569                 }
1570             }
1571             else if (msg && msg.isHtmlMail()) {
1572                 composeMode = Dwt.HTML;
1573             }
1574 		}
1575 		else if (identity) {
1576 			var sameFormat = ac.get(ZmSetting.COMPOSE_SAME_FORMAT);
1577 			var asFormat = ac.get(ZmSetting.COMPOSE_AS_FORMAT);
1578 			if ((!sameFormat && asFormat == ZmSetting.COMPOSE_HTML) ||  (sameFormat && msg && msg.isHtmlMail())) {
1579 				composeMode = Dwt.HTML;
1580 			}
1581 		}
1582 	}
1583 
1584 	return composeMode;
1585 };
1586 
1587 ZmComposeController.prototype._getBodyContent =
1588 function() {
1589 	return this._composeView.getHtmlEditor().getContent();
1590 };
1591 
1592 ZmComposeController.prototype._setFormat =
1593 function(mode) {
1594 
1595 	var curMode = this._composeView.getComposeMode();
1596 	if (mode === curMode) { return false; }
1597 
1598 	var op = (mode === Dwt.HTML) ? ZmOperation.FORMAT_HTML : ZmOperation.FORMAT_TEXT;
1599 	var okCallback = this._formatOkCallback.bind(this, mode);
1600 	var cancelCallback = this._formatCancelCallback.bind(this, curMode);
1601 	//TODO: There shouldn't be a need to warn the user now that we're preserving quoted text and headers.
1602 	//(see bugs 91743 and 92086
1603 	//However since it's the release time, it's safer to keep warning the user.
1604 	//Revisit this after the release.
1605 	if (!this._warnUserAboutChanges(op, okCallback, cancelCallback)) {
1606 		this._composeView.setComposeMode(mode);
1607 		return true;
1608 	}
1609 
1610 	return false;
1611 };
1612 
1613 /**
1614  *
1615  * @return {Boolean} needToPop - whether we need to pop the view
1616  */
1617 ZmComposeController.prototype._processSendMsg =
1618 function(draftType, msg, resp) {
1619 
1620 	this._msgSent = true;
1621 	var isScheduled = draftType == ZmComposeController.DRAFT_TYPE_DELAYSEND;
1622 	var isDraft = (draftType != ZmComposeController.DRAFT_TYPE_NONE && !isScheduled);
1623 	var needToPop = false;
1624 	if (!isDraft) {
1625 		needToPop = true;
1626 		var popped = false;
1627 		if (appCtxt.get(ZmSetting.SHOW_MAIL_CONFIRM)) {
1628 			var confirmController = AjxDispatcher.run("GetMailConfirmController");
1629 			confirmController.showConfirmation(msg, this._currentViewId, this.tabId, this);
1630 			needToPop = false;	// don't pop confirm page
1631 		} else {
1632 			if (appCtxt.isChildWindow && window.parentController) {
1633 				window.onbeforeunload = null;
1634 				if (draftType == ZmComposeController.DRAFT_TYPE_DELAYSEND) {
1635                     window.parentController.setStatusMsg(ZmMsg.messageScheduledSent);
1636                 }
1637                 else if (!appCtxt.isOffline) { // see bug #29372
1638 					window.parentController.setStatusMsg(ZmMsg.messageSent);
1639 				}
1640 			} else {
1641 				if (draftType == ZmComposeController.DRAFT_TYPE_DELAYSEND) {
1642 					appCtxt.setStatusMsg(ZmMsg.messageScheduledSent);
1643 				} else if (!appCtxt.isOffline) { // see bug #29372
1644 					appCtxt.setStatusMsg(ZmMsg.messageSent);
1645 				}
1646 			}
1647 		}
1648 
1649 		if (resp || !appCtxt.get(ZmSetting.SAVE_TO_SENT)) {
1650 
1651 			// bug 36341
1652 			if (!appCtxt.isOffline && resp && appCtxt.get(ZmSetting.SAVE_TO_IMAP_SENT) && msg.identity) {
1653 				var datasources = appCtxt.getDataSourceCollection();
1654 				var datasource = datasources && datasources.getById(msg.identity.id);
1655 				if (datasource && datasource.type == ZmAccount.TYPE_IMAP) {
1656 					var parent = appCtxt.getById(datasource.folderId);
1657 					var folder;
1658 					if (parent) {
1659 						// try to find the sent folder from list of possible choices
1660 						var prefix = parent.getName(false, null, true, true) + "/";
1661 						var folderNames = [
1662 							appCtxt.get(ZmSetting.SENT_FOLDER_NAME) || "Sent",
1663 							ZmMsg.sent, "Sent Messages", "[Gmail]/Sent Mail"
1664 						];
1665 						for (var i = 0; i < folderNames.length; i++) {
1666 							folder = parent.getByPath(prefix+folderNames[i]);
1667 							if (folder) break;
1668 						}
1669 					}
1670 					if (folder) {
1671 						var jsonObj = {
1672 							ItemActionRequest: {
1673 								_jsns:  "urn:zimbraMail",
1674 								action: {
1675 									id:     resp.m[0].id,
1676 									op:     "move",
1677 									l:      folder.id
1678 								}
1679 							}
1680 						};
1681 						var params = {
1682 							jsonObj: jsonObj,
1683 							asyncMode: true,
1684 							noBusyOverlay: true
1685 						};
1686 						appCtxt.getAppController().sendRequest(params);
1687 					}
1688 				}
1689 			}
1690 		}
1691 	} else {
1692 		if (draftType != ZmComposeController.DRAFT_TYPE_AUTO) {
1693 			var transitions = [ ZmToast.FADE_IN, ZmToast.IDLE, ZmToast.PAUSE, ZmToast.FADE_OUT ];
1694 			appCtxt.setStatusMsg(ZmMsg.draftSaved, ZmStatusView.LEVEL_INFO, null, transitions);
1695 		}
1696 		this._draftMsg = msg;
1697 		this._composeView.processMsgDraft(msg);
1698 		// TODO - disable save draft button indicating a draft was saved
1699 
1700 		var listController = this._listController; // non child window case
1701 		if (appCtxt.isChildWindow) {
1702 			//Check if Mail App view has been created and then update the MailListController
1703 			if (window.parentAppCtxt.getAppViewMgr().getAppView(ZmApp.MAIL)) {
1704 				listController = window.parentAppCtxt.getApp(ZmApp.MAIL).getMailListController();
1705 			}
1706 		}
1707 
1708 		//listController is available only when editing an existing draft.
1709 		if (listController && listController._draftSaved) {
1710 			var savedMsg = appCtxt.isChildWindow ? null : msg;
1711 			var savedResp = appCtxt.isChildWindow ? resp.m[0] : null; //Pass the mail response to the parent window such that the ZmMailMsg obj is created in the parent window.
1712 			listController._draftSaved(savedMsg, savedResp);
1713 		}
1714 	}
1715 
1716 	if (isScheduled) {
1717 		if (appCtxt.isChildWindow) {
1718 			var pAppCtxt = window.parentAppCtxt;
1719 			if (pAppCtxt.getAppViewMgr().getAppView(ZmApp.MAIL)) {
1720 				var listController = pAppCtxt.getApp(ZmApp.MAIL).getMailListController();
1721 				if (listController && listController._draftSaved) {
1722 					//Pass the mail response to the parent window such that the ZmMailMsg obj is created in the parent window.
1723 					listController._draftSaved(null, resp.m[0]);
1724 				}
1725 			}
1726 		} else {
1727 			if (this._listController && this._listController._draftSaved) {
1728 				this._listController._draftSaved(msg);
1729 			}
1730 		}
1731 	}
1732 	return needToPop;
1733 };
1734 
1735 
1736 // Listeners
1737 
1738 // Send button was pressed
1739 ZmComposeController.prototype._sendListener =
1740 function(ev) {
1741 	if (!appCtxt.notifyZimlets("onSendButtonClicked", [this, this._msg])) {
1742 	    this._send();
1743     }
1744 };
1745 
1746 ZmComposeController.prototype._send =
1747 function() {
1748 	this._toolbar.enableAll(false); // thwart multiple clicks on Send button
1749 	this._resetDelayTime();
1750 	this.sendMsg();
1751 };
1752 
1753 ZmComposeController.prototype._sendLaterListener =
1754 function(ev) {
1755 	this.showDelayDialog();
1756 };
1757 
1758 // Cancel button was pressed
1759 ZmComposeController.prototype._cancelListener =
1760 function(ev) {
1761 	this._cancelCompose();
1762 };
1763 
1764 ZmComposeController.prototype._cancelCompose = function() {
1765 
1766 	var dirty = this._composeView.isDirty(true, true);
1767     // Prompt the user if compose view is dirty (they may want to save), or if a draft has been
1768     // auto-saved and they might want to delete it
1769 	var needPrompt = dirty || (this._draftType === ZmComposeController.DRAFT_TYPE_AUTO);
1770     this._composeView.enableInputs(!needPrompt);
1771 	this._composeView.reEnableDesignMode();
1772 	this._app.popView(!needPrompt);
1773 };
1774 
1775 // Attachment button was pressed
1776 ZmComposeController.prototype._attachmentListener =
1777 function(isInline) {
1778 	var type =
1779 		isInline ? ZmComposeView.UPLOAD_INLINE : ZmComposeView.UPLOAD_COMPUTER;
1780 	this._composeView.showAttachmentDialog(type);
1781 };
1782 
1783 ZmComposeController.prototype._optionsListener =
1784 function(ev) {
1785 
1786 	var op = ev.item.getData(ZmOperation.KEY_ID);
1787 	if (op === ZmOperation.REQUEST_READ_RECEIPT) {
1788 		return;
1789 	}
1790 
1791 	// Click on "Options" button.
1792 	if (op === ZmOperation.COMPOSE_OPTIONS && this._optionsMenu[this._action]) {
1793 		var button = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
1794 		var bounds = button.getBounds();
1795 		this._optionsMenu[this._action].popup(0, bounds.x, bounds.y + bounds.height, false);
1796 		return;
1797 	}
1798 
1799 	// ignore UNCHECKED for radio buttons
1800 	if (ev.detail !== DwtMenuItem.CHECKED && !ZmComposeController.OP_CHECK[op]) {
1801 		return;
1802 	}
1803 
1804 	if (op === ZmOperation.REPLY || op === ZmOperation.REPLY_ALL || op === ZmOperation.CAL_REPLY || op === ZmOperation.CAL_REPLY_ALL) {
1805 		var cv = this._composeView;
1806 		cv.setAddress(AjxEmailAddress.TO, "");
1807 		cv.setAddress(AjxEmailAddress.CC, "");
1808 		cv._setAddresses(op, AjxEmailAddress.TO, this._toOverride);
1809 		if (this._ccOverride && (op === ZmOperation.REPLY_ALL || op === ZmOperation.CAL_REPLY_ALL)) {
1810 			cv._setAddresses(op, AjxEmailAddress.CC, this._ccOverride);
1811 		}
1812 	}
1813 	else if (op === ZmOperation.FORMAT_HTML || op === ZmOperation.FORMAT_TEXT) {
1814         if (op === ZmOperation.FORMAT_TEXT && this._msg) {
1815             this._msg._resetAllInlineAttachments();
1816         }
1817 		this._setFormat(ev.item.getData(ZmHtmlEditor.VALUE));
1818 	}
1819 	else if (op === ZmOperation.SHOW_BCC) {
1820 		this._composeView._recipients._toggleBccField();
1821 		appCtxt.set(ZmSetting.SHOW_BCC, !appCtxt.get(ZmSetting.SHOW_BCC));
1822 	}
1823 	else if (ZmComposeController.INC_MAP[op] || op === ZmOperation.USE_PREFIX || op === ZmOperation.INCLUDE_HEADERS) {
1824 		// user is changing include options
1825 		if (this._setInclude(op)) {
1826 			this._switchInclude(op);
1827 			this._setDependentOptions();
1828 		}
1829 	}
1830 };
1831 
1832 ZmComposeController.prototype._setInclude =
1833 function(op) {
1834 
1835 	// Only give warning if user has typed text that can't be preserved
1836 	var okCallback = this._switchIncludeOkCallback.bind(this, op);
1837 	var cancelCallback = this._switchIncludeCancelCallback.bind(this, AjxUtil.hashCopy(this._curIncOptions));
1838 	return (!this._warnUserAboutChanges(op, okCallback, cancelCallback));
1839 };
1840 
1841 /**
1842  * Returns the priority flag corresponding to the currently selected priority option.
1843  *
1844  * @returns {string}
1845  * @private
1846  */
1847 ZmComposeController.prototype._getPriority = function() {
1848 
1849 	var menu = this._optionsMenu[this._action],
1850 		map = ZmComposeController.PRIORITY_FLAG_TO_OP;
1851 
1852 	for (var flag in map) {
1853 		var op = map[flag],
1854 			mi = menu && menu.getOp(op);
1855 		if (mi && mi.getChecked()) {
1856 			return flag;
1857 		}
1858 	}
1859 
1860 	return "";
1861 };
1862 
1863 /**
1864  * Sets the priority option in the options menu that corresponds to the given priority flag.
1865  *
1866  * @param {String}  priority        ZmItem.FLAG_*_PRIORITY
1867  * @private
1868  */
1869 ZmComposeController.prototype._setPriority = function(priority) {
1870 
1871 	var op = priority ? ZmComposeController.PRIORITY_FLAG_TO_OP[priority] : ZmOperation.PRIORITY_NORMAL,
1872 		menu = this._optionsMenu[this._action],
1873 		mi = menu && menu.getOp(op);
1874 
1875 	if (mi) {
1876 		mi.setChecked(true, true);
1877 	}
1878 };
1879 
1880 ZmComposeController.prototype._switchInclude = function(op) {
1881 
1882 	var menu = this._optionsMenu[this._action],
1883         cv = this._composeView;
1884 
1885 	if (op === ZmOperation.USE_PREFIX || op === ZmOperation.INCLUDE_HEADERS) {
1886 		var mi = menu.getOp(op);
1887 		if (mi) {
1888 			if (op === ZmOperation.USE_PREFIX) {
1889 				this._curIncOptions.prefix = mi.getChecked();
1890 			}
1891             else {
1892 				this._curIncOptions.headers = mi.getChecked();
1893 			}
1894 		}
1895 	}
1896     else if (ZmComposeController.INC_MAP[op]) {
1897         if (this._curIncOptions.what === ZmSetting.INC_ATTACH) {
1898             cv.removeOrigMsgAtt();
1899         }
1900 		this._curIncOptions.what = ZmComposeController.INC_MAP[op];
1901 	}
1902 
1903 	var cv = this._composeView;
1904 	if (op != ZmOperation.FORMAT_HTML && op != ZmOperation.FORMAT_TEXT) {
1905 		if (cv._composeMode == Dwt.TEXT) {
1906 			AjxTimedAction.scheduleAction(new AjxTimedAction(this, function() { cv.getHtmlEditor().moveCaretToTop(); }), 200);
1907 		}
1908 	}    
1909 
1910 	// forwarding actions are tied to inc option
1911 	var what = this._curIncOptions.what;
1912 	if (this._action == ZmOperation.FORWARD_INLINE && what == ZmSetting.INC_ATTACH) {
1913 		this._action = ZmOperation.FORWARD_ATT;
1914 	}
1915 	if (this._action == ZmOperation.FORWARD_ATT && what != ZmSetting.INC_ATTACH) {
1916 		this._action = ZmOperation.FORWARD_INLINE;
1917 	}
1918 
1919 	var params = {
1920 		action:			    this._action,
1921 		msg:			    this._msg,
1922 		extraBodyText:	    this._composeView.getUserText(),
1923 		op:				    op,
1924         keepAttachments:    true
1925 	};
1926 	this._composeView.resetBody(params);
1927 	if (op === ZmOperation.INC_ATTACHMENT) {
1928 		this.saveDraft(ZmComposeController.DRAFT_TYPE_AUTO);
1929 	}
1930 };
1931 
1932 ZmComposeController.prototype._detachListener =
1933 function(ev) {
1934 	if (!appCtxt.isWebClientOffline()) {
1935 		var atts = this._composeView.getAttFieldValues();
1936 		if (atts.length) {
1937 			this._showMsgDialog(ZmComposeController.MSG_DIALOG_2, ZmMsg.importErrorUpload, null, this._detachCallback.bind(this));
1938 		} else {
1939 			this.detach();
1940 		}
1941 	}
1942 };
1943 
1944 // Save Draft button was pressed
1945 ZmComposeController.prototype._saveDraftListener =
1946 function(ev) {
1947 	this.saveDraft();
1948 };
1949 
1950 ZmComposeController.prototype._autoSaveCallback =
1951 function(idle) {
1952     DBG.println('draft', 'DRAFT autosave check from ' + this._currentViewId);
1953     if (idle && !DwtBaseDialog.getActiveDialog() && !this._composeView.getHtmlEditor().isSpellCheckMode() && this._composeView.isDirty(true, true)) {
1954 		this.saveDraft(ZmComposeController.DRAFT_TYPE_AUTO);
1955 	}
1956 };
1957 
1958 ZmComposeController.prototype.saveDraft =
1959 function(draftType, attId, docIds, callback, contactId) {
1960 
1961 	if (!this._canSaveDraft()) { return; }
1962 
1963 	this._wasDirty = this._composeView._isDirty;
1964 	this._composeView._isDirty = false;
1965 	draftType = draftType || ZmComposeController.DRAFT_TYPE_MANUAL;
1966 	var respCallback = this._handleResponseSaveDraftListener.bind(this, draftType, callback);
1967 	this._resetDelayTime();
1968 	if (!docIds) {
1969         DBG.println('draft', 'SAVE DRAFT for ' + this.getCurrentViewId() + ', type is ' + draftType);
1970 		this.sendMsg(attId, draftType, respCallback, contactId);
1971 	} else {
1972 		this.sendDocs(docIds, draftType, respCallback, contactId);
1973 	}
1974 };
1975 
1976 ZmComposeController.prototype._handleResponseSaveDraftListener =
1977 function(draftType, callback, result) {
1978 	if (draftType == ZmComposeController.DRAFT_TYPE_AUTO &&
1979 		this._draftType == ZmComposeController.DRAFT_TYPE_NONE) {
1980 		this._draftType = ZmComposeController.DRAFT_TYPE_AUTO;
1981 	} else if (draftType == ZmComposeController.DRAFT_TYPE_MANUAL) {
1982 		this._draftType = ZmComposeController.DRAFT_TYPE_MANUAL;
1983 	}
1984 //	this._action = ZmOperation.DRAFT;
1985     // Notify the htmlEditor that the draft has been saved and is not dirty any more.
1986     this._composeView._htmlEditor.clearDirty();
1987 	if (draftType === ZmComposeController.DRAFT_TYPE_MANUAL) {
1988 		this._setCancelText(ZmMsg.close)
1989 	}
1990 
1991 	if (callback) {
1992 		callback.run(result);
1993 	}
1994 };
1995 
1996 ZmComposeController.prototype._spellCheckListener = function(ev) {
1997 
1998 	var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK);
1999 	var htmlEditor = this._composeView.getHtmlEditor();
2000 
2001     if (spellCheckButton) {
2002         if (spellCheckButton.isToggled()) {
2003             var callback = this.toggleSpellCheckButton.bind(this);
2004             if (!htmlEditor.spellCheck(callback)) {
2005                 this.toggleSpellCheckButton(false);
2006             }
2007         } else {
2008             htmlEditor.discardMisspelledWords();
2009         }
2010     }
2011 };
2012 
2013 ZmComposeController.prototype.showDelayDialog =
2014 function() {
2015 	if (!this._delayDialog) {
2016 		this._delayDialog = new ZmTimeDialog({parent:this._shell, buttons:[DwtDialog.OK_BUTTON, DwtDialog.CANCEL_BUTTON]});
2017 		this._delayDialog.setButtonListener(DwtDialog.OK_BUTTON, this._handleDelayDialog.bind(this));
2018 	}
2019 	this._delayDialog.popup();
2020 };
2021 
2022 ZmComposeController.prototype._handleDelayDialog =
2023 function() {
2024 	this._delayDialog.popdown();
2025 	var time = this._delayDialog.getValue(); //Returns {date: Date, timezone: String (see AjxTimezone)}
2026 
2027 	var date = time.date;
2028 	var dateOffset = AjxTimezone.getOffset(AjxTimezone.getClientId(time.timezone), date);
2029 	var utcDate = new Date(date.getTime() - dateOffset*60*1000);
2030 
2031 	var now = new Date();
2032 	var nowOffset = AjxTimezone.getOffset(AjxTimezone.DEFAULT_RULE, now);
2033 	var utcNow = new Date(now.getTime() - nowOffset*60*1000);
2034 
2035     if(!this._delayDialog.isValidDateStr()){
2036         this.showInvalidDateDialog();
2037     }
2038 	else if (utcDate < utcNow) {
2039 		this.showDelayPastDialog();
2040 	} else {
2041 		this._toolbar.enableAll(false); // thwart multiple clicks on Send button
2042 		this._sendTime = time;
2043 		this.sendMsg(null, ZmComposeController.DRAFT_TYPE_DELAYSEND, null);
2044 	}
2045 };
2046 
2047 ZmComposeController.prototype.showDelayPastDialog =
2048 function() {
2049 	this._showMsgDialog(ZmComposeController.MSG_DIALOG_2, ZmMsg.sendLaterPastError, null, this._handleDelayPastDialog.bind(this));
2050 };
2051 
2052 ZmComposeController.prototype.showInvalidDateDialog =
2053 function() {
2054 	this._showMsgDialog(ZmComposeController.MSG_DIALOG_1, ZmMsg.invalidDateFormat, DwtMessageDialog.CRITICAL_STYLE, this._handleInvalidDateDialog.bind(this));
2055 };
2056 
2057 ZmComposeController.prototype._handleInvalidDateDialog =
2058 function() {
2059 	this._currentDlg.popdown();
2060     this._sendLaterListener();
2061 }
2062 
2063 ZmComposeController.prototype._handleDelayPastDialog =
2064 function() {
2065 	this._currentDlg.popdown();
2066 	this._send();
2067 };
2068 
2069 ZmComposeController.prototype._resetDelayTime =
2070 function() {
2071 	this._sendTime = null;
2072 };
2073 
2074 // Callbacks
2075 
2076 ZmComposeController.prototype._detachCallback =
2077 function() {
2078 	// get rid of any lingering attachments since they cannot be detached
2079 	this._composeView.cleanupAttachments();
2080 	this._currentDlg.popdown();
2081 	this.detach();
2082 };
2083 
2084 ZmComposeController.prototype._formatOkCallback =
2085 function(mode) {
2086 	this._currentDlg.popdown();
2087 	this._composeView.setComposeMode(mode);
2088 	this._composeView._isDirty = true;
2089 };
2090 
2091 ZmComposeController.prototype._formatCancelCallback =
2092 function(mode) {
2093 	this._currentDlg.popdown();
2094 
2095 	// reset the radio button for the format button menu
2096 	var menu = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS).getMenu();
2097 	menu.checkItem(ZmHtmlEditor.VALUE, mode, true);
2098 
2099 	this._composeView.reEnableDesignMode();
2100 };
2101 
2102 /**
2103  * Called as: Yes, save as draft
2104  * 			  Yes, go ahead and leave compose
2105  * 			  Yes, keep the auto-saved draft (view is dirty, new draft will be saved)
2106  *
2107  * @param mailtoParams		[Object]*	Used by offline client to pass on mailto handler params
2108  */
2109 ZmComposeController.prototype._popShieldYesCallback = function(mailtoParams) {
2110 
2111 	this._popShield.removePopdownListener(this._dialogPopdownListener);
2112 	this._popShield.popdown();
2113 	this._composeView.enableInputs(true);
2114 	if (this._canSaveDraft()) {
2115 		// save as draft
2116 		var callback = mailtoParams ? this.doAction.bind(this, mailtoParams) : this._popShieldYesDraftSaved.bind(this);
2117 		this._resetDelayTime();
2118 		this.sendMsg(null, ZmComposeController.DRAFT_TYPE_MANUAL, callback);
2119 	}
2120     else {
2121 		// cancel
2122 		if (appCtxt.isChildWindow && window.parentController) {
2123 			window.onbeforeunload = null;
2124 		}
2125 		if (mailtoParams) {
2126 			this.doAction(mailtoParams);
2127 		}
2128         else {
2129             this._dontSavePreHide = true;
2130 			appCtxt.getAppViewMgr().showPendingView(true);
2131 		}
2132 	}
2133 };
2134 
2135 ZmComposeController.prototype._popShieldYesDraftSaved =
2136 function() {
2137 	appCtxt.getAppViewMgr().showPendingView(true);
2138 };
2139 
2140 /**
2141  * Called as: No, don't save as draft
2142  * 			  No, don't leave compose
2143  * 			  Yes, keep the auto-saved draft (view is not dirty, no need to save again)
2144  *
2145  * @param mailtoParams		[Object]*	Used by offline client to pass on mailto handler params
2146  */
2147 ZmComposeController.prototype._popShieldNoCallback = function(mailtoParams) {
2148 
2149 	this._popShield.removePopdownListener(this._dialogPopdownListener);
2150 	this._popShield.popdown();
2151 	this._composeView.enableInputs(true);
2152     this._dontSavePreHide = true;
2153 
2154 	if (this._canSaveDraft()) {
2155 		if (appCtxt.isChildWindow && window.parentController) {
2156 			window.onbeforeunload = null;
2157 		}
2158 		if (!mailtoParams) {
2159 			appCtxt.getAppViewMgr().showPendingView(true);
2160 		}
2161 	}
2162     else {
2163 		if (!mailtoParams) {
2164 			appCtxt.getAppViewMgr().showPendingView(false);
2165 		}
2166 		this._composeView.reEnableDesignMode();
2167 	}
2168 
2169 	if (mailtoParams) {
2170 		this.doAction(mailtoParams);
2171 	}
2172 };
2173 
2174 // Called as: No, do not keep the auto-saved draft
2175 ZmComposeController.prototype._popShieldDiscardCallback =
2176 function() {
2177 	this._deleteDraft(this._draftMsg);
2178 	this._popShieldNoCallback();
2179 };
2180 
2181 // Called as: I changed my mind, just make the pop shield go away
2182 ZmComposeController.prototype._popShieldDismissCallback =
2183 function() {
2184 	this._popShield.removePopdownListener(this._dialogPopdownListener);
2185 	this._popShield.popdown();
2186 	this._cancelViewPop();
2187 	this._initAutoSave(); //re-init autosave since it was killed in preHide
2188 	
2189 };
2190 
2191 ZmComposeController.prototype._switchIncludeOkCallback =
2192 function(op) {
2193 	this._currentDlg.popdown();
2194 	this._switchInclude(op);
2195 	this._setDependentOptions();
2196 };
2197 
2198 ZmComposeController.prototype._switchIncludeCancelCallback =
2199 function(origIncOptions) {
2200 	this._currentDlg.popdown();
2201 	this._setOptionsMenu(null, origIncOptions);
2202 };
2203 
2204 /**
2205  * Handles re-enabling inputs if the pop shield is dismissed via
2206  * Esc. Otherwise, the handling is done explicitly by a callback.
2207  */
2208 ZmComposeController.prototype._dialogPopdownActionListener =
2209 function() {
2210 	this._cancelViewPop();
2211 };
2212 
2213 ZmComposeController.prototype._cancelViewPop =
2214 function() {
2215 	this._composeView.enableInputs(true);
2216 	appCtxt.getAppViewMgr().showPendingView(false);
2217 	this._composeView.reEnableDesignMode();
2218 };
2219 
2220 ZmComposeController.prototype._getDefaultFocusItem =
2221 function() {
2222 	if (this._action == ZmOperation.NEW_MESSAGE ||
2223 		this._action == ZmOperation.FORWARD_INLINE ||
2224 		this._action == ZmOperation.FORWARD_ATT)
2225 	{
2226 		return this._composeView.getAddrInputField(AjxEmailAddress.TO);
2227 	}
2228 
2229 	return (this._composeView.getComposeMode() == Dwt.TEXT)
2230 		? this._composeView._bodyField
2231 		: this._composeView._htmlEditor;
2232 };
2233 
2234 ZmComposeController.prototype._createSignatureMenu =
2235 function(button, account) {
2236 	if (!this._composeView) { return null; }
2237 
2238 	var button = this._getSignatureButton();
2239 	if (!button) { return null; }
2240 
2241 	var menu;
2242 	var options = appCtxt.getSignatureCollection(account).getSignatureOptions();
2243 	if (options.length > 0) {
2244 		menu = new DwtMenu({parent:button});
2245 		var listener = this._handleSelectSignature.bind(this);
2246 		var radioId = this._composeView._htmlElId + "_sig";
2247 		for (var i = 0; i < options.length; i++) {
2248 			var option = options[i];
2249 			var menuitem = new DwtMenuItem({parent:menu, style:DwtMenuItem.RADIO_STYLE, radioGroupId:radioId});
2250 			menuitem.setText(AjxStringUtil.htmlEncode(option.displayValue));
2251 			menuitem.setData(ZmComposeController.SIGNATURE_KEY, option.value);
2252 			menuitem.addSelectionListener(listener);
2253 			menu.checkItem(ZmComposeController.SIGNATURE_KEY, option.value, option.selected);
2254 		}
2255 	}
2256 	return menu;
2257 };
2258 
2259 ZmComposeController.prototype._signatureChangeListener =
2260 function(ev) {
2261 	this.resetSignatureToolbar(this.getSelectedSignature());
2262 };
2263 
2264 /**
2265  * Resets the signature dropdown based on the given account and selects the
2266  * given signature if provided.
2267  *
2268  * @param selected	[String]*			ID of the signature to select
2269  * @param account	[ZmZimbraAccount]*	account for which to load signatures
2270  */
2271 ZmComposeController.prototype.resetSignatureToolbar =
2272 function(selected, account) {
2273 	var button = this._getSignatureButton();
2274 	if (!button) {
2275 		return;
2276 	}
2277 	var previousMenu = button.getMenu();
2278 	previousMenu &&	previousMenu.dispose();
2279 
2280 	var menu = this._createSignatureMenu(null, account);
2281 	if (menu) {
2282 		button.setMenu(menu);
2283 		this.setSelectedSignature(selected || "");
2284 	}
2285 
2286 	this._setAddSignatureVisibility(account);
2287 };
2288 
2289 ZmComposeController.prototype.resetToolbarOperations =
2290 function() {
2291 	if (this.isHidden) { return; }
2292 	this._toolbar.enableAll(true);
2293 	if (ZmComposeController.IS_INVITE_REPLY[this._action]) {
2294 		var ops = [ ZmOperation.SAVE_DRAFT, ZmOperation.ATTACHMENT ];
2295 		this._toolbar.enable(ops, false);
2296         this._composeView.enableAttachButton(false);
2297 	} else {
2298         this._composeView.enableAttachButton(true);
2299     }
2300 
2301 	this._setCancelText(this._action === ZmId.OP_DRAFT ? ZmMsg.close : ZmMsg.cancel);
2302 
2303 	appCtxt.notifyZimlets("resetToolbarOperations", [this._toolbar, 1]);
2304 };
2305 
2306 ZmComposeController.prototype._setCancelText =
2307 function(text) {
2308 	var cancel = this._toolbar.getButton(ZmOperation.CANCEL);
2309 	cancel.setText(text);
2310 };
2311 
2312 ZmComposeController.prototype._canSaveDraft =
2313 function() {
2314 	return !this.isHidden && appCtxt.get(ZmSetting.SAVE_DRAFT_ENABLED) && !ZmComposeController.IS_INVITE_REPLY[this._action];
2315 };
2316 
2317 /*
2318  * Return true/false if read receipt is being requested
2319  */
2320 ZmComposeController.prototype.isRequestReadReceipt =
2321 function(){
2322 
2323   	// check for read receipt
2324 	var requestReadReceipt = false;
2325     var isEnabled = this.isReadReceiptEnabled();
2326 	if (isEnabled) {
2327 		var menu = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS).getMenu();
2328 		if (menu) {
2329 			var mi = menu.getItemById(ZmOperation.KEY_ID, ZmOperation.REQUEST_READ_RECEIPT);
2330 			requestReadReceipt = (!!(mi && mi.getChecked()));
2331 		}
2332 	}
2333 
2334     return requestReadReceipt;
2335 };
2336 
2337 /*
2338  * Return true/false if read receipt is enabled
2339  */
2340 ZmComposeController.prototype.isReadReceiptEnabled =
2341 function(){
2342     var acctName = appCtxt.multiAccounts
2343 		? this._composeView.getFromAccount().name : this._accountName;
2344     var acct = acctName && appCtxt.accountList.getAccountByName(acctName);
2345     if (appCtxt.get(ZmSetting.MAIL_READ_RECEIPT_ENABLED, null, acct)){
2346         return true;
2347     }
2348 
2349     return false;
2350 };
2351 
2352 /**
2353  * Return ZmMailMsg object
2354  * @return {ZmMailMsg} message object
2355  */
2356 ZmComposeController.prototype.getMsg =
2357 function(){
2358     return this._msg;
2359 };
2360 
2361 ZmComposeController.prototype._processImages =
2362 function(callback){
2363 
2364     var idoc = this._composeView._getIframeDoc();//editor iframe document
2365     if (!idoc) {
2366         return;
2367     }
2368 
2369     var imgArray = idoc.getElementsByTagName("img"),
2370         length = imgArray.length;
2371     if (length === 0) {//No image elements in the editor document
2372         return;
2373     }
2374 
2375     var isWebkitFakeURLImage = false;
2376     for (var i = 0; i < length; i++) {
2377         var img = imgArray[i],
2378             imgSrc = img.src;
2379 
2380         if (imgSrc && imgSrc.indexOf("webkit-fake-url://") === 0) {
2381             img.parentNode.removeChild(img);
2382             length--;
2383             i--;
2384             isWebkitFakeURLImage = true;
2385         }
2386     }
2387 
2388     if (isWebkitFakeURLImage) {
2389         appCtxt.setStatusMsg(ZmMsg.invalidPastedImages);
2390         if (length === 0) {//No image elements in the editor document
2391             return;
2392         }
2393     }
2394 
2395     return this._processDataURIImages(imgArray, length, callback);
2396 };
2397 
2398 ZmComposeController.prototype._processDataURIImages =
2399 function (imgArray, length, callback) {
2400 
2401     if ( !(typeof window.atob === "function" && typeof window.Blob === "function") || appCtxt.isWebClientOffline()) {
2402         return;
2403     }
2404 
2405     for (var i = 0, blobArray = []; i < length; i++) {
2406         var img = imgArray[i];
2407         if (img) {
2408             var imgSrc = img.src;
2409             if (imgSrc && imgSrc.indexOf("data:") !== -1) {
2410                 var blob = AjxUtil.dataURItoBlob(imgSrc);
2411                 if (blob) {
2412                     //setting data-zim-uri attribute for image replacement in callback
2413                     var id = Dwt.getNextId();
2414                     img.setAttribute("id", id);
2415                     img.setAttribute("data-zim-uri", id);
2416                     blob.id = id;
2417                     blobArray.push(blob);
2418                 }
2419             }
2420         }
2421     }
2422 
2423     length = blobArray.length;
2424     if (length === 0) {
2425         return;
2426     }
2427 
2428     this._uploadedImageArray = [];
2429     this._dataURIImagesLength = length;
2430 
2431     for (i = 0; i < length; i++) {
2432         var blob = blobArray[i];
2433         var uploadImageCallback = this._handleUploadImage.bind(this, callback, blob.id);
2434         this._uploadImage(blob, uploadImageCallback);
2435     }
2436     return true;
2437 };
2438 
2439 ZmComposeController.prototype._initUploadMyComputerFile =
2440 function(files) {
2441     if (appCtxt.isWebClientOffline()) {
2442         return this._handleOfflineUpload(files);
2443     } else {
2444         var curView = this._composeView;
2445         if (curView) {
2446             var params = {
2447 				attachment:            true,
2448                 files:                 files,
2449                 notes:                 "",
2450                 allResponses:          null,
2451                 start:                 0,
2452                 curView:               this._composeView,
2453                 preAllCallback:        this._preUploadAll.bind(this),
2454                 initOneUploadCallback: curView._startUploadAttachment.bind(curView),
2455                 errorCallback:         curView._resetUpload.bind(curView, true),
2456                 completeOneCallback:   curView.updateAttachFileNode.bind(curView),
2457                 completeAllCallback:   this._completeAllUpload.bind(this)
2458             }
2459 			params.progressCallback =  curView._uploadFileProgress.bind(curView, params);
2460 
2461 
2462 			// Do a SaveDraft at the start, since we will suppress autosave during the upload
2463             var uploadManager = appCtxt.getZmUploadManager();
2464             var uploadCallback = uploadManager.upload.bind(uploadManager, params);
2465             this.saveDraft(ZmComposeController.DRAFT_TYPE_AUTO, null, null, uploadCallback);
2466         }
2467     }
2468 };
2469 
2470 ZmComposeController.prototype._preUploadAll =
2471 function(fileName) {
2472     var curView = this._composeView;
2473     if (!curView) {
2474         return;
2475     }
2476     curView._initProgressSpan(fileName);
2477     // Disable autosave while uploading
2478     if (this._autoSaveTimer) {
2479         this._autoSaveTimer.kill();
2480     }
2481 };
2482 
2483 ZmComposeController.prototype._completeAllUpload =
2484 function(allResponses) {
2485     var curView = this._composeView;
2486     if (!curView){
2487         return;
2488     }
2489     var callback = curView._resetUpload.bind(curView);
2490     // Init autosave, otherwise saveDraft thinks this is a suppressed autosave, and aborts w/o saving
2491     this._initAutoSave();
2492     this.saveDraft(ZmComposeController.DRAFT_TYPE_AUTO, this._syncPrevData(allResponses), null, callback);
2493 }
2494 
2495 ZmComposeController.prototype._syncPrevData =
2496 function(attaData){
2497     var result = []
2498     for (var i=0;i < attaData.length  ; i++){
2499         if (attaData[i] && (i === attaData.length -1 || document.getElementById(attaData[i].aid))){
2500             result.push(attaData[i]);
2501         }
2502     }
2503 
2504     return result;
2505 };
2506 
2507 ZmComposeController.prototype._uploadImage = function(blob, callback, errorCallback){
2508     var req = new XMLHttpRequest();
2509     req.open("POST", appCtxt.get(ZmSetting.CSFE_ATTACHMENT_UPLOAD_URI)+"?fmt=extended,raw", true);
2510     req.setRequestHeader("Cache-Control", "no-cache");
2511     req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
2512     req.setRequestHeader("Content-Type", blob.type);
2513     req.setRequestHeader("Content-Disposition", 'attachment; filename="' + AjxUtil.convertToEntities(blob.name) + '"');
2514     if (window.csrfToken) {
2515         req.setRequestHeader("X-Zimbra-Csrf-Token", window.csrfToken);
2516     }
2517     req.onreadystatechange = function() {
2518         if (req.readyState === 4) {
2519             if (req.status === 200) {
2520                 var resp = eval("["+req.responseText+"]");
2521                 callback.run(resp[2]);
2522             }
2523             else {
2524                 errorCallback && errorCallback();
2525             }
2526         }
2527     };
2528     req.send(blob);
2529 };
2530 
2531 ZmComposeController.prototype._handleUploadImage = function(callback, id, response){
2532     this._dataURIImagesLength--;
2533     var uploadedImageArray = this._uploadedImageArray;
2534     if( response && id ){
2535         response[0]["id"] = id;
2536         uploadedImageArray.push(response[0]);
2537     }
2538     if( this._dataURIImagesLength === 0 && callback ){
2539         if( uploadedImageArray.length > 0 && callback.args ){
2540             uploadedImageArray.clipboardPaste = true;
2541             if(callback.args[0]){  //attid argument of _sendMsg method
2542                 callback.args[0] = callback.args[0].concat(uploadedImageArray);
2543             }
2544             else{
2545                 callback.args[0] = uploadedImageArray;
2546             }
2547         }
2548         callback.run();
2549         delete this._uploadedImageArray;
2550         delete this._dataURIImagesLength;
2551     }
2552 };
2553 
2554 ZmComposeController.prototype._warnUserAboutChanges =
2555 function(op, okCallback, cancelCallback) {
2556 
2557 	var cv = this._composeView;
2558 	var switchToText = (op === ZmOperation.FORMAT_TEXT &&
2559 	                    !AjxUtil.isEmpty(this._getBodyContent()));
2560 	var willLoseChanges = cv.componentContentChanged(ZmComposeView.BC_SIG_PRE) ||
2561 						  cv.componentContentChanged(ZmComposeView.BC_DIVIDER) ||
2562 						  cv.componentContentChanged(ZmComposeView.BC_HEADERS) ||
2563 						  cv.componentContentChanged(ZmComposeView.BC_SIG_POST) ||
2564 						  !cv.canPreserveQuotedText(op) ||
2565 						  switchToText;
2566 	if (willLoseChanges) {
2567 		var callbacks = {};
2568 		callbacks[DwtDialog.OK_BUTTON] = okCallback;
2569 		callbacks[DwtDialog.CANCEL_BUTTON] = cancelCallback;
2570 		var msg = (willLoseChanges && switchToText) ? ZmMsg.switchIncludeAndFormat : 
2571 				willLoseChanges ? ZmMsg.switchIncludeWarning : ZmMsg.switchToText;
2572 		this._showMsgDialog(ZmComposeController.MSG_DIALOG_2, msg, null, callbacks);
2573 		return true;
2574 	}
2575 
2576 	return false;
2577 };
2578 
2579 ZmComposeController.prototype._showMsgDialog =
2580 function(dlgType, msg, style, callbacks) {
2581 
2582 	var ac = window.appCtxt;
2583 	var dlg = this._currentDlg = (dlgType === ZmComposeController.MSG_DIALOG_1) ? ac.getMsgDialog() :
2584 								 (dlgType === ZmComposeController.MSG_DIALOG_2) ? ac.getOkCancelMsgDialog() : ac.getYesNoCancelMsgDialog();
2585 	dlg.reset();
2586 	if (msg) {
2587 		dlg.setMessage(msg, style || DwtMessageDialog.WARNING_STYLE);
2588 	}
2589 	if (callbacks) {
2590 		if (typeof callbacks === "function") {
2591 			var cb = {};
2592 			cb[DwtDialog.OK_BUTTON] = callbacks;
2593 			callbacks = cb;
2594 		}
2595 		for (var buttonId in callbacks) {
2596 			dlg.registerCallback(buttonId, callbacks[buttonId]);
2597 		}
2598 	}
2599 	dlg.popup();
2600 };
2601 
2602 ZmComposeController.prototype._handleOfflineUpload =
2603 function(files) {
2604     var callback = this._readFilesAsDataURLCallback.bind(this);
2605     ZmComposeController.readFilesAsDataURL(files, callback);
2606 };
2607 
2608 /**
2609  * Read files in DataURL Format and execute the callback with param dataURLArray.
2610  *
2611  * dataURLArray is an array of objects, with each object containing name, type, size and data in data-url format for an file.
2612  *
2613  * @param {FileList} files                  Object containing one or more files
2614  * @param {AjxCallback/Bound} callback	    the success callback
2615  * @param {AjxCallback/Bound} errorCallback the error callback
2616  *
2617  * @public
2618  */
2619 ZmComposeController.readFilesAsDataURL =
2620 function(files, callback, errorCallback) {
2621     var i = 0,
2622         filesLength = files.length,
2623         dataURLArray = [],
2624         fileReadErrorCount = 0;
2625 
2626     if (!window.FileReader || filesLength === 0) {
2627         if (errorCallback) {
2628             errorCallback.run(dataURLArray, fileReadErrorCount);
2629         }
2630         return;
2631     }
2632 
2633     for (; i < filesLength; i++) {
2634 
2635         var file = files[i],
2636             reader = new FileReader();
2637 
2638         reader.onload = function(file, ev) {
2639             dataURLArray.push(
2640                 {
2641                     filename : file.name,
2642                     ct : file.type,
2643                     s : file.size,
2644                     data : ev.target.result,
2645                     aid : new Date().getTime().toString(),
2646                     isOfflineUploaded : true
2647                 }
2648             );
2649         }.bind(null, file);
2650 
2651         reader.onerror = function() {
2652             fileReadErrorCount++;
2653         };
2654 
2655         //Called when the read is completed, whether successful or not. This is called after either onload or onerror.
2656         reader.onloadend = function() {
2657             if ((dataURLArray.length + fileReadErrorCount) === filesLength) {
2658                 if (fileReadErrorCount > 0 && errorCallback) {
2659                     errorCallback.run(dataURLArray, fileReadErrorCount);
2660                 }
2661                 if (callback) {
2662                     callback.run(dataURLArray, fileReadErrorCount);
2663                 }
2664             }
2665         };
2666 
2667         reader.readAsDataURL(file);
2668     }
2669 };
2670 
2671 ZmComposeController.prototype._readFilesAsDataURLCallback =
2672 function(filesArray) {
2673     for (var j = 0; j < filesArray.length; j++) {
2674         var file = filesArray[j];
2675         if (file) {
2676             appCtxt.cacheSet(file.aid, file);
2677         }
2678     }
2679     var curView = this._composeView,
2680         callback = curView._resetUpload.bind(curView);
2681     this.saveDraft(ZmComposeController.DRAFT_TYPE_AUTO, filesArray, null, callback);
2682 };
2683