From 83afa3d4bf18e4feb8a33d084ed181508084fc64 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Fri, 6 Jan 2012 23:18:25 +0100
Subject: XEP-0004 form support for user search.

License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php

diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index 3e734df..d90a3f8 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -123,18 +123,24 @@ void UserSearchController::handleSearchResponse(boost::shared_ptr<SearchPayload>
 		window_->setSearchError(true);
 		return;
 	}
+
 	std::vector<UserSearchResult> results;
-	foreach (SearchPayload::Item item, resultsPayload->getItems()) {
-		JID jid(item.jid);
-		std::map<std::string, std::string> fields;
-		fields["first"] = item.first;
-		fields["last"] = item.last;
-		fields["nick"] = item.nick;
-		fields["email"] = item.email;
-		UserSearchResult result(jid, fields);
-		results.push_back(result);
+
+	if (resultsPayload->getForm()) {
+		window_->setResultsForm(resultsPayload->getForm());
+	} else {
+		foreach (SearchPayload::Item item, resultsPayload->getItems()) {
+			JID jid(item.jid);
+			std::map<std::string, std::string> fields;
+			fields["first"] = item.first;
+			fields["last"] = item.last;
+			fields["nick"] = item.nick;
+			fields["email"] = item.email;
+			UserSearchResult result(jid, fields);
+			results.push_back(result);
+		}
+		window_->setResults(results);
 	}
-	window_->setResults(results);
 }
 
 void UserSearchController::handleDiscoWalkFinished() {
diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h
index 0bb7400..0bfc509 100644
--- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h
+++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h
@@ -23,6 +23,7 @@ namespace Swift {
 
 			virtual void clear() = 0;
 			virtual void setResults(const std::vector<UserSearchResult>& results) = 0;
+			virtual void setResultsForm(const Form::ref results) = 0;
 			virtual void addSavedServices(const std::vector<JID>& services) = 0;
 			virtual void setSelectedService(const JID& service) = 0;
 			virtual void setServerSupportsSearch(bool support) = 0;
diff --git a/Swift/QtUI/QtFormResultItemModel.cpp b/Swift/QtUI/QtFormResultItemModel.cpp
new file mode 100644
index 0000000..5461f05
--- /dev/null
+++ b/Swift/QtUI/QtFormResultItemModel.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2012 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "QtFormResultItemModel.h"
+
+#include <boost/algorithm/string/join.hpp>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swiften/Base/foreach.h>
+
+namespace Swift {
+
+QtFormResultItemModel::QtFormResultItemModel(QObject *parent) : QAbstractTableModel(parent) {
+
+}
+
+QtFormResultItemModel::QtFormResultItemModel(QObject* parent, Form::ref formResult) : QAbstractTableModel(parent), formResult_(formResult) {
+
+}
+
+void QtFormResultItemModel::setForm(Form::ref formResult) {
+	emit layoutAboutToBeChanged();
+	formResult_ = formResult;
+	emit layoutChanged();
+}
+
+const Form::ref QtFormResultItemModel::getForm() const {
+	return formResult_;
+}
+
+QVariant QtFormResultItemModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const {
+	if (!formResult_) return QVariant();
+	if (role != Qt::DisplayRole) return QVariant();
+	if (static_cast<size_t>(section) >= formResult_->getReportedFields().size()) return QVariant();
+	return QVariant(QString::fromStdString(formResult_->getReportedFields().at(section)->getLabel()));
+}
+
+int QtFormResultItemModel::rowCount(const QModelIndex &/*parent*/) const {
+	if (!formResult_) return 0;
+	return formResult_->getItems().size();
+}
+
+int QtFormResultItemModel::columnCount(const QModelIndex &/*parent*/) const {
+	if (!formResult_) return 0;
+	return formResult_->getReportedFields().size();
+}
+
+QVariant QtFormResultItemModel::data(const QModelIndex &index, int role) const {
+	if (!formResult_) return QVariant();
+	if (role != Qt::DisplayRole || !index.isValid()) {
+		return QVariant();
+	}
+
+	if (static_cast<size_t>(index.row()) >= formResult_->getItems().size()) return QVariant();
+	if (static_cast<size_t>(index.column()) >= formResult_->getReportedFields().size()) return QVariant();
+
+	Form::FormItem item = formResult_->getItems().at(index.row());
+
+	return QVariant(P2QSTRING(getFieldValue(item, index.column())));
+}
+
+const std::string QtFormResultItemModel::getFieldValue(const Form::FormItem& item, const int column) const {
+	// determine field name
+	std::string name = formResult_->getReportedFields().at(column)->getName();
+
+	foreach(FormField::ref field, item) {
+		if (field->getName() == name) {
+			std::string delimiter = "";
+			if (boost::dynamic_pointer_cast<TextMultiFormField>(field)) {
+				delimiter = "\n";
+			} else if (boost::dynamic_pointer_cast<JIDMultiFormField>(field)) {
+				delimiter = ", ";
+			} else if (boost::dynamic_pointer_cast<ListMultiFormField>(field)) {
+				delimiter = ", ";
+			}
+
+			return boost::algorithm::join(field->getRawValues(), delimiter);
+		}
+	}
+
+	return "";
+}
+
+}
diff --git a/Swift/QtUI/QtFormResultItemModel.h b/Swift/QtUI/QtFormResultItemModel.h
new file mode 100644
index 0000000..f383f74
--- /dev/null
+++ b/Swift/QtUI/QtFormResultItemModel.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2012 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QAbstractTableModel>
+
+#include <Swiften/Elements/Form.h>
+
+namespace Swift {
+
+class QtFormResultItemModel : public QAbstractTableModel {
+	Q_OBJECT
+	public:
+		QtFormResultItemModel(QObject* parent);
+		QtFormResultItemModel(QObject* parent, Form::ref formResult);
+
+		void setForm(Form::ref formResult);
+		const Form::ref getForm() const;
+
+		virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+		virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+		virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+		virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+	private:
+		const std::string getFieldValue(const Form::FormItem& item, const int column) const;
+
+	private:
+		Form::ref formResult_;
+};
+
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 95ec49a..2283001 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -99,6 +99,7 @@ sources = [
     "SystemMessageSnippet.cpp",
     "QtElidingLabel.cpp",
     "QtFormWidget.cpp",
+	"QtFormResultItemModel.cpp",
     "QtLineEdit.cpp",
     "QtJoinMUCWindow.cpp",
     "Roster/RosterModel.cpp",
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp
index 3ef048b..13858a7 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp
+++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp
@@ -8,12 +8,31 @@
 
 namespace Swift {
 
-QtUserSearchFieldsPage::QtUserSearchFieldsPage() {
+QtUserSearchFieldsPage::QtUserSearchFieldsPage() : formWidget_(0) {
 	setupUi(this);
 }
 
 bool QtUserSearchFieldsPage::isComplete() const {
-	return nickInput_->isEnabled() || firstInput_->isEnabled() || lastInput_->isEnabled() || emailInput_->isEnabled();
+	if (formWidget_) {
+		return formWidget_->isEnabled();
+	} else {
+		return nickInput_->isEnabled() || firstInput_->isEnabled() || lastInput_->isEnabled() || emailInput_->isEnabled();
+	}
+}
+
+QtFormWidget* QtUserSearchFieldsPage::getFormWidget() {
+	return formWidget_;
+}
+
+void QtUserSearchFieldsPage::setFormWidget(QtFormWidget *widget) {
+	if (formWidget_) {
+		delete formWidget_;
+		formWidget_ = NULL;
+	}
+	if (widget) {
+		formContainer_->layout()->addWidget(widget);
+	}
+	formWidget_ = widget;
 }
 
 void QtUserSearchFieldsPage::emitCompletenessCheck() {
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h
index 12c28b5..066205b 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h
@@ -8,6 +8,8 @@
 
 #include <QWizardPage>
 
+#include <Swift/QtUI/QtFormWidget.h>
+
 #include <Swift/QtUI/UserSearch/ui_QtUserSearchFieldsPage.h>
 
 namespace Swift {
@@ -16,7 +18,14 @@ namespace Swift {
 		public:
 			QtUserSearchFieldsPage();
 			virtual bool isComplete() const;
+
+			QtFormWidget* getFormWidget();
+			void setFormWidget(QtFormWidget *widget);
+
 		public slots:
 			void emitCompletenessCheck();
+
+		private:
+			QtFormWidget *formWidget_;
 	};
 }
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui
index fa6e376..f972516 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui
+++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.ui
@@ -11,7 +11,7 @@
    </rect>
   </property>
   <property name="windowTitle">
-   <string></string>
+   <string/>
   </property>
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0" colspan="2">
@@ -61,7 +61,30 @@
    <item row="4" column="1">
     <widget class="QLineEdit" name="emailInput_"/>
    </item>
+   <item row="6" column="0">
+    <widget class="QLabel" name="fetchingThrobber_">
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
    <item row="6" column="1">
+    <widget class="QLabel" name="fetchingLabel_">
+     <property name="text">
+      <string>Fetching search fields</string>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="0" colspan="2">
+    <widget class="QWidget" name="formContainer_" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="margin">
+       <number>0</number>
+      </property>
+     </layout>
+    </widget>
+   </item>
+   <item row="7" column="1">
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
@@ -74,20 +97,6 @@
      </property>
     </spacer>
    </item>
-   <item row="5" column="0">
-    <widget class="QLabel" name="fetchingThrobber_">
-     <property name="text">
-      <string></string>
-     </property>
-    </widget>
-   </item>
-   <item row="5" column="1">
-    <widget class="QLabel" name="fetchingLabel_">
-     <property name="text">
-      <string>Fetching search fields</string>
-     </property>
-    </widget>
-   </item>
   </layout>
  </widget>
  <resources/>
diff --git a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui
index f312fde..ceb15e1 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui
+++ b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.ui
@@ -11,10 +11,10 @@
    </rect>
   </property>
   <property name="windowTitle">
-   <string></string>
+   <string/>
   </property>
-  <layout class="QHBoxLayout" name="horizontalLayout">
-   <item>
+  <layout class="QGridLayout" name="layout">
+   <item row="0" column="0">
     <widget class="QTreeView" name="results_">
      <property name="rootIsDecorated">
       <bool>false</bool>
diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
index 0559683..933612c 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
@@ -6,17 +6,20 @@
 
 #include "Swift/QtUI/UserSearch/QtUserSearchWindow.h"
 
+#include <QItemDelegate>
 #include <QModelIndex>
 #include <QWizardPage>
 #include <QMovie>
 #include <boost/smart_ptr/make_shared.hpp>
 
+#include <Swiften/Base/foreach.h>
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
 #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
 #include "Swift/Controllers/UIEvents/AddContactUIEvent.h"
 #include "Swift/QtUI/UserSearch/UserSearchModel.h"
 #include "Swift/QtUI/UserSearch/UserSearchDelegate.h"
 #include "Swift/QtUI/QtSwiftUtil.h"
+#include "Swift/QtUI/QtFormResultItemModel.h"
 #include "QtUserSearchFirstPage.h"
 #include "QtUserSearchFieldsPage.h"
 #include "QtUserSearchResultsPage.h"
@@ -24,7 +27,7 @@
 
 namespace Swift {
 
-QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups) : eventStream_(eventStream), type_(type) {
+QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups) : eventStream_(eventStream), type_(type), model_(NULL) {
 	setupUi(this);
 #ifndef Q_WS_MAC
 	setWindowIcon(QIcon(":/logo-icon-16.png"));
@@ -32,7 +35,6 @@ QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWin
 	QString title(type == UserSearchWindow::AddContact ? tr("Add Contact") : tr("Chat to User"));
 	setWindowTitle(title);
 
-	model_ = new UserSearchModel();
 	delegate_ = new UserSearchDelegate();
 
 	firstPage_ = new QtUserSearchFirstPage(type, title);
@@ -51,9 +53,7 @@ QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWin
 	setPage(2, fieldsPage_);
 
 	resultsPage_ = new QtUserSearchResultsPage();
-	resultsPage_->results_->setModel(model_);
-	resultsPage_->results_->setItemDelegate(delegate_);
-	resultsPage_->results_->setHeaderHidden(true);
+
 #ifdef SWIFT_PLATFORM_MACOSX
 	resultsPage_->results_->setAlternatingRowColors(true);
 #endif
@@ -74,7 +74,7 @@ QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWin
 }
 
 QtUserSearchWindow::~QtUserSearchWindow() {
-
+	delete model_;
 }
 
 void QtUserSearchWindow::handleCurrentChanged(int page) {
@@ -101,9 +101,28 @@ JID QtUserSearchWindow::getServerToSearch() {
 void QtUserSearchWindow::handleAccepted() {
 	JID jid;
 	if (!firstPage_->byJID_->isChecked()) {
-		UserSearchResult* userItem = static_cast<UserSearchResult*>(resultsPage_->results_->currentIndex().internalPointer());
-		if (userItem) { /* Remember to leave this if we change to dynamic cast */
-			jid = userItem->getJID();
+		if (dynamic_cast<UserSearchModel*>(model_)) {
+			UserSearchResult* userItem = static_cast<UserSearchResult*>(resultsPage_->results_->currentIndex().internalPointer());
+			if (userItem) { /* Remember to leave this if we change to dynamic cast */
+				jid = userItem->getJID();
+			}
+		} else {
+			int row = resultsPage_->results_->currentIndex().row();
+
+			Form::FormItem item = dynamic_cast<QtFormResultItemModel*>(model_)->getForm()->getItems().at(row);
+			JID fallbackJid;
+			foreach(FormField::ref field, item) {
+				if (boost::dynamic_pointer_cast<JIDSingleFormField>(field)) {
+					jid = JID(field->getRawValues().at(0));
+					break;
+				}
+				if (field->getName() == "jid") {
+					fallbackJid = field->getRawValues().at(0);
+				}
+			}
+			if (!jid.isValid()) {
+				jid = fallbackJid;
+			}
 		}
 	}
 	else {
@@ -152,17 +171,21 @@ void QtUserSearchWindow::handleFirstPageRadioChange() {
 
 void QtUserSearchWindow::handleSearch() {
 	boost::shared_ptr<SearchPayload> search(new SearchPayload());
-	if (fieldsPage_->nickInput_->isEnabled()) {
-		search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text()));
-	}
-	if (fieldsPage_->firstInput_->isEnabled()) {
-		search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text()));
-	}
-	if (fieldsPage_->lastInput_->isEnabled()) {
-		search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text()));
-	}
-	if (fieldsPage_->emailInput_->isEnabled()) {
-		search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text()));
+	if (fieldsPage_->getFormWidget()) {
+		search->setForm(fieldsPage_->getFormWidget()->getCompletedForm());
+	} else {
+		if (fieldsPage_->nickInput_->isEnabled()) {
+			search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text()));
+		}
+		if (fieldsPage_->firstInput_->isEnabled()) {
+			search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text()));
+		}
+		if (fieldsPage_->lastInput_->isEnabled()) {
+			search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text()));
+		}
+		if (fieldsPage_->emailInput_->isEnabled()) {
+			search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text()));
+		}
 	}
 	onSearchRequested(search, getServerToSearch());
 }
@@ -184,18 +207,41 @@ void QtUserSearchWindow::setSearchFields(boost::shared_ptr<SearchPayload> fields
 	fieldsPage_->fetchingThrobber_->hide();
 	fieldsPage_->fetchingThrobber_->movie()->stop();
 	fieldsPage_->fetchingLabel_->hide();
+
 	fieldsPage_->instructionsLabel_->setText(fields->getInstructions() ? P2QSTRING(fields->getInstructions().get()) : "Enter search terms");
-	bool enabled[8] = {fields->getNick(), fields->getNick(), fields->getFirst(), fields->getFirst(), fields->getLast(), fields->getLast(), fields->getEMail(), fields->getEMail()};
-	QWidget* legacySearchWidgets[8] = {fieldsPage_->nickInputLabel_, fieldsPage_->nickInput_, fieldsPage_->firstInputLabel_, fieldsPage_->firstInput_, fieldsPage_->lastInputLabel_, fieldsPage_->lastInput_, fieldsPage_->emailInputLabel_, fieldsPage_->emailInput_};
-	for (int i = 0; i < 8; i++) {
-		legacySearchWidgets[i]->setVisible(enabled[i]);
-		legacySearchWidgets[i]->setEnabled(enabled[i]);
+	if (fields->getForm()) {
+		fieldsPage_->setFormWidget(new QtFormWidget(fields->getForm(), fieldsPage_));
+	} else {
+		fieldsPage_->setFormWidget(NULL);
+		bool enabled[8] = {fields->getNick(), fields->getNick(), fields->getFirst(), fields->getFirst(), fields->getLast(), fields->getLast(), fields->getEMail(), fields->getEMail()};
+		QWidget* legacySearchWidgets[8] = {fieldsPage_->nickInputLabel_, fieldsPage_->nickInput_, fieldsPage_->firstInputLabel_, fieldsPage_->firstInput_, fieldsPage_->lastInputLabel_, fieldsPage_->lastInput_, fieldsPage_->emailInputLabel_, fieldsPage_->emailInput_};
+		for (int i = 0; i < 8; i++) {
+			legacySearchWidgets[i]->setVisible(enabled[i]);
+			legacySearchWidgets[i]->setEnabled(enabled[i]);
+		}
 	}
 	fieldsPage_->emitCompletenessCheck();
 }
 
 void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results) {
-	model_->setResults(results);
+	UserSearchModel *newModel = new UserSearchModel();
+	newModel->setResults(results);
+	resultsPage_->results_->setModel(newModel);
+	resultsPage_->results_->setItemDelegate(delegate_);
+	resultsPage_->results_->setHeaderHidden(true);
+	delete model_;
+	model_ = newModel;
+}
+
+void QtUserSearchWindow::setResultsForm(Form::ref results) {
+	QtFormResultItemModel *newModel = new QtFormResultItemModel(this);
+	newModel->setForm(results);
+	resultsPage_->results_->setModel(newModel);
+	resultsPage_->results_->setItemDelegate(new QItemDelegate());
+	resultsPage_->results_->setHeaderHidden(false);
+	resultsPage_->results_->header()->setResizeMode(QHeaderView::ResizeToContents);
+	delete model_;
+	model_ = newModel;
 }
 
 void QtUserSearchWindow::setSelectedService(const JID& jid) {
@@ -228,7 +274,9 @@ void QtUserSearchWindow::clear() {
 	firstPage_->howLabel_->setText(howText);
 	firstPage_->byJID_->setChecked(true);
 	clearForm();
-	model_->clear();
+	resultsPage_->results_->setModel(NULL);
+	delete model_;
+	model_ = NULL;
 	handleFirstPageRadioChange();
 	restart();
 	lastPage_ = 1;
diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
index bb40cd3..4cfe93f 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
@@ -21,6 +21,7 @@ namespace Swift {
 	class QtUserSearchFieldsPage;
 	class QtUserSearchResultsPage;
 	class QtUserSearchDetailsPage;
+	class QtFormResultItemModel;
 
 	class QtUserSearchWindow : public QWizard, public UserSearchWindow, private Ui::QtUserSearchWizard {
 		Q_OBJECT
@@ -33,6 +34,7 @@ namespace Swift {
 			virtual void clear();
 			virtual void show();
 			virtual void setResults(const std::vector<UserSearchResult>& results);
+			virtual void setResultsForm(Form::ref results);
 			virtual void setSelectedService(const JID& jid);
 			virtual void setServerSupportsSearch(bool error);
 			virtual void setSearchError(bool error);
@@ -52,7 +54,7 @@ namespace Swift {
 		private:
 			UIEventStream* eventStream_;
 			UserSearchWindow::Type type_;
-			UserSearchModel* model_;
+			QAbstractItemModel* model_;
 			UserSearchDelegate* delegate_;
 			QtUserSearchFirstPage* firstPage_;
 			QtUserSearchFieldsPage* fieldsPage_;
diff --git a/Swiften/Elements/Form.cpp b/Swiften/Elements/Form.cpp
index c34b868..cf9ecf6 100644
--- a/Swiften/Elements/Form.cpp
+++ b/Swiften/Elements/Form.cpp
@@ -24,5 +24,20 @@ FormField::ref Form::getField(const std::string& name) const {
 	return FormField::ref();
 }
 
+void Form::addReportedField(FormField::ref field) {
+	reportedFields_.push_back(field);
+}
+
+const std::vector<FormField::ref>& Form::getReportedFields() const {
+	return reportedFields_;
+}
+
+void Form::addItem(const Form::FormItem& item) {
+	items_.push_back(item);
+}
+
+const std::vector<Form::FormItem>& Form::getItems() const {
+	return items_;
+}
 
 }
diff --git a/Swiften/Elements/Form.h b/Swiften/Elements/Form.h
index 0d1ed63..2c6f963 100644
--- a/Swiften/Elements/Form.h
+++ b/Swiften/Elements/Form.h
@@ -22,6 +22,7 @@ namespace Swift {
 	class Form : public Payload {
 		public:
 			typedef boost::shared_ptr<Form> ref;
+			typedef std::vector<FormField::ref> FormItem;
 
 			enum Type { FormType, SubmitType, CancelType, ResultType };
 
@@ -46,8 +47,15 @@ namespace Swift {
 
 			FormField::ref getField(const std::string& name) const;
 
+			void addReportedField(FormField::ref field);
+			const std::vector<FormField::ref>& getReportedFields() const;
+
+			void addItem(const FormItem& item);
+			const std::vector<FormItem>& getItems() const;
 		private:
 			std::vector<boost::shared_ptr<FormField> > fields_;
+			std::vector<boost::shared_ptr<FormField> > reportedFields_;
+			std::vector<FormItem> items_;
 			std::string title_;
 			std::string instructions_;
 			Type type_;
diff --git a/Swiften/Parser/PayloadParsers/FormParser.cpp b/Swiften/Parser/PayloadParsers/FormParser.cpp
index 3905302..2df0a85 100644
--- a/Swiften/Parser/PayloadParsers/FormParser.cpp
+++ b/Swiften/Parser/PayloadParsers/FormParser.cpp
@@ -6,9 +6,11 @@
 
 #include <Swiften/Parser/PayloadParsers/FormParser.h>
 
+#include <Swiften/Base/foreach.h>
+
 namespace Swift {
 
-FormParser::FormParser() : level_(TopLevel) {
+FormParser::FormParser() : level_(TopLevel), parsingItem_(false), parsingReported_(false) {
 }
 
 void FormParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
@@ -35,32 +37,43 @@ void FormParser::handleStartElement(const std::string& element, const std::strin
 			currentText_.clear();
 		}
 		else if (element == "field") {
-			std::string type = attributes.getAttribute("type");
-			if (type == "boolean") {
+			std::string type;
+			FormField::ref correspondingReportedField;
+			if (!parsingItem_) {
+				type = attributes.getAttribute("type");
+			} else {
+				foreach(FormField::ref field, getPayloadInternal()->getReportedFields()) {
+					if (field->getName() == attributes.getAttribute("var")) {
+						correspondingReportedField = field;
+						break;
+					}
+				}
+			}
+			if (type == "boolean" || boost::dynamic_pointer_cast<BooleanFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = BooleanFormFieldParseHelper::create();
 			}
-			else if (type == "fixed") {
+			else if (type == "fixed" || boost::dynamic_pointer_cast<FixedFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = FixedFormFieldParseHelper::create();
 			}
-			else if (type == "hidden") {
+			else if (type == "hidden" || boost::dynamic_pointer_cast<HiddenFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = HiddenFormFieldParseHelper::create();
 			}
-			else if (type == "jid-multi") {
+			else if (type == "jid-multi" || boost::dynamic_pointer_cast<JIDMultiFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = JIDMultiFormFieldParseHelper::create();
 			}
-			else if (type == "jid-single") {
+			else if (type == "jid-single" || boost::dynamic_pointer_cast<JIDSingleFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = JIDSingleFormFieldParseHelper::create();
 			}
-			else if (type == "list-multi") {
+			else if (type == "list-multi" || boost::dynamic_pointer_cast<ListMultiFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = ListMultiFormFieldParseHelper::create();
 			}
-			else if (type == "list-single") {
+			else if (type == "list-single" || boost::dynamic_pointer_cast<ListSingleFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = ListSingleFormFieldParseHelper::create();
 			}
-			else if (type == "text-multi") {
+			else if (type == "text-multi" || boost::dynamic_pointer_cast<TextMultiFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = TextMultiFormFieldParseHelper::create();
 			}
-			else if (type == "text-private") {
+			else if (type == "text-private" || boost::dynamic_pointer_cast<TextPrivateFormField>(correspondingReportedField)) {
 				currentFieldParseHelper_ = TextPrivateFormFieldParseHelper::create();
 			}
 			else /*if (type == "text-single") || undefined */ {
@@ -71,6 +84,14 @@ void FormParser::handleStartElement(const std::string& element, const std::strin
 				currentFieldParseHelper_->getField()->setLabel(attributes.getAttribute("label"));
 			}
 		}
+		else if (element == "reported") {
+			parsingReported_ = true;
+			level_ = PayloadLevel - 1;
+		}
+		else if (element == "item") {
+			parsingItem_ = true;
+			level_ = PayloadLevel - 1;
+		}
 	}
 	else if (level_ == FieldLevel && currentFieldParseHelper_) {
 		currentText_.clear();
@@ -104,7 +125,13 @@ void FormParser::handleEndElement(const std::string& element, const std::string&
 		}
 		else if (element == "field") {
 			if (currentFieldParseHelper_) {
-				getPayloadInternal()->addField(currentFieldParseHelper_->getField());
+				if (parsingReported_) {
+					getPayloadInternal()->addReportedField(currentFieldParseHelper_->getField());
+				} else if (parsingItem_) {
+					currentFields_.push_back(currentFieldParseHelper_->getField());
+				} else {
+					getPayloadInternal()->addField(currentFieldParseHelper_->getField());
+				}
 				currentFieldParseHelper_.reset();
 			}
 		}
@@ -123,6 +150,16 @@ void FormParser::handleEndElement(const std::string& element, const std::string&
 			currentFieldParseHelper_->addValue(currentText_);
 		}
 	}
+	if (element == "reported") {
+		parsingReported_ = false;
+		level_++;
+	}
+	else if (element == "item") {
+		parsingItem_ = false;
+		level_++;
+		getPayloadInternal()->addItem(currentFields_);
+		currentFields_.clear();
+	}
 }
 
 void FormParser::handleCharacterData(const std::string& text) {
diff --git a/Swiften/Parser/PayloadParsers/FormParser.h b/Swiften/Parser/PayloadParsers/FormParser.h
index eae40a1..a3e2524 100644
--- a/Swiften/Parser/PayloadParsers/FormParser.h
+++ b/Swiften/Parser/PayloadParsers/FormParser.h
@@ -49,6 +49,7 @@ namespace Swift {
 			};
 			class JIDFieldParseHelper : public FieldParseHelper {
 					virtual void addValue(const std::string& s) {
+						getField()->addRawValue(s);
 						boost::dynamic_pointer_cast< GenericFormField<JID> >(getField())->setValue(JID(s));
 					}
 			};
@@ -106,5 +107,8 @@ namespace Swift {
 			std::string currentText_;
 			std::string currentOptionLabel_;
 			boost::shared_ptr<FieldParseHelper> currentFieldParseHelper_;
+			bool parsingItem_;
+			bool parsingReported_;
+			std::vector<FormField::ref> currentFields_;
 	};
 }
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp
index 86845be..c36fbeb 100644
--- a/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp
+++ b/Swiften/Parser/PayloadParsers/UnitTest/FormParserTest.cpp
@@ -16,6 +16,7 @@ class FormParserTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(FormParserTest);
 		CPPUNIT_TEST(testParse_FormInformation);
 		CPPUNIT_TEST(testParse);
+		CPPUNIT_TEST(testParse_FormItems);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -115,6 +116,72 @@ class FormParserTest : public CppUnit::TestFixture {
 
 			CPPUNIT_ASSERT_EQUAL(std::string("foo"), boost::dynamic_pointer_cast<TextSingleFormField>(payload->getFields()[9])->getValue());
 		}
+
+		void testParse_FormItems() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse(
+				"<x xmlns='jabber:x:data' type='result'>"
+					"<field type='hidden' var='FORM_TYPE'>"
+						"<value>jabber:iq:search</value>"
+					"</field>"
+					"<reported>"
+						"<field var='first' label='Given Name' type='text-single'/>"
+						"<field var='last' label='Family Name' type='text-single'/>"
+						"<field var='jid' label='Jabber ID' type='jid-single'/>"
+						"<field var='x-gender' label='Gender' type='list-single'/>"
+					"</reported>"
+					"<item>"
+						"<field var='first'><value>Benvolio</value></field>"
+						"<field var='last'><value>Montague</value></field>"
+						"<field var='jid'><value>benvolio@montague.net</value></field>"
+						"<field var='x-gender'><value>male</value></field>"
+					"</item>"
+					"<item>"
+						"<field var='first'><value>Romeo</value></field>"
+						"<field var='last'><value>Montague</value></field>"
+						"<field var='jid'><value>romeo@montague.net</value></field>"
+						"<field var='x-gender'><value>male</value></field>"
+					"</item>"
+				"</x>"));
+
+			Form* dataForm = dynamic_cast<Form*>(parser.getPayload().get());
+			CPPUNIT_ASSERT(dataForm);
+
+			Form::FormItem reported = dataForm->getReportedFields();
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), reported.size());
+
+			std::vector<Form::FormItem> items = dataForm->getItems();
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size());
+
+			Form::FormItem item = items[0];
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), item.size());
+
+			CPPUNIT_ASSERT_EQUAL(std::string("Benvolio"), item[0]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName());
+			JIDSingleFormField::ref jidField = boost::dynamic_pointer_cast<JIDSingleFormField>(item[2]);
+			CPPUNIT_ASSERT(jidField);
+			CPPUNIT_ASSERT_EQUAL(JID("benvolio@montague.net"), jidField->getValue());
+			CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName());
+
+			item = items[1];
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), item.size());
+
+			CPPUNIT_ASSERT_EQUAL(std::string("Romeo"), item[0]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName());
+			jidField = boost::dynamic_pointer_cast<JIDSingleFormField>(item[2]);
+			CPPUNIT_ASSERT(jidField);
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.net"), jidField->getValue());
+			CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName());
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(FormParserTest);
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp
index c07cd7f..ef48ced 100644
--- a/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp
+++ b/Swiften/Parser/PayloadParsers/UnitTest/SearchPayloadParserTest.cpp
@@ -16,6 +16,8 @@ class SearchPayloadParserTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(SearchPayloadParserTest);
 		CPPUNIT_TEST(testParse_FormRequestResponse);
 		CPPUNIT_TEST(testParse_Results);
+		CPPUNIT_TEST(testParse_FormRequestResponse_XDATA);
+		CPPUNIT_TEST(testParse_Results_XDATA);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -66,6 +68,114 @@ class SearchPayloadParserTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT_EQUAL(std::string("juliet@shakespeare.lit"), payload->getItems()[0].email);
 			CPPUNIT_ASSERT_EQUAL(JID("tybalt@shakespeare.lit"), payload->getItems()[1].jid);
 		}
+
+		void testParse_FormRequestResponse_XDATA() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse(
+							"<query xmlns='jabber:iq:search'>"
+								"<instructions>"
+									"Use the enclosed form to search. If your Jabber client does not"
+									" support Data Forms, visit http://shakespeare.lit/"
+								"</instructions>"
+								"<x xmlns='jabber:x:data' type='form'>"
+									"<title>User Directory Search</title>"
+									"<instructions>"
+										"Please provide the following information"
+										" to search for Shakespearean characters."
+									"</instructions>"
+									"<field type='hidden'"
+										" var='FORM_TYPE'>"
+										"<value>jabber:iq:search</value>"
+									"</field>"
+									"<field type='text-single'"
+									" label='Given Name'"
+									" var='first'/>"
+									"<field type='text-single'"
+									" label='Family Name'"
+									" var='last'/>"
+									"<field type='list-single'"
+									" label='Gender'"
+									" var='x-gender'>"
+										"<option label='Male'><value>male</value></option>"
+										"<option label='Female'><value>female</value></option>"
+									"</field>"
+								"</x>"
+							"</query>"
+				));
+
+			SearchPayload::ref payload = parser.getPayload<SearchPayload>();
+			CPPUNIT_ASSERT_EQUAL(std::string("Use the enclosed form to search. If your Jabber client does not"
+											 " support Data Forms, visit http://shakespeare.lit/"), *payload->getInstructions());
+			CPPUNIT_ASSERT(payload->getForm());
+			CPPUNIT_ASSERT_EQUAL(std::string("Please provide the following information"
+											 " to search for Shakespearean characters."), payload->getForm()->getInstructions());
+		}
+
+		void testParse_Results_XDATA() {
+			PayloadsParserTester parser;
+
+			CPPUNIT_ASSERT(parser.parse("<query xmlns='jabber:iq:search'>"
+										"	<x xmlns='jabber:x:data' type='result'>"
+										"		<field type='hidden' var='FORM_TYPE'>"
+										"			<value>jabber:iq:search</value>"
+										"		</field>"
+										"		<reported>"
+										"			<field var='first' label='Given Name' type='text-single'/>"
+										"			<field var='last' label='Family Name' type='text-single'/>"
+										"			<field var='jid' label='Jabber ID' type='jid-single'/>"
+										"			<field var='x-gender' label='Gender' type='list-single'/>"
+										"		</reported>"
+										"		<item>"
+										"			<field var='first'><value>Benvolio</value></field>"
+										"			<field var='last'><value>Montague</value></field>"
+										"			<field var='jid'><value>benvolio@montague.net</value></field>"
+										"			<field var='x-gender'><value>male</value></field>"
+										"		</item>"
+										"		<item>"
+										"			<field var='first'><value>Romeo</value></field>"
+										"			<field var='last'><value>Montague</value></field>"
+										"			<field var='jid'><value>romeo@montague.net</value></field>"
+										"			<field var='x-gender'><value>male</value></field>"
+										"		</item>"
+										"	</x>"
+										"</query>"));
+			SearchPayload::ref payload = parser.getPayload<SearchPayload>();
+			CPPUNIT_ASSERT(payload);
+
+			Form::ref dataForm = payload->getForm();
+			CPPUNIT_ASSERT(dataForm);
+
+			Form::FormItem reported = dataForm->getReportedFields();
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), reported.size());
+
+			std::vector<Form::FormItem> items = dataForm->getItems();
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), items.size());
+
+			Form::FormItem item = items[0];
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), item.size());
+
+			CPPUNIT_ASSERT_EQUAL(std::string("Benvolio"), item[0]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("benvolio@montague.net"), item[2]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName());
+
+			item = items[1];
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), item.size());
+
+			CPPUNIT_ASSERT_EQUAL(std::string("Romeo"), item[0]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("first"), item[0]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("Montague"), item[1]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("last"), item[1]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("romeo@montague.net"), item[2]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("jid"), item[2]->getName());
+			CPPUNIT_ASSERT_EQUAL(std::string("male"), item[3]->getRawValues()[0]);
+			CPPUNIT_ASSERT_EQUAL(std::string("x-gender"), item[3]->getName());
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(SearchPayloadParserTest);
diff --git a/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp b/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp
index 7a6bb79..15c4f32 100644
--- a/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FormSerializer.cpp
@@ -52,12 +52,27 @@ std::string FormSerializer::serializePayload(boost::shared_ptr<Form> form)  cons
 		multiLineify(form->getInstructions(), "instructions", formElement);
 	}
 	foreach(boost::shared_ptr<FormField> field, form->getFields()) {
-		formElement->addNode(fieldToXML(field));
+		formElement->addNode(fieldToXML(field, true));
 	}
+	if (!form->getReportedFields().empty()) {
+		boost::shared_ptr<XMLElement> reportedElement(new XMLElement("reported"));
+		foreach(FormField::ref field, form->getReportedFields()) {
+			reportedElement->addNode(fieldToXML(field, true));
+		}
+		formElement->addNode(reportedElement);
+	}
+	foreach(Form::FormItem item, form->getItems()) {
+		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
+		foreach(FormField::ref field, item) {
+			itemElement->addNode(fieldToXML(field, false));
+		}
+		formElement->addNode(itemElement);
+	}
+
 	return formElement->serialize();
 }
 
-boost::shared_ptr<XMLElement> FormSerializer::fieldToXML(boost::shared_ptr<FormField> field) const {
+boost::shared_ptr<XMLElement> FormSerializer::fieldToXML(boost::shared_ptr<FormField> field, bool withTypeAttribute) const {
 	boost::shared_ptr<XMLElement> fieldElement(new XMLElement("field"));
 	if (!field->getName().empty()) {
 		fieldElement->setAttribute("var", field->getName());
@@ -115,7 +130,7 @@ boost::shared_ptr<XMLElement> FormSerializer::fieldToXML(boost::shared_ptr<FormF
 		fieldType = "jid-single";
 		boost::shared_ptr<XMLElement> valueElement(new XMLElement("value"));
 		valueElement->addNode(XMLTextNode::create(boost::dynamic_pointer_cast<JIDSingleFormField>(field)->getValue().toString()));
-		fieldElement->addNode(valueElement);
+		if ( boost::dynamic_pointer_cast<JIDSingleFormField>(field)->getValue().isValid()) fieldElement->addNode(valueElement);
 	}
 	else if (boost::dynamic_pointer_cast<ListMultiFormField>(field)) {
 		fieldType = "list-multi";
@@ -133,7 +148,7 @@ boost::shared_ptr<XMLElement> FormSerializer::fieldToXML(boost::shared_ptr<FormF
 	else {
 		assert(false);
 	}
-	if (!fieldType.empty()) {
+	if (!fieldType.empty() && withTypeAttribute) {
 		fieldElement->setAttribute("type", fieldType);
 	}
 
diff --git a/Swiften/Serializer/PayloadSerializers/FormSerializer.h b/Swiften/Serializer/PayloadSerializers/FormSerializer.h
index 43db9e8..d10f649 100644
--- a/Swiften/Serializer/PayloadSerializers/FormSerializer.h
+++ b/Swiften/Serializer/PayloadSerializers/FormSerializer.h
@@ -19,7 +19,7 @@ namespace Swift {
 			virtual std::string serializePayload(boost::shared_ptr<Form>)  const;
 
 		private:
-			boost::shared_ptr<XMLElement> fieldToXML(boost::shared_ptr<FormField> field) const;
+			boost::shared_ptr<XMLElement> fieldToXML(boost::shared_ptr<FormField> field, bool withTypeAttribute) const;
 			void multiLineify(const std::string& text, const std::string& elementName, boost::shared_ptr<XMLElement> parent) const;
 	};
 }
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp
index 29e7e59..4608522 100644
--- a/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/FormSerializerTest.cpp
@@ -8,13 +8,13 @@
 #include <cppunit/extensions/TestFactoryRegistry.h>
 
 #include <Swiften/Serializer/PayloadSerializers/FormSerializer.h>
-
 using namespace Swift;
 
 class FormSerializerTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(FormSerializerTest);
 		CPPUNIT_TEST(testSerializeFormInformation);
 		CPPUNIT_TEST(testSerializeFields);
+		CPPUNIT_TEST(testSerializeFormItems);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -134,6 +134,99 @@ class FormSerializerTest : public CppUnit::TestFixture {
 						"</field>"
 					"</x>"), testling.serialize(form));
 		}
+
+		void testSerializeFormItems() {
+			FormSerializer testling;
+			boost::shared_ptr<Form> form(new Form(Form::ResultType));
+
+
+			FormField::ref field = HiddenFormField::create("jabber:iq:search");
+			field->setName("FORM_TYPE");
+			form->addField(field);
+
+			// reported fields
+			field = TextSingleFormField::create();
+			field->setName("first");
+			field->setLabel("Given Name");
+			form->addReportedField(field);
+
+			field = TextSingleFormField::create();
+			field->setName("last");
+			field->setLabel("Family Name");
+			form->addReportedField(field);
+
+			field = JIDSingleFormField::create();
+			field->setName("jid");
+			field->setLabel("Jabber ID");
+			form->addReportedField(field);
+
+			field = ListSingleFormField::create();
+			field->setName("x-gender");
+			field->setLabel("Gender");
+			form->addReportedField(field);
+
+			Form::FormItem firstItem;
+			field = TextSingleFormField::create("Benvolio");
+			field->setName("first");
+			firstItem.push_back(field);
+
+			field = TextSingleFormField::create("Montague");
+			field->setName("last");
+			firstItem.push_back(field);
+
+			field = JIDSingleFormField::create(JID("benvolio@montague.net"));
+			field->setName("jid");
+			firstItem.push_back(field);
+
+			field = ListSingleFormField::create("male");
+			field->setName("x-gender");
+			firstItem.push_back(field);
+
+			Form::FormItem secondItem;
+			field = TextSingleFormField::create("Romeo");
+			field->setName("first");
+			secondItem.push_back(field);
+
+			field = TextSingleFormField::create("Montague");
+			field->setName("last");
+			secondItem.push_back(field);
+
+			field = JIDSingleFormField::create(JID("romeo@montague.net"));
+			field->setName("jid");
+			secondItem.push_back(field);
+
+			field = ListSingleFormField::create("male");
+			field->setName("x-gender");
+			secondItem.push_back(field);
+
+			form->addItem(firstItem);
+			form->addItem(secondItem);
+
+			CPPUNIT_ASSERT_EQUAL(std::string(
+				"<x type=\"result\" xmlns=\"jabber:x:data\">"
+					"<field type=\"hidden\" var=\"FORM_TYPE\">"
+						"<value>jabber:iq:search</value>"
+					"</field>"
+					"<reported>"
+						"<field label=\"Given Name\" type=\"text-single\" var=\"first\"/>"
+						"<field label=\"Family Name\" type=\"text-single\" var=\"last\"/>"
+						"<field label=\"Jabber ID\" type=\"jid-single\" var=\"jid\"/>"
+						"<field label=\"Gender\" type=\"list-single\" var=\"x-gender\"/>"
+					"</reported>"
+					"<item>"
+						"<field var=\"first\"><value>Benvolio</value></field>"
+						"<field var=\"last\"><value>Montague</value></field>"
+						"<field var=\"jid\"><value>benvolio@montague.net</value></field>"
+						"<field var=\"x-gender\"><value>male</value></field>"
+					"</item>"
+					"<item>"
+						"<field var=\"first\"><value>Romeo</value></field>"
+						"<field var=\"last\"><value>Montague</value></field>"
+						"<field var=\"jid\"><value>romeo@montague.net</value></field>"
+						"<field var=\"x-gender\"><value>male</value></field>"
+					"</item>"
+				"</x>"), testling.serialize(form));
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(FormSerializerTest);
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp
index d0dcbef..42bff72 100644
--- a/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/SearchPayloadSerializerTest.cpp
@@ -15,6 +15,7 @@ class SearchPayloadSerializerTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE(SearchPayloadSerializerTest);
 		CPPUNIT_TEST(testSerialize_Request);
 		CPPUNIT_TEST(testSerialize_Items);
+		CPPUNIT_TEST(testSerialize_DataForm);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -70,6 +71,105 @@ class SearchPayloadSerializerTest : public CppUnit::TestFixture {
 					"</query>"
 				), testling.serialize(payload));
 		}
+
+		void testSerialize_DataForm() {
+			SearchPayloadSerializer testling;
+
+			SearchPayload::ref payload(new SearchPayload());
+			boost::shared_ptr<Form> form(new Form(Form::ResultType));
+
+			FormField::ref field = HiddenFormField::create("jabber:iq:search");
+			field->setName("FORM_TYPE");
+			form->addField(field);
+
+			// reported fields
+			field = TextSingleFormField::create();
+			field->setName("first");
+			field->setLabel("Given Name");
+			form->addReportedField(field);
+
+			field = TextSingleFormField::create();
+			field->setName("last");
+			field->setLabel("Family Name");
+			form->addReportedField(field);
+
+			field = JIDSingleFormField::create();
+			field->setName("jid");
+			field->setLabel("Jabber ID");
+			form->addReportedField(field);
+
+			field = ListSingleFormField::create();
+			field->setName("x-gender");
+			field->setLabel("Gender");
+			form->addReportedField(field);
+
+			Form::FormItem firstItem;
+			field = TextSingleFormField::create("Benvolio");
+			field->setName("first");
+			firstItem.push_back(field);
+
+			field = TextSingleFormField::create("Montague");
+			field->setName("last");
+			firstItem.push_back(field);
+
+			field = TextSingleFormField::create("benvolio@montague.net");
+			field->setName("jid");
+			firstItem.push_back(field);
+
+			field = ListSingleFormField::create("male");
+			field->setName("x-gender");
+			firstItem.push_back(field);
+
+			Form::FormItem secondItem;
+			field = TextSingleFormField::create("Romeo");
+			field->setName("first");
+			secondItem.push_back(field);
+
+			field = TextSingleFormField::create("Montague");
+			field->setName("last");
+			secondItem.push_back(field);
+
+			field = TextSingleFormField::create("romeo@montague.net");
+			field->setName("jid");
+			secondItem.push_back(field);
+
+			field = ListSingleFormField::create("male");
+			field->setName("x-gender");
+			secondItem.push_back(field);
+
+			form->addItem(firstItem);
+			form->addItem(secondItem);
+
+			payload->setForm(form);
+
+			CPPUNIT_ASSERT_EQUAL(std::string(
+				"<query xmlns=\"jabber:iq:search\">"
+					"<x type=\"result\" xmlns=\"jabber:x:data\">"
+						"<field type=\"hidden\" var=\"FORM_TYPE\">"
+							"<value>jabber:iq:search</value>"
+						"</field>"
+						"<reported>"
+							"<field label=\"Given Name\" type=\"text-single\" var=\"first\"/>"
+							"<field label=\"Family Name\" type=\"text-single\" var=\"last\"/>"
+							"<field label=\"Jabber ID\" type=\"jid-single\" var=\"jid\"/>"
+							"<field label=\"Gender\" type=\"list-single\" var=\"x-gender\"/>"
+						"</reported>"
+						"<item>"
+							"<field var=\"first\"><value>Benvolio</value></field>"
+							"<field var=\"last\"><value>Montague</value></field>"
+							"<field var=\"jid\"><value>benvolio@montague.net</value></field>"
+							"<field var=\"x-gender\"><value>male</value></field>"
+						"</item>"
+						"<item>"
+							"<field var=\"first\"><value>Romeo</value></field>"
+							"<field var=\"last\"><value>Montague</value></field>"
+							"<field var=\"jid\"><value>romeo@montague.net</value></field>"
+							"<field var=\"x-gender\"><value>male</value></field>"
+						"</item>"
+					"</x>"
+				"</query>"), testling.serialize(payload));
+
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(SearchPayloadSerializerTest);
-- 
cgit v0.10.2-6-g49f6