From 6625ffdde488c2e4bd1b94e63baf21f26c5c18a1 Mon Sep 17 00:00:00 2001
From: Ralf Becker <ralfbecker@outdoor-training.de>
Date: Fri, 6 Feb 2015 13:36:25 +0000
Subject: [PATCH] * All apps: printing of lists improved a lot, asks now how
 many lines to print r51437: Work in progress of printing nextmatches, still
 needs some prettying up & edge case testing r51453: Bug fixes on nextmatch
 printing  - fix loaded rows check  - fix hidden etemplate check r51454:
 Printing CSS improvements r51588: Attempt to get nextmatch printing always on
 the page (landscape) r51589: Attempt to get nextmatch printing always on the
 page (landscape) - put things back if they cancel at nextmatch dialog r51612:
 disable footer for print

---
 etemplate/js/et2_core_interfaces.js        |  17 +++
 etemplate/js/et2_extension_nextmatch.js    | 137 ++++++++++++++++++++-
 etemplate/js/etemplate2.js                 |  63 ++++++++++
 etemplate/templates/default/etemplate2.css |  25 +++-
 jdots/print.css                            |  20 ++-
 phpgwapi/js/framework/fw_base.js           |  12 +-
 pixelegg/print.css                         |   2 +-
 7 files changed, 268 insertions(+), 8 deletions(-)

diff --git a/etemplate/js/et2_core_interfaces.js b/etemplate/js/et2_core_interfaces.js
index 15a5a98d22..70d2508de9 100644
--- a/etemplate/js/et2_core_interfaces.js
+++ b/etemplate/js/et2_core_interfaces.js
@@ -144,3 +144,20 @@ var et2_IDetachedDOM = new Interface({
 
 });
 
+/**
+ * Interface for widgets that need to do something special before printing
+ */
+var et2_IPrint = new Interface({
+	/**
+	 * Set up for printing
+	 *
+	 * @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
+	 *  (waiting for data)
+	 */
+	beforePrint: function() {},
+
+	/**
+	 * Reset after printing
+	 */
+	afterPrint: function() {}
+});
\ No newline at end of file
diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js
index 8cadd189da..a52450ea38 100644
--- a/etemplate/js/et2_extension_nextmatch.js
+++ b/etemplate/js/et2_extension_nextmatch.js
@@ -68,7 +68,7 @@ var et2_INextmatchSortable = new Interface({
  *
  * @augments et2_DOMWidget
  */
-var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
+var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrint],
 {
 	attributes: {
 		// These normally set in settings, but broken out into attributes to allow run-time changes
@@ -1836,6 +1836,141 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
 	set_value: function(_value)
 	{
 		this.value = _value;
+	},
+
+	// Printing
+	/**
+	 * Prepare for printing
+	 *
+	 * We check for un-loaded rows, and ask the user what they want to do about them.
+	 * If they want to print them all, we ask the server and print when they're loaded.
+	 */
+	beforePrint: function() {
+		// Add the class, if needed
+		this.div.addClass('print');
+
+		// Trigger resize, so we can fit on a page
+		this.dynheight.outerNode.css('max-width',this.div.css('max-width'));
+		this.resize();
+
+		// Check for rows that aren't loaded yet, or lots of rows
+		var range = this.controller._grid.getIndexRange();
+		this.old_height = this.controller._grid._scrollHeight;
+		var loaded_count = range.bottom - range.top +1;
+		var total = this.controller._grid.getTotalCount();
+		if(loaded_count != total ||
+			this.controller._grid.getTotalCount() > 100)
+		{
+			// Defer the printing
+			var defer = jQuery.Deferred();
+
+			// Something not in the grid, lets ask
+			et2_dialog.show_prompt(jQuery.proxy(function(button, value) {
+					if(button == 'dialog[cancel]') {
+						// Give dialog a chance to close, or it will be in the print
+						window.setTimeout(function() {defer.reject();}, 0);
+						return;
+					}
+					value = parseInt(value);
+					if(value > total)
+					{
+						value = total;
+					}
+					
+					// If they want the whole thing, treat it as all
+					if(button == 'dialog[ok]' && value == this.controller._grid.getTotalCount())
+					{
+						button = 'dialog[all]';
+						// Add the class, gives more reliable sizing
+						this.div.addClass('print');
+					}
+					// We need more rows
+					if(button == 'dialog[all]' || value > loaded_count)
+					{
+						var count = 0;
+						var fetchedCount = 0;
+						var cancel = false;
+						var nm = this;
+						var dialog = et2_dialog.show_dialog(
+							// Abort the long task if they canceled the data load
+							function() {count = total; cancel=true;window.setTimeout(function() {defer.reject();},0)},
+							egw.lang('Loading'), egw.lang('please wait...'),{},[
+								{"button_id": et2_dialog.CANCEL_BUTTON,"text": 'cancel',id: 'dialog[cancel]',image: 'cancel'}
+							]
+						);
+
+						// dataFetch() is asyncronous, so all these requests just get fired off...
+						// 200 rows chosen arbitrarily to reduce requests.
+						do {
+							var ctx = {
+								"self": this.controller,
+								"start": count,
+								"count": Math.min(value,200),
+								"lastModification": this.controller._lastModification
+							};
+							if(nm.controller.dataStorePrefix)
+							{
+								ctx.prefix = nm.controller.dataStorePrefix;
+							}
+							nm.controller.dataFetch({start:count, num_rows: Math.min(value,200)}, function(data)  {		
+								// Keep track
+								if(data && data.order)
+								{
+									fetchedCount += data.order.length;
+								}
+								nm.controller._fetchCallback.apply(this, arguments);
+								
+								if(fetchedCount >= value)
+								{
+									if(cancel)
+									{
+										dialog.destroy();
+										defer.reject();
+										return;
+									}
+									nm.controller._grid.setScrollHeight(nm.controller._grid.getAverageHeight() * (value+1));
+									// Grid needs to redraw before it can be printed, so wait
+									window.setTimeout(jQuery.proxy(function() {
+										dialog.destroy();
+										// Should be OK to print now
+										defer.resolve();
+									},nm),ET2_GRID_INVALIDATE_TIMEOUT);
+									
+								}
+								
+							},ctx);
+							count += 200;
+						} while (count < value)
+						nm.controller._grid.setScrollHeight(nm.controller._grid.getAverageHeight() * (value+1));
+					}
+					else
+					{
+						// Don't need more rows, limit to requested and finish
+						this.controller._grid.setScrollHeight(this.controller._grid.getAverageHeight() * (value+1));
+						// Give dialog a chance to close, or it will be in the print
+						window.setTimeout(function() {defer.resolve();}, 0);
+					}
+					//this.controller._gridCallback(0, button == et2_dialog.OK_BUTTON ? value : this.controller._grid.getTotalCount());
+				},this),
+				egw.lang('How many rows to print'), egw.lang('Print'),
+				Math.min(100, total),
+				[
+					{"button_id": 1,"text": egw.lang('Ok'), id: 'dialog[ok]', image: 'check', "default":true},
+					// Nice for small lists, kills server for large lists
+					//{"button_id": 2,"text": egw.lang('All'), id: 'dialog[all]', image: ''},
+					{"button_id": 0,"text": egw.lang('Cancel'), id: 'dialog[cancel]', image: 'cancel'},
+				]
+			);
+			return defer;
+		}
+		// Don't return anything, just work normally
+	},
+	afterPrint: function() {
+		this.div.removeClass('print');
+		this.controller._grid.setScrollHeight(this.old_height);
+		delete this.old_height;
+		this.dynheight.outerNode.css('max-width','inherit');
+		this.resize();
 	}
 });
 et2_register_widget(et2_nextmatch, ["nextmatch"]);
diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js
index a785bc846b..d5d9c0aec8 100644
--- a/etemplate/js/etemplate2.js
+++ b/etemplate/js/etemplate2.js
@@ -904,6 +904,69 @@ etemplate2.app_refresh = function(_msg, _app, _id, _type)
 	return refresh_done;
 };
 
+/**
+ * "Intelligently" print a given app
+ *
+ * Mostly, we let the nextmatch change how many rows it's showing, so you don't
+ * get just one printed page.
+ */
+etemplate2.print = function(_app)
+{
+	// Allow any widget to change for printing
+	var et2 = etemplate2.getByApplication(_app);
+
+	// Sometimes changes take time
+	var deferred = [];
+	for(var i = 0; i < et2.length; i++)
+	{
+		// Skip hidden templates
+		if(!jQuery(et2[i].DOMContainer).filter(':visible').length) continue;
+
+		et2[i].widgetContainer.iterateOver(function(_widget) {
+			// Skip widgets from a different etemplate (home)
+			if(_widget.getInstanceManager() != et2[i]) return;
+			var result = _widget.beforePrint();
+			if (typeof result == "object" && result.done)
+			{
+				deferred.push(result);
+			}
+		},et2,et2_IPrint);
+	}
+
+	// Try to clean up after - not guaranteed
+	var afterPrint = function() {
+		for(var i = 0; i < et2.length; i++)
+		{
+			// Skip hidden templates
+			if(!jQuery(et2[i].DOMContainer).filter(':visible')) continue;
+			et2[i].widgetContainer.iterateOver(function(_widget) {
+				_widget.afterPrint();
+			},et2,et2_IPrint);
+		}
+		var mediaQueryList = window.matchMedia('print');
+        mediaQueryList
+	};
+	if(egw.window.matchMedia) {
+		var mediaQueryList = window.matchMedia('print');
+		var listener = function(mql) {
+            if (!mql.matches) {
+                afterPrint();
+				mediaQueryList.removeListener(listener);
+            }
+        };
+		mediaQueryList.addListener(listener);
+    }
+
+    egw.window.onafterprint = afterPrint;
+
+	// Wait for everything to be loaded, then send it off
+	jQuery.when.apply(jQuery, deferred).done(function() {
+		egw.window.print();
+	}).fail(function() {
+		afterPrint();
+	});
+}
+
 // Some static things to make getting into widget context a little easier //
 
 /**
diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css
index b1298b5171..80c5f726f7 100644
--- a/etemplate/templates/default/etemplate2.css
+++ b/etemplate/templates/default/etemplate2.css
@@ -462,6 +462,21 @@ which caused click on free space infront of a tag stops nm row selection*/
 .et2_nextmatch .egwGridView_grid tr td div.et2_vbox a  {
 	display: table-row;
 }
+/*
+These are set via javascript before printing to help tame nextmatch's layout
+for printing
+*/
+.et2_nextmatch.print .egwGridView_scrollarea {
+	height: auto !important;
+	width: auto !important;
+}
+.et2_nextmatch.print  > div {
+	height: auto !important;
+}
+.et2_nextmatch.print {
+	/* This is fairly arbitrary, but makes it fit in Chrome and Firefox*/
+	max-width: 700pt !important;
+}
 /**
  * Diff widget
  */
@@ -1115,13 +1130,19 @@ div.message.floating {
 	.et2_nextmatch .egwGridView_grid > tbody > tr {
 		display: block;
 	}
+	.et2_nextmatch .egwGridView_spacer {
+		display:none;
+	}
 	.egwGridView_grid > tbody > tr {
 		page-break-inside: avoid;
 		-webkit-region-break-inside: avoid;
 	}
 	.et2_nextmatch > div {
-		width: 100% !important;
-		height: auto;
+		width: auto !important;
+		height: auto !important;
+	}
+	.et2_nextmatch table.egwGridView_outer thead tr th div.innerContainer {
+		max-height: inherit;
 	}
 	#cke_1_top.cke_top {
 		display: none;
diff --git a/jdots/print.css b/jdots/print.css
index 02852e7b5e..02b8fd0f86 100644
--- a/jdots/print.css
+++ b/jdots/print.css
@@ -18,21 +18,35 @@
 @media print {
 
 	html, body {
-		width: 100% !important;
+		width: auto !important;
+		height: auto !important;
 	}
 	#egw_fw_sidebar, #egw_fw_topmenu, .egw_fw_ui_tabs_header, #egw_fw_topmenu_slide,#egw_fw_print,#egw_fw_logout {
 		display: none;
+		width: 0px;
 	}
 
 	#egw_fw_main, #egw_fw_tabs {
 		margin: 0px !important;
 		float: none !important;
+		width: auto !important;
 	}
 	#egw_fw_basecontainer {
 		position: inherit;
+		width: auto;
 	}
-	.egw_fw_ui_tab_content, .egw_fw_ui_tab_content > div {
-		height: auto;
+	.egw_fw_ui_tab_content {
+		position: relative;
+		float: none;
+		width: auto !important;
+	}
+	#egw_fw_main .egw_fw_ui_tab_content > div {
+		width: auto !important;
+	}
+	.egw_fw_ui_tab_content, .egw_fw_ui_tab_content div.egw_fw_content_browser_div {
+		height: auto !important;
+		overflow-y: hidden;
+		position: relative;
 	}
 	body {
 		background: white;
diff --git a/phpgwapi/js/framework/fw_base.js b/phpgwapi/js/framework/fw_base.js
index 55d5268fe5..dd88d0d5d8 100644
--- a/phpgwapi/js/framework/fw_base.js
+++ b/phpgwapi/js/framework/fw_base.js
@@ -958,7 +958,17 @@ var fw_base =  Class.extend({
 			if (appWindow)
 			{
 				appWindow.focus();
-				appWindow.print();
+
+				// et2 available, let its widgets prepare
+				if(typeof etemplate2 == "function" && etemplate2.print)
+				{
+					etemplate2.print(this.activeApp.appName);
+				}
+				else
+				{
+					// Print
+					appWindow.print();
+				}
 			}
 		}
 	}
diff --git a/pixelegg/print.css b/pixelegg/print.css
index 1ac39f43db..27ae35c75c 100644
--- a/pixelegg/print.css
+++ b/pixelegg/print.css
@@ -16,7 +16,7 @@
  */
 
 @media print {
-	#egw_fw_topmenu_addons, #egw_fw_header {
+	#egw_fw_topmenu_addons, #egw_fw_header, #egw_fw_footer {
 		display: none;
 	}
 } /* @media print */
\ No newline at end of file