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 ZmDoublePaneView = function(params) {
 25 
 26 	if (arguments.length == 0) { return; }
 27 
 28 	var view = this._view = params.view = params.controller.getCurrentViewId();
 29 	params.id = ZmId.getViewId(view);
 30 	DwtComposite.call(this, params);
 31 
 32 	this._controller = params.controller;
 33 	this._initHeader();
 34 
 35 	params.className = null;
 36 	params.id = DwtId.getListViewId(view);
 37 	params.parent = this;
 38 	params.posStyle = Dwt.ABSOLUTE_STYLE;
 39 	this._mailListView = this._createMailListView(params);
 40 
 41 	// create the item view
 42 	params.className = null;
 43 	this._itemView = this._createMailItemView(params);
 44 
 45     var viewType = appCtxt.getViewTypeFromId(view);
 46     if (viewType === ZmId.VIEW_TRAD || viewType === ZmId.VIEW_CONVLIST) {
 47         this._createSashes();
 48     }
 49 	this.setReadingPane();
 50 };
 51 
 52 ZmDoublePaneView.prototype = new DwtComposite;
 53 ZmDoublePaneView.prototype.constructor = ZmDoublePaneView;
 54 
 55 ZmDoublePaneView.prototype.isZmDoublePaneView = true;
 56 ZmDoublePaneView.prototype.toString = function() { return "ZmDoublePaneView"; };
 57 
 58 // consts
 59 
 60 ZmDoublePaneView.SASH_THRESHOLD = 5;
 61 ZmDoublePaneView.MIN_LISTVIEW_WIDTH = 40;
 62 
 63 ZmDoublePaneView._TAG_IMG = "TI";
 64 
 65 
 66 // public methods
 67 
 68 ZmDoublePaneView.prototype.getController =
 69 function() {
 70 	return this._controller;
 71 };
 72 
 73 ZmDoublePaneView.prototype.getTitle =
 74 function() {
 75 	return this._mailListView.getTitle();
 76 };
 77 
 78 /**
 79  * Displays the reading pane, based on the current settings.
 80  */
 81 ZmDoublePaneView.prototype.setReadingPane =
 82 function(noSet) {
 83 
 84 	var mlv = this._mailListView,
 85         mv = this._itemView,
 86         sashesPresent = this._vertSash && this._horizSash;
 87 
 88 	var readingPaneEnabled = this._controller.isReadingPaneOn();
 89 	if (!readingPaneEnabled) {
 90 		mv.setVisible(false);
 91         if (sashesPresent) {
 92             this._vertSash.setVisible(false);
 93             this._horizSash.setVisible(false);
 94         }
 95 	}
 96     else {
 97 		if (!mv.getVisible()) {
 98 			if (mlv.getSelectionCount() == 1) {
 99 				this._controller._setSelectedItem();
100 			} else {
101 				mv.reset();
102 			}
103 		}
104 		var readingPaneOnRight = this._controller.isReadingPaneOnRight();
105 		mv.setVisible(true, readingPaneOnRight);
106         if (sashesPresent) {
107             var newSash = readingPaneOnRight ? this._vertSash : this._horizSash;
108             var oldSash = readingPaneOnRight ? this._horizSash : this._vertSash;
109             oldSash.setVisible(false);
110             newSash.setVisible(true);
111         }
112 	}
113 
114 	mlv.reRenderListView();
115     if (!noSet) {
116 	    mv.setReadingPane();
117     }
118 
119 	mv.noTab = !readingPaneEnabled || AjxEnv.isIE;
120 	var sz = this.getSize();
121 	this._resetSize(sz.x, sz.y, true);
122 };
123 
124 ZmDoublePaneView.prototype.getMailListView =
125 function() {
126 	return this._mailListView;
127 };
128 
129 ZmDoublePaneView.prototype.getItemView = 
130 function() {
131 	return this._itemView;
132 };
133 
134 // back-compatibility
135 ZmDoublePaneView.prototype.getMsgView = ZmDoublePaneView.prototype.getItemView;
136 
137 ZmDoublePaneView.prototype.getInviteMsgView =
138 function() {
139 	return this._itemView.getInviteMsgView();
140 };
141 
142 
143 ZmDoublePaneView.prototype.getSelectionCount = 
144 function() {
145 	return this._mailListView.getSelectionCount();
146 };
147 
148 ZmDoublePaneView.prototype.getSelection = 
149 function() {
150 	return this._mailListView.getSelection();
151 };
152 
153 ZmDoublePaneView.prototype.reset =
154 function() {
155 	this._mailListView.reset();
156 	this._itemView.reset();
157 };
158 
159 ZmDoublePaneView.prototype.getItem =
160 function() {
161 	return this._itemView.getItem();
162 };
163 
164 ZmDoublePaneView.prototype.setItem =
165 function(item, force, dontFocus) {
166 	this._itemView.set(item, force);
167 	this._controller._checkKeepReading();
168  };
169 
170 ZmDoublePaneView.prototype.clearItem =
171 function() {
172 	this._itemView.set();
173 };
174 
175 // TODO: see if we can remove these
176 ZmDoublePaneView.prototype.getMsg =
177 function() {
178 	return (this._controller.getCurrentViewType() == ZmId.VIEW_TRAD) ? this._itemView.getMsg() : null;
179 };
180 
181 ZmDoublePaneView.prototype.setMsg =
182 function(msg) {
183 	this._itemView.set(msg);
184 	this._controller._restoreFocus();	// bug 47700
185 };
186 
187 ZmDoublePaneView.prototype.addInviteReplyListener =
188 function (listener){
189 	this._itemView.addInviteReplyListener(listener);
190 };
191 
192 ZmDoublePaneView.prototype.addShareListener =
193 function (listener){
194 	this._itemView.addShareListener(listener);
195 };
196 
197 ZmDoublePaneView.prototype.addSubscribeListener =
198 function(listener) {
199 	this._itemView.addSubscribeListener(listener);
200 };
201 
202 
203 ZmDoublePaneView.prototype.resetMsg = 
204 function(newMsg) {
205 	this._itemView.resetMsg(newMsg);
206 };
207 
208 ZmDoublePaneView.prototype.isReadingPaneVisible =
209 function() {
210 	return this._itemView.getVisible();
211 };
212 
213 ZmDoublePaneView.prototype.setBounds = 
214 function(x, y, width, height) {
215 	DwtComposite.prototype.setBounds.call(this, x, y, width, height);
216 	this._resetSize(width, height);
217 };
218 
219 ZmDoublePaneView.prototype.setList =
220 function(list) {
221 	this._mailListView.set(list, ZmItem.F_DATE);
222 	this.isStale = false;
223 };
224 
225 // Private / Protected methods
226 
227 ZmDoublePaneView.prototype._initHeader = function() {};
228 ZmDoublePaneView.prototype._createMailListView = function(params) {};
229 ZmDoublePaneView.prototype._createMailItemView = function(params) {};
230 
231 // create a sash for each of the two reading pane locations
232 ZmDoublePaneView.prototype._createSashes = function() {
233 
234     var params = {
235         parent:     this,
236         style:      DwtSash.HORIZONTAL_STYLE,
237         className:  "AppSash-horiz",
238         threshold:  ZmDoublePaneView.SASH_THRESHOLD,
239         posStyle:   Dwt.ABSOLUTE_STYLE
240     };
241 
242     this._vertSash = new DwtSash(params);
243     this._vertSash.registerCallback(this._sashCallback, this);
244     this._vertSash.addListener(DwtEvent.ONMOUSEUP, this._sashVertRelease.bind(this));
245 
246     params.style = DwtSash.VERTICAL_STYLE;
247     params.className = "AppSash-vert";
248     this._horizSash = new DwtSash(params);
249     this._horizSash.registerCallback(this._sashCallback, this);
250     this._horizSash.addListener(DwtEvent.ONMOUSEUP, this._sashHorizRelease.bind(this));
251     this.addListener(DwtEvent.CONTROL, this._controlEventListener.bind(this));
252 };
253 
254 ZmDoublePaneView.prototype._resetSize = 
255 function(newWidth, newHeight, force) {
256 
257 	if (newWidth <= 0 || newHeight <= 0) { return; }
258 	if (!force && newWidth == this._lastResetWidth && newHeight == this._lastResetHeight) { return; }
259 
260 	var readingPaneOnRight = this._controller.isReadingPaneOnRight();
261 
262 	if (this.isReadingPaneVisible()) {
263 		var sash = this.getSash();
264 		var sashSize = sash.getSize();
265 		var sashThickness = readingPaneOnRight ? sashSize.x : sashSize.y;
266 		var itemViewMargins = this._itemView.getMargins();
267 		if (readingPaneOnRight) {
268 			var listViewWidth = this.getReadingSashPosition(true) || (Number(ZmMsg.LISTVIEW_WIDTH)) || Math.floor(newWidth / 2.5);
269 			this._mailListView.resetSize(listViewWidth, newHeight);
270 			sash.setLocation(listViewWidth, 0);
271 			this._itemView.setBounds(listViewWidth + sashThickness, 0,
272 									newWidth - (listViewWidth + sashThickness + itemViewMargins.left + itemViewMargins.right), newHeight);
273 		} else {
274 			var listViewHeight = this.getReadingSashPosition(false) || (Math.floor(newHeight / 2) - DwtListView.HEADERITEM_HEIGHT);
275 			this._mailListView.resetSize(newWidth, listViewHeight);
276 			sash.setLocation(0, listViewHeight);
277 			this._itemView.setBounds(0, listViewHeight + sashThickness, newWidth - itemViewMargins.left - itemViewMargins.right,
278 									newHeight - (listViewHeight + sashThickness));
279 		}
280 	} else {
281 		this._mailListView.resetSize(newWidth, newHeight);
282 	}
283 	this._mailListView._resetColWidth();
284 
285 	this._lastResetWidth = newWidth;
286 	this._lastResetHeight = newHeight;
287 };
288 
289 ZmDoublePaneView.prototype._sashCallback =
290 function(delta) {
291 
292 	var readingPaneOnRight = this._controller.isReadingPaneOnRight();
293 	var listView = this._mailListView;
294 	var itemView = this._itemView;
295 	//See bug 69593 for the reason for "true"
296 	var itemViewSize = itemView.getSize(true);
297 	var listViewSize = listView.getSize(true);
298 
299 	var newListViewSize;
300 	var newItemViewBounds;
301 
302 	var absDelta = Math.abs(delta);
303 
304 	if (readingPaneOnRight) {
305 		var currentListViewWidth = AjxEnv.isIE ? this._vertSash.getLocation().x : listViewSize.x;
306 		var currentItemViewWidth = itemViewSize.x;
307 		delta = this._getLimitedDelta(delta, currentItemViewWidth, itemView.getMinWidth(), currentListViewWidth, ZmDoublePaneView.MIN_LISTVIEW_WIDTH);
308 		if (!delta) {
309 			return 0;
310 		}
311 		newListViewSize = {width: currentListViewWidth + delta, height: Dwt.DEFAULT};
312 		newItemViewBounds = {
313 			left: itemView.getLocation().x + delta,
314 			top: Dwt.DEFAULT,
315 			width: currentItemViewWidth - delta,
316 			height: Dwt.DEFAULT
317 		};
318 	}
319 	else {
320 		//reading pane on bottom
321 		var currentListViewHeight = AjxEnv.isIE ? this._horizSash.getLocation().y : listViewSize.y;
322 		var currentItemViewHeight = itemViewSize.y;
323 		delta = this._getLimitedDelta(delta, currentItemViewHeight, itemView.getMinHeight(), currentListViewHeight, this._getMinListViewHeight(listView));
324 		if (!delta) {
325 			return 0;
326 		}
327 		newListViewSize = {width: Dwt.DEFAULT, height: currentListViewHeight + delta};
328 		newItemViewBounds = {
329 			left: Dwt.DEFAULT,
330 			top: itemView.getLocation().y + delta,
331 			width: Dwt.DEFAULT,
332 			height: currentItemViewHeight - delta
333 		};
334 	}
335 
336 	listView.resetSize(newListViewSize.width, newListViewSize.height);
337 	itemView.setBounds(newItemViewBounds.left, newItemViewBounds.top, newItemViewBounds.width, newItemViewBounds.height);
338 
339 	listView._resetColWidth();
340 	if (readingPaneOnRight) {
341 		this._vertSashX = this._vertSash.getLocation().x + delta;
342 	}
343 	else {
344 		this._horizSashY = this._horizSash.getLocation().y + delta;
345 	}
346 
347 	return delta;
348 };
349 
350 /**
351  * returns the delta after limiting it based on minimum view dimension (which is either width/height - this code doesn't care)
352  *
353  * @param delta
354  * @param currentItemViewDimension
355  * @param minItemViewDimension
356  * @param currentListViewDimension
357  * @param minListViewDimension
358  * @returns {number}
359  * @private
360  */
361 ZmDoublePaneView.prototype._getLimitedDelta =
362 function(delta, currentItemViewDimension, minItemViewDimension, currentListViewDimension, minListViewDimension) {
363 	if (delta > 0) {
364 		// moving sash right or down
365 		return Math.max(0, Math.min(delta, currentItemViewDimension - minItemViewDimension));
366 	}
367 	// moving sash left or up
368 	var absDelta = Math.abs(delta);
369 	return -Math.max(0, Math.min(absDelta, currentListViewDimension - minListViewDimension));
370 };
371 
372 ZmDoublePaneView.prototype._getMinListViewHeight =
373 function(listView) {
374 	if (this._minListViewHeight) {
375 		return this._minListViewHeight;
376 	}
377 
378 	var list = listView.getList();
379 	if (!list || !list.size()) {
380 		return DwtListView.HEADERITEM_HEIGHT;
381 	}
382 	//only cache it if there's a list, to prevent a subtle bug of setting to just the header height if
383 	//user first views an empty list.
384 	var item = list.get(0);
385 	var div = document.getElementById(listView._getItemId(item));
386 	this._minListViewHeight = DwtListView.HEADERITEM_HEIGHT + Dwt.getSize(div).y * 2;
387 	return this._minListViewHeight;
388 };
389 
390 ZmDoublePaneView.prototype._selectFirstItem =
391 function() {
392 	var list = this._mailListView.getList();
393 	var selectedItem = list ? list.get(0) : null;
394 	if (selectedItem) {
395 		this._mailListView.setSelection(selectedItem, false);
396 	}
397 };
398 
399 ZmDoublePaneView.prototype.getSash =
400 function() {
401 	var readingPaneOnRight = this._controller.isReadingPaneOnRight();
402 	return readingPaneOnRight ? this._vertSash : this._horizSash;
403 };
404 
405 ZmDoublePaneView.prototype.getLimit =
406 function(offset) {
407 	return this._mailListView.getLimit(offset);
408 };
409 
410 ZmDoublePaneView.prototype._staleHandler =
411 function() {
412 
413 	var search = this._controller._currentSearch;
414 	if (search) {
415 		search.lastId = search.lastSortVal = null;
416 		search.offset = search.limit = 0;
417 		var params = {isRefresh: true};
418 		var mlv = this._mailListView
419 		if (mlv.getSelectionCount() == 1) {
420 			var sel = mlv.getSelection();
421 			var selItem = sel && sel[0];
422 			var curItem = this.getItem();
423 			if (selItem && curItem && selItem.id == curItem.id) {
424 				params.selectedItem = selItem;
425 			}
426 		}
427 		appCtxt.getSearchController().redoSearch(search, false, params);
428 	}
429 };
430 
431 ZmDoublePaneView.prototype.handleRemoveAttachment =
432 function(oldMsgId, newMsg) {
433 	this._itemView.handleRemoveAttachment(oldMsgId, newMsg);
434 };
435 
436 /**
437  * Returns the sash location (in pixels) based on reading pane preference.
438  * 
439  * @param {boolean}		readingPaneOnRight   true if reading pane is on the right
440  */
441 ZmDoublePaneView.prototype.getReadingSashPosition =
442 function(readingPaneOnRight) {
443 	if (readingPaneOnRight) {
444 		if (!this._vertSashX) {
445 			var value = this._readingPaneSashVertPos || appCtxt.get(ZmSetting.READING_PANE_SASH_VERTICAL);
446 			var percentWidth = value / 100;
447 			var screenWidth = this.getSize().x;
448 			this._vertSashX = Math.round(percentWidth * screenWidth);
449 		}
450 		return this._vertSashX;
451 	}
452 	else {
453 		if (!this._horizSashY) {
454 			var value = this._readingPaneSashHorizPos || appCtxt.get(ZmSetting.READING_PANE_SASH_HORIZONTAL);
455 			var percentHeight = value / 100;
456 			var screenHeight = this.getSize().y;
457 			this._horizSashY = Math.round(percentHeight * screenHeight);
458 		}
459 		return this._horizSashY;
460 	}
461 };
462 
463 /**
464  * Sets the location of sash (in percentage) depending upon reading pane preference.
465  * 
466  * @param {boolean}		readingPaneOnRight	true if reading pane is on the right
467  * @param {int}			value   			location of sash (in pixels)
468  */
469 ZmDoublePaneView.prototype.setReadingSashPosition =
470 function(readingPaneOnRight, value) {
471 	if (readingPaneOnRight) {
472 		var screenWidth = this.getSize().x;
473 		var sashWidthPercent = Math.round((value / screenWidth) * 100);
474 		if (this._controller.isSearchResults) {
475 			this._readingPaneSashVertPos = sashWidthPercent;
476 		}
477 		else {
478 			appCtxt.set(ZmSetting.READING_PANE_SASH_VERTICAL, sashWidthPercent);
479 		}
480 	}
481 	else {
482 		var screenHeight = this.getSize().y;
483 		var sashHeightPercent = Math.round((value/screenHeight) * 100);
484 		if (this._controller.isSearchResults) {
485 			this._readingPaneSashHorizPos = sashHeightPercent;
486 		}
487 		else {
488 			appCtxt.set(ZmSetting.READING_PANE_SASH_HORIZONTAL, sashHeightPercent);
489 		}
490 	}
491 };
492 
493 ZmDoublePaneView.prototype._sashVertRelease =
494 function() {
495 	this.setReadingSashPosition(true, this._vertSashX);
496 };
497 
498 ZmDoublePaneView.prototype._sashHorizRelease =
499 function() {
500 	this.setReadingSashPosition(false, this._horizSashY);
501 };
502 
503 ZmDoublePaneView.prototype._controlEventListener =
504 function(ev) {
505 	//resize can be called multiple times based on the browser so wait till resizing is complete
506 	if (ev && (ev.newWidth == ev.requestedWidth) && (ev.newHeight == ev.requestedHeight))  {
507 		var readingPaneOnRight = this._controller.isReadingPaneOnRight();
508 		//reset the sash values and resize the pane based on settings
509 		if (readingPaneOnRight) {
510 			this._readingPaneSashVertPos = appCtxt.get(ZmSetting.READING_PANE_SASH_VERTICAL);
511 			delete this._vertSashX;
512 		} else {
513 			this._readingPaneSashHorizPos = appCtxt.get(ZmSetting.READING_PANE_SASH_HORIZONTAL);
514 			delete this._horizSashY;
515 		}
516 		var sz = this.getSize();
517 		this._resetSize(sz.x, sz.y, true);
518 	}
519 };
520 
521