From 5cf50d46aed4d2dac333e5e3b3bc2f57f7a1b835 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Wed, 29 Oct 2014 15:02:39 +0100
Subject: Prevent user from adding contacts twice to a roster.

This removes roster JIDs from the suggesting in the 'Add User'-dialog. In
addition, an indication is added when a manually entered JID is invalid or
already on the roster.

Test-Information:

Tested scenarios with recent JIDs and JIDs from the roster. Tested that 'Start
Chat'-dialog suggestions still work.

Change-Id: I1ff51637adb4224184b78a1af9090a04b1e18fff

diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index f1849c9..2d3f1ae 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -18,6 +18,7 @@
 #include <Swiften/Presence/PresenceOracle.h>
 #include <Swiften/Avatars/AvatarManager.h>
 #include <Swift/Controllers/ContactEditController.h>
+#include <Swift/Controllers/Intl.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
@@ -194,6 +195,19 @@ void UserSearchController::handleNameSuggestionRequest(const JID &jid) {
 	}
 }
 
+void UserSearchController::handleJIDEditingFinished(const JID& jid) {
+	if (jid.isValid()) {
+		if (rosterController_->getItem(jid)) {
+			window_->setWarning(QT_TRANSLATE_NOOP("", "This contact is already on your contact list."));
+		} else {
+			window_->setWarning(boost::optional<std::string>());
+		}
+	}
+	else {
+		window_->setWarning(QT_TRANSLATE_NOOP("", "The address you have entered is invalid."));
+	}
+}
+
 void UserSearchController::handleContactSuggestionsRequested(std::string text) {
 	const std::vector<JID> existingJIDs = window_->getJIDs();
 	std::vector<Contact::ref> suggestions = contactSuggester_->getSuggestions(text, false);
@@ -207,6 +221,14 @@ void UserSearchController::handleContactSuggestionsRequested(std::string text) {
 				break;
 			}
 		}
+
+		// remove contact suggestions which are already on the contact list in add-contact-mode
+		if (type_ == AddContact) {
+			if (!found && !!rosterController_->getItem((*i)->jid)) {
+				found = true;
+			}
+		}
+
 		if (found) {
 			i = suggestions.erase(i);
 		} else {
@@ -307,6 +329,7 @@ void UserSearchController::initializeUserWindow() {
 		window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1));
 		window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
 		window_->onJIDAddRequested.connect(boost::bind(&UserSearchController::handleJIDAddRequested, this, _1));
+		window_->onJIDEditFieldChanged.connect(boost::bind(&UserSearchController::handleJIDEditingFinished, this, _1));
 		window_->setSelectedService(JID(jid_.getDomain()));
 		window_->clear();
 	}
diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h
index d630580..b89bffd 100644
--- a/Swift/Controllers/Chat/UserSearchController.h
+++ b/Swift/Controllers/Chat/UserSearchController.h
@@ -69,6 +69,7 @@ namespace Swift {
 			void handlePresenceChanged(Presence::ref presence);
 			void handleJIDUpdateRequested(const std::vector<JID>& jids);
 			void handleJIDAddRequested(const std::vector<JID>& jids);
+			void handleJIDEditingFinished(const JID& jid);
 			Contact::ref convertJIDtoContact(const JID& jid);
 			void endDiscoWalker();
 			void initializeUserWindow();
diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h
index 9a095aa..224c0b5 100644
--- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h
+++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h
@@ -41,6 +41,7 @@ namespace Swift {
 			virtual void updateContacts(const std::vector<Contact::ref>& contacts) = 0;
 			virtual void addContacts(const std::vector<Contact::ref>& contacts) = 0;
 			virtual void setCanSupplyDescription(bool allowed) = 0;
+			virtual void setWarning(const boost::optional<std::string>& message) = 0;
 
 			virtual void show() = 0;
 
@@ -50,5 +51,6 @@ namespace Swift {
 			boost::signal<void (const std::string&)> onContactSuggestionsRequested;
 			boost::signal<void (const std::vector<JID>&)> onJIDUpdateRequested;
 			boost::signal<void (const std::vector<JID>&)> onJIDAddRequested;
+			boost::signal<void (const JID&)> onJIDEditFieldChanged;
 	};
 }
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp
index af53a26..f36ff2b 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp
@@ -18,17 +18,22 @@ QtUserSearchFirstPage::QtUserSearchFirstPage(UserSearchWindow::Type type, const
 	setSubTitle(QString(tr("%1. If you know their address you can enter it directly, or you can search for them.")).arg(type == UserSearchWindow::AddContact ? tr("Add another user to your contact list") : tr("Chat to another user")));
 	jid_ = new QtSuggestingJIDInput(this, settings);
 	horizontalLayout_2->addWidget(jid_);
+	jidWarning_ = new QLabel(this);
+	jidWarning_->setPixmap(QPixmap(":icons/warn.png"));
+	jidWarning_->hide();
+	horizontalLayout_2->addWidget(jidWarning_);
 	setTabOrder(byJID_, jid_);
 	setTabOrder(jid_, byLocalSearch_);
 	setTabOrder(byLocalSearch_, byRemoteSearch_);
 	connect(jid_, SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck()));
+	connect(jid_, SIGNAL(editingDone()), this, SLOT(emitCompletenessCheck()));
 	connect(service_->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck()));
 }
 
 bool QtUserSearchFirstPage::isComplete() const {
 	bool complete = false;
 	if (byJID_->isChecked()) {
-		complete = JID(Q2PSTRING(jid_->text().trimmed())).isValid();
+		complete = JID(Q2PSTRING(jid_->text().trimmed())).isValid() && jidWarning_->toolTip().isEmpty();
 	} else if (byLocalSearch_->isChecked()) {
 		complete = true;
 	} else if (byRemoteSearch_->isChecked()) {
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h
index d7487b0..32a7e70 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h
@@ -28,5 +28,6 @@ namespace Swift {
 			void emitCompletenessCheck();
 		public:
 			QtSuggestingJIDInput* jid_;
+			QLabel* jidWarning_;
 	};
 }
diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
index babe115..b197483 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
@@ -167,6 +167,20 @@ void QtUserSearchWindow::addContact() {
 	}
 }
 
+void QtUserSearchWindow::setWarning(const boost::optional<std::string>& message) {
+	if (message) {
+		firstPage_->jidWarning_->setToolTip(P2QSTRING((*message)));
+		firstPage_->jidWarning_->setAccessibleDescription(P2QSTRING((*message)));
+		firstPage_->jidWarning_->show();
+	}
+	else {
+		firstPage_->jidWarning_->setToolTip("");
+		firstPage_->jidWarning_->setAccessibleDescription("");
+		firstPage_->jidWarning_->hide();
+	}
+	firstPage_->emitCompletenessCheck();
+}
+
 int QtUserSearchWindow::nextId() const {
 	if (type_ == AddContact) {
 		switch (currentId()) {
@@ -466,6 +480,10 @@ void QtUserSearchWindow::setSelectedService(const JID& jid) {
 	myServer_ = jid;
 }
 
+void QtUserSearchWindow::handleJIDEditingDone() {
+	onJIDEditFieldChanged(JID(Q2PSTRING(firstPage_->jid_->text())));
+}
+
 void QtUserSearchWindow::setFirstPage(QString title) {
 	if (page(1) != 0) {
 		removePage(1);
@@ -473,6 +491,7 @@ void QtUserSearchWindow::setFirstPage(QString title) {
 	if (type_ == AddContact) {
 		firstPage_ = new QtUserSearchFirstPage(type_, title.isEmpty() ? firstPage_->title() : title, settings_);
 		connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString)));
+		connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleJIDEditingDone()), Qt::UniqueConnection);
 		firstPage_->jid_->onUserSelected.connect(boost::bind(&QtUserSearchWindow::handleOnSearchedJIDSelected, this, _1));
 		connect(firstPage_->byJID_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
 		connect(firstPage_->byLocalSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
index 255b8fe..d1d29f2 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
@@ -52,6 +52,7 @@ namespace Swift {
 			virtual void updateContacts(const std::vector<Contact::ref> &contacts);
 			virtual void addContacts(const std::vector<Contact::ref>& contacts);
 			virtual void setCanSupplyDescription(bool allowed);
+			virtual void setWarning(const boost::optional<std::string>& message);
 
 		protected:
 			virtual int nextId() const;
@@ -65,6 +66,7 @@ namespace Swift {
 			void handleAddViaSearch();
 			void handleListChanged(std::vector<Contact::ref> list);
 			void handleJIDsAdded(std::vector<JID> jids);
+			void handleJIDEditingDone();
 
 		private:
 			void setFirstPage(QString title = "");
-- 
cgit v0.10.2-6-g49f6