1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file contains the briefcase controller class.
 27  * 
 28  */
 29 
 30 /**
 31  * Creates the briefcase controller.
 32  * @class
 33  * This class represents the briefcase controller for the content view used by the briefcase application.
 34  *
 35  * @author Parag Shah
 36  *
 37  * @param {DwtControl}					container					the containing shell
 38  * @param {ZmApp}						app							the containing application
 39  * @param {constant}					type						type of controller
 40  * @param {string}						sessionId					the session id
 41  * @param {ZmSearchResultsController}	searchResultsController		containing controller
 42  * 
 43  * @extends		ZmListController
 44  */
 45 ZmBriefcaseController = function(container, app, type, sessionId, searchResultsController) {
 46 
 47  	if (arguments.length == 0) { return; }
 48 
 49 	ZmListController.apply(this, arguments);
 50 
 51 	this._idMap = {};
 52 
 53 	this._listChangeListener = this._fileListChangeListener.bind(this);
 54 	
 55 	this._listeners[ZmOperation.OPEN_FILE]			= this._openFileListener.bind(this);
 56 	this._listeners[ZmOperation.SAVE_FILE]			= this._saveFileListener.bind(this);
 57 	this._listeners[ZmOperation.SEND_FILE]			= this._sendFileListener.bind(this);
 58 	this._listeners[ZmOperation.SEND_FILE_AS_ATT]	= this._sendFileAsAttachmentListener.bind(this);
 59 	this._listeners[ZmOperation.NEW_FILE]			= this._uploadFileListener.bind(this);
 60 	this._listeners[ZmOperation.VIEW_FILE_AS_HTML]	= this._viewAsHtmlListener.bind(this);
 61     this._listeners[ZmOperation.EDIT_FILE]			= this._editFileListener.bind(this);
 62     this._listeners[ZmOperation.RENAME_FILE]		= this._renameFileListener.bind(this);
 63     this._listeners[ZmOperation.DETACH_WIN]			= this._newWinListener.bind(this);
 64 
 65 	this._listeners[ZmOperation.NEW_DOC]			= this._handleDoc.bind(this, ZmOperation.NEW_DOC);
 66 
 67     this._listeners[ZmOperation.CHECKIN]			= this._handleCheckin.bind(this);
 68     this._listeners[ZmOperation.CHECKOUT]			= this._checkoutListener.bind(this);
 69     this._listeners[ZmOperation.DISCARD_CHECKOUT]	= this._handleDiscardCheckout.bind(this);
 70     this._listeners[ZmOperation.RESTORE_VERSION]	= this._restoreVerListener.bind(this);
 71 
 72 	if (this.supportsDnD()) {
 73 		this._dragSrc = new DwtDragSource(Dwt.DND_DROP_MOVE);
 74 		this._dragSrc.addDragListener(this._dragListener.bind(this));
 75 	}
 76 
 77     this._parentView = {};
 78 };
 79 
 80 ZmBriefcaseController.prototype = new ZmListController;
 81 ZmBriefcaseController.prototype.constructor = ZmBriefcaseController;
 82 
 83 ZmBriefcaseController.prototype.isZmBriefcaseController = true;
 84 ZmBriefcaseController.prototype.toString = function() { return "ZmBriefcaseController"; };
 85 
 86 // Constants
 87 ZmBriefcaseController._VIEWS = {};
 88 ZmBriefcaseController._VIEWS[ZmId.VIEW_BRIEFCASE_DETAIL]	= "ZmPreviewPaneView";
 89 
 90 ZmBriefcaseController.RP_IDS = [ZmSetting.RP_BOTTOM, ZmSetting.RP_RIGHT, ZmSetting.RP_OFF];
 91 
 92 // reading pane options
 93 ZmBriefcaseController.PREVIEW_PANE_TEXT = {};
 94 ZmBriefcaseController.PREVIEW_PANE_TEXT[ZmSetting.RP_OFF]	= ZmMsg.previewPaneOff;
 95 ZmBriefcaseController.PREVIEW_PANE_TEXT[ZmSetting.RP_BOTTOM]	= ZmMsg.previewPaneAtBottom;
 96 ZmBriefcaseController.PREVIEW_PANE_TEXT[ZmSetting.RP_RIGHT]	= ZmMsg.previewPaneOnRight;
 97 
 98 ZmBriefcaseController.PREVIEW_PANE_ICON = {};
 99 ZmBriefcaseController.PREVIEW_PANE_ICON[ZmSetting.RP_OFF]	    = "SplitPaneOff";
100 ZmBriefcaseController.PREVIEW_PANE_ICON[ZmSetting.RP_BOTTOM]	= "SplitPane";
101 ZmBriefcaseController.PREVIEW_PANE_ICON[ZmSetting.RP_RIGHT]	    = "SplitPaneVertical";
102 
103 // convert key mapping to view menu item
104 ZmBriefcaseController.ACTION_CODE_TO_MENU_ID = {};
105 ZmBriefcaseController.ACTION_CODE_TO_MENU_ID[ZmKeyMap.READING_PANE_OFF]		= ZmSetting.RP_OFF;
106 ZmBriefcaseController.ACTION_CODE_TO_MENU_ID[ZmKeyMap.READING_PANE_BOTTOM]	= ZmSetting.RP_BOTTOM;
107 ZmBriefcaseController.ACTION_CODE_TO_MENU_ID[ZmKeyMap.READING_PANE_RIGHT]	= ZmSetting.RP_RIGHT;
108 
109 //List Views
110 
111 ZmBriefcaseController.LIST_VIEW = {};
112 ZmBriefcaseController.LIST_VIEW[ZmId.VIEW_BRIEFCASE_DETAIL] =   {image: "GenericDoc", text: ZmMsg.byLatestFile };
113 ZmBriefcaseController.LIST_VIEW[ZmId.VIEW_BRIEFCASE_REVISION] = {image: "VersionHistory", text: ZmMsg.byVersionHistory };
114 
115 /**
116  * The list view as a whole is the drop target, since it's the lowest-level widget. Still, we
117  * need to find out which item got dropped onto, so we get that from the original UI event
118  * (a mouseup). The header is within the list view, but not an item, so it's not a valid drop
119  * target. One drawback of having the list view be the drop target is that we can't exercise
120  * fine-grained control on what's a valid drop target. If you enter via an item and then drag to
121  * the header, it will appear to be valid.
122  * 
123  * @protected
124  */
125 ZmBriefcaseController.prototype._dropListener =
126 function(ev) {
127 	var view = this._listView[this._currentViewId];
128 	var div = view.getTargetItemDiv(ev.uiEvent);
129 	var item = view.getItemFromElement(div);
130 	if(!item || !( item.isRevision || item.isFolder) ) {
131 		ZmListController.prototype._dropListener.call(this,ev);
132 	} else {
133 		ev.doIt = false;
134 	}
135 }
136 
137 ZmBriefcaseController.prototype._standardActionMenuOps =
138 function() {
139 	return [ZmOperation.TAG_MENU, ZmOperation.DELETE, ZmOperation.MOVE];
140 };
141 
142 /**
143  * @private
144  */
145 ZmBriefcaseController.prototype._getSecondaryToolBarOps =
146 function() {
147 	var list = [];
148 	if (appCtxt.get(ZmSetting.MAIL_ENABLED)) {
149 		list.push(ZmOperation.SEND_FILE, ZmOperation.SEND_FILE_AS_ATT, ZmOperation.SEP);
150 	}
151 	list.push(ZmOperation.DETACH_WIN, ZmOperation.SEP);
152 
153 	list.push(ZmOperation.CHECKOUT, ZmOperation.CHECKIN, ZmOperation.DISCARD_CHECKOUT, ZmOperation.RESTORE_VERSION);
154 
155 	list.push(ZmOperation.SEP);
156 	list.push(ZmOperation.RENAME_FILE);
157 
158 	return list;
159 };
160 
161 
162 ZmBriefcaseController.prototype._getToolBarOps =
163 function() {
164     var ops = [ZmOperation.NEW_FILE,
165             ZmOperation.SAVE_FILE,
166 			ZmOperation.SEP,
167             ZmOperation.EDIT_FILE,
168 			ZmOperation.SEP,
169 			ZmOperation.DELETE,
170 			ZmOperation.SEP,
171 			ZmOperation.MOVE_MENU,
172 			ZmOperation.TAG_MENU
173 			];
174 
175 	/*if (appCtxt.get(ZmSetting.DOCS_ENABLED)) {
176 		   ops.push(ZmOperation.NEW_DOC,ZmOperation.SEP);
177 	}*/
178 
179 
180 	return ops;
181 };
182 
183 ZmBriefcaseController.prototype._getRightSideToolBarOps =
184 function(noViewMenu) {
185 	return [ZmOperation.VIEW_MENU];
186 };
187 
188 
189 ZmBriefcaseController.prototype._handleDoc =
190 function(op) {
191 	this._app.handleOp(op);
192 };
193 
194 ZmBriefcaseController.prototype._initializeToolBar =
195 function(view) {
196 
197 	if (!this._toolbar[view]) {
198 		ZmListController.prototype._initializeToolBar.call(this, view);
199 		this._setupViewMenu(view, true);
200         var toolbar = this._toolbar[view];
201 		toolbar.addFiller();
202 		this._initializeNavToolBar(view);
203 		appCtxt.notifyZimlets("initializeToolbar", [this._app, toolbar, this, view], {waitUntilLoaded:true});
204 	} else {
205         this._setupDeleteButton(this._toolbar[view]);
206         this._setupViewMenu(view, false);
207 	}
208 };
209 
210 // If we're in the Trash folder, change the "Delete" button tooltip
211 ZmBriefcaseController.prototype._setupDeleteButton =
212 function(parent) {
213     var folder = this._getSearchFolder();
214     var inTrashFolder = (folder && folder.nId == ZmFolder.ID_TRASH);
215     var tooltip = inTrashFolder ? ZmMsg.deletePermanentTooltip : ZmMsg.deleteTooltip;
216     var deleteButton = parent.getButton(ZmOperation.DELETE);
217     if(deleteButton){
218         deleteButton.setToolTipContent(ZmOperation.getToolTip(ZmOperation.DELETE, this.getKeyMapName(), tooltip));
219     }
220 };
221 
222 ZmBriefcaseController.prototype._initializeNavToolBar =
223 function(view) {
224 	this._itemCountText[view] = this._toolbar[view].getButton(ZmOperation.TEXT);
225 };
226 
227 ZmBriefcaseController.prototype._resetOperations =
228 function(parent, num) {
229 	if (!parent) { return; }
230 
231 	// call base class
232 	ZmListController.prototype._resetOperations.call(this, parent, num);
233 
234 	var items = this._listView[this._currentViewId].getSelection();
235 	var isFolderSelected=false, noOfFolders = 0, isRevisionSelected=false, isBriefcaseItemSelected=false, isMixedSelected=false;
236     var isWebDocSelected= false, hasLocked = false, allLocked = true, sameLockOwner=true;
237     var hasHighestRevisionSelected = false, hasOldRevisionSelected = false;
238 	if (items) {
239 		for (var i = 0; i < items.length; i++) {
240 			var item = items[i];
241 			if (item.isFolder) {
242 				isFolderSelected = true;
243 				noOfFolders++;
244 			}else if(item.isRevision){
245                 isRevisionSelected = true;
246                 if(item.parent.version == item.version){
247                     hasHighestRevisionSelected = true;
248                 }
249 				else {
250 					hasOldRevisionSelected = true;
251 				}
252             }else{
253                 isBriefcaseItemSelected = true;
254             }
255 
256             isWebDocSelected = isWebDocSelected || ( !item.isFolder && item.isWebDoc() );
257 
258             allLocked = allLocked && item.locked;
259 
260             hasLocked = hasLocked || item.locked;
261 
262             sameLockOwner = sameLockOwner && (item.locked && item.lockUser == appCtxt.getActiveAccount().name);
263 		}
264 	}
265 
266     isMixedSelected = isFolderSelected ? (isBriefcaseItemSelected || isRevisionSelected) :  (isBriefcaseItemSelected && isRevisionSelected);
267 
268     var briefcase = appCtxt.getById(this._folderId);
269     if(!(briefcase instanceof ZmBriefcase)){
270         briefcase = null;
271     }
272     var isTrash = (briefcase && briefcase.nId == ZmOrganizer.ID_TRASH);
273     var isShared = ((briefcase && briefcase.nId != ZmOrganizer.ID_TRASH && briefcase.isShared()));
274 	var isReadOnly = briefcase ? briefcase.isReadOnly() : false;
275 	var isMultiFolder = (noOfFolders > 1);
276 	var isItemSelected = (num>0);
277 	var isZimbraAccount = appCtxt.getActiveAccount().isZimbraAccount;
278 	var isMailEnabled = appCtxt.get(ZmSetting.MAIL_ENABLED);
279     var isAdmin = briefcase && briefcase.isAdmin(); 
280 
281     var item = items[0];
282     //bug 65351
283     // treat the latest revision selection as if it was a file selection.
284     // isOldRevision is true if the item is a revision but not the latest.
285     var isOldRevision = hasOldRevisionSelected ? true : item && item.revision && !hasHighestRevisionSelected;
286 	
287 	parent.enable([ZmOperation.SEND_FILE, ZmOperation.SEND_FILE_AS_ATT], (isZimbraAccount && isMailEnabled && isItemSelected && !isMultiFolder && !isFolderSelected));
288 	parent.enable(ZmOperation.TAG_MENU, (!isReadOnly && isItemSelected && !isFolderSelected && !isOldRevision));
289 	parent.enable([ZmOperation.NEW_FILE, ZmOperation.VIEW_MENU], true);
290 	parent.enable([ZmOperation.NEW_DOC], true);
291 	parent.enable([ZmOperation.MOVE, ZmOperation.MOVE_MENU], ( isItemSelected &&  !isReadOnly && !isShared && !isOldRevision));
292     parent.enable(ZmOperation.NEW_FILE, !(isTrash || isReadOnly));
293     parent.enable(ZmOperation.DETACH_WIN, (isItemSelected && !isFolderSelected && num==1));
294 
295     var firstItem = items && items[0];
296     var isWebDoc = firstItem && !firstItem.isFolder && firstItem.isWebDoc();
297     var isLocked = firstItem && !firstItem.isFolder && firstItem.locked;
298     var isLockOwner = isLocked && (item.lockUser == appCtxt.getActiveAccount().name);
299 
300 
301     //Rename Operation
302     parent.enable(ZmOperation.RENAME_FILE, ( num ==1 && !isFolderSelected && !isReadOnly && !isOldRevision && (isLocked ? isLockOwner : true) ));
303 
304     //Download - Files
305     parent.enable(ZmOperation.SAVE_FILE, num >0 && (!isFolderSelected || isBriefcaseItemSelected));
306 
307     // Edit
308     parent.enable(ZmOperation.OPEN_FILE, (num == 1 && isWebDoc));
309     parent.enable(ZmOperation.EDIT_FILE, !isReadOnly && (  !isLocked || isLockOwner ) && isWebDoc && !isOldRevision && num == 1);
310 
311     //Delete Operation
312     parent.enable(ZmOperation.DELETE, (!isReadOnly && isItemSelected && (hasHighestRevisionSelected ? !hasOldRevisionSelected : true) && !isMixedSelected && (isLocked ? isLockOwner : true)));
313 
314     if(parent &&  parent instanceof ZmActionMenu){
315 
316         //Open - webDocs
317         parent.getOp(ZmOperation.OPEN_FILE) && parent.getOp(ZmOperation.OPEN_FILE).setVisible(isItemSelected && !isMultiFolder && isWebDoc);
318 
319 	}
320 	//Case 1: Multiple Admins
321 	//Case 2: Stale Lock ( Handle exception )
322 
323 	//Checkin
324 	var op = parent.getOp(ZmOperation.CHECKIN);
325 	if (op) {
326 		var checkinEnabled = !isReadOnly && isLockOwner && !isWebDoc && !isOldRevision;
327 		op.setVisible(checkinEnabled);
328 		parent.enable(ZmOperation.CHECKIN, checkinEnabled && num == 1);
329 	}
330 
331 	//Checkout
332 	op = parent.getOp(ZmOperation.CHECKOUT);
333 	if (op) {
334 		var checkoutEnabled = !isReadOnly && !hasLocked && !isRevisionSelected && !isFolderSelected;
335 		op.setVisible(!isOldRevision && !isLocked);
336 		parent.enable(ZmOperation.CHECKOUT, checkoutEnabled);
337 	}
338 
339 	//Discard Checkout
340 	op = parent.getOp(ZmOperation.DISCARD_CHECKOUT);
341 	if (op) {
342 		var discardCheckoutEnabled = sameLockOwner && !isRevisionSelected;
343 		op.setVisible(discardCheckoutEnabled);
344 		parent.enable(ZmOperation.DISCARD_CHECKOUT, discardCheckoutEnabled && (isAdmin || sameLockOwner || !isShared));
345 	}
346 
347 	//Versioning
348 	op = parent.getOp(ZmOperation.RESTORE_VERSION);
349 	if (op) {
350 		var versionEnabled = (!isReadOnly && num == 1 && isOldRevision);
351 		var isHightestVersion = item && item.isRevision && ( item.parent.version == item.version );
352 		op.setVisible(isOldRevision);
353 		parent.enable(ZmOperation.RESTORE_VERSION, versionEnabled && !isHightestVersion);
354 	}
355 
356     var isDocOpEnabled = !(isTrash || isReadOnly);
357     if (appCtxt.get(ZmSetting.DOCS_ENABLED)) {
358         parent.enable(ZmOperation.NEW_DOC, isDocOpEnabled);
359     }
360 
361     // ZmShare is not present when the virtual account loads
362     AjxPackage.require("Briefcase");
363 	AjxPackage.require("Share");
364 
365     if (appCtxt.isExternalAccount() && items.length && isItemSelected) {
366 
367         var roleFromPerm = ZmShare.getRoleFromPerm(briefcase.perm);
368 
369         if (roleFromPerm === ZmShare.ROLE_NONE) {
370             parent.enable ([ZmOperation.SEND_FILE,
371                 ZmOperation.SEND_FILE_AS_ATT,
372                 ZmOperation.RENAME_FILE,
373                 ZmOperation.MOVE,
374                 ZmOperation.MOVE_MENU,
375                 ZmOperation.NEW_FILE,
376                 ZmOperation.TAG_MENU,
377                 ZmOperation.EDIT_FILE,
378                 ZmOperation.OPEN_FILE,
379                 ZmOperation.CHECKIN,
380                 ZmOperation.CHECKOUT,
381                 ZmOperation.DISCARD_CHECKOUT,
382                 ZmOperation.RESTORE_VERSION,
383                 ZmOperation.DETACH_WIN,
384                 ZmOperation.DELETE
385             ], false);
386             parent.setItemVisible(ZmOperation.TAG_MENU, false);
387         }
388         else if (roleFromPerm === ZmShare.ROLE_MANAGER) {
389             parent.enable ([
390                 ZmOperation.RENAME_FILE,
391                 ZmOperation.NEW_FILE,
392                 ZmOperation.OPEN_FILE,
393                 ZmOperation.CHECKIN,
394                 ZmOperation.CHECKOUT,
395                 ZmOperation.DISCARD_CHECKOUT,
396                 ZmOperation.DETACH_WIN,
397                 ZmOperation.DELETE
398             ], true);
399         }
400     }
401 };
402 
403 ZmBriefcaseController.prototype._getTagMenuMsg =
404 function() {
405 	return ZmMsg.tagFile;
406 };
407 
408 ZmBriefcaseController.prototype._doDelete = function(items, hardDelete) {
409 
410 	items = items || this._listView[this._currentViewId].getSelection();
411     var item = items instanceof Array ? items[0] : items;
412     if (!item) {
413         return;
414     }
415 
416 	var message = items.length > 1 ? item.isRevision  ? ZmMsg.confirmPermanentDeleteItemList : ZmMsg.confirmDeleteItemList : null;
417 	if (!message) {
418 		if (hardDelete || this._folderId == String(ZmOrganizer.ID_TRASH) || (item.isRevision && item.parent.version !== item.version)) {
419 			var pattern = ZmMsg.confirmPermanentDeleteItem;
420 		}
421 		else {
422 			var pattern = ZmMsg.confirmDeleteItem;
423 		}
424 		var delMsgFormatter = new AjxMessageFormat(pattern);
425 		message = delMsgFormatter.format(AjxStringUtil.htmlEncode(item.name));
426 	}
427 
428     var dialog = appCtxt.getConfirmationDialog();
429 	if (AjxEnv.isIE || AjxEnv.isModernIE) {
430 		dialog.addPopupListener(ZmBriefcaseController._onDeleteDialogPopup);
431 	}
432 	dialog.popup(message, this._doDelete2.bind(this, items, hardDelete));
433 };
434 
435 ZmBriefcaseController.prototype._doDelete2 = function(items, hardDelete) {
436 
437     var item = items instanceof Array ? items[0] : items,
438         i;
439 
440     if (item.isRevision && item.parent.version !== item.version) {
441         var view = this._parentView[this._currentViewId];
442         view.deleteVersions(items);
443     }
444     else if (item.isFolder) {
445         //Bug fix # 80600 force the BatchCommand to use JSON, mimicking the way right click delete behaves
446         var delBatchCmd = new ZmBatchCommand(true, null, true), folder;
447         for (i = 0; i < items.length; i++) {
448             folder = items[i].folder;
449             if (folder.isHardDelete()) {
450                 delBatchCmd.add(new AjxCallback(folder, folder._delete, [delBatchCmd]));
451             }
452             else {
453                 var trashFolder = appCtxt.getById(ZmFolder.ID_TRASH);
454                 delBatchCmd.add(new AjxCallback(folder, folder.move, [trashFolder, false, null, delBatchCmd]));
455             }
456         }
457         delBatchCmd.run();
458     }
459     else {
460 		for (i = 0; i < items.length; i++) {
461 			if (items[i].isRevision) {
462 				items[i] = items[i].parent;
463 			}
464 		}
465         ZmListController.prototype._doDelete.call(this, items, hardDelete, null, true);
466     }
467 };
468 
469 // view management
470 
471 ZmBriefcaseController.getDefaultViewType =
472 function() {
473 	return ZmId.VIEW_BRIEFCASE_DETAIL;
474 };
475 ZmBriefcaseController.prototype.getDefaultViewType = ZmBriefcaseController.getDefaultViewType;
476 
477 ZmBriefcaseController.prototype._createNewView =
478 function(view) {
479 
480 	var viewType = appCtxt.getViewTypeFromId(view);
481     var viewCtor = eval(ZmBriefcaseController._VIEWS[viewType]);
482 	this._parentView[view] = new viewCtor(this._container, this, this._dropTgt);
483 	var listView = this._parentView[view].getListView();
484 	if (this._dragSrc) {
485 		listView.setDragSource(this._dragSrc);
486 	}
487 
488 	return listView;
489 };
490 
491 ZmBriefcaseController.prototype._setViewContents =
492 function(view) {
493 	// If the controller is being used via the ZmBriefcaseTabView (for attaching briefcase files
494 	// to a mail message), then there is only a list view in use, not a parent with multiple views.
495 	if (this._parentView[view]) {
496 		this._parentView[view].set(this._list, this._switchView);
497 	}
498     this._switchView = false;
499 };
500 
501 ZmBriefcaseController.prototype._getDefaultFocusItem =
502 function() {
503 	return this._listView[this._currentViewId];
504 };
505 
506 // Returns a list of subfolders of the given folder, as ZmBriefcaseItem objects
507 ZmBriefcaseController.prototype._getSubfolders =
508 function(folderId) {
509 
510 	var folderId = folderId || this._currentSearch.folderId;
511 	var folder = folderId && appCtxt.getById(folderId);
512 	var subfolders = [];
513 	if (folder) {
514 		var children = folder.children;
515 		for (var i = 0, len = children.size(); i < len; i++) {
516             folder = children.get(i);
517             if(folder.type == ZmOrganizer.BRIEFCASE)
518 			    subfolders.push(new ZmBriefcaseFolderItem(children.get(i)));
519 		}
520 	}
521 
522 	return subfolders;
523 };
524 
525 
526 // view management
527 
528 /**
529  * Shows the search results.
530  * 
531  * @param	{Object}	results		the search results
532  */
533 ZmBriefcaseController.prototype.show =
534 function(results) {
535 
536 	this._folderId = results && results.search && results.search.folderId;
537 	this.setList(results.getResults(ZmItem.BRIEFCASE_ITEM));
538 	this._list.setHasMore(results.getAttribute("more"));
539 
540 	ZmListController.prototype.show.call(this, results, this._currentViewId);
541 
542 	this._setup(this._currentViewId);
543 
544 	// start fresh with search results
545 	var lv = this._listView[this._currentViewId];
546 	lv.offset = 0;
547 	lv._folderId = this._folderId;
548 
549 	var elements = this.getViewElements(this._currentViewId, this._parentView[this._currentViewId]);
550 
551 	this._setView({	view:		this._currentViewId,
552 					viewType:	this._currentViewType,
553 					noPush:		this.isSearchResults,
554 					elements:	elements,
555 					isAppView:	true});
556 	if (this.isSearchResults) {
557 		// if we are switching views, make sure app view mgr is up to date on search view's components
558 		appCtxt.getAppViewMgr().setViewComponents(this.searchResultsController.getCurrentViewId(), elements, true);
559 	}
560 	this._resetNavToolBarButtons();
561 };
562 
563 ZmBriefcaseController.prototype.getFolderId = function() {
564 	return this._folderId;
565 }
566 
567 /**
568  * Change how briefcase items are displayed.
569  * 
570  * @param {constant}	view			the view to show
571  * @param {Boolean}	force			if <code>true</code>, render view even if it's the current view
572  */
573 ZmBriefcaseController.prototype.switchView =
574 function(view, force) {
575 
576 	var viewChanged = (force || view != this._currentViewId);
577 
578 	if (viewChanged) {
579         var lv = this._listView[this._currentViewId];
580         if (lv) {
581 			lv.cleanup();
582 		}
583         this._switchView = true;
584 		this._currentViewId = view;
585 		this._setup(view);
586 	}
587 	this._resetOperations(this._toolbar[view], 0);
588 
589 	if (viewChanged) {
590 		var elements = this.getViewElements(view, this._parentView[view]);
591 		
592 		this._setView({ view:		view,
593 						viewType:	this._currentViewType,
594 						elements:	elements,
595 						isAppView:	true});
596 		this._resetNavToolBarButtons();
597 	}
598 	Dwt.setTitle(this.getCurrentView().getTitle());
599 };
600 
601 ZmBriefcaseController.prototype._preHideCallback =
602 function() {
603 
604     var lv = this._listView[this._currentViewId];
605     if(lv) lv.cleanup();
606 
607     return ZmController.prototype._preHideCallback.call(this);
608 };
609 
610 ZmBriefcaseController.prototype.getItemById =
611 function(itemId) {
612 	return (this._idMap[itemId] ? this._idMap[itemId].item : null);
613 };
614 
615 ZmBriefcaseController.prototype.__popupUploadDialog =
616 function(title, callback) {
617 
618 
619 	var folderId = this._folderId;
620     if(!folderId || folderId == ZmOrganizer.ID_TRASH)
621         folderId = ZmOrganizer.ID_BRIEFCASE;
622     
623     if(this.chkFolderPermission(folderId)){
624         var cFolder = appCtxt.getById(folderId);
625 		var uploadDialog = appCtxt.getUploadDialog();
626          uploadDialog.popup(this, cFolder, callback, title, null, false, true, true, ZmBriefcaseApp.ACTION_KEEP_MINE);
627     }	
628 };
629 
630 ZmBriefcaseController.prototype.chkFolderPermission =
631 function(folderId){
632     var briefcase = appCtxt.getById(folderId);
633     if(briefcase.isRemote() && briefcase.isReadOnly()){
634         var dialog = appCtxt.getMsgDialog();
635         dialog.setMessage(ZmMsg.errorPermissionCreate, DwtMessageDialog.WARNING_STYLE);
636         dialog.popup();
637         return false;
638     }
639     return true;
640 };
641 
642 ZmBriefcaseController.prototype._listSelectionListener =
643 function(ev) {
644 	Dwt.setLoadingTime("ZmBriefcaseItem");
645 	ZmListController.prototype._listSelectionListener.call(this, ev);
646 
647 	if (ev.detail == DwtListView.ITEM_DBL_CLICKED) {
648 		var item = ev.item;
649 
650         if(item.isFolder){
651             this._app.search({folderId:item.id});
652             return;
653         }
654 
655 		var restUrl = item.getRestUrl(false, false, true); //get it with the version number even if clicked on the base item (see ZmBriefcaseBaseItem.prototype.getRestUrl in ZmBriefcaseItem.js)
656         //added for bug: 45150
657         restUrl = AjxStringUtil.fixCrossDomainReference(restUrl);
658         if (item.isWebDoc()) {
659             restUrl += (restUrl.match(/\?/) ? "&" : "?") + "localeId=" + AjxEnv.DEFAULT_LOCALE;
660 
661 		}
662 		if (restUrl) {
663             if(item.isDownloadable() && !this._alwaysOpenInNewWindow(item)) {
664                 this._downloadFile(restUrl);
665             }else {
666 			    window.open(restUrl, this._getWindowName(item.name), item.isWebDoc() ? "" : ZmBriefcaseApp.getDocWindowFeatures());
667             }
668 		}
669 	}
670 };
671 
672 ZmBriefcaseController.prototype._alwaysOpenInNewWindow =
673 function(item){
674 
675     return (item.contentType == ZmMimeTable.APP_ADOBE_PDF && this.hasPDFReader())
676             || (item.contentType == ZmMimeTable.TEXT_XML) || (item.contentType == ZmMimeTable.APP_XML);
677 
678 };
679 
680 ZmBriefcaseController.prototype.hasPDFReader =
681 function(){
682     if(AjxUtil.isUndefined(this._hasPDFReader)){
683         this._hasPDFReader = AjxPluginDetector.detectPDFReader();
684     }
685     return this._hasPDFReader;
686 }
687 
688 ZmBriefcaseController.prototype._listActionListener =
689 function(ev) {
690 
691 	var item = ev.item;
692 
693 	if (item && item.isFolder) {
694 		ev.detail = DwtTree.ITEM_ACTIONED;
695 		var overviewController = appCtxt.getOverviewController();
696 		var treeController = overviewController.getTreeController(ZmOrganizer.BRIEFCASE);
697 		item.setData(ZmTreeView.KEY_TYPE, ZmOrganizer.BRIEFCASE);
698 		item.setData(Dwt.KEY_OBJECT, item.folder);
699 		item.setData(ZmTreeView.KEY_ID, this._app.getOverviewId());
700 		item.setData(Dwt.KEY_ID, item.id);
701 		treeController._treeViewListener(ev);
702 		return;
703 	}
704 
705 	ZmListController.prototype._listActionListener.call(this, ev);
706 
707 	var actionMenu = this.getActionMenu();
708 	actionMenu.popup(0, ev.docX, ev.docY);
709 	if (ev.ersatz) {
710 		actionMenu.setSelectedItem(0); // menu popped up via keyboard nav
711 	}
712 
713     
714 };
715 
716 ZmBriefcaseController.prototype._restoreVerListener =
717 function(){
718     var view = this._parentView[this._currentViewId];
719     view._restoreVerListener();
720 
721 };
722 
723 //Checkin/Checkout
724 
725 ZmBriefcaseController.prototype._checkoutListener =
726 function(){
727      var items = this._getSelectedItems();
728      if(items.length > 1){
729         for(var i=0; i< items.length; i++){
730            var item = items[i];
731            if(item && item instanceof ZmBriefcaseItem){
732                 this.checkout(item);
733            }
734         }
735      }else{
736         var item = items[0];
737         if(item && item instanceof ZmBriefcaseItem){
738             this.checkout(item, item.isWebDoc() ? null : new AjxCallback(this, this._postCheckout, item));
739         }
740      }
741 };
742 
743 ZmBriefcaseController.prototype._postCheckout =
744 function(item){
745     if(AjxEnv.isSafari){
746         setTimeout(AjxCallback.simpleClosure(this.downloadFile, this, item), 100);
747     }else{
748         this.downloadFile(item);
749     }
750 };
751 
752 ZmBriefcaseController.prototype._handleCheckin =
753 function(){    
754     var item = this._getSelectedItem();
755     if(item && item instanceof ZmBriefcaseItem){
756         var dlg = this._getCheckinDlg();                                        
757         dlg.popup(item, this._doneCheckin.bind(this, item));
758     }
759 };
760 
761 ZmBriefcaseController.prototype._doneCheckin =
762 function(item, files){
763     //Update item attribs
764 	var file = files[0];
765     item.version = file.version;
766     item.name = file.name;
767     this.unlockItem(item, new AjxCallback(this, this.refreshItem, item));
768 
769 };
770 
771 ZmBriefcaseController.prototype._handleDiscardCheckout =
772 function(){    
773     var items = this._getSelectedItems();
774     for(var i=0; i< items.length; i++){
775         var item = items[i];
776         if(item && item instanceof ZmBriefcaseItem)
777             this.unlockItem(item);
778     }
779 };
780 
781 ZmBriefcaseController.prototype.refreshItem =
782 function(item){
783     //TODO: Handle version notifications than hard refresh
784     var view = this._parentView[this._currentViewId];
785     view.refreshItem(item);
786 };
787 
788 ZmBriefcaseController.prototype.checkout =
789 function(item, callback){        
790     this.lockItem(item, callback);
791 };
792 
793 ZmBriefcaseController.prototype.checkin =
794 function(item, callback){
795     this.unlockItem(item, callback);
796 };
797 
798 ZmBriefcaseController.prototype.unlockItem =
799 function(item, callback){
800    item.unlock(callback, new AjxCallback(this, this._handleErrorResponse, item)); 
801 };
802 
803 ZmBriefcaseController.prototype.lockItem =
804 function(item, callback){
805    item.lock(callback, new AjxCallback(this, this._handleErrorResponse, item));
806 };
807 
808 ZmBriefcaseController.prototype._handleErrorResponse =
809 function(item, response){
810     if(!(response && response.code)) return;
811 
812     var msg;
813     switch(response.code){
814         case ZmCsfeException.CANNOT_UNLOCK:
815             msg = ZmMsg.unlockSufficientPermission;
816             break;
817 
818         case ZmCsfeException.CANNOT_LOCK:
819             msg = ZmMsg.lockSuffientPermissions;
820             break;
821     }
822 
823     if(msg){
824         var dialog = appCtxt.getMsgDialog();
825         dialog.setMessage(msg, DwtMessageDialog.WARNING_STYLE);
826         dialog.popup();
827     }
828 
829     return msg;
830 };        
831 
832 ZmBriefcaseController.prototype._getSelectedItem =
833 function(){
834     var view = this._listView[this._currentViewId];
835 	var items = view.getSelection();    
836     return ( items && items.length > 0 ) ? items[0] : null;
837 };
838 
839 ZmBriefcaseController.prototype._getSelectedItems =
840 function(){
841     var view = this._listView[this._currentViewId];
842 	return view.getSelection();
843 };
844 
845 ZmBriefcaseController.prototype._getCheckinDlg =
846 function(){
847     if(!this._checkinDlg){
848        this._checkinDlg = new ZmCheckinDialog(appCtxt.getShell());
849     }
850     return this._checkinDlg;
851 };
852 
853 //End of Checkin/Checkout
854 
855 ZmBriefcaseController.prototype._getActionMenuOps =
856 function() {
857     var list = [
858         ZmOperation.OPEN_FILE,
859         ZmOperation.SAVE_FILE,
860         ZmOperation.EDIT_FILE
861     ];
862 
863     if (!appCtxt.isExternalAccount()) {
864         list.push(ZmOperation.SEND_FILE);
865         list.push(ZmOperation.SEND_FILE_AS_ATT);
866     }
867 
868     list.push(ZmOperation.SEP);
869     list.push(ZmOperation.CHECKOUT, ZmOperation.CHECKIN, ZmOperation.DISCARD_CHECKOUT, ZmOperation.RESTORE_VERSION/*, ZmOperation.DELETE_VERSION*/);
870 
871 	list.push(ZmOperation.SEP);
872 	list = list.concat(this._standardActionMenuOps());
873     list.push(ZmOperation.RENAME_FILE);
874 	return list;
875 };
876 
877 ZmBriefcaseController.prototype._renameFileListener =
878 function(){
879 
880     var view = this._listView[this._currentViewId];
881 	var items = view.getSelection();
882 	if (!items) { return; }
883 
884     view.renameFile(items[0]);
885 };
886 
887 ZmBriefcaseController.prototype._newWinListener =
888 function(){
889     var view = this._listView[this._currentViewId];
890 	var items = view.getSelection();
891 	if (!items) { return; }
892     items = AjxUtil.toArray(items);
893     var item = items[0];
894     if (item) {
895         this.openFile(item);
896     }
897 };
898 
899 
900 ZmBriefcaseController.prototype._editFileListener =
901 function() {
902 	var view = this._listView[this._currentViewId];
903 	var items = view.getSelection();
904 	if (!items) { return; }
905     items = AjxUtil.toArray(items);
906     var item = items[0];
907     if(item){
908         this.editFile(item);
909     }
910 };
911 
912 ZmBriefcaseController.prototype.editFile =
913 function(items){
914     items = AjxUtil.toArray(items);
915     for (var i = 0; i < items.length; i++) {
916         var item = items[i];
917         if (item.isWebDoc()) {
918 			var win = appCtxt.getNewWindow(false, null, null, this._getWindowName(item.name));
919 	        if (win) {
920 	            win.command = "documentEdit";
921 	            win.params = {
922 					restUrl: item.getRestUrl(),
923 					id: item.id,
924 					name: item.name,
925 					folderId: item.folderId
926 				};
927 	        }
928         }
929     }
930 };
931 
932 ZmBriefcaseController.prototype._getWindowName =
933 function(name){
934     if(!name){
935         return ZmMsg.briefcase;    
936     }
937     //IE does not like special chars as part of window name.
938     return AjxEnv.isIE ? name.replace(/[^\w]/g,'') : name;    
939 };
940 
941 ZmBriefcaseController.prototype._openFileListener =
942 function() {
943 	var view = this._listView[this._currentViewId];
944 	var items = view.getSelection();
945 	if (!items) { return; }
946 
947     this.openFile(items);
948 };
949 
950 ZmBriefcaseController.prototype.openFile =
951 function(items){
952     items = AjxUtil.toArray(items);
953 	for (var i = 0; i < items.length; i++) {
954 		var item = items[i];
955 		var restUrl = item.getRestUrl(false, false, true);
956 		if (!restUrl) {
957 			continue;
958 		}
959 		restUrl = AjxStringUtil.fixCrossDomainReference(restUrl);
960 		if (item.isWebDoc()) {
961 			//added for bug: 45150
962 			restUrl += (restUrl.match(/\?/) ? "&" : "?") + "localeId=" + AjxEnv.DEFAULT_LOCALE;
963 		} else {
964             // do not try to
965             //ZD doesn't support ConvertD.
966 			if (!ZmMimeTable.isRenderable(item.contentType) && !ZmMimeTable.isMultiMedia(item.contentType) && !appCtxt.isOffline) {
967                	restUrl += (restUrl.match(/\?/) ? "&" : "?") + "view=html";
968 			}
969         }
970 
971 		var win = window.open(restUrl, this._getWindowName(item.name), item.isWebDoc() ? "" : ZmBriefcaseApp.getDocWindowFeatures());
972         appCtxt.handlePopupBlocker(win);
973 
974         // avoid losing focus in IE8 and earlier (bug 52206)
975         if (win && AjxEnv.isIE && !AjxEnv.isIE9up) {
976 		    var ta = new AjxTimedAction(win, win.focus);
977 		    AjxTimedAction.scheduleAction(ta, 100);
978         }
979 	}
980 };
981 
982 ZmBriefcaseController.prototype._saveFileListener =
983 function() {
984 	var view = this._listView[this._currentViewId];
985 	var items = view.getSelection();
986 	if (!items) { return; }
987 
988 	items = AjxUtil.toArray(items);
989 
990 	// Allow download to only one file.
991     this.downloadFile(items);
992 };
993 
994 ZmBriefcaseController.prototype.downloadFile =
995 function(items){
996 
997     var restUrl, item, length= items.length;
998     if(length > 1){
999         var params = [];
1000         var organizer = appCtxt.getById(items[0].folderId);
1001         for(var i=0; i< length; i++){
1002             item = items[i];
1003 	        if (!item.isFolder) {
1004 				var itemId;
1005 				if (appCtxt.isOffline && organizer.isShared()) {
1006 					itemId = item.id;
1007 				} else {
1008 					itemId = item.getNormalizedItemId();
1009 				}
1010 				params.push((item.isRevision ? item.parent.id : itemId )+"."+item.version);
1011 	        }
1012         }
1013         restUrl = [ ((organizer.isShared() && !appCtxt.isOffline ) ? organizer.getOwnerRestUrl() : organizer.getRestUrl()), "?fmt=zip&list=", params.join(',')].join('');
1014     }else{
1015         item = AjxUtil.isArray(items) ? items[0] : items;
1016         restUrl = item.getRestUrl();
1017         restUrl += ( restUrl.indexOf('?') == -1 ) ? "?" : "&";
1018         restUrl += "disp=a"+(item.version ? "&ver="+item.version : "");
1019     }
1020 
1021     if (!restUrl) {
1022         return false;
1023     }
1024     restUrl = AjxStringUtil.fixCrossDomainReference(restUrl);
1025     if (restUrl) {
1026         this._downloadFile(restUrl)
1027     }
1028 };
1029 
1030 ZmBriefcaseController.prototype._downloadFile =
1031 function(downloadUrl){
1032     if(downloadUrl){
1033         ZmZimbraMail.unloadHackCallback();
1034         location.href = downloadUrl;
1035     }
1036 };
1037 
1038 ZmBriefcaseController.prototype._viewAsHtmlListener =
1039 function() {
1040 	var view = this._listView[this._currentViewId];
1041 	var items = view.getSelection();
1042 	if (!items) { return; }
1043 
1044 	items = AjxUtil.toArray(items);
1045 	for (var i = 0; i<items.length; i++) {
1046 		var item = items[i];
1047 		var restUrl = item.getRestUrl();
1048 		if (item && restUrl) {
1049 			this.viewAsHtml(restUrl);
1050 		}
1051 	}
1052 };
1053 
1054 ZmBriefcaseController.prototype.viewAsHtml =
1055 function(restUrl) {
1056 	if (restUrl.match(/\?/)) {
1057 		restUrl+= "&view=html";
1058 	} else {
1059 		restUrl+= "?view=html";
1060 	}
1061 	window.open(restUrl);
1062 };
1063 
1064 ZmBriefcaseController.prototype._uploadFileListener =
1065 function() {
1066     this.__popupUploadDialog(ZmMsg.uploadFileToBriefcase, new AjxCallback(this, this._handlePostUpload));
1067 };
1068 
1069 ZmBriefcaseController.prototype.resetSelection = function() {
1070 	var view = this._listView[this._currentViewId];
1071 	if (view) {
1072 		view.deselectAll();
1073 	}
1074 	var lv = this.getCurrentView();
1075 	if (lv) {
1076 		lv._selectFirstItem()
1077 	}
1078 }
1079 
1080 ZmBriefcaseController.prototype._sendFileListener =
1081 function(event) {
1082 	var view = this._listView[this._currentViewId];
1083 	var items = view.getSelection();
1084 	items = AjxUtil.toArray(items);
1085 
1086 	var names = [];
1087 	var urls = [];
1088 	var inNewWindow = this._app._inNewWindow(event);
1089 
1090 	var briefcase, shares;
1091 	var noprompt = false;
1092 
1093 	for (var i = 0; i < items.length; i++) {
1094 		var item = items[i];
1095 		briefcase = appCtxt.getById(item.folderId);
1096 		var url;
1097 		if (briefcase.restUrl) {
1098 			//present if the briefcase is a share from another user. In this case, keep that URL as the base.
1099 			url = [briefcase.restUrl, "/", AjxStringUtil.urlComponentEncode(item.name)].join("")
1100 		}
1101 		else {
1102 			//item is in this user's briefcase, so build the rest url.
1103 			url = item.getRestUrl();
1104 		}
1105 		if (appCtxt.isOffline) {
1106 			var remoteUri = appCtxt.get(ZmSetting.OFFLINE_REMOTE_SERVER_URI);
1107 			url = remoteUri + url.substring((url.indexOf("/",7)));
1108 		}
1109         
1110 		urls.push(url);
1111 		names.push(item.name);
1112 
1113 		if (noprompt) { continue; }
1114 
1115 		shares = briefcase && briefcase.shares;
1116 		if (shares) {
1117 			for (var j = 0; j < shares.length; j++) {
1118 				noprompt = noprompt || shares[j].grantee.type == ZmShare.TYPE_PUBLIC;
1119 			}
1120 		}
1121 	}
1122 
1123 	if (!shares || !noprompt) {
1124 		var args = [names, urls, inNewWindow];
1125 		var callback = new AjxCallback(this, this._sendFileListener2, args);
1126 
1127 		var dialog = appCtxt.getConfirmationDialog();
1128 		dialog.popup(ZmMsg.errorPermissionRequired, callback);
1129 	} else {
1130 		this._sendFileListener2(names, urls);
1131 	}
1132 };
1133 
1134 ZmBriefcaseController.prototype._sendFileListener2 =
1135 function(names, urls, inNewWindow) {
1136 	var action = ZmOperation.NEW_MESSAGE;
1137 	var msg = new ZmMailMsg();
1138 	var toOverride = null;
1139 	var subjOverride = new AjxListFormat().format(names);
1140 	var htmlCompose = appCtxt.get(ZmSetting.COMPOSE_AS_FORMAT) == ZmSetting.COMPOSE_HTML;
1141 	var extraBodyText = urls.join(htmlCompose ? "<br>" : "\n");
1142 	AjxDispatcher.run("Compose", {action: action, inNewWindow: inNewWindow, msg: msg,
1143 								  toOverride: toOverride, subjOverride: subjOverride,
1144 								  extraBodyText: extraBodyText});
1145 };
1146 
1147 ZmBriefcaseController.prototype._sendFileAsAttachmentListener =
1148 function(event) {
1149 	var view = this._listView[this._currentViewId];
1150 	var items = view.getSelection();
1151 
1152     this.sendFilesAsAttachment(items);	
1153 };
1154 
1155 ZmBriefcaseController.prototype.sendFilesAsAttachment =
1156 function(items, callback){
1157 
1158     items = AjxUtil.toArray(items);
1159     var docInfo = [];
1160     for (var i = 0; i < items.length; i++) {
1161         var item = items[i];
1162         docInfo.push({
1163             id:     ( item.isRevision ? item.parent.id : item.id ),
1164             ver:    ( item.isRevision ? item.version : null ),
1165             ct:     item.contentType,
1166             s:      item.size
1167         });
1168     }
1169 
1170     if (docInfo.length == 0) { return; }
1171 
1172     var action = ZmOperation.NEW_MESSAGE;
1173     var msg = new ZmMailMsg();
1174     var toOverride;
1175 
1176     var cc = AjxDispatcher.run("GetComposeController");
1177     cc._setView({action:action, msg:msg, toOverride:toOverride, inNewWindow:false});
1178     var draftType = ZmComposeController.DRAFT_TYPE_AUTO;
1179     var sendDocsCallback = new AjxCallback(cc, cc._handleResponseSaveDraftListener, [draftType, callback]);
1180     cc.saveDraft(draftType, null, docInfo, sendDocsCallback);
1181 };
1182 
1183 ZmBriefcaseController.prototype._resetOpForCurrentView =
1184 function(num) {
1185 	this._resetOperations(this._toolbar[this._currentViewId], num || 0);
1186 };
1187 
1188 
1189 ZmBriefcaseController.prototype._setupViewMenu =
1190 function(view, firstTime) {
1191 
1192 	var btn, menu;
1193 	if (firstTime) {
1194 		btn = this._toolbar[view].getButton(ZmOperation.VIEW_MENU);
1195 		var menu = btn.getMenu();
1196 		if (!menu) {
1197 			menu = new ZmPopupMenu(btn);
1198 			btn.setMenu(menu);
1199 
1200             this._setupPreviewPaneMenu(menu, btn);
1201 		}
1202 	}
1203 
1204     if(!menu){
1205        btn = this._toolbar[view].getButton(ZmOperation.VIEW_MENU);
1206        menu = btn && btn.getMenu();
1207     }
1208 	
1209     this._resetPreviewPaneMenu(menu, view);
1210 };
1211 
1212 ZmBriefcaseController.prototype._setupPreviewPaneMenu =
1213 function(menu, btn){
1214 
1215     if (menu.getItemCount() > 0) {
1216 		new DwtMenuItem({parent:menu, style:DwtMenuItem.SEPARATOR_STYLE, id:"PREVIEW_SEPERATOR"});
1217 	}
1218 
1219 	var miParams = {text:ZmMsg.readingPaneAtBottom, style:DwtMenuItem.RADIO_STYLE, radioGroupId:"RP"};
1220 	var ids = ZmDoublePaneController.RP_IDS;
1221 	var pref = appCtxt.get(ZmSetting.READING_PANE_LOCATION_BRIEFCASE);
1222 	for (var i = 0; i < ids.length; i++) {
1223 		var id = ids[i];
1224 		if (!menu._menuItems[id]) {
1225 			miParams.text = ZmBriefcaseController.PREVIEW_PANE_TEXT[id];
1226 			miParams.image = ZmBriefcaseController.PREVIEW_PANE_ICON[id];
1227             var mi = menu.createMenuItem(id, miParams);
1228 			mi.setData(ZmOperation.MENUITEM_ID, id);
1229 			mi.addSelectionListener(new AjxListener(this, this._previewPaneListener, id));
1230 			if (id == pref) {
1231 				mi.setChecked(true, true);
1232 				btn.setImage(mi.getImage());
1233 			}
1234 		}
1235 	}
1236 
1237 };
1238 
1239 ZmBriefcaseController.prototype._resetPreviewPaneMenu =
1240 function(menu, view){
1241     view = view || this._currentViewId;
1242     var ids = ZmDoublePaneController.RP_IDS;
1243     for (var i = 0; i < ids.length; i++) {
1244 		var id = ids[i];
1245 		if (menu._menuItems[id]) {
1246             menu._menuItems[id].setEnabled(true);
1247         }
1248     }
1249 };
1250 
1251 /**
1252  * Checks if the reading pane is "on".
1253  *
1254  * @return	{Boolean}	<code>true</code> if the reading pane is "on"
1255  */
1256 ZmBriefcaseController.prototype.isReadingPaneOn =
1257 function() {
1258 	return (this._getReadingPanePref() != ZmSetting.RP_OFF);
1259 };
1260 
1261 /**
1262  * Checks if the reading pane is "on" right.
1263  *
1264  * @return	{Boolean}	<code>true</code> if the reading pane is "on" right.
1265  */
1266 ZmBriefcaseController.prototype.isReadingPaneOnRight =
1267 function() {
1268 	return (this._getReadingPanePref() == ZmSetting.RP_RIGHT);
1269 };
1270 
1271 ZmBriefcaseController.prototype._getReadingPanePref =
1272 function() {
1273 	return (this._readingPaneLoc || appCtxt.get(ZmSetting.READING_PANE_LOCATION_BRIEFCASE));
1274 };
1275 
1276 ZmBriefcaseController.prototype._setReadingPanePref =
1277 function(value) {
1278 	if (this.isSearchResults || appCtxt.isExternalAccount()) {
1279 		this._readingPaneLoc = value;
1280 	}
1281 	else {
1282 		appCtxt.set(ZmSetting.READING_PANE_LOCATION_BRIEFCASE, value);
1283 	}
1284 };
1285 
1286 ZmBriefcaseController.prototype._previewPaneListener =
1287 function(newPreviewStatus){
1288     var oldPreviewStatus = appCtxt.get(ZmSetting.READING_PANE_LOCATION_BRIEFCASE);
1289     this._setReadingPanePref(newPreviewStatus);
1290     var lv = this._parentView[this._currentViewId];
1291     lv.resetPreviewPane(newPreviewStatus, oldPreviewStatus);
1292 	//update view button icon to reflect current selection
1293 	var btn = this._toolbar[this._currentViewId].getButton(ZmOperation.VIEW_MENU);
1294 	if (btn) {
1295 		btn.setImage(ZmBriefcaseController.PREVIEW_PANE_ICON[newPreviewStatus]);
1296 	}
1297 
1298 };
1299 
1300 ZmBriefcaseController.CONVERTABLE = {
1301 	doc:/\.doc$/i,
1302 	xls:/\.xls$/i,
1303 	pdf:/\.pdf$/i,
1304 	ppt:/\.ppt$/i,
1305 	zip:/\.zip$/i,
1306     txt:/\.txt$/i
1307 };
1308 
1309 ZmBriefcaseController.prototype.isConvertable =
1310 function(item) {
1311 	var name = item.name;
1312 	for (var type in ZmBriefcaseController.CONVERTABLE) {
1313 		var regex = ZmBriefcaseController.CONVERTABLE[type];
1314 		if (name.match(regex)) {
1315 			return true;
1316 		}
1317 	}
1318 	return false;
1319 };
1320 
1321 ZmBriefcaseController.prototype._fileListChangeListener =
1322 function(ev) {
1323 	if (ev.handled) { return; }
1324 	var details = ev._details;
1325 	if (!details) { return; }
1326 	this._list._notify(ev.event,{items:details.items});
1327 };
1328 
1329 ZmBriefcaseController.prototype.getCurrentView =
1330 function() {
1331 	return this._parentView[this._currentViewId];
1332 };
1333 ZmBriefcaseController.prototype.getParentView = ZmBriefcaseController.prototype.getCurrentView;
1334 
1335 ZmBriefcaseController.prototype._addListListeners =
1336 function(colView) {
1337 	colView.addActionListener(new AjxListener(this, this._listActionListener));
1338 };
1339 
1340 ZmBriefcaseController.prototype.isMultiColView =
1341 function() {
1342 	return (this._currentViewType == ZmId.VIEW_BRIEFCASE_COLUMN);
1343 };
1344 
1345 ZmBriefcaseController.prototype.mapSupported =
1346 function(map) {
1347 	return (map == "list" && (this._currentViewType != ZmId.VIEW_BRIEFCASE));
1348 };
1349 
1350 ZmBriefcaseController.prototype.getItemTooltip =
1351 function(item, listView) {
1352 
1353 	if (item.isFolder) { return null; }
1354 
1355 	var prop = [{name:ZmMsg.briefcasePropName, value:item.name}];
1356 	if (item.size) {
1357 		prop.push({name:ZmMsg.briefcasePropSize, value:AjxUtil.formatSize(item.size)});
1358 	}
1359 	if (item.contentChangeDate) {
1360 		var dateFormatter = AjxDateFormat.getDateTimeInstance(AjxDateFormat.FULL, AjxDateFormat.MEDIUM);
1361 		var dateStr = dateFormatter.format(item.contentChangeDate);
1362 		prop.push({name:ZmMsg.briefcasePropModified, value:dateStr});
1363 	}
1364 
1365 	var subs = {
1366 		fileProperties: prop,
1367 		tagTooltip: listView._getTagToolTip(item)
1368 	};
1369 	return AjxTemplate.expand("briefcase.Briefcase#Tooltip", subs);
1370 };
1371 
1372 ZmBriefcaseController.prototype._getDateInLocaleFormat =
1373 function(date) {
1374 	var dateFormatter = AjxDateFormat.getDateTimeInstance(AjxDateFormat.FULL, AjxDateFormat.MEDIUM);
1375 	return dateFormatter.format(date);
1376 };
1377 
1378 ZmBriefcaseController.prototype._resetToolbarOperations =
1379 function() {
1380 	if (this._listView[this._currentViewId] != null) {
1381 		this._resetOperations(this._toolbar[this._currentViewId], this._listView[this._currentViewId].getSelectionCount());
1382 	}
1383 };
1384 
1385 // item count doesn't include subfolders
1386 ZmBriefcaseController.prototype._getItemCount =
1387 function() {
1388 	var lv = this._listView[this._currentViewId];
1389 	var list = lv && lv._list;
1390 	if (!list) { return null; }
1391 	var a = list.getArray();
1392 	var num = 0;
1393 	for (var i = 0, len = a.length; i < len; i++) {
1394 		var item = a[i];
1395 		if (item && item.type == ZmItem.BRIEFCASE_ITEM && !item.isFolder) {
1396 			num++;
1397 		}
1398 	}
1399 	return num;
1400 };
1401 
1402 
1403 ZmBriefcaseController.prototype.handleCreateNotify =
1404 function(create){
1405 
1406     if(this.isMultiColView()){
1407         var isTrash = (this._folderId == String(ZmOrganizer.ID_TRASH));
1408         if(!isTrash)
1409             this.getCurrentView().handleNotifyCreate(create);
1410     }else{
1411         var list = this.getList();
1412         if (list) {
1413             var item = ZmBriefcaseItem.createFromDom(create, {list:list});
1414             if (list.search && list.search.matches(item)) {
1415                 list.notifyCreate(create);
1416             }
1417         }
1418     }
1419 };
1420 
1421 ZmBriefcaseController.prototype.handleModifyNotify =
1422 function(modifies){
1423     var view = this._listView[this._currentViewId];
1424     if (view) {
1425         view.deselectAll();
1426 	}
1427     this._resetToolbarOperations();
1428 };
1429 
1430 ZmBriefcaseController.prototype._actionErrorCallback =
1431 function(ex){
1432 
1433     var handled = false;
1434     if(ex.code == ZmCsfeException.MAIL_ALREADY_EXISTS){
1435         handled = true;
1436         var dlg = appCtxt.getMsgDialog();
1437         dlg.setMessage(ZmMsg.errorFileAlreadyExistsResolution, DwtMessageDialog.WARNING_STYLE);
1438         dlg.popup();
1439     }
1440 
1441     return handled;
1442 };
1443 
1444 
1445 //Add to Briefcase
1446 
1447 ZmBriefcaseController.prototype.createFromAttachment =
1448 function(msgId, partId, name){
1449 
1450      var dlg = this._saveAttDialog = appCtxt.getChooseFolderDialog(this._app.getName());
1451 	 var chooseCb = new AjxCallback(this, this._chooserCallback, [msgId, partId, name]);
1452 	 ZmController.showDialog(dlg, chooseCb, this._getCopyParams(dlg, msgId, partId));
1453 
1454 };
1455 
1456 ZmBriefcaseController.prototype._getCopyParams =
1457 function(dlg, msgId, partId) {
1458 	var params = {
1459 		data:			{msgId:msgId,partId:partId},
1460 		treeIds:		[ZmOrganizer.BRIEFCASE],
1461 		overviewId:		dlg.getOverviewId(this._app._name),
1462 		title:			ZmMsg.addToBriefcaseTitle,
1463 		description:	ZmMsg.targetFolder,
1464 		appName:		ZmApp.BRIEFCASE,
1465 		noRootSelect:	true
1466 	};
1467     params.omit = {};
1468     params.omit[ZmFolder.ID_DRAFTS] = true;
1469     params.omit[ZmFolder.ID_TRASH] = true;
1470     return params;
1471 };
1472 
1473 ZmBriefcaseController.prototype._chooserCallback =
1474 function(msgId, partId, name, folder) {
1475     //TODO: Avoid using search, instead try renaming on failure
1476 	var callback = new AjxCallback(this, this._handleDuplicateCheck, [msgId, partId, name, folder]);
1477 	this._app.search({query:folder.createQuery(), noRender:true, callback:callback, accountName:(folder && folder.account && folder.account.name) || undefined});
1478 };
1479 
1480 ZmBriefcaseController.prototype._handleDuplicateCheck =
1481 function(msgId, partId, name, folder, results) {
1482 
1483 	var msg = appCtxt.getById(msgId);
1484 
1485 	var briefcase = folder;
1486 	if (briefcase.isReadOnly(folder.id)) {
1487 		ZmOrganizer._showErrorMsg(ZmMsg.errorPermission);
1488 		return;
1489 	}
1490 
1491 	if (msgId.indexOf(":") < 0) {
1492 		msgId = msg.getAccount().id + ":" + msg.id;
1493 	}
1494 
1495 
1496 	var searchResult = results.getResponse();
1497 	var items = searchResult && searchResult.getResults(ZmItem.BRIEFCASE_ITEM);
1498 	if (items instanceof ZmList) {
1499 		items = items.getArray();
1500 	}
1501 
1502     var itemFound = false;
1503 	for (var i = 0, len = items.length; i < len; i++) {
1504 		if (items[i].name == name) {
1505 			itemFound = items[i];
1506 			break;
1507 		}
1508 	}
1509 
1510     var folderId = (!folder.account || folder.account == appCtxt.getActiveAccount() || (folder.id.indexOf(":") != -1)) ? folder.id : [folder.account.id, folder.id].join(":");
1511     if(itemFound){
1512         var dlg = this._conflictDialog = this._getFileConflictDialog();
1513         dlg.setButtonListener(DwtDialog.OK_BUTTON, this._handleConflictDialog.bind(this, msgId, partId, name, folderId, itemFound));
1514 		dlg.setEnterListener(DwtDialog.OK_BUTTON, this._handleConflictDialog.bind(this, msgId, partId, name, folderId, itemFound));
1515 	    this._renameField.value = "";
1516 	    dlg.popup();
1517     }else{
1518        this._createFromAttachment(msgId, partId, name, folderId);
1519     }
1520 
1521     if(this._saveAttDialog.isPoppedUp())
1522         this._saveAttDialog.popdown();
1523 };
1524 
1525 ZmBriefcaseController.prototype._popupConflictDialog = 
1526 function(dlg) {
1527 	if (dlg) {
1528 		dlg.popdown();
1529 	}
1530 	if (!this._conflictDialog) {
1531 		this._conflictDialog = this._getFileConflictDialog();
1532 	}
1533 	this._conflictDialog.popup();
1534 };
1535 
1536 ZmBriefcaseController.prototype._handleConflictDialog =
1537 function(msgId, partId, name, folderId, itemFound){
1538 
1539     var attribs = {};
1540     if(this._renameRadio.checked){
1541         var newName = this._renameField.value;
1542         var errorMsg = this.checkInvalidFileName(newName, itemFound && itemFound.name);
1543         if(errorMsg){
1544 		    var dialog = appCtxt.getMsgDialog();
1545 		    dialog.setMessage(errorMsg, DwtMessageDialog.WARNING_STYLE);
1546 		    dialog.popup();
1547 		    return false;
1548         }
1549         attribs.rename = newName;
1550     }else{
1551         attribs.id = itemFound.id;
1552         attribs.version = itemFound.version;
1553     }
1554 	this._conflictDialog.popdown(); //hide dialog so user doesn't get it into a state that can be hung
1555     this._createFromAttachment(msgId, partId, name, folderId, attribs);
1556 };
1557 
1558 ZmBriefcaseController.prototype.checkInvalidFileName =
1559 function(fileName, itemFound) {
1560 
1561     var message;
1562     fileName = fileName.replace(/^\s+/,"").replace(/\s+$/,"");
1563 
1564     if(fileName == ""){
1565         message = ZmMsg.emptyDocName;
1566     }
1567     else if (!ZmOrganizer.VALID_NAME_RE.test(fileName)) {
1568         message = AjxMessageFormat.format(ZmMsg.errorInvalidName, AjxStringUtil.htmlEncode(fileName));
1569     } 
1570     else if (fileName.length > ZmOrganizer.MAX_NAME_LENGTH){
1571         message = AjxMessageFormat.format(ZmMsg.nameTooLong, ZmOrganizer.MAX_NAME_LENGTH);
1572     }
1573 	else if (itemFound === fileName) {
1574 	    message = AjxMessageFormat.format(ZmMsg.errorFileExistsWarning, AjxStringUtil.htmlEncode(fileName));
1575     }
1576 
1577     return message;
1578 };
1579 
1580 ZmBriefcaseController.prototype._createFromAttachment =
1581 function(msgId, partId, name, folderId, attribs){
1582 
1583     attribs = attribs || {};
1584     if(attribs.id || attribs.rename)
1585         attribs.callback = new AjxCallback(this, this._handleSuccessCreateFromAttachment, [msgId, partId, name, folderId]);
1586     if(attribs.rename)
1587         attribs.errorCallback = new AjxCallback(this, this._handleErrorCreateFromAttachment, [msgId, partId, attribs.rename, folderId]);
1588 
1589     var srcData = new ZmBriefcaseItem();
1590     srcData.createFromAttachment(msgId, partId, name, folderId, attribs);
1591 };
1592 
1593 ZmBriefcaseController.prototype._handleSuccessCreateFromAttachment =
1594 function(msgId, partId, name, folderId, response){
1595     if(this._conflictDialog){
1596         this._renameField.value = "";
1597         this._conflictDialog.popdown();
1598     }
1599 };
1600 
1601 ZmBriefcaseController.prototype._handleErrorCreateFromAttachment =
1602 function(msgId, partId, name, folderId, ex){
1603 
1604     var handled = false;
1605     if(ex.code == ZmCsfeException.MAIL_ALREADY_EXISTS){
1606         handled = true;
1607         var dlg = appCtxt.getMsgDialog();
1608         dlg.setMessage(AjxMessageFormat.format(ZmMsg.errorFileExistsWarning, name), DwtMessageDialog.WARNING_STYLE);
1609 	    dlg.setButtonListener(DwtDialog.OK_BUTTON, this._popupConflictDialog.bind(this, dlg));
1610         dlg.popup();
1611     }
1612 
1613     return handled;
1614 };
1615 
1616 ZmBriefcaseController.prototype._getFileConflictDialog =
1617     function(){
1618         if(!this._nameConflictDialog){
1619 
1620             var dlg = new DwtMessageDialog({parent:appCtxt.getShell(), buttons:[DwtDialog.OK_BUTTON, DwtDialog.CANCEL_BUTTON],
1621                 id: "Briefcase_FileConflictDialog"});
1622             this._nameConflictDialog = dlg;
1623             var id = this._nameConflictId = Dwt.getNextId();
1624             dlg.setTitle(ZmMsg.addToBriefcaseTitle);
1625             dlg.setContent(AjxTemplate.expand("briefcase.Briefcase#NameConflictDialog", {id: id}));
1626 
1627             this._renameRadio = document.getElementById(id+'_rename');
1628             this._renameField = document.getElementById(id+'_newname');
1629 
1630         }
1631         return this._nameConflictDialog;
1632     };
1633 
1634 ZmBriefcaseController.prototype.getKeyMapName =
1635 function() {
1636 	return ZmKeyMap.MAP_BRIEFCASE;
1637 };
1638 
1639 ZmBriefcaseController.prototype.handleKeyAction =
1640 function(actionCode) {
1641 	DBG.println(AjxDebug.DBG3, "ZmBriefcaseController.handleKeyAction");
1642 
1643     switch(actionCode) {
1644 
1645         case ZmKeyMap.READING_PANE_BOTTOM:
1646 		case ZmKeyMap.READING_PANE_RIGHT:
1647 		case ZmKeyMap.READING_PANE_OFF:
1648 			var menuId = ZmBriefcaseController.ACTION_CODE_TO_MENU_ID[actionCode];
1649 			this._previewPaneListener(menuId, true);
1650 			break;
1651 
1652         default:
1653             return ZmListController.prototype.handleKeyAction.call(this, actionCode);
1654     }
1655     return true;
1656 };
1657 
1658 /**
1659  * Tag/untag items
1660  *
1661  * @private
1662  */
1663 ZmBriefcaseController.prototype._doTag =
1664 function(items, tag, doTag) {
1665 	items = AjxUtil.toArray(items);
1666 	if (!items.length) { return; }
1667 	
1668 	for (var i=0; i<items.length; i++) {
1669 		if (items[i].isRevision) {
1670 			items[i] = items[i].parent;
1671 		}	
1672 	}
1673 	return ZmListController.prototype._doTag.call(this, items, tag, doTag);
1674 };
1675 
1676 
1677 /**
1678  * Moves a list of items to the given folder. Any item already in that folder is excluded.
1679  *
1680  * @param {Array}	items		a list of items to move
1681  * @param {ZmFolder}	folder		the destination folder
1682  * @param {Object}	attrs		the additional attrs for SOAP command
1683  * @param {Boolean}		isShiftKey	<code>true</code> if forcing a copy action
1684  * @param {Boolean}		noUndo	<code>true</code> undo not allowed
1685  * @private
1686  */
1687 ZmBriefcaseController.prototype._doMove =
1688 function(items, folder, attrs, isShiftKey, noUndo) {
1689 	items = AjxUtil.toArray(items);
1690 	if (!items.length) { return; }
1691 
1692 	for (var i=0; i<items.length; i++) {
1693 		if (items[i].isRevision) {
1694 			items[i] = items[i].parent;
1695 		}
1696 	}
1697 	return ZmListController.prototype._doMove.call(this, items, folder, attrs, isShiftKey, noUndo);
1698 };
1699 
1700 /**
1701  * Remove all tags for given items
1702  *
1703  * @private
1704  */
1705 ZmBriefcaseController.prototype._doRemoveAllTags =
1706 function(items) {
1707 
1708 	items = AjxUtil.toArray(items);
1709 	if (!items.length) { return; }
1710 
1711 	for (var i=0; i<items.length; i++) {
1712 		if (items[i].isRevision) {
1713 			items[i] = items[i].parent;
1714 		}
1715 	}
1716 	return ZmListController.prototype._doRemoveAllTags.call(this, items);
1717 };
1718 
1719 /*
1720 ** Using iframe provides a barrier to block any object below it
1721 */
1722 ZmBriefcaseController._onDeleteDialogPopup = function(dialog) {
1723 	var veilOverlay = appCtxt.getShell()._veilOverlay;
1724 	if (!veilOverlay) {
1725 		return;
1726 	}
1727 	var iframe = document.createElement("IFRAME");
1728 	iframe.style.cssText = veilOverlay.style.cssText;
1729 	iframe.style.zIndex = veilOverlay.style.zIndex - 1;
1730 	document.body.appendChild(iframe);
1731 	var onDeleteDialogPopdown = function(dialog) {
1732 		iframe.parentNode.removeChild(iframe);
1733 		dialog.removePopupListener(ZmBriefcaseController._onDeleteDialogPopup);
1734 		dialog.removePopdownListener(onDeleteDialogPopdown);
1735 	};
1736 	dialog.addPopdownListener(onDeleteDialogPopdown);
1737 };
1738