From d3bbd300be4480c0a3b7285c91b3b27e82ca9c2f Mon Sep 17 00:00:00 2001
From: Peter Burgess <pete.burgess@isode.com>
Date: Fri, 20 Apr 2018 16:18:20 +0100
Subject: Add a XEP-346 FDP Form Submission Dialog

A new form submission dialog that will discover nodes containing
fdp form templates in a given pubsub domain, and will present the form
to the user ready to be filled out and submitted.

Test-Infomation:
Personally tested the request for all node items, loading the
latest form from one of the discovered nodes, submitting a form
and checking it turns up on my mlink server using mlc, checked that
the submitted infomation is correct, what happens when an incorrect
domain is queried, what happens when a valid domain is queried but
it has no fdp nodes, and what happens when a form is requested from
an invalid node.
Unit tests written to test success and failure of the three controller
functions requestPubSubNodeData(), requestTemplateForm() and
testSubmitForm().

Change-Id: If8c57bb4e3120dd44d52f7332069eb18a14cb385

diff --git a/Swift/Controllers/AccountController.cpp b/Swift/Controllers/AccountController.cpp
index 048d140..fe7c200 100644
--- a/Swift/Controllers/AccountController.cpp
+++ b/Swift/Controllers/AccountController.cpp
@@ -56,6 +56,7 @@
 #include <Swift/Controllers/ContactsFromXMPPRoster.h>
 #include <Swift/Controllers/EventNotifier.h>
 #include <Swift/Controllers/EventWindowController.h>
+#include <Swift/Controllers/FdpFormSubmitController.h>
 #include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
 #include <Swift/Controllers/FileTransferListController.h>
 #include <Swift/Controllers/Highlighting/HighlightEditorController.h>
@@ -125,7 +126,8 @@ AccountController::AccountController(
             loginWindow_(nullptr) ,
             useDelayForLatency_(useDelayForLatency),
             ftOverview_(nullptr),
-            emoticons_(emoticons) {
+            emoticons_(emoticons),
+            fdpFormSubmitController_(nullptr) {
     storages_ = nullptr;
     certificateStorage_ = nullptr;
     certificateTrustChecker_ = nullptr;
@@ -580,6 +582,7 @@ void AccountController::performLoginFromCachedCredentials() {
         presence->setShow(static_cast<StatusShow::Type>(profileSettings_->getIntSetting("lastShow", StatusShow::Online)));
         presence->setStatus(profileSettings_->getStringSetting("lastStatus"));
         statusTracker_->setRequestedPresence(presence);
+        fdpFormSubmitController_ = std::make_unique<FdpFormSubmitController>(jid_, client_->getIQRouter(), uiEventStream_, uiFactory_);
     } else {
         /* In case we're in the middle of another login, make sure they don't overlap */
         client_->disconnect();
diff --git a/Swift/Controllers/AccountController.h b/Swift/Controllers/AccountController.h
index 30ae265..774aa8b 100644
--- a/Swift/Controllers/AccountController.h
+++ b/Swift/Controllers/AccountController.h
@@ -81,6 +81,7 @@ namespace Swift {
     class BlockListController;
     class ContactSuggester;
     class ContactsFromXMPPRoster;
+    class FdpFormSubmitController;
 
     class AccountController {
         public:
@@ -197,5 +198,6 @@ namespace Swift {
             HighlightEditorController* highlightEditorController_;
             std::map<std::string, std::string> emoticons_;
             boost::signals2::connection enableCarbonsRequestHandlerConnection_;
+            std::unique_ptr<FdpFormSubmitController> fdpFormSubmitController_;
     };
 }
diff --git a/Swift/Controllers/FdpFormSubmitController.cpp b/Swift/Controllers/FdpFormSubmitController.cpp
new file mode 100644
index 0000000..639b4e9
--- /dev/null
+++ b/Swift/Controllers/FdpFormSubmitController.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/FdpFormSubmitController.h>
+
+#include <Swiften/Disco/GetDiscoItemsRequest.h>
+#include <Swiften/Elements/DiscoItems.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Queries/PubSubRequest.h>
+
+#include <Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h>
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindowFactory.h>
+
+namespace Swift {
+
+FdpFormSubmitController::FdpFormSubmitController(const JID& self, IQRouter* iqRouter, UIEventStream* uiEventStream, FdpFormSubmitWindowFactory* factory) : selfJID_(self), iqRouter_(iqRouter), uiEventStream_(uiEventStream), factory_(factory), formSubmitWindow_(nullptr) {
+    fdpFormSubmitWindowOpenUIEventConnection_= uiEventStream_->onUIEvent.connect( [this](const std::shared_ptr<UIEvent>& uiEvent){ handleUIEvent(uiEvent); });
+}
+
+FdpFormSubmitController::~FdpFormSubmitController() {
+}
+
+void FdpFormSubmitController::handleUIEvent(const std::shared_ptr<UIEvent>& uiEvent) {
+    if (auto openEvent = std::dynamic_pointer_cast<FdpFormSubmitWindowOpenUIEvent>(uiEvent)) {
+        if (formSubmitWindow_) {
+            formSubmitWindow_->raise();
+        }
+        else {
+            createFormSubmitWindow();
+        }
+    }
+}
+
+void FdpFormSubmitController::createFormSubmitWindow() {
+    formSubmitWindow_ = factory_->createFdpFormSubmitWindow();
+    formSubmitWindow_->onCloseEvent.connect([this](){ closeFormSubmitWindow(); });
+    formSubmitWindow_->onRequestPubSubNodeData.connect([this](const std::string& string){ requestPubSubNodeData(string); });
+    formSubmitWindow_->onRequestTemplateForm.connect([this](const std::string& fdpTemplateNodeName){ requestTemplateForm(fdpTemplateNodeName); });
+    formSubmitWindow_->onSubmitForm.connect([this](const std::shared_ptr<Form>& form){ submitForm(form); });
+    formSubmitWindow_->show();
+}
+
+void FdpFormSubmitController::closeFormSubmitWindow() {
+    formSubmitWindow_.reset();
+}
+
+void FdpFormSubmitController::requestPubSubNodeData(const std::string& domainName) {
+    JID domainJID(domainName);
+    auto discoItemsRequest = GetDiscoItemsRequest::create(domainJID, iqRouter_);
+    discoItemsRequest->onResponse.connect(
+        [this, domainName](std::shared_ptr<DiscoItems> discoItems, ErrorPayload::ref errorPayloadRef) {
+            if (!discoItems || errorPayloadRef) {
+                formSubmitWindow_->showNodePlaceholder(FdpFormSubmitWindow::NodeError::DomainNotFound);
+                return;
+            }
+            currentDomain_ = domainName;
+
+            bool templateNodeAdded = false;
+            formSubmitWindow_->clearNodeData();
+            auto discoItemList = discoItems->getItems();
+            for (auto discoItem : discoItemList) {
+                auto node = discoItem.getNode();
+                if (node.substr(0, 13) != "fdp/template/") {
+                    continue;
+                }
+
+                std::string nodeName = discoItem.getName().empty() ? node : discoItem.getName();
+
+                formSubmitWindow_->addNode(node, nodeName);
+                templateNodeAdded = true;
+            }
+            if (!templateNodeAdded) {
+                formSubmitWindow_->showNodePlaceholder(FdpFormSubmitWindow::NodeError::NoFdpNodesInDomain);
+            }
+        }
+    );
+    discoItemsRequest->send();
+}
+
+void FdpFormSubmitController::requestTemplateForm(const std::string& nodeName) {
+    auto pubSubItems = std::make_shared<PubSubItems>(nodeName);
+    pubSubItems->setMaximumItems(1);
+    auto formRequest = std::make_shared<PubSubRequest<PubSubItems>>(IQ::Type::Get, selfJID_, currentDomain_, pubSubItems, iqRouter_);
+
+    formRequest->onResponse.connect(
+        [this, nodeName](std::shared_ptr<PubSubItems> response, ErrorPayload::ref errorPayload) {
+            if (!response || errorPayload) {
+                formSubmitWindow_->showFormPlaceholder(FdpFormSubmitWindow::TemplateError::RequestFailed);
+                return;
+            }
+            auto pubSubItemList = response->getItems();
+            if (pubSubItemList.empty()) {
+                formSubmitWindow_->showFormPlaceholder(FdpFormSubmitWindow::TemplateError::CannotLocateForm);
+                return;
+            }
+            auto payloadList = pubSubItemList[0]->getData();
+            if (payloadList.empty()) {
+                formSubmitWindow_->showFormPlaceholder(FdpFormSubmitWindow::TemplateError::CannotLocateForm);
+                return;
+            }
+            if (auto form = std::dynamic_pointer_cast<Form>(payloadList[0])) {
+                currentTemplateNode_ = nodeName;
+                formSubmitWindow_->setFormData(form);
+            }
+            else {
+                formSubmitWindow_->showFormPlaceholder(FdpFormSubmitWindow::TemplateError::InvalidPayload);
+                return;
+            }
+        }
+    );
+
+    formRequest->send();
+}
+
+void FdpFormSubmitController::submitForm(const std::shared_ptr<Form>& form) {
+    std::string submittedNode = currentTemplateNode_;
+    submittedNode.replace(submittedNode.find("/template/", 0), 10, "/submitted/");
+    auto pubSubItem = std::make_shared<PubSubItem>();
+    auto pubSubItems = std::make_shared<PubSubItems>(submittedNode);
+    auto pubSubPublish = std::make_shared<PubSubPublish>();
+    pubSubPublish->setNode(submittedNode);
+    pubSubPublish->addItem(pubSubItem);
+    pubSubItem->addData(form);
+    pubSubItems->addItem(pubSubItem);
+    auto formRequest = std::make_shared<PubSubRequest<PubSubPublish>>(IQ::Type::Set, selfJID_, currentDomain_, pubSubPublish, iqRouter_);
+
+    formRequest->onResponse.connect(
+        [this](std::shared_ptr<PubSubPublish> response, ErrorPayload::ref errorPayload) {
+            if (!response || errorPayload) {
+                formSubmitWindow_->handleSubmitServerResponse(false);
+                return;
+            }
+            formSubmitWindow_->handleSubmitServerResponse(true);
+        }
+    );
+
+    formRequest->send();
+}
+
+}
diff --git a/Swift/Controllers/FdpFormSubmitController.h b/Swift/Controllers/FdpFormSubmitController.h
new file mode 100644
index 0000000..69db08c
--- /dev/null
+++ b/Swift/Controllers/FdpFormSubmitController.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <boost/signals2.hpp>
+
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+    class FdpFormSubmitWindow;
+    class FdpFormSubmitWindowFactory;
+    class Form;
+    class IQRouter;
+    class UIEvent;
+    class UIEventStream;
+
+    class FdpFormSubmitController {
+    public:
+        FdpFormSubmitController(const JID& self, IQRouter* iqRouter, UIEventStream* uiEventStream, FdpFormSubmitWindowFactory* factory);
+        ~FdpFormSubmitController();
+
+    private:
+        void handleUIEvent(const std::shared_ptr<UIEvent>& uiEvent);
+        void createFormSubmitWindow();
+        void closeFormSubmitWindow();
+        void requestPubSubNodeData(const std::string& domainName);
+        void requestTemplateForm(const std::string& nodeName);
+        void submitForm(const std::shared_ptr<Form>& form);
+
+        JID selfJID_;
+        IQRouter* iqRouter_;
+        UIEventStream* uiEventStream_;
+        FdpFormSubmitWindowFactory* factory_;
+        std::unique_ptr<FdpFormSubmitWindow> formSubmitWindow_;
+        std::string currentDomain_;
+        std::string currentTemplateNode_;
+
+        boost::signals2::scoped_connection fdpFormSubmitWindowOpenUIEventConnection_;
+    };
+}
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index cbd3bf3..25d326a 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -41,6 +41,7 @@ if env["SCONS_STAGE"] == "build" :
             "ContactsFromXMPPRoster.cpp",
             "EventNotifier.cpp",
             "EventWindowController.cpp",
+            "FdpFormSubmitController.cpp",
             "FileTransfer/FileTransferController.cpp",
             "FileTransfer/FileTransferOverview.cpp",
             "FileTransfer/FileTransferProgressInfo.cpp",
@@ -85,6 +86,7 @@ if env["SCONS_STAGE"] == "build" :
             "Translator.cpp",
             "UIEvents/UIEvent.cpp",
             "UIInterfaces/ChatListWindow.cpp",
+            "UIInterfaces/FdpFormSubmitWindow.cpp",
             "UIInterfaces/HighlightEditorWindow.cpp",
             "UIInterfaces/XMLConsoleWidget.cpp",
             "WhiteboardManager.cpp",
@@ -106,6 +108,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"),
             File("UnitTest/ChatMessageSummarizerTest.cpp"),
             File("UnitTest/ContactSuggesterTest.cpp"),
+            File("UnitTest/FdpFormSubmitControllerTest.cpp"),
             File("UnitTest/MockChatWindow.cpp"),
             File("UnitTest/PresenceNotifierTest.cpp"),
             File("UnitTest/PreviousStatusStoreTest.cpp"),
diff --git a/Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h b/Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h
new file mode 100644
index 0000000..d540cb2
--- /dev/null
+++ b/Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+    class FdpFormSubmitWindowOpenUIEvent : public UIEvent {
+        public:
+            FdpFormSubmitWindowOpenUIEvent() {
+            }
+    };
+}
diff --git a/Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.cpp b/Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.cpp
new file mode 100644
index 0000000..47ef9cd
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h>
+
+#include <Swift/Controllers/Intl.h>
+
+namespace Swift {
+
+std::string FdpFormSubmitWindow::getNodeErrorText(NodeError nodeError) const {
+    switch(nodeError) {
+        case NodeError::DomainNotFound: return QT_TRANSLATE_NOOP("", "Error: No pubsub domain found");
+        case NodeError::NoFdpNodesInDomain: return QT_TRANSLATE_NOOP("", "Error: Domain does not contain an FDP template node");
+        case NodeError::NoError: return "";
+    }
+    return "";
+}
+
+std::string FdpFormSubmitWindow::getTemplateErrorText(TemplateError templateError) const {
+    switch(templateError) {
+        case TemplateError::CannotLocateForm: return QT_TRANSLATE_NOOP("", "Error: Could not locate template form");
+        case TemplateError::InvalidPayload: return QT_TRANSLATE_NOOP("", "Error: Invalid payload returned from node");
+        case TemplateError::RequestFailed: return QT_TRANSLATE_NOOP("", "Error: Pubsub request failed");
+        case TemplateError::NoError: return "";
+    }
+    return "";
+}
+
+}
diff --git a/Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h b/Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h
new file mode 100644
index 0000000..572f910
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <boost/signals2.hpp>
+
+namespace Swift {
+
+    class Form;
+
+    class FdpFormSubmitWindow {
+
+        public:
+            enum class NodeError {
+                DomainNotFound,
+                NoFdpNodesInDomain,
+                NoError,
+            };
+            enum class TemplateError {
+                CannotLocateForm,
+                InvalidPayload,
+                RequestFailed,
+                NoError,
+            };
+
+            virtual ~FdpFormSubmitWindow() {}
+
+            virtual void show() = 0;
+            virtual void raise() = 0;
+            virtual void addNode(const std::string& node, const std::string& nodeName) = 0;
+            virtual void clearNodeData() = 0;
+            virtual void setFormData(const std::shared_ptr<Form>& form) = 0;
+            virtual void showNodePlaceholder(NodeError nodeError) = 0;
+            virtual void showFormPlaceholder(TemplateError templateError) = 0;
+            virtual void handleSubmitServerResponse(bool submissionSuccess) = 0;
+
+            boost::signals2::signal<void ()> onCloseEvent;
+            boost::signals2::signal<void (const std::string&)> onRequestPubSubNodeData;
+            boost::signals2::signal<void (const std::string&)> onRequestTemplateForm;
+            boost::signals2::signal<void (const std::shared_ptr<Form>&)> onSubmitForm;
+
+        protected:
+
+            std::string getNodeErrorText(NodeError nodeError) const;
+            std::string getTemplateErrorText(TemplateError templateError) const;
+    };
+}
diff --git a/Swift/Controllers/UIInterfaces/FdpFormSubmitWindowFactory.h b/Swift/Controllers/UIInterfaces/FdpFormSubmitWindowFactory.h
new file mode 100644
index 0000000..ef11eaf
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/FdpFormSubmitWindowFactory.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+namespace Swift {
+    class FdpFormSubmitWindow;
+
+    class FdpFormSubmitWindowFactory {
+        public:
+            virtual ~FdpFormSubmitWindowFactory() {}
+            virtual std::unique_ptr<FdpFormSubmitWindow> createFdpFormSubmitWindow() = 0;
+    };
+}
diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h
index bfd8c67..e4b4657 100644
--- a/Swift/Controllers/UIInterfaces/MainWindow.h
+++ b/Swift/Controllers/UIInterfaces/MainWindow.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -13,12 +13,12 @@
 
 #include <Swiften/Elements/DiscoItems.h>
 #include <Swiften/Elements/StatusShow.h>
-#include <Swiften/JID/JID.h>
 #include <Swiften/TLS/Certificate.h>
 
 #include <Swift/Controllers/Roster/ContactRosterItem.h>
 
 namespace Swift {
+    class JID;
     class Roster;
 
     class MainWindow {
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index a0976dc..c49364a 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -12,6 +12,7 @@
 #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/EventWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h>
 #include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h>
@@ -42,7 +43,8 @@ namespace Swift {
             public FileTransferListWidgetFactory,
             public WhiteboardWindowFactory,
             public HighlightEditorWindowFactory,
-            public BlockListEditorWidgetFactory {
+            public BlockListEditorWidgetFactory,
+            public FdpFormSubmitWindowFactory {
         public:
             virtual ~UIFactory() {}
     };
diff --git a/Swift/Controllers/UnitTest/FdpFormSubmitControllerTest.cpp b/Swift/Controllers/UnitTest/FdpFormSubmitControllerTest.cpp
new file mode 100644
index 0000000..8dc3442
--- /dev/null
+++ b/Swift/Controllers/UnitTest/FdpFormSubmitControllerTest.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <memory>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <Swiften/Client/DummyStanzaChannel.h>
+#include <Swiften/Elements/DiscoItems.h>
+#include <Swiften/Elements/Form.h>
+#include <Swiften/Elements/FormField.h>
+#include <Swiften/Elements/IQ.h>
+#include <Swiften/Elements/Payload.h>
+#include <Swiften/Elements/PubSub.h>
+#include <Swiften/Elements/PubSubItems.h>
+#include <Swiften/Elements/PubSubPublish.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Queries/IQRouter.h>
+
+#include <Swift/Controllers/FdpFormSubmitController.h>
+#include <Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UnitTest/MockFdpFormSubmitWindowFactory.h>
+#include <Swift/Controllers/UnitTest/MockFdpFormSubmitWindow.h>
+
+namespace Swift {
+
+class FdpFormSubmitControllerTest : public ::testing::Test {
+
+    protected:
+        void SetUp() {
+            clientJid_ = JID(clientJIDString_);
+            stanzaChannel_ = std::make_unique<DummyStanzaChannel>();
+            iqRouter_ = std::make_unique<IQRouter>(stanzaChannel_.get());
+            uiEventStream_ = std::make_unique<UIEventStream>();
+            fdpWindowFactory_ = std::make_unique<MockFdpFormSubmitWindowFactory>();
+            fdpController_ = std::make_unique<FdpFormSubmitController>(clientJid_, iqRouter_.get(), uiEventStream_.get(), fdpWindowFactory_.get());
+            auto fdpWindowOpenUIEvent = std::make_shared<FdpFormSubmitWindowOpenUIEvent>();
+            uiEventStream_->send(fdpWindowOpenUIEvent);
+            fdpWindow_ = fdpWindowFactory_->getMockFdpFormSubmitWindow();
+        }
+
+        void TearDown() {
+        }
+
+        std::shared_ptr<DiscoItems> createDiscoItemsResult();
+        std::shared_ptr<PubSub> createTemplatePubSubResult(const std::shared_ptr<Form>& form);
+        std::shared_ptr<PubSub> createSubmittedPubSubResult();
+
+        std::string clientJIDString_ = "testjid@test.im/swift";
+        JID clientJid_;
+        std::unique_ptr<DummyStanzaChannel> stanzaChannel_;
+        std::unique_ptr<IQRouter> iqRouter_;
+        std::unique_ptr<UIEventStream> uiEventStream_;
+        std::unique_ptr<MockFdpFormSubmitWindowFactory> fdpWindowFactory_;
+        std::unique_ptr<FdpFormSubmitController> fdpController_;
+        MockFdpFormSubmitWindow* fdpWindow_;
+};
+
+std::shared_ptr<DiscoItems> FdpFormSubmitControllerTest::createDiscoItemsResult() {
+    auto discoItems = std::make_shared<DiscoItems>();
+    discoItems->addItem(DiscoItems::Item("node0", JID(), "fdp/template/testNode0"));
+    discoItems->addItem(DiscoItems::Item("node1", JID(), "fdp/template/testNode1"));
+    discoItems->addItem(DiscoItems::Item("node2", JID(), "fdp/template/testNode2"));
+    return discoItems;
+}
+
+std::shared_ptr<PubSub> FdpFormSubmitControllerTest::createTemplatePubSubResult(const std::shared_ptr<Form>& form) {
+    auto pubSubItem = std::make_shared<PubSubItem>();
+    pubSubItem->addData(form);
+    auto pubSubItems = std::make_shared<PubSubItems>();
+    pubSubItems->addItem(pubSubItem);
+    auto pubSub = std::make_shared<PubSub>();
+    pubSub->setPayload(pubSubItems);
+    return pubSub;
+}
+
+std::shared_ptr<PubSub> FdpFormSubmitControllerTest::createSubmittedPubSubResult() {
+    auto pubSubItem = std::make_shared<PubSubItem>();
+    pubSubItem->setID("testID");
+    auto pubSubPublish = std::make_shared<PubSubPublish>();
+    pubSubPublish->addItem(pubSubItem);
+    pubSubPublish->setNode("fdp/submitted/test");
+    auto pubSub = std::make_shared<PubSub>();
+    pubSub->setPayload(pubSubPublish);
+    return pubSub;
+}
+
+TEST_F(FdpFormSubmitControllerTest, testRequestPubSubNodeData) {
+    std::string domainName = "fdp.example.test";
+    fdpWindow_->onRequestPubSubNodeData(domainName);
+    ASSERT_EQ(1, stanzaChannel_->sentStanzas.size());
+    auto requestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+    ASSERT_EQ(domainName, requestIq->getTo());
+    auto discoItemsPayloads = requestIq->getPayloads<DiscoItems>();
+    ASSERT_EQ(1, discoItemsPayloads.size());
+
+    auto discoItemsResult = createDiscoItemsResult();
+    auto resultIq = IQ::createResult(clientJid_, domainName, requestIq->getID(), discoItemsResult);
+    stanzaChannel_->onIQReceived(resultIq);
+
+    auto discoItemsList = discoItemsResult->getItems();
+    ASSERT_EQ(discoItemsList.size(), fdpWindow_->nodeData.size());
+    for (unsigned int i = 0; i < fdpWindow_->nodeData.size(); i++) {
+        auto nodeItem = fdpWindow_->nodeData[i];
+        auto discoItem = discoItemsList[i];
+        ASSERT_EQ(discoItem.getNode(), nodeItem.first);
+        ASSERT_EQ(discoItem.getName(), nodeItem.second);
+    }
+    ASSERT_EQ(FdpFormSubmitWindow::NodeError::NoError, fdpWindow_->nodeError_);
+}
+
+TEST_F(FdpFormSubmitControllerTest, testRequestPubSubNodeDataError) {
+    std::string domainName = "fdp.example.test";
+    fdpWindow_->onRequestPubSubNodeData(domainName);
+    ASSERT_EQ(1, stanzaChannel_->sentStanzas.size());
+    auto requestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+    ASSERT_EQ(domainName, requestIq->getTo());
+
+    auto resultIq = IQ::createError(clientJid_, domainName, requestIq->getID());
+    stanzaChannel_->onIQReceived(resultIq);
+
+    ASSERT_EQ(true, fdpWindow_->nodeData.empty());
+    ASSERT_EQ(FdpFormSubmitWindow::NodeError::DomainNotFound, fdpWindow_->nodeError_);
+}
+
+TEST_F(FdpFormSubmitControllerTest, testRequestTemplateForm) {
+    std::string domainName = "fdp.example.test";
+    fdpWindow_->onRequestPubSubNodeData(domainName);
+    auto nodeDataRequestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+    auto discoItemsResult = createDiscoItemsResult();
+    auto discoItemsResultIq = IQ::createResult(clientJid_, domainName, nodeDataRequestIq->getID(), discoItemsResult);
+    stanzaChannel_->onIQReceived(discoItemsResultIq);
+
+    std::string templateNodeName = "fdp/template/test";
+    fdpWindow_->onRequestTemplateForm(templateNodeName);
+    ASSERT_EQ(2, stanzaChannel_->sentStanzas.size());
+    auto requestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]);
+    ASSERT_EQ(domainName, requestIq->getTo());
+    auto pubSubPayloads = requestIq->getPayloads<PubSub>();
+    ASSERT_EQ(1, pubSubPayloads.size());
+
+    std::string value0("value0");
+    std::string value1("value1@example.test");
+    auto field0 = std::make_shared<FormField>(FormField::TextSingleType, value0);
+    auto field1 = std::make_shared<FormField>(FormField::JIDSingleType, value1);
+    auto form = std::make_shared<Form>();
+    form->addField(field0);
+    form->addField(field1);
+    auto pubSubResult = createTemplatePubSubResult(form);
+    auto resultIq = IQ::createResult(clientJid_, domainName, requestIq->getID(), pubSubResult);
+    stanzaChannel_->onIQReceived(resultIq);
+
+    ASSERT_EQ(form, fdpWindow_->templateForm_);
+    auto fields = fdpWindow_->templateForm_->getFields();
+    ASSERT_EQ(2, fields.size());
+    ASSERT_EQ(field0, fields[0]);
+    ASSERT_EQ(field1, fields[1]);
+    ASSERT_EQ(value0, fields[0]->getTextSingleValue());
+    ASSERT_EQ(value1, fields[1]->getJIDSingleValue());
+    ASSERT_EQ(FdpFormSubmitWindow::TemplateError::NoError, fdpWindow_->templateError_);
+}
+
+TEST_F(FdpFormSubmitControllerTest, testRequestTemplateFormError) {
+    std::string domainName = "fdp.example.test";
+    fdpWindow_->onRequestPubSubNodeData(domainName);
+    auto nodeDataRequestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+    auto discoItemsResult = createDiscoItemsResult();
+    auto discoItemsResultIq = IQ::createResult(clientJid_, domainName, nodeDataRequestIq->getID(), discoItemsResult);
+    stanzaChannel_->onIQReceived(discoItemsResultIq);
+
+    std::string templateNodeName = "fdp/template/test";
+    fdpWindow_->onRequestTemplateForm(templateNodeName);
+    ASSERT_EQ(2, stanzaChannel_->sentStanzas.size());
+    auto requestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]);
+    ASSERT_EQ(domainName, requestIq->getTo());
+
+    auto resultIq = IQ::createError(clientJid_, domainName, requestIq->getID());
+    stanzaChannel_->onIQReceived(resultIq);
+
+    ASSERT_EQ(nullptr, fdpWindow_->templateForm_);
+    ASSERT_EQ(FdpFormSubmitWindow::TemplateError::RequestFailed, fdpWindow_->templateError_);
+}
+
+TEST_F(FdpFormSubmitControllerTest, testSubmitForm) {
+    std::string domainName = "fdp.example.test";
+    std::string templateNodeName = "fdp/template/test";
+    fdpWindow_->onRequestPubSubNodeData(domainName);
+    auto nodeDataRequestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+    auto discoItemsResult = createDiscoItemsResult();
+    auto discoItemsResultIq = IQ::createResult(clientJid_, domainName, nodeDataRequestIq->getID(), discoItemsResult);
+    stanzaChannel_->onIQReceived(discoItemsResultIq);
+    fdpWindow_->onRequestTemplateForm(templateNodeName);
+    auto templateFormRequestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]);
+    auto templateForm = std::make_shared<Form>();
+    auto templatePubSubResult = createTemplatePubSubResult(templateForm);
+    auto templatePubSubResultIq = IQ::createResult(clientJid_, domainName, templateFormRequestIq->getID(), templatePubSubResult);
+    stanzaChannel_->onIQReceived(templatePubSubResultIq);
+
+    std::string value0("value0");
+    std::string value1("value1@example.test");
+    auto field0 = std::make_shared<FormField>(FormField::TextSingleType, value0);
+    auto field1 = std::make_shared<FormField>(FormField::JIDSingleType, value1);
+    auto form = std::make_shared<Form>();
+    form->addField(field0);
+    form->addField(field1);
+    fdpWindow_->onSubmitForm(form);
+
+    ASSERT_EQ(3, stanzaChannel_->sentStanzas.size());
+    auto requestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[2]);
+    ASSERT_EQ(domainName, requestIq->getTo());
+    auto pubSubPayloads = requestIq->getPayloads<PubSub>();
+    ASSERT_EQ(1, pubSubPayloads.size());
+    auto pubSubPayload = pubSubPayloads[0];
+    auto pubSubPublishPayload = std::dynamic_pointer_cast<PubSubPublish>(pubSubPayload->getPayload());
+    ASSERT_TRUE(pubSubPublishPayload);
+    auto pubSubItems = pubSubPublishPayload->getItems();
+    ASSERT_EQ(1, pubSubItems.size());
+    auto dataList = pubSubItems[0]->getData();
+    ASSERT_EQ(1, dataList.size());
+    auto submittedForm = std::dynamic_pointer_cast<Form>(dataList[0]);
+    ASSERT_TRUE(submittedForm);
+    ASSERT_EQ(form, submittedForm);
+    auto fields = submittedForm->getFields();
+    ASSERT_EQ(2, fields.size());
+    ASSERT_EQ(field0, fields[0]);
+    ASSERT_EQ(field1, fields[1]);
+    ASSERT_EQ(value0, fields[0]->getTextSingleValue());
+    ASSERT_EQ(value1, fields[1]->getJIDSingleValue());
+
+    auto pubSubResult = createSubmittedPubSubResult();
+    auto resultIq = IQ::createResult(clientJid_, domainName, requestIq->getID(), pubSubResult);
+    stanzaChannel_->onIQReceived(resultIq);
+
+    ASSERT_EQ(true, fdpWindow_->submissionSuccess_);
+}
+
+TEST_F(FdpFormSubmitControllerTest, testSubmitFormError) {
+    std::string domainName = "fdp.example.test";
+    std::string templateNodeName = "fdp/template/test";
+    fdpWindow_->onRequestPubSubNodeData(domainName);
+    auto nodeDataRequestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[0]);
+    auto discoItemsResult = createDiscoItemsResult();
+    auto discoItemsResultIq = IQ::createResult(clientJid_, domainName, nodeDataRequestIq->getID(), discoItemsResult);
+    stanzaChannel_->onIQReceived(discoItemsResultIq);
+    fdpWindow_->onRequestTemplateForm(templateNodeName);
+    auto templateFormRequestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[1]);
+    auto templateForm = std::make_shared<Form>();
+    auto templatePubSubResult = createTemplatePubSubResult(templateForm);
+    auto templatePubSubResultIq = IQ::createResult(clientJid_, domainName, templateFormRequestIq->getID(), templatePubSubResult);
+    stanzaChannel_->onIQReceived(templatePubSubResultIq);
+
+    auto form = std::make_shared<Form>();
+    fdpWindow_->onSubmitForm(form);
+    ASSERT_EQ(3, stanzaChannel_->sentStanzas.size());
+    auto requestIq = std::dynamic_pointer_cast<IQ>(stanzaChannel_->sentStanzas[2]);
+    ASSERT_EQ(domainName, requestIq->getTo());
+
+    auto resultIq = IQ::createError(clientJid_, domainName, requestIq->getID());
+    stanzaChannel_->onIQReceived(resultIq);
+
+    ASSERT_EQ(false, fdpWindow_->submissionSuccess_);
+}
+
+}
diff --git a/Swift/Controllers/UnitTest/MockFdpFormSubmitWindow.h b/Swift/Controllers/UnitTest/MockFdpFormSubmitWindow.h
new file mode 100644
index 0000000..28ef35f
--- /dev/null
+++ b/Swift/Controllers/UnitTest/MockFdpFormSubmitWindow.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h>
+
+namespace Swift {
+
+    class Form;
+
+    class MockFdpFormSubmitWindow : public FdpFormSubmitWindow {
+        public:
+            MockFdpFormSubmitWindow() : FdpFormSubmitWindow() {}
+            virtual void show() override {}
+            virtual void raise() override {}
+            virtual void addNode(const std::string& node, const std::string& nodeName) override { nodeData.push_back(std::pair<std::string, std::string>(node, nodeName)); }
+            virtual void clearNodeData() override { nodeData.clear(); }
+            virtual void setFormData(const std::shared_ptr<Form>& form) override { templateForm_ = form; }
+            virtual void showNodePlaceholder(NodeError nodeError) override { nodeError_ = nodeError; }
+            virtual void showFormPlaceholder(TemplateError templateError) override { templateError_ = templateError; }
+            virtual void handleSubmitServerResponse(bool submissionSuccess) override { submissionSuccess_ = submissionSuccess; }
+
+            std::vector<std::pair<std::string, std::string>> nodeData;
+            std::shared_ptr<Form> templateForm_ = nullptr;
+            NodeError nodeError_ = NodeError::NoError;
+            TemplateError templateError_ = TemplateError::NoError;
+            bool submissionSuccess_ = false;
+    };
+
+}
diff --git a/Swift/Controllers/UnitTest/MockFdpFormSubmitWindowFactory.h b/Swift/Controllers/UnitTest/MockFdpFormSubmitWindowFactory.h
new file mode 100644
index 0000000..8015419
--- /dev/null
+++ b/Swift/Controllers/UnitTest/MockFdpFormSubmitWindowFactory.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindowFactory.h>
+#include <Swift/Controllers/UnitTest/MockFdpFormSubmitWindow.h>
+
+namespace Swift {
+
+    class MockFdpFormSubmitWindowFactory : public FdpFormSubmitWindowFactory {
+        public:
+            MockFdpFormSubmitWindowFactory() : FdpFormSubmitWindowFactory() {}
+
+            virtual std::unique_ptr<FdpFormSubmitWindow> createFdpFormSubmitWindow() override {
+                std::unique_ptr<FdpFormSubmitWindow> fdpFormSubmitWindow = std::make_unique<MockFdpFormSubmitWindow>();
+                mockFdpFormSubmitWindow_ = static_cast<MockFdpFormSubmitWindow*>(fdpFormSubmitWindow.get());
+                return fdpFormSubmitWindow;
+            }
+
+            MockFdpFormSubmitWindow* getMockFdpFormSubmitWindow() { return mockFdpFormSubmitWindow_; }
+
+        private:
+            MockFdpFormSubmitWindow* mockFdpFormSubmitWindow_;
+    };
+}
diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h
index 6ae2aa7..9265310 100644
--- a/Swift/Controllers/UnitTest/MockMainWindow.h
+++ b/Swift/Controllers/UnitTest/MockMainWindow.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -26,6 +26,7 @@ namespace Swift {
             virtual void setStreamEncryptionStatus(bool /*tlsInPlaceAndValid*/) {}
             virtual void openCertificateDialog(const std::vector<Certificate::ref>& /*chain*/) {}
             virtual void setBlockingCommandAvailable(bool /*isAvailable*/) {}
+            virtual void openFdpFormSubmitDialog(const JID& /*self*/, IQRouter* /*iqRouter*/) {}
             Roster* roster;
 
     };
diff --git a/Swift/QtUI/QtFdpFormSubmitWindow.cpp b/Swift/QtUI/QtFdpFormSubmitWindow.cpp
new file mode 100644
index 0000000..5719f87
--- /dev/null
+++ b/Swift/QtUI/QtFdpFormSubmitWindow.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtFdpFormSubmitWindow.h>
+
+#include <QCloseEvent>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QSpacerItem>
+#include <QVBoxLayout>
+
+#include <Swift/QtUI/QtFormWidget.h>
+#include <Swift/QtUI/QtListWidget.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+QtFdpFormSubmitWindow::QtFdpFormSubmitWindow(QWidget* parent) : QDialog(parent), FdpFormSubmitWindow() {
+    layout_ = new QVBoxLayout(this);
+    subLayout_ = new QHBoxLayout(this);
+
+    initNodeViewLayout();
+    initFormLayout();
+    subLayout_->addLayout(nodeViewLayout_);
+    subLayout_->addLayout(formLayout_);
+    subLayout_->setStretchFactor(nodeViewLayout_, 2);
+    subLayout_->setStretchFactor(formLayout_, 3);
+    layout_->addLayout(subLayout_);
+
+    submitButton_ = new QPushButton(tr("Submit Form"), this);
+    submitButton_->setEnabled(false);
+    okButton_ = new QPushButton(tr("Ok"), this);
+    okButton_->hide();
+    cancelButton_ = new QPushButton(tr("Cancel"), this);
+    connect(submitButton_, &QPushButton::clicked, this, &QtFdpFormSubmitWindow::handleSubmitClicked);
+    connect(okButton_, &QPushButton::clicked, this, &QWidget::close);
+    connect(cancelButton_, &QPushButton::clicked, this, &QWidget::close);
+    auto buttonSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
+    buttonLayout_ = new QHBoxLayout(this);
+    buttonLayout_->addItem(buttonSpacer);
+    buttonLayout_->addWidget(submitButton_);
+    buttonLayout_->addWidget(cancelButton_);
+    layout_->addLayout(buttonLayout_);
+
+    setMinimumWidth(800);
+    setMinimumHeight(600);
+
+    this->setWindowTitle(tr("FDP Form Submission"));
+}
+
+QtFdpFormSubmitWindow::~QtFdpFormSubmitWindow() {
+}
+
+void QtFdpFormSubmitWindow::closeEvent(QCloseEvent* event) {
+    event->ignore();
+    onCloseEvent();
+}
+
+void QtFdpFormSubmitWindow::initNodeViewLayout() {
+    nodeViewLayout_ = new QVBoxLayout(this);
+    auto domainSearchLayout = new QHBoxLayout(this);
+    pubSubDomainEdit_ = new QLineEdit(this);
+    loadDomainButton_ = new QPushButton(tr("Load"), this);
+    pubSubNodeView_ = new QtListWidget(this);
+    pubSubDomainEdit_->setPlaceholderText(tr("Enter pubsub domain here"));
+    pubSubNodeView_->setMinimumWidth(300);
+    connect(loadDomainButton_, &QPushButton::clicked, this, &QtFdpFormSubmitWindow::handleLoadDomainButtonClicked);
+    connect(pubSubNodeView_, &QListWidget::itemDoubleClicked, this, &QtFdpFormSubmitWindow::handlePubSubNodeViewItemDoubleClicked);
+    domainSearchLayout->addWidget(pubSubDomainEdit_);
+    domainSearchLayout->addWidget(loadDomainButton_);
+    nodeViewLayout_->addLayout(domainSearchLayout);
+    nodeViewLayout_->addWidget(pubSubNodeView_);
+}
+
+void QtFdpFormSubmitWindow::initFormLayout() {
+    formPlaceholder_ = new QLabel(tr("No form loaded"));
+    formPlaceholder_->setAlignment(Qt::AlignCenter);
+    formPlaceholder_->setMinimumWidth(200);
+    formPlaceholder_->setStyleSheet("QLabel { color : #AAAAAA; }");
+    formLayout_ = new QVBoxLayout(this);
+    formLayout_->addWidget(formPlaceholder_, Qt::AlignCenter);
+}
+
+void QtFdpFormSubmitWindow::show() {
+    QDialog::show();
+}
+
+void QtFdpFormSubmitWindow::raise() {
+    QDialog::raise();
+}
+
+void QtFdpFormSubmitWindow::addNode(const std::string& node, const std::string& nodeName) {
+    auto listItem = new QListWidgetItem(P2QSTRING(nodeName));
+    listItem->setData(Qt::UserRole, P2QSTRING(node));
+    pubSubNodeView_->addItem(listItem);
+}
+
+void QtFdpFormSubmitWindow::clearNodeData() {
+    pubSubNodeView_->clear();
+    pubSubNodeView_->setWordWrap(false);
+    disconnect(pubSubNodeView_, &QtListWidget::onResize, this, &QtFdpFormSubmitWindow::handleNodeListResize);
+}
+
+void QtFdpFormSubmitWindow::setFormData(const std::shared_ptr<Form>& form) {
+    if (formWidget_) {
+        formLayout_->removeWidget(formWidget_);
+        formWidget_->deleteLater();
+        formWidget_ = nullptr;
+    }
+    else if (!formPlaceholder_->isHidden()) {
+        formLayout_->removeWidget(formPlaceholder_);
+        formPlaceholder_->hide();
+    }
+    formWidget_ = new QtFormWidget(form);
+    formWidget_->setEditable(true);
+    formLayout_->addWidget(formWidget_);
+
+    if (!okButton_->isHidden()) {
+        buttonLayout_->removeWidget(okButton_);
+        okButton_->hide();
+    }
+    if (submitButton_->isHidden()) {
+        buttonLayout_->insertWidget(1, submitButton_);
+        submitButton_->show();
+    }
+    submitButton_->setEnabled(true);
+    cancelButton_->setEnabled(true);
+}
+
+void QtFdpFormSubmitWindow::showNodePlaceholder(NodeError nodeError) {
+    pubSubNodeView_->clear();
+    pubSubNodeView_->setWordWrap(true);
+    auto listItem = new QListWidgetItem;
+    auto placeholderText = P2QSTRING(getNodeErrorText(nodeError));
+    listItem->setText(placeholderText);
+    listItem->setTextAlignment(Qt::AlignCenter);
+    listItem->setFlags(Qt::NoItemFlags);
+    listItem->setSizeHint(QSize(listItem->sizeHint().width(), pubSubNodeView_->height()));
+    connect(pubSubNodeView_, &QtListWidget::onResize, this, &QtFdpFormSubmitWindow::handleNodeListResize);
+    pubSubNodeView_->addItem(listItem);
+}
+
+void QtFdpFormSubmitWindow::showFormPlaceholder(TemplateError templateError) {
+    if (formWidget_) {
+        formLayout_->removeWidget(formWidget_);
+        formWidget_->deleteLater();
+        formWidget_ = nullptr;
+    }
+    auto placeholderText = P2QSTRING(getTemplateErrorText(templateError));
+    formPlaceholder_->setText(placeholderText);
+    if (formPlaceholder_->isHidden()) {
+        formLayout_->addWidget(formPlaceholder_, Qt::AlignCenter);
+        formPlaceholder_->show();
+    }
+
+    if (!okButton_->isHidden()) {
+        buttonLayout_->removeWidget(okButton_);
+        okButton_->hide();
+    }
+    if (submitButton_->isHidden()) {
+        buttonLayout_->insertWidget(1, submitButton_);
+        submitButton_->show();
+    }
+    submitButton_->setEnabled(false);
+    cancelButton_->setEnabled(true);
+}
+
+void QtFdpFormSubmitWindow::handleLoadDomainButtonClicked() {
+    std::string domainUri = Q2PSTRING(pubSubDomainEdit_->text());
+    onRequestPubSubNodeData(domainUri);
+}
+
+void QtFdpFormSubmitWindow::handlePubSubListViewTemplateSelected(const std::string& nodeName) {
+    onRequestTemplateForm(nodeName);
+}
+
+void QtFdpFormSubmitWindow::handlePubSubNodeViewItemDoubleClicked(QListWidgetItem* item) {
+    handlePubSubListViewTemplateSelected(Q2PSTRING(item->data(Qt::UserRole).toString()));
+}
+
+void QtFdpFormSubmitWindow::handleSubmitClicked() {
+    auto form = formWidget_->getCompletedForm();
+    formWidget_->setDisabled(true);
+    submitButton_->setEnabled(false);
+    onSubmitForm(form);
+}
+
+void QtFdpFormSubmitWindow::handleSubmitServerResponse(bool submissionSuccess) {
+    if (submissionSuccess) {
+        if (!submitButton_->isHidden()) {
+            buttonLayout_->removeWidget(submitButton_);
+            submitButton_->hide();
+        }
+        if (okButton_->isHidden()) {
+            buttonLayout_->insertWidget(1, okButton_);
+            okButton_->show();
+        }
+        cancelButton_->setEnabled(false);
+    }
+    else {
+        formWidget_->setDisabled(false);
+        submitButton_->setEnabled(true);
+    }
+}
+
+void QtFdpFormSubmitWindow::handleNodeListResize() {
+    auto placeholderItem = pubSubNodeView_->item(0);
+    placeholderItem->setSizeHint(QSize(placeholderItem->sizeHint().width(), pubSubNodeView_->height()));
+}
+
+}
diff --git a/Swift/QtUI/QtFdpFormSubmitWindow.h b/Swift/QtUI/QtFdpFormSubmitWindow.h
new file mode 100644
index 0000000..c178a5b
--- /dev/null
+++ b/Swift/QtUI/QtFdpFormSubmitWindow.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <QDialog>
+
+#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h>
+
+class QHBoxLayout;
+class QLabel;
+class QLineEdit;
+class QListWidgetItem;
+class QPushButton;
+class QTextEdit;
+class QVBoxLayout;
+
+namespace Swift {
+
+    class Form;
+    class QtFormWidget;
+    class QtListWidget;
+    class QtPubSubNodeController;
+
+    class QtFdpFormSubmitWindow : public QDialog, public FdpFormSubmitWindow {
+        Q_OBJECT
+
+        public:
+            QtFdpFormSubmitWindow(QWidget* parent = nullptr);
+            virtual ~QtFdpFormSubmitWindow();
+
+        protected:
+            virtual void closeEvent(QCloseEvent* event) override;
+
+        private:
+            void initNodeViewLayout();
+            void initFormLayout();
+            virtual void show() override;
+            virtual void raise() override;
+            virtual void addNode(const std::string& node, const std::string& nodeName) override;
+            virtual void clearNodeData() override;
+            virtual void setFormData(const std::shared_ptr<Form>& form) override;
+            virtual void showNodePlaceholder(NodeError nodeError) override;
+            virtual void showFormPlaceholder(TemplateError templateError) override;
+            virtual void handleSubmitServerResponse(bool submissionSuccess) override;
+
+        private slots:
+            void handleLoadDomainButtonClicked();
+            void handlePubSubListViewTemplateSelected(const std::string& nodeName);
+            void handlePubSubNodeViewItemDoubleClicked(QListWidgetItem* item);
+            void handleSubmitClicked();
+            void handleNodeListResize();
+
+        private:
+            QVBoxLayout* layout_;
+            QHBoxLayout* subLayout_;
+            QHBoxLayout* buttonLayout_;
+            QVBoxLayout* nodeViewLayout_;
+            QVBoxLayout* formLayout_;
+            QLineEdit* pubSubDomainEdit_;
+            QPushButton* loadDomainButton_;
+            QtListWidget* pubSubNodeView_;
+            QLabel* formPlaceholder_;
+            QTextEdit* nodePlaceholderTextEdit_ = nullptr;
+            QtFormWidget* formWidget_ = nullptr;
+            QPushButton* cancelButton_;
+            QPushButton* submitButton_ = nullptr;
+            QPushButton* okButton_ = nullptr;
+    };
+}
diff --git a/Swift/QtUI/QtListWidget.cpp b/Swift/QtUI/QtListWidget.cpp
new file mode 100644
index 0000000..e35bbbb
--- /dev/null
+++ b/Swift/QtUI/QtListWidget.cpp
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/QtListWidget.h>
+
+#include <QListWidget>
+
+namespace Swift {
+
+QtListWidget::QtListWidget(QWidget* parent) : QListWidget(parent) {
+}
+
+void QtListWidget::resizeEvent(QResizeEvent*) {
+    emit onResize();
+}
+
+}
diff --git a/Swift/QtUI/QtListWidget.h b/Swift/QtUI/QtListWidget.h
new file mode 100644
index 0000000..b7380c4
--- /dev/null
+++ b/Swift/QtUI/QtListWidget.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <QListWidget>
+
+namespace Swift {
+
+class QtListWidget : public QListWidget {
+    Q_OBJECT
+    public:
+        QtListWidget(QWidget* parent = nullptr);
+    protected:
+        virtual void resizeEvent(QResizeEvent*);
+    signals:
+        void onResize();
+};
+
+}
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 7eec8d1..92488ae 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -27,6 +27,7 @@
 
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
@@ -199,6 +200,13 @@ QtMainWindow::QtMainWindow(Chattables& chattables, SettingsProvider* settings, U
     }
     serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this);
     actionsMenu->addMenu(serverAdHocMenu_);
+    if (settings_->getSetting(SettingConstants::FUTURE)) {
+        actionsMenu->addSeparator();
+        submitFormAction_ = new QAction(tr("Submit Form"), this);
+        connect(submitFormAction_, &QAction::triggered, this, &QtMainWindow::handleSubmitFormActionTriggered);
+        actionsMenu->addAction(submitFormAction_);
+        onlineOnlyActions_ << submitFormAction_;
+    }
     actionsMenu->addSeparator();
     QAction* signOutAction = new QAction(tr("&Sign Out"), this);
     connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction()));
@@ -437,4 +445,8 @@ void QtMainWindow::setBlockingCommandAvailable(bool isAvailable) {
     openBlockingListEditor_->setVisible(isAvailable);
 }
 
+void QtMainWindow::handleSubmitFormActionTriggered() {
+    uiEventStream_->send(std::make_shared<FdpFormSubmitWindowOpenUIEvent>());
+}
+
 }
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index 0e7f290..b285831 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -81,6 +81,7 @@ namespace Swift {
             void handleShowCertificateInfo();
             void handleEditBlockingList();
             void handleSomethingSelectedChanged(bool itemSelected);
+            void handleSubmitFormActionTriggered();
 
         private:
             Chattables& chattables_;
@@ -110,5 +111,6 @@ namespace Swift {
             std::vector<DiscoItems::Item> serverAdHocCommands_;
             QList<QAction*> serverAdHocCommandActions_;
             QList<QAction*> onlineOnlyActions_;
+            QAction* submitFormAction_;
     };
 }
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 2762d68..93fca5f 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -22,6 +22,7 @@
 #include <Swift/QtUI/QtChatWindow.h>
 #include <Swift/QtUI/QtChatWindowFactory.h>
 #include <Swift/QtUI/QtContactEditWindow.h>
+#include <Swift/QtUI/QtFdpFormSubmitWindow.h>
 #include <Swift/QtUI/QtFileTransferListWidget.h>
 #include <Swift/QtUI/QtHighlightNotificationConfigDialog.h>
 #include <Swift/QtUI/QtHistoryWindow.h>
@@ -175,6 +176,10 @@ AdHocCommandWindow* QtUIFactory::createAdHocCommandWindow(std::shared_ptr<Outgoi
     return new QtAdHocCommandWindow(command);
 }
 
+std::unique_ptr<FdpFormSubmitWindow> QtUIFactory::createFdpFormSubmitWindow() {
+    return std::make_unique<QtFdpFormSubmitWindow>();
+}
+
 void QtUIFactory::showTabs() {
     if (!tabs_->isVisible()) {
         tabs_->show();
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index ad8dc1f..04836fe 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -18,6 +18,7 @@ class QSplitter;
 namespace Swift {
     class AutoUpdater;
     class Chattables;
+    class FdpFormSubmitWindow;
     class QtChatTabs;
     class QtChatTheme;
     class QtChatWindow;
@@ -54,6 +55,7 @@ namespace Swift {
             virtual HighlightEditorWindow* createHighlightEditorWindow();
             virtual BlockListEditorWidget* createBlockListEditorWidget();
             virtual AdHocCommandWindow* createAdHocCommandWindow(std::shared_ptr<OutgoingAdHocCommandSession> command);
+            virtual std::unique_ptr<FdpFormSubmitWindow> createFdpFormSubmitWindow();
 
         private slots:
             void handleChatWindowFontResized(int);
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 535ccaf..a2ad9b1 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -177,6 +177,7 @@ sources = [
     "QtEmojisSelector.cpp",
     "QtEmoticonsGrid.cpp",
     "QtExpandedListView.cpp",
+    "QtFdpFormSubmitWindow.cpp",
     "QtFileTransferListItemModel.cpp",
     "QtFileTransferListWidget.cpp",
     "QtFormResultItemModel.cpp",
@@ -185,6 +186,7 @@ sources = [
     "QtHistoryWindow.cpp",
     "QtJoinMUCWindow.cpp",
     "QtLineEdit.cpp",
+    "QtListWidget.cpp",
     "QtLoginWindow.cpp",
     "QtMainWindow.cpp",
     "QtMUCConfigurationWindow.cpp",
diff --git a/Swiften/Elements/PubSubItem.cpp b/Swiften/Elements/PubSubItem.cpp
index 4dc0907..b5f17cc 100644
--- a/Swiften/Elements/PubSubItem.cpp
+++ b/Swiften/Elements/PubSubItem.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Isode Limited.
+ * Copyright (c) 2013-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -11,5 +11,8 @@ using namespace Swift;
 PubSubItem::PubSubItem() {
 }
 
+PubSubItem::PubSubItem(const std::string& id) : id_(id) {
+}
+
 PubSubItem::~PubSubItem() {
 }
diff --git a/Swiften/Elements/PubSubItem.h b/Swiften/Elements/PubSubItem.h
index ba13150..161b733 100644
--- a/Swiften/Elements/PubSubItem.h
+++ b/Swiften/Elements/PubSubItem.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2017 Isode Limited.
+ * Copyright (c) 2013-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -18,32 +18,32 @@ namespace Swift {
         public:
 
             PubSubItem();
+            PubSubItem(const std::string& id);
 
             virtual ~PubSubItem();
 
             const std::vector< std::shared_ptr<Payload> >& getData() const {
-                return data;
+                return data_;
             }
 
             void setData(const std::vector< std::shared_ptr<Payload> >& value) {
-                this->data = value ;
+                this->data_ = value ;
             }
 
             void addData(std::shared_ptr<Payload> value) {
-                this->data.push_back(value);
+                this->data_.push_back(value);
             }
 
             const std::string& getID() const {
-                return id;
+                return id_;
             }
 
             void setID(const std::string& value) {
-                this->id = value ;
+                this->id_ = value ;
             }
 
-
         private:
-            std::vector< std::shared_ptr<Payload> > data;
-            std::string id;
+            std::vector< std::shared_ptr<Payload> > data_;
+            std::string id_;
     };
 }
diff --git a/Swiften/Elements/PubSubItems.h b/Swiften/Elements/PubSubItems.h
index c60adca..c8b7f53 100644
--- a/Swiften/Elements/PubSubItems.h
+++ b/Swiften/Elements/PubSubItems.h
@@ -61,7 +61,6 @@ namespace Swift {
                 this->subscriptionID = value ;
             }
 
-
         private:
             std::string node;
             std::vector< std::shared_ptr<PubSubItem> > items;
-- 
cgit v0.10.2-6-g49f6