From 70ff5e8cf1717f0878e079235a1b427b46b08e5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20M=C3=BCller?= <d_muel20@uni-muenster.de>
Date: Fri, 30 Nov 2018 14:37:59 +0100
Subject: [PATCH] * Added first version of Barista Training Task

---
 Grinder/Grinder.pro                           |  20 +-
 Grinder/Version.h                             |   6 +-
 .../serialization/JsonSettingsCodec.cpp       |   2 +-
 .../serialization/SettingsContainer.cpp       |  19 +-
 .../common/serialization/SettingsContainer.h  |   4 +-
 Grinder/task/Task.h                           |   2 +-
 Grinder/task/TaskCatalog.cpp                  |   2 +
 Grinder/task/TaskType.cpp                     |   2 +
 Grinder/task/TaskType.h                       |   2 +
 Grinder/task/tasks/BaristaMessage.cpp         |  53 +++
 Grinder/task/tasks/BaristaMessage.h           |  45 +++
 Grinder/task/tasks/BaristaProtocol.h          |  15 +
 Grinder/task/tasks/BaristaTask.cpp            | 312 ++++++++++++++++++
 Grinder/task/tasks/BaristaTask.h              | 109 ++++++
 Grinder/task/tasks/BaristaTrainingTask.cpp    | 149 +++++++++
 Grinder/task/tasks/BaristaTrainingTask.h      |  68 ++++
 Grinder/task/tasks/GenericTask.cpp            |   4 +-
 Grinder/ui/dlg/TextViewerDialog.cpp           |   1 +
 .../ui/mainwnd/PropertyTreeItemDelegate.cpp   |  61 ++--
 Grinder/ui/mainwnd/PropertyTreeItemDelegate.h |   2 +
 Grinder/ui/mainwnd/PropertyTreeWidget.cpp     |   2 +-
 Grinder/ui/task/ConfigureTaskDialog.cpp       |   2 +-
 Grinder/ui/task/ConfigureTaskDialog.ui        |   6 +-
 Grinder/ui/task/TaskWidget.cpp                |   8 +
 Grinder/ui/task/TaskWidget.h                  |   1 +
 Grinder/ui/task/TaskWidget.ui                 |   4 +-
 .../task/tasks/BaristaTrainingTaskWidget.cpp  |  57 ++++
 .../ui/task/tasks/BaristaTrainingTaskWidget.h |  38 +++
 .../task/tasks/BaristaTrainingTaskWidget.ui   | 140 ++++++++
 ...icTaskWidget.cpp => GenericTaskWidget.cpp} |  18 +-
 ...enericTaskWidget.h => GenericTaskWidget.h} |  14 +-
 ...ericTaskWidget.ui => GenericTaskWidget.ui} |   4 +-
 32 files changed, 1114 insertions(+), 58 deletions(-)
 create mode 100644 Grinder/task/tasks/BaristaMessage.cpp
 create mode 100644 Grinder/task/tasks/BaristaMessage.h
 create mode 100644 Grinder/task/tasks/BaristaProtocol.h
 create mode 100644 Grinder/task/tasks/BaristaTask.cpp
 create mode 100644 Grinder/task/tasks/BaristaTask.h
 create mode 100644 Grinder/task/tasks/BaristaTrainingTask.cpp
 create mode 100644 Grinder/task/tasks/BaristaTrainingTask.h
 create mode 100644 Grinder/ui/task/tasks/BaristaTrainingTaskWidget.cpp
 create mode 100644 Grinder/ui/task/tasks/BaristaTrainingTaskWidget.h
 create mode 100644 Grinder/ui/task/tasks/BaristaTrainingTaskWidget.ui
 rename Grinder/ui/task/tasks/{ConfigureGenericTaskWidget.cpp => GenericTaskWidget.cpp} (66%)
 rename Grinder/ui/task/tasks/{ConfigureGenericTaskWidget.h => GenericTaskWidget.h} (60%)
 rename Grinder/ui/task/tasks/{ConfigureGenericTaskWidget.ui => GenericTaskWidget.ui} (95%)

diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro
index e3758e0..39328b1 100644
--- a/Grinder/Grinder.pro
+++ b/Grinder/Grinder.pro
@@ -344,8 +344,12 @@ SOURCES += \
     ui/task/TaskWidget.cpp \
     ui/dlg/TextViewerDialog.cpp \
     ui/task/ConfigureTaskDialog.cpp \
-    ui/task/tasks/ConfigureGenericTaskWidget.cpp \
-    ui/task/ConfigureTaskWidgetBase.cpp
+    ui/task/ConfigureTaskWidgetBase.cpp \
+    task/tasks/BaristaTask.cpp \
+    ui/task/tasks/GenericTaskWidget.cpp \
+    task/tasks/BaristaMessage.cpp \
+    task/tasks/BaristaTrainingTask.cpp \
+    ui/task/tasks/BaristaTrainingTaskWidget.cpp
 
 HEADERS += \
 	ui/mainwnd/GrinderWindow.h \
@@ -734,9 +738,14 @@ HEADERS += \
     ui/dlg/TextViewerDialog.h \
     ui/task/ConfigureTaskDialog.h \
     ui/task/ConfigureTaskWidget.h \
-    ui/task/tasks/ConfigureGenericTaskWidget.h \
     ui/task/ConfigureTaskWidget.impl.h \
-    ui/task/ConfigureTaskWidgetBase.h
+    ui/task/ConfigureTaskWidgetBase.h \
+    task/tasks/BaristaTask.h \
+    ui/task/tasks/GenericTaskWidget.h \
+    task/tasks/BaristaMessage.h \
+    task/tasks/BaristaTrainingTask.h \
+    ui/task/tasks/BaristaTrainingTaskWidget.h \
+    task/tasks/BaristaProtocol.h
 
 FORMS += \
 	ui/mainwnd/GrinderWindow.ui \
@@ -752,7 +761,8 @@ FORMS += \
     ui/task/TaskWidget.ui \
     ui/dlg/TextViewerDialog.ui \
     ui/task/ConfigureTaskDialog.ui \
-    ui/task/tasks/ConfigureGenericTaskWidget.ui
+    ui/task/tasks/GenericTaskWidget.ui \
+    ui/task/tasks/BaristaTrainingTaskWidget.ui
 
 RESOURCES += \
 	res/Grinder.qrc
diff --git a/Grinder/Version.h b/Grinder/Version.h
index 50990ec..bb9b2eb 100644
--- a/Grinder/Version.h
+++ b/Grinder/Version.h
@@ -10,14 +10,14 @@
 
 #define GRNDR_INFO_TITLE		"Grinder"
 #define GRNDR_INFO_COPYRIGHT	"Copyright (c) WWU Muenster"
-#define GRNDR_INFO_DATE			"08.11.2018"
+#define GRNDR_INFO_DATE			"30.11.2018"
 #define GRNDR_INFO_COMPANY		"WWU Muenster"
 #define GRNDR_INFO_WEBSITE		"http://www.uni-muenster.de"
 
 #define GRNDR_VERSION_MAJOR		0
-#define GRNDR_VERSION_MINOR		9
+#define GRNDR_VERSION_MINOR		10
 #define GRNDR_VERSION_REVISION	0
-#define GRNDR_VERSION_BUILD		284
+#define GRNDR_VERSION_BUILD		290
 
 namespace grndr
 {
diff --git a/Grinder/common/serialization/JsonSettingsCodec.cpp b/Grinder/common/serialization/JsonSettingsCodec.cpp
index 699ce22..4ee5c7b 100644
--- a/Grinder/common/serialization/JsonSettingsCodec.cpp
+++ b/Grinder/common/serialization/JsonSettingsCodec.cpp
@@ -149,7 +149,7 @@ std::vector<JsonSettingsCodec::Token> JsonSettingsCodec::readJsonDocument(QTextS
 		TokenizerEntry{R"(\])", TokenType::ArrayEnd},
 		TokenizerEntry{R"(:)", TokenType::Colon},
 		TokenizerEntry{R"(,)", TokenType::Comma},
-		TokenizerEntry{R"([^,]+)", TokenType::Value},
+		TokenizerEntry{R"([^,{}]+)", TokenType::Value},
 	};
 
 	// Tokenize the entire document, line by line
diff --git a/Grinder/common/serialization/SettingsContainer.cpp b/Grinder/common/serialization/SettingsContainer.cpp
index c8f2239..7d8bc3f 100644
--- a/Grinder/common/serialization/SettingsContainer.cpp
+++ b/Grinder/common/serialization/SettingsContainer.cpp
@@ -46,8 +46,14 @@ SettingsContainer& SettingsContainer::operator <<(const SettingsContainer& child
 	return *this;
 }
 
-SettingsContainer* SettingsContainer::createChild(QString name, bool isArray)
+SettingsContainer* SettingsContainer::createChildEx(QString name, bool unique, bool isArray)
 {
+	if (unique)
+	{
+		if (auto childContainer = child(name))
+			return childContainer;
+	}
+
 	auto container = std::make_shared<SettingsContainer>(name, isArray);
 	_childContainers.push_back(container);
 	return container.get();
@@ -75,3 +81,14 @@ QVariant& SettingsContainer::value(QString name, QVariant defaultValue)
 
 	return _values[name];
 }
+
+bool SettingsContainer::contains(QStringList names) const
+{
+	for (QString name : names)
+	{
+		if (!contains(name))
+			return false;
+	}
+
+	return true;
+}
diff --git a/Grinder/common/serialization/SettingsContainer.h b/Grinder/common/serialization/SettingsContainer.h
index dc035af..f518a75 100644
--- a/Grinder/common/serialization/SettingsContainer.h
+++ b/Grinder/common/serialization/SettingsContainer.h
@@ -37,7 +37,8 @@ namespace grndr
 		SettingsContainer& operator <<(const SettingsContainer& child);
 		SettingsContainer& operator <<(SettingsContainer&& child);
 
-		SettingsContainer* createChild(QString name, bool isArray = false);
+		SettingsContainer* createChild(QString name, bool isArray = false) { return createChildEx(name, false, isArray); }
+		SettingsContainer* createChildEx(QString name, bool unique, bool isArray = false);
 
 		SettingsContainer* child(QString name) { return _child<SettingsContainer*>(name); }
 		const SettingsContainer* child(QString name) const { return _child<const SettingsContainer*>(name); }
@@ -60,6 +61,7 @@ namespace grndr
 
 		QStringList keys() const { return _values.keys(); }
 		bool contains(QString name) const { return _values.find(name) != _values.cend(); }
+		bool contains(QStringList names) const;
 
 		void removeValue(QString name) { _values.remove(name); }
 		void clearValues() { _values.clear(); }
diff --git a/Grinder/task/Task.h b/Grinder/task/Task.h
index 56cc9ec..1934336 100644
--- a/Grinder/task/Task.h
+++ b/Grinder/task/Task.h
@@ -63,7 +63,7 @@ namespace grndr
 		void pauseTask(bool setPause = true);
 		void refreshTask();
 		void stopTask();
-		void finishTask(bool succeeded = false);
+		void finishTask(bool succeeded);
 
 		void updateTask();
 
diff --git a/Grinder/task/TaskCatalog.cpp b/Grinder/task/TaskCatalog.cpp
index 2eb7482..18a51aa 100644
--- a/Grinder/task/TaskCatalog.cpp
+++ b/Grinder/task/TaskCatalog.cpp
@@ -8,6 +8,7 @@
 #include "Task.h"
 
 #include "tasks/GenericTask.h"
+#include "tasks/BaristaTrainingTask.h"
 
 #define REGISTER_TASK_TYPE(cls)	registerTaskType(cls::type_value, [](TaskPool* taskPool, QString name) { return std::make_unique<cls>(taskPool, name); })
 
@@ -56,4 +57,5 @@ std::unique_ptr<Task> TaskCatalog::createTask(TaskPool* taskPool, TaskType type,
 void TaskCatalog::registerStandardTasks()
 {
 	REGISTER_TASK_TYPE(GenericTask);
+	REGISTER_TASK_TYPE(BaristaTrainingTask);
 }
diff --git a/Grinder/task/TaskType.cpp b/Grinder/task/TaskType.cpp
index fa33a20..3ac136c 100644
--- a/Grinder/task/TaskType.cpp
+++ b/Grinder/task/TaskType.cpp
@@ -9,3 +9,5 @@
 const char* TaskType::Undefined = "";
 
 const char* TaskType::Generic = "Generic";
+
+const char* TaskType::BaristaTraining = "BaristaTraining";
diff --git a/Grinder/task/TaskType.h b/Grinder/task/TaskType.h
index 68476d9..ef4b5d9 100644
--- a/Grinder/task/TaskType.h
+++ b/Grinder/task/TaskType.h
@@ -17,6 +17,8 @@ namespace grndr
 
 		static const char* Generic;
 
+		static const char* BaristaTraining;
+
 	public:
 		using QString::QString;
 
diff --git a/Grinder/task/tasks/BaristaMessage.cpp b/Grinder/task/tasks/BaristaMessage.cpp
new file mode 100644
index 0000000..d9d91f6
--- /dev/null
+++ b/Grinder/task/tasks/BaristaMessage.cpp
@@ -0,0 +1,53 @@
+/******************************************************************************
+ * File: BaristaMessage.cpp
+ * Date: 25.11.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "BaristaMessage.h"
+
+#define BARISTA_MESSAGE_HEADER "header"
+#define BARISTA_MESSAGE_HEADER_BLOCKING "blocking"
+#define BARISTA_MESSAGE_PAYLOAD "payload"
+#define BARISTA_MESSAGE_PAYLOAD_KEY "key"
+
+void BaristaMessage::createMessage(QString key, bool blocking)
+{
+	if (auto headerData = _messageData.createChildEx(BARISTA_MESSAGE_HEADER, true))
+		headerData->value(BARISTA_MESSAGE_HEADER_BLOCKING) = blocking;
+
+	if (auto payloadData = _messageData.createChildEx(BARISTA_MESSAGE_PAYLOAD, true))
+		payloadData->value(BARISTA_MESSAGE_PAYLOAD_KEY) = key;
+}
+
+bool BaristaMessage::isBlocking() const
+{
+	if (auto headerData = _messageData.child(BARISTA_MESSAGE_HEADER))
+		return headerData->value(BARISTA_MESSAGE_HEADER_BLOCKING, false).toBool();
+
+	return false;
+}
+
+QString BaristaMessage::getPayloadKey() const
+{
+	if (auto payloadData = _messageData.child(BARISTA_MESSAGE_PAYLOAD))
+		return payloadData->value(BARISTA_MESSAGE_PAYLOAD_KEY).toString();
+
+	return "";
+}
+
+bool BaristaMessage::isMessage() const
+{
+	// Make sure that the message contains a header and a payload
+	return _messageData.child(BARISTA_MESSAGE_HEADER) && _messageData.child(BARISTA_MESSAGE_PAYLOAD);
+}
+
+bool BaristaMessage::isMessage(QString key) const
+{
+	return isMessage() && (getPayloadKey().compare(key, Qt::CaseInsensitive) == 0);
+}
+
+SettingsContainer& BaristaMessage::payload()
+{
+	return *_messageData.createChildEx(BARISTA_MESSAGE_PAYLOAD, true);
+}
diff --git a/Grinder/task/tasks/BaristaMessage.h b/Grinder/task/tasks/BaristaMessage.h
new file mode 100644
index 0000000..a8c3a83
--- /dev/null
+++ b/Grinder/task/tasks/BaristaMessage.h
@@ -0,0 +1,45 @@
+/******************************************************************************
+ * File: BaristaMessage.h
+ * Date: 25.11.2018
+ *****************************************************************************/
+
+#ifndef BARISTAMESSAGE_H
+#define BARISTAMESSAGE_H
+
+#include "common/serialization/SettingsContainer.h"
+
+namespace grndr
+{
+	class BaristaMessage
+	{
+	public:
+		BaristaMessage() { }
+		BaristaMessage(QString key, bool blocking = false) { createMessage(key, blocking); }
+		BaristaMessage(const SettingsContainer& settings) { createMessage(settings); }
+		BaristaMessage(SettingsContainer&& settings) { createMessage(std::move(settings)); }
+
+		operator SettingsContainer() const { return _messageData; }
+		operator const SettingsContainer&() const { return _messageData; }
+
+	public:
+		void createMessage(QString key, bool blocking = false);
+		void createMessage(const SettingsContainer& settings) { _messageData = settings; }
+		void createMessage(SettingsContainer&& settings) { _messageData = std::move(settings); }
+
+	public:
+		bool isBlocking() const;
+
+		QString getPayloadKey() const;
+		SettingsContainer& payload();
+
+		const SettingsContainer& messageData() const { return _messageData; }
+
+		bool isMessage() const;
+		bool isMessage(QString key) const;
+
+	private:
+		SettingsContainer _messageData;
+	};
+}
+
+#endif
diff --git a/Grinder/task/tasks/BaristaProtocol.h b/Grinder/task/tasks/BaristaProtocol.h
new file mode 100644
index 0000000..abbdef9
--- /dev/null
+++ b/Grinder/task/tasks/BaristaProtocol.h
@@ -0,0 +1,15 @@
+/******************************************************************************
+ * File: BaristaProtocol.h
+ * Date: 30.11.2018
+ *****************************************************************************/
+
+#ifndef BARISTAPROTOCOL_H
+#define BARISTAPROTOCOL_H
+
+#define BARISTA_COMMAND_SETLIBRARY "setlibrarypath"
+#define BARISTA_COMMAND_STARTTRAINING "starttraining"
+#define BARISTA_COMMAND_UPDATE "iterationupdate"
+#define BARISTA_COMMAND_TRAININGDONE "trainingfinished"
+#define BARISTA_COMMAND_SHUTDOWN "shutdown"
+
+#endif
diff --git a/Grinder/task/tasks/BaristaTask.cpp b/Grinder/task/tasks/BaristaTask.cpp
new file mode 100644
index 0000000..50d13e6
--- /dev/null
+++ b/Grinder/task/tasks/BaristaTask.cpp
@@ -0,0 +1,312 @@
+/******************************************************************************
+ * File: BaristaTask.cpp
+ * Date: 20.11.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "BaristaTask.h"
+#include "BaristaMessage.h"
+#include "BaristaProtocol.h"
+#include "task/TaskExceptions.h"
+#include "common/serialization/JsonSettingsCodec.h"
+#include "common/serialization/SerializationExceptions.h"
+
+const char* BaristaTask::Serialization_Value_BaristaPort = "BaristaPort";
+const char* BaristaTask::Serialization_Value_LibraryPath = "LibraryPath";
+
+BaristaTask::BaristaTask(TaskPool* taskPool, TaskType type, QString name) : Task(taskPool, type, Task::Capability::All, name)
+{
+	// When the task has been stopped, immediately finish it
+	connect(this, &Task::taskStopped, [this]() { finishTask(false); });
+}
+
+void BaristaTask::serialize(SerializationContext& ctx) const
+{
+	Task::serialize(ctx);
+
+	// Serialize values
+	ctx.settings()(Serialization_Value_BaristaPort) = _baristaPort;
+	ctx.settings()(Serialization_Value_LibraryPath) = _libraryPath;
+}
+
+void BaristaTask::deserialize(DeserializationContext& ctx)
+{
+	Task::deserialize(ctx);
+
+	// Deserialize values
+	_baristaPort = ctx.settings()(Serialization_Value_BaristaPort).toUInt();
+	_libraryPath = ctx.settings()(Serialization_Value_LibraryPath).toString();
+}
+
+void BaristaTask::initiateBaristaConnection()
+{
+	changeTaskState(TaskState::Initiating);
+
+	// Create the ZMQ objects
+	try {
+		_context = std::make_unique<zmq::context_t>(1);
+
+		auto createSocket = [this](int type) {
+			auto socket = std::make_unique<zmq::socket_t>(*_context, type);
+
+			// Make sure that all pending messages are sent on close
+			int linger = -1;
+			socket->setsockopt(ZMQ_LINGER, &linger, sizeof(linger));
+
+			return socket;
+		};
+
+		_replySocket = createSocket(ZMQ_REP);
+		_subscriberSocket = createSocket(ZMQ_SUB);
+		_requestSocket = createSocket(ZMQ_REQ);
+
+		// The subscriber should just receive everything
+		_subscriberSocket->setsockopt(ZMQ_SUBSCRIBE, "", 0);
+
+		// Bind the reply port
+		 _replySocket->bind(QString{"tcp://*:%1"}.arg(_baristaPort).toStdString());
+	} catch (std::exception& e) {
+		throw TaskException{this, _EXCPT(QString{"Unable to create the ZMQ objects (%1)"}.arg(e.what()))};
+	}
+
+	// Enter the awaiting connection state
+	changeTaskState(TaskState::AwaitingConnection, "Waiting for Barista to connect...");
+}
+
+void BaristaTask::shutdownBaristaConnection(bool shutdownBarista)
+{
+	changeTaskState(TaskState::Shutdown, "Shutting down Barista connection...");
+
+	if (shutdownBarista)
+	{
+		if (_requestSocket && _taskState > TaskState::Initiating)
+		{
+			BaristaMessage shutdownMessage{BARISTA_COMMAND_SHUTDOWN};
+			sendMessage(shutdownMessage, _requestSocket);
+		}
+	}
+
+	// Just set all pointers to zero and let RAII handle the rest
+	_replySocket = nullptr;
+	_requestSocket = nullptr;
+	_subscriberSocket = nullptr;
+
+	_context = nullptr;
+}
+
+void BaristaTask::reportBaristaError(QString error, BaristaMessage* message)
+{
+	if (message)
+	{
+		QString baristaError = message->payload().value("error").toString();
+
+		if (!baristaError.isEmpty())
+			error += QString{" (%1)"}.arg(baristaError);
+	}
+
+	addMessageLog(error);
+
+	shutdownBaristaConnection();
+	finishTask(false);
+}
+
+bool BaristaTask::encodeMessage(zmq::message_t& message, const SettingsContainer& settings)
+{
+	try {
+		// Encode the settings to JSON and store it in the ZMQ message object
+		QString messageData;
+		QTextStream textStream{&messageData};
+		JsonSettingsCodec codec;
+
+		codec.encodeSettings(settings, textStream);
+
+		// Use a std::string as QString uses a 16bit representation of chars
+		std::string messageString = messageData.toStdString();
+		message.rebuild(messageString.size());
+		memcpy(message.data(), messageString.data(), messageString.size());
+	} catch (SerializationException& e) {
+		addMessageLog(QString{"! Unable to encode a message (%1)"}.arg(GetExceptionMessage(e.what())));
+		return false;
+	} catch (std::exception& e) {
+		addMessageLog(QString{"! Unable to encode a message (%1)"}.arg(e.what()));
+		return false;
+	}
+
+	return true;
+}
+
+bool BaristaTask::decodeMessage(const zmq::message_t& message, SettingsContainer& settings)
+{
+	try {
+		// Convert the ZMQ message to a SettingsContainer
+		auto messageData = QString::fromLatin1(static_cast<const char*>(message.data()), static_cast<int>(message.size()));
+		QTextStream textStream{&messageData};
+		JsonSettingsCodec codec;
+
+		settings.clear();
+		codec.decodeSettings(settings, textStream);
+	} catch (SerializationException& e) {
+		addMessageLog(QString{"! Unable to decode a message (%1)"}.arg(GetExceptionMessage(e.what())));
+		return false;
+	} catch (std::exception& e) {
+		addMessageLog(QString{"! Unable to decode a message (%1)"}.arg(e.what()));
+		return false;
+	}
+
+	return true;
+}
+
+void BaristaTask::sendMessage(const SettingsContainer& settings, std::unique_ptr<zmq::socket_t>& socket, bool receiveAck)
+{
+	// Just encode the message and send it over the socket
+	zmq::message_t message;
+
+	if (encodeMessage(message, settings))
+	{
+		try {
+			socket->send(message);
+		} catch (std::exception& e) {
+			// Ignore ZMQ errors here
+		}
+
+		if (receiveAck)
+		{
+			// Receive a corresponding ACK message from Barista; we are not really interested in it, though, so we just ignore it
+			zmq::message_t ackMessage;
+			socket->recv(&ackMessage);
+		}
+	}
+}
+
+bool BaristaTask::checkMessageStatus(BaristaMessage& message) const
+{
+	return message.payload()("status", false).toBool();
+}
+
+bool BaristaTask::handleReplyMessage(const SettingsContainer& messageData)
+{
+	switch (_taskState)
+	{
+	case TaskState::AwaitingConnection:
+		handleAwaitingConnection(messageData);
+		return true;
+	}
+
+	return false;
+}
+
+bool BaristaTask::handleSubscriberMessage(const SettingsContainer& messageData)
+{
+	switch (_taskState)
+	{
+	case TaskState::SettingLibrary:
+		handleSettingLibrary(messageData);
+		return true;
+	}
+
+	return false;
+}
+
+void BaristaTask::update()
+{
+	if (_taskState > TaskState::Initiating)
+	{
+		// Read messages from all sockets
+		if (_replySocket)
+			pollMessage(_replySocket, &BaristaTask::handleReplyMessage);
+
+		if (_subscriberSocket)
+			pollMessage(_subscriberSocket, &BaristaTask::handleSubscriberMessage);
+	}
+}
+
+void BaristaTask::pollMessage(std::unique_ptr<zmq::socket_t>& socket, std::function<void(BaristaTask*, const SettingsContainer&)> callback)
+{
+	// Poll the given socket for a message
+	zmq::pollitem_t pollItems[] = {{*socket, 0, ZMQ_POLLIN, 0}};
+	zmq::poll(&pollItems[0], 1, 0);
+
+	if (pollItems[0].revents & ZMQ_POLLIN)
+	{
+		// Some data is waiting on the port, so receive it
+		zmq::message_t message;
+		socket->recv(&message);
+
+		if (callback && message.size() > 0)
+		{
+			SettingsContainer messageSettings;
+
+			if (decodeMessage(message, messageSettings))
+			{
+				try {
+					callback(this, messageSettings);
+				} catch (TaskException& e) {
+					// Show errors but ignore them otherwise
+					addMessageLog(QString{"! %1"}.arg(GetExceptionMessage(e.what())));
+				}
+			}
+		}
+	}
+}
+
+void BaristaTask::handleAwaitingConnection(const SettingsContainer& messageData)
+{
+	// Get the Barista address from the reply
+	_baristaAddress.ip = messageData("ip").toString();
+	_baristaAddress.replyPort = messageData("repPort").toInt();
+	_baristaAddress.publisherPort = messageData("pubPort").toInt();
+
+	if (_baristaAddress.ip.isEmpty() || _baristaAddress.replyPort < 1024 || _baristaAddress.publisherPort < 1024)
+		throw TaskException{this, _EXCPT("No valid Barista address could be retrieved")};
+
+	// Establish the connection to Barista
+	_requestSocket->connect(QString{"tcp://%1:%2"}.arg(_baristaAddress.ip).arg(_baristaAddress.replyPort).toStdString());
+	_subscriberSocket->connect(QString{"tcp://%1:%2"}.arg(_baristaAddress.ip).arg(_baristaAddress.publisherPort).toStdString());
+
+	addMessageLog(QString{"\tConnected to Barista at %1"}.arg(_baristaAddress.ip));
+
+	baristaConnected();
+
+	// Set the library to be used by Barista
+	changeTaskState(TaskState::SettingLibrary, "Setting up the Barista library...");
+	sendSetLibraryMessage();
+}
+
+void BaristaTask::handleSettingLibrary(const SettingsContainer& messageData)
+{
+	BaristaMessage message{messageData};
+
+	if (message.isMessage(BARISTA_COMMAND_SETLIBRARY))
+	{
+		if (checkMessageStatus(message))
+		{
+			addMessageLog(QString{"\tLibrary set to %1"}.arg(_libraryPath));
+
+			// Let the task-specific implementation handle the rest from here on
+			baristaReady();
+		}
+		else
+			reportBaristaError(QString{"\tFailed to set library to %1"}.arg(_libraryPath), &message);
+	}
+}
+
+void BaristaTask::sendSetLibraryMessage()
+{
+	BaristaMessage message{BARISTA_COMMAND_SETLIBRARY};
+	SettingsContainer data{"data"};
+	data("path") = _libraryPath;
+	message.payload() << std::move(data);
+
+	sendMessage(message, _requestSocket);
+}
+
+void BaristaTask::changeTaskState(int state, QString msg)
+{
+	if (state != _taskState)
+	{
+		_taskState = state;
+
+		if (!msg.isEmpty())
+			addMessageLog(msg);
+	}
+}
diff --git a/Grinder/task/tasks/BaristaTask.h b/Grinder/task/tasks/BaristaTask.h
new file mode 100644
index 0000000..a640ec8
--- /dev/null
+++ b/Grinder/task/tasks/BaristaTask.h
@@ -0,0 +1,109 @@
+/******************************************************************************
+ * File: BaristaTask.h
+ * Date: 20.11.2018
+ *****************************************************************************/
+
+#ifndef BARISTATASK_H
+#define BARISTATASK_H
+
+#include <zmq.hpp>
+
+#include "task/Task.h"
+
+namespace grndr
+{
+	class BaristaMessage;
+
+	class BaristaTask : public Task
+	{
+		Q_OBJECT
+
+	public:
+		static const char* Serialization_Value_BaristaPort;
+		static const char* Serialization_Value_LibraryPath;
+
+	public:
+		BaristaTask(TaskPool* taskPool, TaskType type, QString name = "");
+		virtual ~BaristaTask() { shutdownBaristaConnection(); }
+
+	public:
+		unsigned int getBaristaPort() const { return _baristaPort; }
+		void setBaristaPort(unsigned int port) { _baristaPort = port; }
+
+		QString getLibraryPath() const { return _libraryPath; }
+		void setLibraryPath(QString path) { _libraryPath = path; }
+
+	public:
+		virtual void serialize(SerializationContext& ctx) const override;
+		virtual void deserialize(DeserializationContext& ctx) override;
+
+	protected:
+		void initiateBaristaConnection();
+		void shutdownBaristaConnection(bool shutdownBarista = true);
+
+		void reportBaristaError(QString error, BaristaMessage* message = nullptr);
+
+	protected:
+		bool encodeMessage(zmq::message_t& message, const SettingsContainer& settings);
+		bool decodeMessage(const zmq::message_t& message, SettingsContainer& settings);
+		void sendMessage(const SettingsContainer& settings, std::unique_ptr<zmq::socket_t>& socket, bool receiveAck = true);
+
+		bool checkMessageStatus(BaristaMessage& message) const;
+
+	protected:
+		virtual void baristaConnected() { }
+		virtual void baristaReady() { }
+
+		virtual bool handleReplyMessage(const SettingsContainer& messageData);
+		virtual bool handleSubscriberMessage(const SettingsContainer& messageData);
+
+	protected:
+		virtual void update() override;
+
+	private:
+		void pollMessage(std::unique_ptr<zmq::socket_t>& socket, std::function<void(BaristaTask*, const SettingsContainer&)> callback);
+
+	private:
+		void handleAwaitingConnection(const SettingsContainer& messageData);
+		void handleSettingLibrary(const SettingsContainer& messageData);
+
+		void sendSetLibraryMessage();
+
+	protected:
+		unsigned int _baristaPort{6980};
+
+		struct
+		{
+			QString ip{""};
+			unsigned int replyPort{0};
+			unsigned int publisherPort{0};
+		} _baristaAddress;
+
+		QString _libraryPath{""};
+
+	protected:
+		enum TaskState
+		{
+			Shutdown,
+			Initiating,
+			AwaitingConnection,
+			SettingLibrary,
+
+			// Must always be the last state
+			TypeSpecificBase,
+		};
+
+		int _taskState{TaskState::Shutdown};
+
+		void changeTaskState(int state, QString msg = "");
+
+	protected:
+		std::unique_ptr<zmq::context_t> _context;
+
+		std::unique_ptr<zmq::socket_t> _replySocket;
+		std::unique_ptr<zmq::socket_t> _subscriberSocket;
+		std::unique_ptr<zmq::socket_t> _requestSocket;
+	};
+}
+
+#endif
diff --git a/Grinder/task/tasks/BaristaTrainingTask.cpp b/Grinder/task/tasks/BaristaTrainingTask.cpp
new file mode 100644
index 0000000..8bc5015
--- /dev/null
+++ b/Grinder/task/tasks/BaristaTrainingTask.cpp
@@ -0,0 +1,149 @@
+/******************************************************************************
+ * File: BaristaCaffeWorkerTask.cpp
+ * Date: 20.11.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "BaristaTrainingTask.h"
+#include "BaristaMessage.h"
+#include "BaristaProtocol.h"
+#include "ui/task/tasks/BaristaTrainingTaskWidget.h"
+
+const TaskType BaristaTrainingTask::type_value = TaskType::BaristaTraining;
+
+const char* BaristaTrainingTask::Serialization_Value_SessionPath = "SessionPath";
+const char* BaristaTrainingTask::Serialization_Value_SolverPath = "SolverPath";
+
+BaristaTrainingTask::BaristaTrainingTask(TaskPool* taskPool, QString name) : BaristaTask(taskPool, type_value, name)
+{
+
+}
+
+ConfigureTaskWidgetBase* BaristaTrainingTask::createEditor(bool newTask, QWidget* parent)
+{
+	return new BaristaTrainingTaskWidget{this, newTask, parent};
+}
+
+void BaristaTrainingTask::serialize(SerializationContext& ctx) const
+{
+	BaristaTask::serialize(ctx);
+
+	// Serialize values	
+	ctx.settings()(Serialization_Value_SessionPath) = _sessionPath;
+	ctx.settings()(Serialization_Value_SolverPath) = _solverPath;
+}
+
+void BaristaTrainingTask::deserialize(DeserializationContext& ctx)
+{
+	BaristaTask::deserialize(ctx);
+
+	// Deserialize values	
+	_sessionPath = ctx.settings()(Serialization_Value_SessionPath).toString();
+	_solverPath = ctx.settings()(Serialization_Value_SolverPath).toString();
+}
+
+void BaristaTrainingTask::execute()
+{
+	initiateBaristaConnection();
+}
+
+void BaristaTrainingTask::stop()
+{
+	shutdownBaristaConnection();
+}
+
+void BaristaTrainingTask::baristaReady()
+{
+	// Start the training
+	changeTaskState(WorkerTaskState::StartTraining, "Starting training...");
+	sendStartTrainingMessage();
+}
+
+bool BaristaTrainingTask::handleReplyMessage(const SettingsContainer& messageData)
+{
+	if (BaristaTask::handleReplyMessage(messageData))
+		return true;
+
+	return false;
+}
+
+bool BaristaTrainingTask::handleSubscriberMessage(const SettingsContainer& messageData)
+{
+	if (BaristaTask::handleSubscriberMessage(messageData))
+		return true;
+
+	switch (_taskState)
+	{	
+	case WorkerTaskState::StartTraining:
+		handleStartingTraining(messageData);
+		return true;
+
+	case WorkerTaskState::Training:
+		handleTraining(messageData);
+		return true;
+	}
+
+	return false;
+}
+
+void BaristaTrainingTask::handleStartingTraining(const SettingsContainer& messageData)
+{
+	BaristaMessage message{messageData};
+
+	if (message.isMessage(BARISTA_COMMAND_STARTTRAINING))
+	{
+		if (checkMessageStatus(message))
+		{
+			addMessageLog("\tTraining started");
+			addMessageLog("", false);
+
+			// Just let the training run...
+			changeTaskState(WorkerTaskState::Training);
+		}
+		else
+			reportBaristaError("\tFailed to start the training", &message);
+	}
+}
+
+void BaristaTrainingTask::handleTraining(const SettingsContainer& messageData)
+{
+	BaristaMessage message{messageData};
+
+	if (message.isMessage(BARISTA_COMMAND_UPDATE))
+	{
+		if (auto data = message.payload().child("data"))
+		{
+			// Just log some status information
+			auto iteration = data->value("iteration").toUInt();
+			auto eta = data->value("eta").toFloat();
+
+			addMessageLog(QString{"Training iteration %1 [ETA %2]"}.arg(iteration).arg(QTime{0, 0}.addSecs(static_cast<int>(std::ceil(eta))).toString("HH:mm:ss")));
+		}
+	}
+	else if (message.isMessage(BARISTA_COMMAND_TRAININGDONE))
+	{
+		addMessageLog("", false);
+
+		if (checkMessageStatus(message))
+		{
+			addMessageLog("The training has been successfully finished");
+
+			// The training has finished, so break the Barista connection and finish the task
+			shutdownBaristaConnection();
+			finishTask(true);
+		}
+		else
+			reportBaristaError("The training did not finish successfully", &message);
+	}
+}
+
+void BaristaTrainingTask::sendStartTrainingMessage()
+{
+	BaristaMessage message{BARISTA_COMMAND_STARTTRAINING};
+	SettingsContainer data{"data"};
+	data("dir") = _sessionPath;
+	data("solver") = _solverPath;
+	message.payload() << std::move(data);
+
+	sendMessage(message, _requestSocket);
+}
diff --git a/Grinder/task/tasks/BaristaTrainingTask.h b/Grinder/task/tasks/BaristaTrainingTask.h
new file mode 100644
index 0000000..4b6cf8c
--- /dev/null
+++ b/Grinder/task/tasks/BaristaTrainingTask.h
@@ -0,0 +1,68 @@
+/******************************************************************************
+ * File: BaristaCaffeWorkerTask.h
+ * Date: 20.11.2018
+ *****************************************************************************/
+
+#ifndef BARISTATRAININGTASK_H
+#define BARISTATRAININGTASK_H
+
+#include "BaristaTask.h"
+
+namespace grndr
+{
+	class BaristaTrainingTask : public BaristaTask
+	{
+		Q_OBJECT
+
+	public:
+		static const TaskType type_value;
+
+		static const char* Serialization_Value_SessionPath;
+		static const char* Serialization_Value_SolverPath;
+
+	public:
+		BaristaTrainingTask(TaskPool* taskPool, QString name = "");
+
+	public:
+		virtual ConfigureTaskWidgetBase* createEditor(bool newTask, QWidget* parent) override;
+
+	public:		
+		QString getSessionPath() const { return _sessionPath; }
+		void setSessionPath(QString path) { _sessionPath = path; }
+		QString getSolverPath() const { return _solverPath; }
+		void setSolverPath(QString path) { _solverPath = path; }
+
+	public:
+		virtual void serialize(SerializationContext& ctx) const override;
+		virtual void deserialize(DeserializationContext& ctx) override;
+
+	protected:
+		virtual void execute() override;
+		virtual void stop() override;
+
+	protected:
+		virtual void baristaReady() override;
+
+		virtual bool handleReplyMessage(const SettingsContainer& messageData) override;
+		virtual bool handleSubscriberMessage(const SettingsContainer& messageData) override;
+
+	protected:		
+		void handleStartingTraining(const SettingsContainer& messageData);
+		void handleTraining(const SettingsContainer& messageData);
+
+		void sendStartTrainingMessage();
+
+	protected:
+		enum WorkerTaskState
+		{
+			StartTraining = TypeSpecificBase,
+			Training,
+		};
+
+	protected:		
+		QString _sessionPath{""};
+		QString _solverPath{""};
+	};
+}
+
+#endif
diff --git a/Grinder/task/tasks/GenericTask.cpp b/Grinder/task/tasks/GenericTask.cpp
index 2640952..b7b13c4 100644
--- a/Grinder/task/tasks/GenericTask.cpp
+++ b/Grinder/task/tasks/GenericTask.cpp
@@ -6,7 +6,7 @@
 #include "Grinder.h"
 #include "GenericTask.h"
 #include "task/TaskExceptions.h"
-#include "ui/task/tasks/ConfigureGenericTaskWidget.h"
+#include "ui/task/tasks/GenericTaskWidget.h"
 
 const TaskType GenericTask::type_value = TaskType::Generic;
 
@@ -20,7 +20,7 @@ GenericTask::GenericTask(TaskPool* taskPool, QString name) : Task(taskPool, type
 
 ConfigureTaskWidgetBase* GenericTask::createEditor(bool newTask, QWidget* parent)
 {
-	return new ConfigureGenericTaskWidget{this, newTask, parent};
+	return new GenericTaskWidget{this, newTask, parent};
 }
 
 void GenericTask::serialize(SerializationContext& ctx) const
diff --git a/Grinder/ui/dlg/TextViewerDialog.cpp b/Grinder/ui/dlg/TextViewerDialog.cpp
index aa6f7da..0e18ab1 100644
--- a/Grinder/ui/dlg/TextViewerDialog.cpp
+++ b/Grinder/ui/dlg/TextViewerDialog.cpp
@@ -31,6 +31,7 @@ void TextViewerDialog::setupUi(QString title, QString caption, QString text, QSt
 	setWindowTitle(title);
 
 	ui->lblCaption->setText(caption);
+	ui->textField->setTabStopDistance(40);
 
 	if (!text.isEmpty())
 		ui->textField->setPlainText(text);
diff --git a/Grinder/ui/mainwnd/PropertyTreeItemDelegate.cpp b/Grinder/ui/mainwnd/PropertyTreeItemDelegate.cpp
index 280048d..266cc51 100644
--- a/Grinder/ui/mainwnd/PropertyTreeItemDelegate.cpp
+++ b/Grinder/ui/mainwnd/PropertyTreeItemDelegate.cpp
@@ -29,14 +29,17 @@ void PropertyTreeItemDelegate::paint(QPainter* painter, const QStyleOptionViewIt
 {
 	bool performDefault = true;
 
-	if (auto item = _widget->valuePropertyItem(index))
+	if (index.column() == 1)
 	{
-		if (auto renderer = item->valueRenderer())
+		if (auto item = _widget->valuePropertyItem(index))
 		{
-			if (renderer->getRendererFlags().testFlag(PropertyRenderer::RendererFlag::OverridePainting))
+			if (auto renderer = item->valueRenderer())
 			{
-				renderer->render(painter, rendererStyleFromStyleOption(option), renderFlagsFromStyleOption(option));
-				performDefault = false;
+				if (renderer->getRendererFlags().testFlag(PropertyRenderer::RendererFlag::OverridePainting))
+				{
+					renderer->render(painter, rendererStyleFromStyleOption(option), renderFlagsFromStyleOption(option));
+					performDefault = false;
+				}
 			}
 		}
 	}
@@ -49,15 +52,18 @@ QWidget* PropertyTreeItemDelegate::createEditor(QWidget* parent, const QStyleOpt
 {
 	Q_UNUSED(option);
 
-	if (auto item = _widget->valuePropertyItem(index))
+	if (index.column() == 1)
 	{
-		if (auto editor = item->createEditor(parent))
+		if (auto item = _widget->valuePropertyItem(index))
 		{
-			editor->setProperty(PropertyEditorBase::property_font, _widget->font());	// Forward the tree widget's font to the editor as a dynamic property
-			editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+			if (auto editor = item->createEditor(parent))
+			{
+				editor->setProperty(PropertyEditorBase::property_font, _widget->font());	// Forward the tree widget's font to the editor as a dynamic property
+				editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
 
-			_currentEditor = editor;
-			return editor;
+				_currentEditor = editor;
+				return editor;
+			}
 		}
 	}
 
@@ -86,18 +92,21 @@ void PropertyTreeItemDelegate::initStyleOption(QStyleOptionViewItem* option, con
 {
 	QStyledItemDelegate::initStyleOption(option, index);
 
-	if (auto item = _widget->valuePropertyItem(index))
+	if (index.column() == 1)
 	{
-		if (auto renderer = item->valueRenderer())
+		if (auto item = _widget->valuePropertyItem(index))
 		{
-			if (renderer->getRendererFlags().testFlag(PropertyRenderer::RendererFlag::OverrideText))
-				option->text = renderer->formatPropertyText(renderFlagsFromStyleOption(*option));
+			if (auto renderer = item->valueRenderer())
+			{
+				if (renderer->getRendererFlags().testFlag(PropertyRenderer::RendererFlag::OverrideText))
+					option->text = renderer->formatPropertyText(renderFlagsFromStyleOption(*option));
+			}
 		}
-	}
 
-	// If editing the given item, hide its text so that it doesn't appear below the editor
-	if (_widget->isEditing() && index.row() == _widget->currentIndex().row())
-		option->text = "";
+		// If editing the given item, hide its text so that it doesn't appear below the editor
+		if (_widget->isEditing() && (getAbsoluteRowIndex(index) == getAbsoluteRowIndex(_widget->currentIndex())))
+			option->text = "";
+	}
 }
 
 PropertyRenderer::RendererStyle PropertyTreeItemDelegate::rendererStyleFromStyleOption(const QStyleOptionViewItem& option) const
@@ -120,3 +129,17 @@ PropertyRenderer::RenderFlags PropertyTreeItemDelegate::renderFlagsFromStyleOpti
 
 	return flags;
 }
+
+int PropertyTreeItemDelegate::getAbsoluteRowIndex(const QModelIndex& index) const
+{
+	int row = 0;
+	auto indexAbove = _widget->indexAbove(index);
+
+	while (indexAbove.isValid())
+	{
+		row += 1;
+		indexAbove = _widget->indexAbove(indexAbove);
+	}
+
+	return row;
+}
diff --git a/Grinder/ui/mainwnd/PropertyTreeItemDelegate.h b/Grinder/ui/mainwnd/PropertyTreeItemDelegate.h
index 2571b72..6d15288 100644
--- a/Grinder/ui/mainwnd/PropertyTreeItemDelegate.h
+++ b/Grinder/ui/mainwnd/PropertyTreeItemDelegate.h
@@ -40,6 +40,8 @@ namespace grndr
 		PropertyRenderer::RendererStyle rendererStyleFromStyleOption(const QStyleOptionViewItem& option) const;
 		PropertyRenderer::RenderFlags renderFlagsFromStyleOption(const QStyleOptionViewItem& option) const;
 
+		int getAbsoluteRowIndex(const QModelIndex& index) const;
+
 	private:
 		PropertyTreeWidget* _widget{nullptr};
 
diff --git a/Grinder/ui/mainwnd/PropertyTreeWidget.cpp b/Grinder/ui/mainwnd/PropertyTreeWidget.cpp
index b62d82d..a031e57 100644
--- a/Grinder/ui/mainwnd/PropertyTreeWidget.cpp
+++ b/Grinder/ui/mainwnd/PropertyTreeWidget.cpp
@@ -18,6 +18,7 @@ PropertyTreeWidget::PropertyTreeWidget(QWidget *parent) : QTreeWidget(parent),
 	_itemDelegate{new PropertyTreeItemDelegate{this}}
 {
 	// Set the delegate for the second column which handles editing of the properties
+	setItemDelegateForColumn(0, _itemDelegate);
 	setItemDelegateForColumn(1, _itemDelegate);
 
 	// Listen for pipeline switches to clear all shown properties and to listen for selection changes
@@ -49,7 +50,6 @@ void PropertyTreeWidget::setupUi(QLabel* propertyDescLabel)
 
 void PropertyTreeWidget::clear()
 {
-
 	_itemDelegate->resetCurrentEditor();
 
 	for (int i = 0; i < topLevelItemCount(); ++i)
diff --git a/Grinder/ui/task/ConfigureTaskDialog.cpp b/Grinder/ui/task/ConfigureTaskDialog.cpp
index 37c9cc9..8332d49 100644
--- a/Grinder/ui/task/ConfigureTaskDialog.cpp
+++ b/Grinder/ui/task/ConfigureTaskDialog.cpp
@@ -36,7 +36,7 @@ void ConfigureTaskDialog::setupUi()
 
 	if (_taskWidget)
 	{
-		ui->grpTaskSettings->setLayout(new QGridLayout{});
+		ui->grpTaskSettings->setLayout(new QHBoxLayout{});
 		ui->grpTaskSettings->layout()->addWidget(_taskWidget);
 
 		_taskWidget->applySettings(false);
diff --git a/Grinder/ui/task/ConfigureTaskDialog.ui b/Grinder/ui/task/ConfigureTaskDialog.ui
index 82df21a..7f9f06d 100644
--- a/Grinder/ui/task/ConfigureTaskDialog.ui
+++ b/Grinder/ui/task/ConfigureTaskDialog.ui
@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>400</width>
-    <height>217</height>
+    <width>358</width>
+    <height>140</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -21,7 +21,7 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <property name="sizeConstraint">
-    <enum>QLayout::SetFixedSize</enum>
+    <enum>QLayout::SetMinimumSize</enum>
    </property>
    <item>
     <widget class="QGroupBox" name="groupBox">
diff --git a/Grinder/ui/task/TaskWidget.cpp b/Grinder/ui/task/TaskWidget.cpp
index 0060453..d61344d 100644
--- a/Grinder/ui/task/TaskWidget.cpp
+++ b/Grinder/ui/task/TaskWidget.cpp
@@ -29,6 +29,12 @@ TaskWidget::~TaskWidget()
 	delete ui;
 }
 
+void TaskWidget::showEvent(QShowEvent* event)
+{
+	QWidget::showEvent(event);
+	updateUi();
+}
+
 void TaskWidget::resizeEvent(QResizeEvent* event)
 {
 	QWidget::resizeEvent(event);
@@ -48,6 +54,8 @@ void TaskWidget::setupUi()
 void TaskWidget::updateUi()
 {
 	static auto setEllidedText = [](QLabel* label, QString text) {
+		text.replace("\t", "");
+
 		QFontMetrics metrics(label->font());
 		int width = label->width() - 2;
 		QString clippedText = metrics.elidedText(text, Qt::ElideRight, width);
diff --git a/Grinder/ui/task/TaskWidget.h b/Grinder/ui/task/TaskWidget.h
index 18bf6bd..584d794 100644
--- a/Grinder/ui/task/TaskWidget.h
+++ b/Grinder/ui/task/TaskWidget.h
@@ -30,6 +30,7 @@ namespace grndr
 		const Task* task() const { return _task.lock().get(); }
 
 	protected:
+		virtual void showEvent(QShowEvent* event) override;
 		virtual void resizeEvent(QResizeEvent* event) override;
 
 	private:
diff --git a/Grinder/ui/task/TaskWidget.ui b/Grinder/ui/task/TaskWidget.ui
index 48b80f2..d208c6e 100644
--- a/Grinder/ui/task/TaskWidget.ui
+++ b/Grinder/ui/task/TaskWidget.ui
@@ -50,7 +50,7 @@
          <item row="0" column="0" rowspan="2" colspan="2">
           <widget class="QLabel" name="lblTitle">
            <property name="sizePolicy">
-            <sizepolicy hsizetype="Ignored" vsizetype="Preferred">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
@@ -101,7 +101,7 @@ border-radius: 5px;</string>
          <item row="2" column="0" colspan="2">
           <widget class="QLabel" name="lblMessage">
            <property name="sizePolicy">
-            <sizepolicy hsizetype="Ignored" vsizetype="Preferred">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
              <horstretch>0</horstretch>
              <verstretch>0</verstretch>
             </sizepolicy>
diff --git a/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.cpp b/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.cpp
new file mode 100644
index 0000000..5adc76b
--- /dev/null
+++ b/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.cpp
@@ -0,0 +1,57 @@
+/******************************************************************************
+ * File: BaristaCaffeWorkerTaskWidget.cpp
+ * Date: 20.11.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "BaristaTrainingTaskWidget.h"
+#include "ui_BaristaTrainingTaskWidget.h"
+#include "task/tasks/BaristaTrainingTask.h"
+
+BaristaTrainingTaskWidget::BaristaTrainingTaskWidget(BaristaTrainingTask* task, bool newTask, QWidget *parent) : ConfigureTaskWidget(task, newTask, parent),
+	ui{new Ui::BaristaTrainingTaskWidget}
+{
+	setupUi();
+}
+
+BaristaTrainingTaskWidget::~BaristaTrainingTaskWidget()
+{
+	delete ui;
+}
+
+void BaristaTrainingTaskWidget::verifySettings()
+{
+	if (ui->txtLibraryPath->text().isEmpty())
+		showError("Please enter a library path.", ui->txtLibraryPath);
+
+	if (ui->txtSessionPath->text().isEmpty())
+		showError("Please enter a session path.", ui->txtSessionPath);
+
+	if (ui->txtSolverPath->text().isEmpty())
+		showError("Please enter a solver path.", ui->txtSolverPath);
+}
+
+void BaristaTrainingTaskWidget::setupUi()
+{
+	ui->setupUi(this);
+}
+
+void BaristaTrainingTaskWidget::applySettings(bool save)
+{
+	if (save)
+	{
+		_task->setBaristaPort(ui->txtWorkerPort->value());
+
+		_task->setLibraryPath(ui->txtLibraryPath->text());
+		_task->setSessionPath(ui->txtSessionPath->text());
+		_task->setSolverPath(ui->txtSolverPath->text());
+	}
+	else
+	{
+		ui->txtWorkerPort->setValue(_task->getBaristaPort());
+
+		ui->txtLibraryPath->setText(_task->getLibraryPath());
+		ui->txtSessionPath->setText(_task->getSessionPath());
+		ui->txtSolverPath->setText(_task->getSolverPath());
+	}
+}
diff --git a/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.h b/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.h
new file mode 100644
index 0000000..9d29473
--- /dev/null
+++ b/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.h
@@ -0,0 +1,38 @@
+/******************************************************************************
+ * File: BaristaCaffeWorkerTaskWidget.h
+ * Date: 20.11.2018
+ *****************************************************************************/
+
+#ifndef BARISTACAFFEWORKERTASKWIDGET_H
+#define BARISTACAFFEWORKERTASKWIDGET_H
+
+#include "ui/task/ConfigureTaskWidget.h"
+
+namespace Ui
+{
+	class BaristaTrainingTaskWidget;
+}
+
+namespace grndr
+{
+	class BaristaTrainingTask;
+
+	class BaristaTrainingTaskWidget : public ConfigureTaskWidget<BaristaTrainingTask>
+	{
+		Q_OBJECT
+
+	public:
+		BaristaTrainingTaskWidget(BaristaTrainingTask* task, bool newTask, QWidget* parent = nullptr);
+		virtual ~BaristaTrainingTaskWidget();
+
+	public:
+		virtual void verifySettings() override;
+		virtual void applySettings(bool save) override;
+
+	private:
+		Ui::BaristaTrainingTaskWidget *ui;
+		void setupUi();
+	};
+}
+
+#endif
diff --git a/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.ui b/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.ui
new file mode 100644
index 0000000..6b0700f
--- /dev/null
+++ b/Grinder/ui/task/tasks/BaristaTrainingTaskWidget.ui
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BaristaTrainingTaskWidget</class>
+ <widget class="QWidget" name="BaristaTrainingTaskWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>371</width>
+    <height>116</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Library &amp;path:</string>
+     </property>
+     <property name="buddy">
+      <cstring>txtLibraryPath</cstring>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1" colspan="2">
+    <widget class="QLineEdit" name="txtLibraryPath"/>
+   </item>
+   <item row="0" column="1">
+    <widget class="QSpinBox" name="txtWorkerPort">
+     <property name="minimum">
+      <number>1024</number>
+     </property>
+     <property name="maximum">
+      <number>65536</number>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>&amp;Worker port:</string>
+     </property>
+     <property name="buddy">
+      <cstring>txtWorkerPort</cstring>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <spacer name="verticalSpacer_2">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>6</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="5" column="0">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="3" column="1" colspan="2">
+    <widget class="QLineEdit" name="txtSessionPath"/>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Sessio&amp;n path:</string>
+     </property>
+     <property name="buddy">
+      <cstring>txtSessionPath</cstring>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0">
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>&amp;Solver path:</string>
+     </property>
+     <property name="buddy">
+      <cstring>txtSolverPath</cstring>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="1" colspan="2">
+    <widget class="QLineEdit" name="txtSolverPath"/>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>txtWorkerPort</tabstop>
+  <tabstop>txtLibraryPath</tabstop>
+  <tabstop>txtSessionPath</tabstop>
+  <tabstop>txtSolverPath</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Grinder/ui/task/tasks/ConfigureGenericTaskWidget.cpp b/Grinder/ui/task/tasks/GenericTaskWidget.cpp
similarity index 66%
rename from Grinder/ui/task/tasks/ConfigureGenericTaskWidget.cpp
rename to Grinder/ui/task/tasks/GenericTaskWidget.cpp
index e136d9d..fc3524b 100644
--- a/Grinder/ui/task/tasks/ConfigureGenericTaskWidget.cpp
+++ b/Grinder/ui/task/tasks/GenericTaskWidget.cpp
@@ -4,34 +4,34 @@
  *****************************************************************************/
 
 #include "Grinder.h"
-#include "ConfigureGenericTaskWidget.h"
-#include "ui_ConfigureGenericTaskWidget.h"
+#include "GenericTaskWidget.h"
+#include "ui_GenericTaskWidget.h"
 #include "task/tasks/GenericTask.h"
 #include "ui/UIUtils.h"
 
-ConfigureGenericTaskWidget::ConfigureGenericTaskWidget(GenericTask* task, bool newTask, QWidget *parent) : ConfigureTaskWidget(task, newTask, parent),
-	ui{new Ui::ConfigureGenericTaskWidget}
+GenericTaskWidget::GenericTaskWidget(GenericTask* task, bool newTask, QWidget *parent) : ConfigureTaskWidget(task, newTask, parent),
+	ui{new Ui::GenericTaskWidget}
 {
 	setupUi();
 }
 
-ConfigureGenericTaskWidget::~ConfigureGenericTaskWidget()
+GenericTaskWidget::~GenericTaskWidget()
 {
 	delete ui;
 }
 
-void ConfigureGenericTaskWidget::setupUi()
+void GenericTaskWidget::setupUi()
 {
 	ui->setupUi(this);
 }
 
-void ConfigureGenericTaskWidget::verifySettings()
+void GenericTaskWidget::verifySettings()
 {
 	if (ui->txtCommand->text().isEmpty())
 		showError("Please enter a command to execute.", ui->txtCommand);
 }
 
-void ConfigureGenericTaskWidget::applySettings(bool save)
+void GenericTaskWidget::applySettings(bool save)
 {
 	if (save)
 	{
@@ -47,7 +47,7 @@ void ConfigureGenericTaskWidget::applySettings(bool save)
 	}
 }
 
-void ConfigureGenericTaskWidget::on_btnBrowse_clicked()
+void GenericTaskWidget::on_btnBrowse_clicked()
 {
 	auto command = UIUtils::askFileName(false, "GenericTaskCommand", this, "Select an executable", "All files (*.*)");
 
diff --git a/Grinder/ui/task/tasks/ConfigureGenericTaskWidget.h b/Grinder/ui/task/tasks/GenericTaskWidget.h
similarity index 60%
rename from Grinder/ui/task/tasks/ConfigureGenericTaskWidget.h
rename to Grinder/ui/task/tasks/GenericTaskWidget.h
index 67bc7cd..ca170b1 100644
--- a/Grinder/ui/task/tasks/ConfigureGenericTaskWidget.h
+++ b/Grinder/ui/task/tasks/GenericTaskWidget.h
@@ -3,27 +3,27 @@
  * Date: 07.11.2018
  *****************************************************************************/
 
-#ifndef CONFIGUREGENERICTASKWIDGET_H
-#define CONFIGUREGENERICTASKWIDGET_H
+#ifndef GENERICTASKWIDGET_H
+#define GENERICTASKWIDGET_H
 
 #include "ui/task/ConfigureTaskWidget.h"
 
 namespace Ui
 {
-	class ConfigureGenericTaskWidget;
+	class GenericTaskWidget;
 }
 
 namespace grndr
 {
 	class GenericTask;
 
-	class ConfigureGenericTaskWidget : public ConfigureTaskWidget<GenericTask>
+	class GenericTaskWidget : public ConfigureTaskWidget<GenericTask>
 	{
 		Q_OBJECT
 
 	public:
-		ConfigureGenericTaskWidget(GenericTask* task, bool newTask, QWidget* parent = nullptr);
-		~ConfigureGenericTaskWidget();
+		GenericTaskWidget(GenericTask* task, bool newTask, QWidget* parent = nullptr);
+		virtual ~GenericTaskWidget();
 
 	public:
 		virtual void verifySettings() override;
@@ -33,7 +33,7 @@ namespace grndr
 		void on_btnBrowse_clicked();
 
 	private:
-		Ui::ConfigureGenericTaskWidget* ui;
+		Ui::GenericTaskWidget* ui;
 		void setupUi();
 	};
 }
diff --git a/Grinder/ui/task/tasks/ConfigureGenericTaskWidget.ui b/Grinder/ui/task/tasks/GenericTaskWidget.ui
similarity index 95%
rename from Grinder/ui/task/tasks/ConfigureGenericTaskWidget.ui
rename to Grinder/ui/task/tasks/GenericTaskWidget.ui
index dddd768..57eede4 100644
--- a/Grinder/ui/task/tasks/ConfigureGenericTaskWidget.ui
+++ b/Grinder/ui/task/tasks/GenericTaskWidget.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>ConfigureGenericTaskWidget</class>
- <widget class="QWidget" name="ConfigureGenericTaskWidget">
+ <class>GenericTaskWidget</class>
+ <widget class="QWidget" name="GenericTaskWidget">
   <property name="geometry">
    <rect>
     <x>0</x>
-- 
GitLab