[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-1-0' - 4 commits - loleaflet/build loleaflet/dist loleaflet/src loolwsd/README loolwsd/test

Henry Castro hcastro at collabora.com
Thu Sep 1 17:42:22 UTC 2016


 loleaflet/build/deps.js                       |   22 ++
 loleaflet/dist/leaflet.css                    |    3 
 loleaflet/dist/spreadsheet.css                |   70 ++++++-
 loleaflet/src/control/Control.ColumnHeader.js |  142 ++++++++++++++--
 loleaflet/src/control/Control.Header.js       |   84 +++++++++
 loleaflet/src/control/Control.MetricInput.js  |  102 +++++++++++
 loleaflet/src/control/Control.RowHeader.js    |  147 +++++++++++++++-
 loleaflet/src/control/Control.Scroll.js       |    4 
 loleaflet/src/control/Control.js              |    1 
 loleaflet/src/layer/tile/CalcTileLayer.js     |    7 
 loleaflet/src/map/Map.js                      |   13 +
 loolwsd/README                                |    6 
 loolwsd/test/data/empty.ods                   |binary
 loolwsd/test/httpwstest.cpp                   |  228 ++++++++++++++++++++++++++
 loolwsd/test/run_unit.sh.in                   |   11 +
 loolwsd/test/test.cpp                         |   65 +++++--
 16 files changed, 844 insertions(+), 61 deletions(-)

New commits:
commit 3d3a11d9c3080848a8f4cb22d1a86c7975069bd4
Author: Henry Castro <hcastro at collabora.com>
Date:   Sat Aug 13 20:11:24 2016 -0400

    loolwsd: test: column/row re-size

diff --git a/loolwsd/test/data/empty.ods b/loolwsd/test/data/empty.ods
new file mode 100755
index 0000000..86414ec
Binary files /dev/null and b/loolwsd/test/data/empty.ods differ
diff --git a/loolwsd/test/httpwstest.cpp b/loolwsd/test/httpwstest.cpp
index 718b78f..bd57d03 100644
--- a/loolwsd/test/httpwstest.cpp
+++ b/loolwsd/test/httpwstest.cpp
@@ -83,6 +83,8 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST(testEditAnnotationWriter);
     CPPUNIT_TEST(testInsertAnnotationCalc);
     CPPUNIT_TEST(testStateUnoCommand);
+    CPPUNIT_TEST(testColumnRowResize);
+    CPPUNIT_TEST(testOptimalResize);
 
     CPPUNIT_TEST_SUITE_END();
 
@@ -114,6 +116,8 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
     void testEditAnnotationWriter();
     void testInsertAnnotationCalc();
     void testStateUnoCommand();
+    void testColumnRowResize();
+    void testOptimalResize();
 
     void loadDoc(const std::string& documentURL);
 
@@ -141,6 +145,8 @@ class HTTPWSTest : public CPPUNIT_NS::TestFixture
                                              int newWidth, int newHeight)> checkhandler);
 
     void testStateChanged(const std::string& filename, std::vector<std::string>& vecComands);
+    double getColRowSize(const std::string& property, const std::string& message, int index);
+    double getColRowSize(const std::shared_ptr<Poco::Net::WebSocket>& socket, const std::string& item, int index);
 
 public:
     HTTPWSTest()
@@ -1846,6 +1852,228 @@ void HTTPWSTest::testStateUnoCommand()
     }
 }
 
+double HTTPWSTest::getColRowSize(const std::string& property, const std::string& message, int index)
+{
+    Poco::JSON::Parser parser;
+    const auto result = parser.parse(message);
+    const auto& command = result.extract<Poco::JSON::Object::Ptr>();
+    auto text = command->get("commandName").toString();
+
+    CPPUNIT_ASSERT_EQUAL(std::string(".uno:ViewRowColumnHeaders"), text);
+    CPPUNIT_ASSERT(command->isArray(property));
+
+    auto array = command->getArray(property);
+
+    CPPUNIT_ASSERT(array->isObject(index));
+
+    auto item = array->getObject(index);
+
+    CPPUNIT_ASSERT(item->has("size"));
+
+    return item->getValue<double>("size");
+}
+
+double HTTPWSTest::getColRowSize(const std::shared_ptr<Poco::Net::WebSocket>& socket, const std::string& item, int index)
+{
+    std::vector<char> response;
+    response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
+    CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
+    std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
+    json.push_back(0);
+    return getColRowSize(item, json.data(), index);
+}
+
+void HTTPWSTest::testColumnRowResize()
+{
+    try
+    {
+        std::vector<char> response;
+        std::string documentPath, documentURL;
+        double oldHeight, oldWidth;
+
+        getDocumentPathAndURL("setclientpart.ods", documentPath, documentURL);
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
+
+        auto socket = loadDocAndGetSocket(_uri, documentURL, "testColumnRowResize ");
+
+        const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
+        sendTextFrame(socket, commandValues);
+        response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
+        CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
+        {
+            std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
+            json.push_back(0);
+
+            // get column 2
+            oldHeight = getColRowSize("rows", json.data(), 1);
+            // get row 2
+            oldWidth = getColRowSize("columns", json.data(), 1);
+        }
+
+        // send column width
+        {
+            std::ostringstream oss;
+            Poco::JSON::Object objJSON, objColumn, objWidth;
+            double newWidth;
+
+            // change column 2
+            objColumn.set("type", "unsigned short");
+            objColumn.set("value", 2);
+
+            objWidth.set("type", "unsigned short");
+            objWidth.set("value", oldWidth + 100);
+
+            objJSON.set("Column", objColumn);
+            objJSON.set("Width", objWidth);
+
+            Poco::JSON::Stringifier::stringify(objJSON, oss);
+            sendTextFrame(socket, "uno .uno:ColumnWidth " + oss.str());
+            sendTextFrame(socket, commandValues);
+            response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
+            CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
+            std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
+            json.push_back(0);
+            newWidth = getColRowSize("columns", json.data(), 1);
+            CPPUNIT_ASSERT(newWidth > oldWidth);
+        }
+
+        // send row height
+        {
+            std::ostringstream oss;
+            Poco::JSON::Object objJSON, objRow, objHeight;
+            double newHeight;
+
+            // change row 2
+            objRow.set("type", "unsigned short");
+            objRow.set("value", 2);
+
+            objHeight.set("type", "unsigned short");
+            objHeight.set("value", oldHeight + 100);
+
+            objJSON.set("Row", objRow);
+            objJSON.set("Height", objHeight);
+
+            Poco::JSON::Stringifier::stringify(objJSON, oss);
+            sendTextFrame(socket, "uno .uno:RowHeight " + oss.str());
+            sendTextFrame(socket, commandValues);
+            response = getResponseMessage(socket, "commandvalues:", "testColumnRowResize ");
+            CPPUNIT_ASSERT_MESSAGE("did not receive a commandvalues: message as expected", !response.empty());
+            std::vector<char> json(response.begin() + std::string("commandvalues:").length(), response.end());
+            json.push_back(0);
+            newHeight = getColRowSize("rows", json.data(), 1);
+            CPPUNIT_ASSERT(newHeight > oldHeight);
+        }
+    }
+    catch (const Poco::Exception& exc)
+    {
+        CPPUNIT_FAIL(exc.displayText());
+    }
+}
+
+void HTTPWSTest::testOptimalResize()
+{
+    try
+    {
+        //std::vector<char> response;
+        std::string documentPath, documentURL;
+        double newWidth, newHeight;
+        Poco::JSON::Object objIndex, objSize, objModifier;
+
+        // row/column index 0
+        objIndex.set("type", "unsigned short");
+        objIndex.set("value", 1);
+
+        // size in twips
+        objSize.set("type", "unsigned short");
+        objSize.set("value", 3840);
+
+        // keyboard modifier
+        objModifier.set("type", "unsigned short");
+        objModifier.set("value", 0);
+
+        getDocumentPathAndURL("empty.ods", documentPath, documentURL);
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, documentURL);
+        auto socket = loadDocAndGetSocket(_uri, documentURL, "testOptimalResize ");
+
+        const std::string commandValues = "commandvalues command=.uno:ViewRowColumnHeaders";
+        // send new column width
+        {
+            std::ostringstream oss;
+            Poco::JSON::Object objJSON;
+
+            objJSON.set("Column", objIndex);
+            objJSON.set("Width", objSize);
+
+            Poco::JSON::Stringifier::stringify(objJSON, oss);
+            sendTextFrame(socket, "uno .uno:ColumnWidth " + oss.str());
+            sendTextFrame(socket, commandValues);
+            newWidth = getColRowSize(socket, "columns", 0);
+        }
+        // send new row height
+        {
+            std::ostringstream oss;
+            Poco::JSON::Object objJSON;
+
+            objJSON.set("Row", objIndex);
+            objJSON.set("Height", objSize);
+
+            Poco::JSON::Stringifier::stringify(objJSON, oss);
+            sendTextFrame(socket, "uno .uno:RowHeight " + oss.str());
+            sendTextFrame(socket, commandValues);
+            newHeight = getColRowSize(socket, "rows", 0);
+        }
+
+        objIndex.set("value", 0);
+
+        // send optimal column width
+        {
+            std::ostringstream oss;
+            Poco::JSON::Object objJSON;
+            double optimalWidth;
+
+            objJSON.set("Col", objIndex);
+            objJSON.set("Modifier", objModifier);
+
+            Poco::JSON::Stringifier::stringify(objJSON, oss);
+            sendTextFrame(socket, "uno .uno:SelectColumn " + oss.str());
+            sendTextFrame(socket, "uno .uno:SetOptimalColumnWidthDirect");
+            sendTextFrame(socket, commandValues);
+            optimalWidth = getColRowSize(socket, "columns", 0);
+            CPPUNIT_ASSERT(optimalWidth < newWidth);
+        }
+
+        // send optimal row height
+        {
+            std::ostringstream oss;
+            Poco::JSON::Object objSelect, objOptHeight, objExtra;
+            double optimalHeight;
+
+            objSelect.set("Row", objIndex);
+            objSelect.set("Modifier", objModifier);
+
+            objExtra.set("type", "unsigned short");
+            objExtra.set("value", 0);
+
+            objOptHeight.set("aExtraHeight", objExtra);
+
+            Poco::JSON::Stringifier::stringify(objSelect, oss);
+            sendTextFrame(socket, "uno .uno:SelectRow " + oss.str());
+            oss.str(""); oss.clear();
+
+            Poco::JSON::Stringifier::stringify(objOptHeight, oss);
+            sendTextFrame(socket, "uno .uno:SetOptimalRowHeight " + oss.str());
+
+            sendTextFrame(socket, commandValues);
+            optimalHeight = getColRowSize(socket, "rows", 0);
+            CPPUNIT_ASSERT(optimalHeight < newHeight);
+        }
+    }
+    catch (const Poco::Exception& exc)
+    {
+        CPPUNIT_FAIL(exc.displayText());
+    }
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(HTTPWSTest);
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 49419c269014b041e4f198838d27eb6131d2995d
Author: Henry Castro <hcastro at collabora.com>
Date:   Thu Aug 4 16:48:47 2016 -0400

    loleaflet: add feature column/row re-size

diff --git a/loleaflet/build/deps.js b/loleaflet/build/deps.js
index 105cc6b..1425ed1 100644
--- a/loleaflet/build/deps.js
+++ b/loleaflet/build/deps.js
@@ -260,30 +260,44 @@ var deps = {
 		desc: 'Parts preview sidebar'
 	},
 
+	ControlHeader: {
+		src: ['control/Control.js',
+		      'control/Control.Header.js'],
+		heading: 'Controls',
+		desc: 'Header Item'
+	},
+
 	ControlColumnHeader: {
 		src: ['control/Control.js',
-			  'control/Control.ColumnHeader.js'],
+		      'control/Control.ColumnHeader.js'],
 		heading: 'Controls',
 		desc: 'Column Header bar'
 	},
 
 	ControlRowHeader: {
 		src: ['control/Control.js',
-			  'control/Control.RowHeader.js'],
+		      'control/Control.RowHeader.js'],
 		heading: 'Controls',
 		desc: 'Row Header bar'
 	},
 
+	ControlMetricInput: {
+		src: ['control/Control.js',
+		      'control/Control.MetricInput.js'],
+		heading: 'Controls',
+		desc: 'Metric Input'
+	},
+
 	ControlContextmenu: {
 		src: ['control/Control.js',
-			'control/Control.ContextMenu.js'],
+		      'control/Control.ContextMenu.js'],
 		heading: 'Controls',
 		desc: 'Context Menu'
 	},
 
 	ControlMenubar: {
 		src: ['control/Control.js',
-			'control/Control.Menubar.js'],
+		      'control/Control.Menubar.js'],
 		heading: 'Controls',
 		desc: 'Menu bar'
 	},
diff --git a/loleaflet/dist/leaflet.css b/loleaflet/dist/leaflet.css
index e3c12b3..7d06e57 100644
--- a/loleaflet/dist/leaflet.css
+++ b/loleaflet/dist/leaflet.css
@@ -111,6 +111,9 @@
 .leaflet-top {
 	top: 0;
 	}
+.leaflet-middle {
+	left: 50%;
+	}
 .leaflet-right {
 	right: 0;
 	}
diff --git a/loleaflet/dist/spreadsheet.css b/loleaflet/dist/spreadsheet.css
index 7537600..30c992c 100644
--- a/loleaflet/dist/spreadsheet.css
+++ b/loleaflet/dist/spreadsheet.css
@@ -99,24 +99,13 @@
 
 .spreadsheet-header-column {
 	border-right: 1px solid darkgrey;
-	font: 12px/1.5 "Segoe UI", Tahoma, Arial, Helvetica, sans-serif;
-
 	display: inline-block;
-	text-align: center;
-	text-overflow: ellipsis;
-	white-space: nowrap;
+	text-align: left;
 	padding: 0px;
 	padding-top: 1px;
 	margin: 0px;
 	height: 100%;
-	cursor: pointer;
-
-	/* Make the text unselectable for all browsers */
-	-webkit-touch-callout: none;
-	-webkit-user-select: none;
-	-khtml-user-select: none;
-	-moz-user-select: none;
-	-ms-user-select: none;
+	overflow: hidden;
 	}
 
 .spreadsheet-header-column:hover {
@@ -146,14 +135,57 @@
 
 .spreadsheet-header-row {
 	border-bottom: 1px solid darkgrey;
-	font: 12px/1.5 "Segoe UI", Tahoma, Arial, Helvetica, sans-serif;
+	padding: 0px;
+	margin: 0px;
+	height: 100%;
+	cursor: pointer;
+	overflow: hidden;
+	}
+
+.spreadsheet-header-row:hover {
+	background-color: #DDD;
+	}
 
+.spreadsheet-header-column-text {
+	display: inline-block;
+	vertical-align: top;
+	font: 12px/1.5 "Segoe UI", Tahoma, Arial, Helvetica, sans-serif;
 	text-overflow: ellipsis;
-	text-align: center;
 	white-space: nowrap;
+	text-align: center;
+	height: 100%;
+	margin: 0px;
+	border: 0px;
 	padding: 0px;
+	cursor: pointer;
+
+	/* Make the text unselectable for all browsers */
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	}
+
+.spreadsheet-header-column-resize {
+	display: inline-block;
+	vertical-align: top;
+	cursor: col-resize;
+	height: 100%;
 	margin: 0px;
+	border: 0px;
+	padding: 0px;
+	}
+
+.spreadsheet-header-row-text {
+	font: 12px/1.5 "Segoe UI", Tahoma, Arial, Helvetica, sans-serif;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	text-align: center;
 	height: 100%;
+	margin: 0px;
+	border: 0px;
+	padding: 0px;
 	cursor: pointer;
 
 	/* Make the text unselectable for all browsers */
@@ -164,6 +196,10 @@
 	-ms-user-select: none;
 	}
 
-.spreadsheet-header-row:hover {
-	background-color: #DDD;
+.spreadsheet-header-row-resize {
+	cursor: row-resize;
+	height: 100%;
+	margin: 0px;
+	border: 0px;
+	padding: 0px;
 	}
diff --git a/loleaflet/src/control/Control.ColumnHeader.js b/loleaflet/src/control/Control.ColumnHeader.js
index a5cfcdc..ef67d60 100644
--- a/loleaflet/src/control/Control.ColumnHeader.js
+++ b/loleaflet/src/control/Control.ColumnHeader.js
@@ -3,7 +3,11 @@
 */
 
 /* global $ _ */
-L.Control.ColumnHeader = L.Control.extend({
+L.Control.ColumnHeader = L.Control.Header.extend({
+	options: {
+		cursor: 'col-resize'
+	},
+
 	onAdd: function (map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
 		this._initialized = false;
@@ -27,7 +31,7 @@ L.Control.ColumnHeader = L.Control.extend({
 
 		var colHeaderObj = this;
 		$.contextMenu({
-			selector: '.spreadsheet-header-column',
+			selector: '.spreadsheet-header-column-text',
 			className: 'loleaflet-font',
 			items: {
 				'insertcolbefore': {
@@ -43,12 +47,31 @@ L.Control.ColumnHeader = L.Control.extend({
 						var colAlpha = options.$trigger.attr('rel').split('spreadsheet-column-')[1];
 						colHeaderObj.deleteColumn.call(colHeaderObj, colAlpha);
 					}
+				},
+				'optimalwidth': {
+					name: _('Optimal Width') + '...',
+					callback: function(key, options) {
+						var colAlpha = options.$trigger.attr('rel').split('spreadsheet-column-')[1];
+						colHeaderObj.optimalWidth.call(colHeaderObj, colAlpha);
+					}
 				}
 			},
 			zIndex: 10
 		});
 	},
 
+	optimalWidth: function(colAlpha) {
+		if (!this._dialog) {
+			this._dialog = L.control.metricInput(this._onDialogResult, this,
+							     this._map._docLayer.twipsToHMM(this._map._docLayer.STD_EXTRA_WIDTH),
+							     {title: _('Optimal Column Width')});
+		}
+		this._selectColumn(colAlpha, 0);
+		this._dialog.addTo(this._map);
+		this._map.enable(false);
+		this._dialog.show();
+	},
+
 	insertColumn: function(colAlpha) {
 		// First select the corresponding column because
 		// .uno:InsertColumn doesn't accept any column number
@@ -92,24 +115,37 @@ L.Control.ColumnHeader = L.Control.extend({
 	},
 
 	fillColumns: function (columns, converter, context) {
-		var iterator, twip, width, text;
+		var iterator, twip, width, column, text, resize;
 
 		this.clearColumns();
 		for (iterator = 0; iterator < columns.length; iterator++) {
 			width = columns[iterator].size - (iterator > 0 ? columns[iterator - 1].size : 0);
 			twip = new L.Point(width, width);
-			text = L.DomUtil.create('div', 'spreadsheet-header-column', this._columns);
+			column = L.DomUtil.create('div', 'spreadsheet-header-column', this._columns);
+			text = L.DomUtil.create('div', 'spreadsheet-header-column-text', column);
+			resize = L.DomUtil.create('div', 'spreadsheet-header-column-resize', column);
 			var content = columns[iterator].text;
 			text.setAttribute('rel', 'spreadsheet-column-' + content); // for easy addressing
 			text.innerHTML = content;
-			width = Math.round(converter.call(context, twip).x) - 1 + 'px';
-			if (width === '-1px') {
-				L.DomUtil.setStyle(text, 'display', 'none');
-			}
-			else {
-				L.DomUtil.setStyle(text, 'width', width);
+			width = Math.round(converter.call(context, twip).x) - 1;
+			if (width <= 0) {
+				L.DomUtil.setStyle(column, 'display', 'none');
+			} else if (width < 10) {
+				text.column = iterator + 1;
+				text.width = width;
+				L.DomUtil.setStyle(column, 'width', width + 'px');
+				L.DomUtil.setStyle(column, 'cursor', 'col-resize');
+				L.DomUtil.setStyle(text, 'cursor', 'col-resize');
+				L.DomUtil.setStyle(resize, 'display', 'none');
+				this.mouseInit(text);
+			} else {
+				resize.column = iterator + 1;
+				resize.width = width;
+				L.DomUtil.setStyle(column, 'width', width + 'px');
+				L.DomUtil.setStyle(text, 'width', width - 3 + 'px');
+				L.DomUtil.setStyle(resize, 'width', '3px');
+				this.mouseInit(resize);
 			}
-
 			L.DomEvent.addListener(text, 'click', this._onColumnHeaderClick, this);
 		}
 	},
@@ -160,6 +196,90 @@ L.Control.ColumnHeader = L.Control.extend({
 		this._map.sendUnoCommand('.uno:SelectAll');
 	},
 
+	_onDialogResult: function (e) {
+		if (e.type === 'submit' && !isNaN(e.value)) {
+			var extra = {
+				aExtraWidth: {
+					type: 'unsigned short',
+					value: e.value
+				}
+			};
+
+			this._map.sendUnoCommand('.uno:SetOptimalColumnWidth', extra);
+		}
+
+		this._map.enable(true);
+	},
+
+	_getVertLatLng: function (start, offset, e) {
+		var limit = this._map.mouseEventToContainerPoint({clientX: start.x, clientY: start.y});
+		var drag = this._map.mouseEventToContainerPoint(e);
+		return [
+			this._map.containerPointToLatLng(new L.Point(Math.max(limit.x, drag.x + offset.x), 0)),
+			this._map.containerPointToLatLng(new L.Point(Math.max(limit.x, drag.x + offset.x), this._map.getSize().y))
+		];
+	},
+
+	onDragStart: function (item, start, offset, e) {
+		if (!this._vertLine) {
+			this._vertLine = L.polyline(this._getVertLatLng(start, offset, e), {color: 'darkblue', weight: 1});
+		}
+		else {
+			this._vertLine.setLatLngs(this._getVertLatLng(start, offset, e));
+		}
+
+		this._map.addLayer(this._vertLine);
+	},
+
+	onDragMove: function (item, start, offset, e) {
+		if (this._vertLine) {
+			this._vertLine.setLatLngs(this._getVertLatLng(start, offset, e));
+		}
+	},
+
+	onDragEnd: function (item, start, offset, e) {
+		var end = new L.Point(e.clientX + offset.x, e.clientY);
+		var distance = this._map._docLayer._pixelsToTwips(end.subtract(start));
+
+		if (item.width != distance.x) {
+			var command = {
+				Column: {
+					type: 'unsigned short',
+					value: item.parentNode && item.parentNode.nextSibling &&
+					       L.DomUtil.getStyle(item.parentNode.nextSibling, 'display') === 'none' ? item.column + 1 : item.column
+				},
+				Width: {
+					type: 'unsigned short',
+					value: Math.max(distance.x, 0)
+				}
+			};
+
+			this._map.sendUnoCommand('.uno:ColumnWidth', command);
+		}
+
+		this._map.removeLayer(this._vertLine);
+	},
+
+	onDragClick: function (item, clicks, e) {
+		this._map.removeLayer(this._vertLine);
+
+		if (clicks === 2) {
+			var command = {
+				Col: {
+					type: 'unsigned short',
+					value: item.column - 1
+				},
+				Modifier: {
+					type: 'unsigned short',
+					value: 0
+				}
+			};
+
+			this._map.sendUnoCommand('.uno:SelectColumn ', command);
+			this._map.sendUnoCommand('.uno:SetOptimalColumnWidthDirect');
+		}
+	},
+
 	_onUpdatePermission: function (e) {
 		if (this._map.getDocType() !== 'spreadsheet') {
 			return;
diff --git a/loleaflet/src/control/Control.Header.js b/loleaflet/src/control/Control.Header.js
new file mode 100644
index 0000000..24ed4f7
--- /dev/null
+++ b/loleaflet/src/control/Control.Header.js
@@ -0,0 +1,84 @@
+/*
+* Control.Header
+*/
+
+L.Control.Header = L.Control.extend({
+	options: {
+		cursor: 'col-resize'
+	},
+
+	initialize: function () {
+		this._clicks = 0;
+	},
+
+	mouseInit: function (element) {
+		L.DomEvent.on(element, 'mousedown', this._onMouseDown, this);
+	},
+
+	_onMouseDown: function (e) {
+		var target = e.target || e.srcElement;
+
+		if (!target || this._dragging) {
+			return false;
+		}
+
+		L.DomUtil.disableImageDrag();
+		L.DomUtil.disableTextSelection();
+
+		L.DomEvent.stopPropagation(e);
+		L.DomEvent.on(document, 'mousemove', this._onMouseMove, this)
+		L.DomEvent.on(document, 'mouseup', this._onMouseUp, this);
+
+		var rect = target.parentNode.getBoundingClientRect();
+		this._start = new L.Point(rect.left, rect.top);
+		this._offset = new L.Point(rect.right - e.clientX, rect.bottom - e.clientY);
+		this._item = target;
+
+		this.onDragStart(this.item, this._start, this._offset, e);
+	},
+
+	_onMouseMove: function (e) {
+		this._dragging = true;
+
+		var target = e.target || e.srcElement;
+		if ((L.DomUtil.hasClass(target, 'spreadsheet-header-column-text') ||
+		     L.DomUtil.hasClass(target, 'spreadsheet-header-row-text')) &&
+		    target.style.cursor != this.options.cursor) {
+			this._cursor = target.style.cursor;
+			this._target = target;
+			target.style.cursor = this.options.cursor;
+		}
+
+		L.DomEvent.preventDefault(e);
+
+		this.onDragMove(this._item, this._start, this._offset, e);
+	},
+
+	_onMouseUp: function (e) {
+		if (this._target) {
+			this._target.style.cursor = this._oldCursor;
+		}
+
+		L.DomEvent.off(document, 'mousemove', this._onMouseMove, this);
+		L.DomEvent.off(document, 'mouseup', this._onMouseUp, this);
+
+		L.DomUtil.enableImageDrag();
+		L.DomUtil.enableTextSelection();
+
+		if (this._dragging) {
+			this.onDragEnd(this._item, this._start, this._offset, e);
+			this._clicks = 0;
+		} else {
+			this.onDragClick(this._item, ++this._clicks, e);
+			setTimeout(L.bind(this.initialize, this), 400);
+		}
+
+		this._target = this._cursor = this._item = this._start = this._offset = null;
+		this._dragging = false;
+	},
+
+	onDragStart: function () {},
+	onDragMove: function () {},
+	onDragEnd: function () {},
+	onDragClick: function () {}
+});
diff --git a/loleaflet/src/control/Control.MetricInput.js b/loleaflet/src/control/Control.MetricInput.js
new file mode 100644
index 0000000..df616d5
--- /dev/null
+++ b/loleaflet/src/control/Control.MetricInput.js
@@ -0,0 +1,102 @@
+/*
+ * L.Control.MetricInput.
+ */
+
+L.Control.MetricInput = L.Control.extend({
+	options: {
+		position: 'topmiddle',
+		title: ''
+	},
+
+	initialize: function (callback, context, value, options) {
+		L.setOptions(this, options);
+
+		this._callback = callback;
+		this._context = context;
+		this._default = value;
+	},
+
+	onAdd: function (map) {
+		this._initLayout();
+
+		return this._container;
+	},
+
+	_initLayout: function () {
+		var className = 'leaflet-control-layers',
+		container = this._container = L.DomUtil.create('div', className);
+		container.style.visibility = 'hidden';
+
+		var closeButton = L.DomUtil.create('a', 'leaflet-popup-close-button', container);
+		closeButton.href = '#close';
+		closeButton.innerHTML = '×';
+		L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
+
+		var wrapper = L.DomUtil.create('div', 'leaflet-popup-content-wrapper', container);
+		var content = L.DomUtil.create('div', 'leaflet-popup-content', wrapper);
+		var labelTitle = document.createElement('span');
+		labelTitle.innerHTML = '<b>' + this.options.title + ' ' + _('(100th/mm)') + '</b>';
+		content.appendChild(labelTitle);
+		content.appendChild(document.createElement('br'));
+		content.appendChild(document.createElement('br'));
+
+		var labelAdd = document.createElement('span');
+		labelAdd.innerHTML = _('Add: ');
+		content.appendChild(labelAdd);
+
+		var inputMetric = this._input = document.createElement('input');
+		inputMetric.type = 'text';
+		inputMetric.value = this._default;
+		content.appendChild(inputMetric);
+		content.appendChild(document.createElement('br'));
+		content.appendChild(document.createElement('br'));
+
+		var inputValue = document.createElement('input');
+		inputValue.type = 'checkbox';
+		inputValue.checked = true;
+		L.DomEvent.on(inputValue, 'click', this._onDefaultClick, this);
+		content.appendChild(inputValue);
+
+		var labelValue = document.createElement('span');
+		labelValue.innerHTML = _('Default value');
+		content.appendChild(labelValue);
+		content.appendChild(document.createElement('br'));
+		content.appendChild(document.createElement('br'));
+
+		var inputButton = document.createElement('input');
+		inputButton.type = 'button';
+		inputButton.value = _('Submit');
+		L.DomEvent.on(inputButton, 'click', this._onOKButtonClick, this);
+
+		content.appendChild(inputButton);
+	},
+
+	onRemove: function (map) {
+		this._input = null;
+	},
+
+	show: function () {
+		this._container.style.marginLeft = (-this._container.offsetWidth / 2) + 'px';
+		this._container.style.visibility = '';
+		this._input.focus();
+	},
+
+	_onDefaultClick: function (e) {
+		this._input.value = this._default;
+	},
+
+	_onOKButtonClick: function (e) {
+		var data = parseFloat(this._input.value);
+		this.remove();
+		this._callback.call(this._context, {type: 'submit', value: data});
+	},
+
+	_onCloseButtonClick: function (e) {
+		this.remove();
+		this._callback.call(this._context, {type : 'close'});
+	}
+});
+
+L.control.metricInput = function (callback, context, value, options) {
+	return new L.Control.MetricInput(callback, context, value, options);
+};
diff --git a/loleaflet/src/control/Control.RowHeader.js b/loleaflet/src/control/Control.RowHeader.js
index 11cf0da..2236c20 100644
--- a/loleaflet/src/control/Control.RowHeader.js
+++ b/loleaflet/src/control/Control.RowHeader.js
@@ -3,7 +3,11 @@
 */
 
 /* global $ _ */
-L.Control.RowHeader = L.Control.extend({
+L.Control.RowHeader = L.Control.Header.extend({
+	options: {
+		cursor: 'row-resize'
+	},
+
 	onAdd: function (map) {
 		map.on('updatepermission', this._onUpdatePermission, this);
 		this._initialized = false;
@@ -25,7 +29,7 @@ L.Control.RowHeader = L.Control.extend({
 
 		var rowHeaderObj = this;
 		$.contextMenu({
-			selector: '.spreadsheet-header-row',
+			selector: '.spreadsheet-header-row-text',
 			className: 'loleaflet-font',
 			items: {
 				'insertrowabove': {
@@ -41,12 +45,29 @@ L.Control.RowHeader = L.Control.extend({
 						var row = parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
 						rowHeaderObj.deleteRow.call(rowHeaderObj, row);
 					}
+				},
+				'optimalheight': {
+					name: _('Optimal Height') + '...',
+					callback: function(key, options) {
+						var row = parseInt(options.$trigger.attr('rel').split('spreadsheet-row-')[1]);
+						rowHeaderObj.optimalHeight.call(rowHeaderObj, row);
+					}
 				}
 			},
 			zIndex: 10
 		});
 	},
 
+	optimalHeight: function(row) {
+		if (!this._dialog) {
+			this._dialog = L.control.metricInput(this._onDialogResult, this, 0, {title: _('Optimal Row Height')});
+		}
+		this._selectRow(row, 0);
+		this._dialog.addTo(this._map);
+		this._map.enable(false);
+		this._dialog.show();
+	},
+
 	insertRow: function(row) {
 		// First select the corresponding row because
 		// .uno:InsertRows doesn't accept any row number
@@ -90,24 +111,39 @@ L.Control.RowHeader = L.Control.extend({
 	},
 
 	fillRows: function (rows, converter, context) {
-		var iterator, twip, height, text;
+		var iterator, twip, height, row, text, resize;
 
 		this.clearRows();
 		for (iterator = 0; iterator < rows.length; iterator++) {
 			height = rows[iterator].size - (iterator > 0 ? rows[iterator - 1].size : 0);
 			twip = new L.Point(height, height);
-			text = L.DomUtil.create('div', 'spreadsheet-header-row', this._rows);
+			row = L.DomUtil.create('div', 'spreadsheet-header-row', this._rows);
+			text = L.DomUtil.create('div', 'spreadsheet-header-row-text', row);
+			resize = L.DomUtil.create('div', 'spreadsheet-header-row-resize', row);
 			var content = rows[iterator].text;
 			text.setAttribute('rel', 'spreadsheet-row-' + content); // for easy addressing
 			text.innerHTML = content;
-			height = Math.round(converter.call(context, twip).y) - 1 + 'px';
-			if (height === '-1px') {
-				L.DomUtil.setStyle(text, 'display', 'none');
+			height = Math.round(converter.call(context, twip).y) - 1;
+			if (height <= 0) {
+				L.DomUtil.setStyle(row, 'display', 'none');
+			} else if (height < 10) {
+				text.row = iterator + 1;
+				text.height = height;
+				L.DomUtil.setStyle(row, 'height', height + 'px');
+				L.DomUtil.setStyle(row, 'cursor', 'row-resize');
+				L.DomUtil.setStyle(text, 'line-height', height + 'px');
+				L.DomUtil.setStyle(text, 'cursor', 'row-resize');
+				L.DomUtil.setStyle(resize, 'display', 'none');
+				this.mouseInit(text);
 			} else {
-				L.DomUtil.setStyle(text, 'line-height', height);
-				L.DomUtil.setStyle(text, 'height', height);
+				resize.row = iterator + 1;
+				resize.height = height;
+				L.DomUtil.setStyle(row, 'height', height + 'px');
+				L.DomUtil.setStyle(text, 'line-height', height + 'px');
+				L.DomUtil.setStyle(text, 'height', height - 3 + 'px');
+				L.DomUtil.setStyle(resize, 'height', '3px');
+				this.mouseInit(resize);
 			}
-
 			L.DomEvent.addListener(text, 'click', this._onRowHeaderClick, this);
 		}
 	},
@@ -140,6 +176,97 @@ L.Control.RowHeader = L.Control.extend({
 		this._selectRow(row, modifier);
 	},
 
+	_onDialogResult: function (e) {
+		if (e.type === 'submit' && !isNaN(e.value)) {
+			var extra = {
+				aExtraHeight: {
+					type: 'unsigned short',
+					value: e.value
+				}
+			};
+
+			this._map.sendUnoCommand('.uno:SetOptimalRowHeight', extra);
+		}
+
+		this._map.enable(true);
+	},
+
+	_getHorzLatLng: function (start, offset, e) {
+		var limit = this._map.mouseEventToContainerPoint({clientX: start.x, clientY: start.y});
+		var drag = this._map.mouseEventToContainerPoint(e);
+		return [
+			this._map.containerPointToLatLng(new L.Point(0, Math.max(limit.y, drag.y + offset.y))),
+			this._map.containerPointToLatLng(new L.Point(this._map.getSize().x, Math.max(limit.y, drag.y + offset.y)))
+		];
+	},
+
+	onDragStart: function (item, start, offset, e) {
+		if (!this._horzLine) {
+			this._horzLine = L.polyline(this._getHorzLatLng(start, offset, e), {color: 'darkblue', weight: 1});
+		}
+		else {
+			this._horzLine.setLatLngs(this._getHorzLatLng(start, offset, e));
+		}
+
+		this._map.addLayer(this._horzLine);
+	},
+
+	onDragMove: function (item, start, offset, e) {
+		if (this._horzLine) {
+			this._horzLine.setLatLngs(this._getHorzLatLng(start, offset, e));
+		}
+	},
+
+	onDragEnd: function (item, start, offset, e) {
+		var end = new L.Point(e.clientX, e.clientY + offset.y);
+		var distance = this._map._docLayer._pixelsToTwips(end.subtract(start));
+
+		if (item.height != distance.y) {
+			var command = {
+				Row: {
+					type: 'unsigned short',
+					value: item.parentNode && item.parentNode.nextSibling &&
+					       L.DomUtil.getStyle(item.parentNode.nextSibling, 'display') === 'none' ? item.row + 1 : item.row
+				},
+				Height: {
+					type: 'unsigned short',
+					value: Math.max(distance.y, 0)
+				}
+			};
+
+			this._map.sendUnoCommand('.uno:RowHeight', command);
+		}
+
+		this._map.removeLayer(this._horzLine);
+	},
+
+	onDragClick: function (item, clicks, e) {
+		this._map.removeLayer(this._horzLine);
+
+		if (clicks === 2) {
+			var command = {
+				Row: {
+					type: 'long',
+					value: item.row - 1
+				},
+				Modifier: {
+					type: 'unsigned short',
+					value: 0
+				}
+			};
+
+			var extra = {
+				aExtraHeight: {
+					type: 'unsigned short',
+					value: 0
+				}
+			};
+
+			this._map.sendUnoCommand('.uno:SelectRow', command);
+			this._map.sendUnoCommand('.uno:SetOptimalRowHeight', extra);
+		}
+	},
+
 	_onUpdatePermission: function (e) {
 		if (this._map.getDocType() !== 'spreadsheet') {
 			return;
diff --git a/loleaflet/src/control/Control.Scroll.js b/loleaflet/src/control/Control.Scroll.js
index b5e6b1b..3131440 100644
--- a/loleaflet/src/control/Control.Scroll.js
+++ b/loleaflet/src/control/Control.Scroll.js
@@ -35,6 +35,10 @@ L.Control.Scroll = L.Control.extend({
 	},
 
 	_onScroll: function (e) {
+		if (!this._map._enabled) {
+			return;
+		}
+
 		if (this._ignoreScroll) {
 			this._ignoreScroll = null;
 			return;
diff --git a/loleaflet/src/control/Control.js b/loleaflet/src/control/Control.js
index aa7f9c1..ba46995 100644
--- a/loleaflet/src/control/Control.js
+++ b/loleaflet/src/control/Control.js
@@ -125,6 +125,7 @@ L.Map.include({
 		}
 
 		createCorner('top', 'left');
+		createCorner('top', 'middle');
 		createCorner('top', 'right');
 		createCorner('bottom', 'left');
 		createCorner('bottom', 'right');
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 9b067f3..83fe155 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -3,6 +3,13 @@
  */
 
 L.CalcTileLayer = L.TileLayer.extend({
+	STD_EXTRA_WIDTH: 113, /* 2mm extra for optimal width,
+                              * 0.1986cm with TeX points,
+                              * 0.1993cm with PS points. */
+
+	twipsToHMM: function (twips) {
+		return (twips * 127 + 36) / 72;
+	},
 
 	beforeAdd: function (map) {
 		map._addZoomLimit(this);
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index 981734b..7467a8e 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -65,6 +65,7 @@ L.Map = L.Evented.extend({
 		this._sizeChanged = true;
 		this._bDisableKeyboard = false;
 		this._active = true;
+		this._enabled = true;
 
 		vex.dialogID = -1;
 
@@ -825,7 +826,7 @@ L.Map = L.Evented.extend({
 	},
 
 	_handleDOMEvent: function (e) {
-		if (!this._loaded || L.DomEvent._skipped(e)) { return; }
+		if (!this._loaded || !this._enabled || L.DomEvent._skipped(e)) { return; }
 
 		// find the layer the event is propagating from
 		var target = this._targets[L.stamp(e.target || e.srcElement)],
@@ -996,6 +997,16 @@ L.Map = L.Evented.extend({
 		    max = this.getMaxZoom();
 
 		return Math.max(min, Math.min(max, zoom));
+	},
+
+	enable: function(enabled) {
+		this._enabled = enabled;
+		if (this._enabled) {
+			$('.scroll-container').mCustomScrollbar('update');
+		}
+		else {
+			$('.scroll-container').mCustomScrollbar('disable');
+		}
 	}
 });
 
commit e8fbbc8be94e2a7d2b6dffd089468a7e263ec3c0
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun Aug 28 16:32:34 2016 -0400

    loolwsd: CPPUNIT_TEST_NAME can be regex
    
    To run selective tests only, CPPUNIT_TEST_NAME
    envar can be defined to hold the test-name,
    for example TileCacheTests::testClientPartImpress.
    
    Now it's possible to pass a regex value instead,
    for example TileCacheTests::testC.* to run both
    TileCacheTests::testClientPartImpress and
    TileCacheTests::testClientPartCalc.
    
    Change-Id: I96aa1ce242fd8afaf073527777be50308dce867c
    Reviewed-on: https://gerrit.libreoffice.org/28515
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>

diff --git a/loolwsd/test/test.cpp b/loolwsd/test/test.cpp
index bfaa5af..af0ff59 100644
--- a/loolwsd/test/test.cpp
+++ b/loolwsd/test/test.cpp
@@ -8,45 +8,79 @@
  */
 
 #include <iostream>
-#include <cppunit/TestRunner.h>
+
+#include <cppunit/BriefTestProgressListener.h>
+#include <cppunit/CompilerOutputter.h>
 #include <cppunit/TestResult.h>
 #include <cppunit/TestResultCollector.h>
+#include <cppunit/TestRunner.h>
 #include <cppunit/TextTestProgressListener.h>
-#include <cppunit/BriefTestProgressListener.h>
 #include <cppunit/extensions/TestFactoryRegistry.h>
-#include <cppunit/CompilerOutputter.h>
+
+#include <Poco/RegularExpression.h>
 
 class HTTPGetTest;
 
-int main(int /*argc*/, char** /*argv*/)
+bool filterTests(CPPUNIT_NS::TestRunner& runner, CPPUNIT_NS::Test* testRegistry)
 {
-    CPPUNIT_NS::TestResult controller;
-    CPPUNIT_NS::TestResultCollector result;
-    controller.addListener(&result);
-    CPPUNIT_NS::BriefTestProgressListener progress;
-    controller.addListener(&progress);
-    controller.addListener(new CPPUNIT_NS::TextTestProgressListener());
-
-    CPPUNIT_NS::TestRunner runner;
-    const char* testName = getenv("CPPUNIT_TEST_NAME");
-    if (testName)
+    const char* envar = getenv("CPPUNIT_TEST_NAME");
+    if (envar)
     {
-        // Single test.
-        CPPUNIT_NS::Test* testRegistry = CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest();
+        std::string testName(envar);
+        if (testName.empty())
+        {
+            return false;
+        }
+
+        Poco::RegularExpression re(testName, Poco::RegularExpression::RE_CASELESS);
+        Poco::RegularExpression::Match reMatch;
+
+        bool haveTests = false;
         for (int i = 0; i < testRegistry->getChildTestCount(); ++i)
         {
             CPPUNIT_NS::Test* testSuite = testRegistry->getChildTestAt(i);
             for (int j = 0; j < testSuite->getChildTestCount(); ++j)
             {
                 CPPUNIT_NS::Test* testCase = testSuite->getChildTestAt(j);
-                if (testCase->getName() == testName)
-                    runner.addTest(testCase);
+                try
+                {
+                    if (re.match(testCase->getName(), reMatch))
+                    {
+                        runner.addTest(testCase);
+                        haveTests = true;
+                    }
+                }
+                catch (const std::exception& exc)
+                {
+                    // Nothing to do; skip.
+                }
             }
         }
+
+        std::cerr << "Failed to match [" << testName << "] to any names in the test-suite. Running all tests." << std::endl;
+        return haveTests;
     }
-    else
+
+    return false;
+}
+
+int main(int /*argc*/, char** /*argv*/)
+{
+    CPPUNIT_NS::TestResult controller;
+    CPPUNIT_NS::TestResultCollector result;
+    controller.addListener(&result);
+    CPPUNIT_NS::BriefTestProgressListener progress;
+    controller.addListener(&progress);
+    controller.addListener(new CPPUNIT_NS::TextTestProgressListener());
+
+    CPPUNIT_NS::Test* testRegistry = CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest();
+
+    CPPUNIT_NS::TestRunner runner;
+    if (!filterTests(runner, testRegistry))
+    {
         // All tests.
-        runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
+        runner.addTest(testRegistry);
+    }
 
     runner.run(controller);
 
commit 0d5d4d0ccfd66a75a34aa8052c37085cfa28dc82
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date:   Fri Jul 8 09:04:27 2016 +0200

    loolwsd: support CPPUNIT_TEST_NAME, similarly to core.git
    
    Conflicts:
    	loolwsd/README

diff --git a/loolwsd/README b/loolwsd/README
index a58b948..ec1ae2e 100644
--- a/loolwsd/README
+++ b/loolwsd/README
@@ -204,6 +204,12 @@ Use the ps command to find out exactly the path to use.
 
 Set LOOL_DEBUG=1 to trap SIGSEGV and SEGBUS and prompt for debugger.
 
+In order to run and debug one unit test, set CPPUNIT_TEST_NAME to something
+non-empty:
+
+    make check CPPUNIT_TEST_NAME="unit-prefork"
+    make check CPPUNIT_TEST_NAME="HTTPWSTest::testCalcEditRendering"
+
 Protocol description
 --------------------
 
diff --git a/loolwsd/test/run_unit.sh.in b/loolwsd/test/run_unit.sh.in
index 91029e0..2f2cf33 100755
--- a/loolwsd/test/run_unit.sh.in
+++ b/loolwsd/test/run_unit.sh.in
@@ -31,6 +31,15 @@ done
 # drop .la suffix
 tst=`echo $tst | sed s/\.la//`;
 
+if test "z$tst" != "z" && test "z$CPPUNIT_TEST_NAME" != "z"; then
+    # $tst is not empty, but $CPPUNIT_TEST_NAME is set, exit early if they
+    # don't match.
+    if test "z$tst" != "z$CPPUNIT_TEST_NAME"; then
+        echo ":test-result: SKIP $tst (disabled by CPPUNIT_TEST_NAME)" > $test_output
+        exit 0;
+    fi
+fi
+
 export LOOL_LOGLEVEL=trace
 
 if test "z$enable_debug" != "ztrue"; then
@@ -98,3 +107,5 @@ else # newer unit tests.
         echo ":test-result: FAIL $tst" >> $test_output
     fi
 fi
+
+# vim:set shiftwidth=4 expandtab:
diff --git a/loolwsd/test/test.cpp b/loolwsd/test/test.cpp
index 78d0d92..bfaa5af 100644
--- a/loolwsd/test/test.cpp
+++ b/loolwsd/test/test.cpp
@@ -18,22 +18,6 @@
 
 class HTTPGetTest;
 
-/// Dump all the tests registered.
-void dumpTests(CPPUNIT_NS::Test* test)
-{
-    if (test != nullptr)
-    {
-        std::cout << test->getName() << std::endl;
-        if (test->getChildTestCount())
-        {
-            for (auto i = 0; i < test->getChildTestCount(); ++i)
-            {
-                dumpTests(test->getChildTestAt(i));
-            }
-        }
-    }
-}
-
 int main(int /*argc*/, char** /*argv*/)
 {
     CPPUNIT_NS::TestResult controller;
@@ -43,16 +27,27 @@ int main(int /*argc*/, char** /*argv*/)
     controller.addListener(&progress);
     controller.addListener(new CPPUNIT_NS::TextTestProgressListener());
 
-    auto all = CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest();
-    //dumpTests(all);
-    //CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry("httpgettest");
-    //CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry("httpposttest");
-    //CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry("httpwstest");
-    //CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry("httpcrashtest");
-
     CPPUNIT_NS::TestRunner runner;
-    runner.addTest(all);
-    //runner.addTest(registry.makeTest());
+    const char* testName = getenv("CPPUNIT_TEST_NAME");
+    if (testName)
+    {
+        // Single test.
+        CPPUNIT_NS::Test* testRegistry = CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest();
+        for (int i = 0; i < testRegistry->getChildTestCount(); ++i)
+        {
+            CPPUNIT_NS::Test* testSuite = testRegistry->getChildTestAt(i);
+            for (int j = 0; j < testSuite->getChildTestCount(); ++j)
+            {
+                CPPUNIT_NS::Test* testCase = testSuite->getChildTestAt(j);
+                if (testCase->getName() == testName)
+                    runner.addTest(testCase);
+            }
+        }
+    }
+    else
+        // All tests.
+        runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());
+
     runner.run(controller);
 
     CPPUNIT_NS::CompilerOutputter outputter(&result, std::cerr);


More information about the Libreoffice-commits mailing list