diff options
| author | Kevin Smith <git@kismith.co.uk> | 2010-11-18 12:09:08 (GMT) | 
|---|---|---|
| committer | Kevin Smith <git@kismith.co.uk> | 2010-12-22 20:19:07 (GMT) | 
| commit | 9f04a7ec3429303118f12607703b877d8ba43888 (patch) | |
| tree | 3e868395fa3c4f9cbbf84614094bb0251b425c15 | |
| parent | 04b99ce8430109d0467c29c85cb6bacb85c54c44 (diff) | |
| download | swift-9f04a7ec3429303118f12607703b877d8ba43888.zip swift-9f04a7ec3429303118f12607703b877d8ba43888.tar.bz2 | |
Basic User Search support, and Find Rooms cleanup.
Adds a throbber to the MUC search, turns the Add Contact dialog into something searchy, adds the option to open chats to arbitrary JIDs.
Resolves: #614
Resolves: #695
Resolves: #436
Release-Notes: On servers that support it, users can now perform searches for contacts to add or chat to.
32 files changed, 1269 insertions, 104 deletions
| diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index 7368dbb..c254e51 100644 --- a/Swift/Controllers/Chat/MUCSearchController.cpp +++ b/Swift/Controllers/Chat/MUCSearchController.cpp @@ -11,22 +11,26 @@  #include <boost/bind.hpp>  #include <boost/shared_ptr.hpp> -#include "Swiften/Disco/GetDiscoInfoRequest.h" -#include "Swiften/Disco/GetDiscoItemsRequest.h" +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindow.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindow.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> +#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swiften/Client/NickResolver.h>  namespace Swift {  static const String SEARCHED_SERVICES = "searchedServices"; -MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings) : jid_(jid) { +MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings, NickResolver *nickResolver) : jid_(jid) {  	iqRouter_ = iqRouter;  	settings_ = settings;  	uiEventStream_ = uiEventStream; +	nickResolver_ = nickResolver; +	itemsInProgress_ = 0;  	uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&MUCSearchController::handleUIEvent, this, _1));  	window_ = NULL;  	factory_ = factory; @@ -34,6 +38,9 @@ MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventS  }  MUCSearchController::~MUCSearchController() { +	foreach (DiscoServiceWalker* walker, walksInProgress_) { +		delete walker; +	}  	delete window_;  } @@ -42,12 +49,12 @@ void MUCSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) {  	if (searchEvent) {  		if (!window_) {  			window_ = factory_->createMUCSearchWindow(uiEventStream_); -			window_->onAddService.connect(boost::bind(&MUCSearchController::handleAddService, this, _1, true)); +			window_->onAddService.connect(boost::bind(&MUCSearchController::handleAddService, this, _1));  			window_->addSavedServices(savedServices_); -			handleAddService(JID(jid_.getDomain()), true); +			handleAddService(JID(jid_.getDomain()));  		}  		window_->setMUC(""); -		window_->setNick(jid_.getNode()); +		window_->setNick(nickResolver_->jidToNick(jid_));  		window_->show();  		return;  	} @@ -79,76 +86,66 @@ void MUCSearchController::addAndSaveServices(const JID& jid) {  	window_->addSavedServices(savedServices_);  } -void MUCSearchController::handleAddService(const JID& jid, bool userTriggered) { -	if (userTriggered) { -		addAndSaveServices(jid); -	} -	if (std::find(services_.begin(), services_.end(), jid) != services_.end()) { -		if (!userTriggered) { -			/* No infinite recursion. (Some buggy servers do infinitely deep disco of themselves)*/ -			return; -		} -	} else if (userTriggered) { -		services_.push_back(jid); -		serviceDetails_[jid].setComplete(false); -		refreshView(); -	} +void MUCSearchController::handleAddService(const JID& jid) {  	if (!jid.isValid()) {  		//Set Window to say error this isn't valid  		return;  	} -	GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_); -	discoInfoRequest->onResponse.connect(boost::bind(&MUCSearchController::handleDiscoInfoResponse, this, _1, _2, jid)); -	discoInfoRequest->send(); -} - -void MUCSearchController::removeService(const JID& jid) { -	serviceDetails_.erase(jid); -	services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end());  +	addAndSaveServices(jid); +	services_.push_back(jid); +	serviceDetails_[jid].setComplete(false); +	window_->setSearchInProgress(true);  	refreshView(); +	DiscoServiceWalker* walker = new DiscoServiceWalker(jid, iqRouter_); +	walker->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); +	walker->onWalkComplete.connect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this, walker)); +	walksInProgress_.push_back(walker); +	walker->beginWalk();  } -void MUCSearchController::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, const JID& jid) { -	if (error) { -		handleDiscoError(jid, error); -		return; -	} -	GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); -	bool mucService = false; -	bool couldContainServices = false; +void MUCSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { +	bool isMUC;  	String name;  	foreach (DiscoInfo::Identity identity, info->getIdentities()) { -		if ((identity.getCategory() == "directory"  -			&& identity.getType() == "chatroom") -			|| (identity.getCategory() == "conference"  -			&& identity.getType() == "text")) { -			mucService = true; -			name = identity.getName(); -		} -		if (identity.getCategory() == "server") { -			couldContainServices = true; -			name = identity.getName(); -		} +			if ((identity.getCategory() == "directory" +				&& identity.getType() == "chatroom") +				|| (identity.getCategory() == "conference" +				&& identity.getType() == "text")) { +				isMUC = true; +				name = identity.getName(); +			}  	} -	services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); /* Bring it back to the end on a refresh */ -	services_.push_back(jid); -	serviceDetails_[jid].setName(name); -	serviceDetails_[jid].setJID(jid); -	serviceDetails_[jid].setComplete(false); - -	if (mucService) { +	if (isMUC) { +		services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); /* Bring it back to the end on a refresh */ +		services_.push_back(jid); +		serviceDetails_[jid].setName(name); +		serviceDetails_[jid].setJID(jid); +		serviceDetails_[jid].setComplete(false); +		itemsInProgress_++; +		GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_);  		discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid)); -	} else if (couldContainServices) { -		discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleServerItemsResponse, this, _1, _2, jid)); +		discoItemsRequest->send();  	} else {  		removeService(jid); -		return;  	} -	discoItemsRequest->send(); +	refreshView(); +} + +void MUCSearchController::handleDiscoWalkFinished(DiscoServiceWalker* walker) { +	walksInProgress_.erase(std::remove(walksInProgress_.begin(), walksInProgress_.end(), walker), walksInProgress_.end()); +	updateInProgressness(); +	delete walker; +} + +void MUCSearchController::removeService(const JID& jid) { +	serviceDetails_.erase(jid); +	services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end());   	refreshView();  }  void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { +	itemsInProgress_--; +	updateInProgressness();  	if (error) {  		handleDiscoError(jid, error);  		return; @@ -161,25 +158,6 @@ void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems>  	refreshView();  } -void MUCSearchController::handleServerItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { -	if (error) { -		handleDiscoError(jid, error); -		return; -	} -	if (jid.isValid()) { -		removeService(jid); -	} -	foreach (DiscoItems::Item item, items->getItems()) { -		if (item.getNode().isEmpty()) { -			/* Don't look at noded items. It's possible that this will exclude some services, -			 * but I've never seen one in the wild, and it's an easy fix for not looping. -			 */ -			handleAddService(item.getJID()); -		} -	} -	refreshView(); -} -  void MUCSearchController::handleDiscoError(const JID& jid, ErrorPayload::ref error) {  	serviceDetails_[jid].setComplete(true);  	serviceDetails_[jid].setError(error->getText()); @@ -192,4 +170,8 @@ void MUCSearchController::refreshView() {  	}  } +void MUCSearchController::updateInProgressness() { +	window_->setSearchInProgress(walksInProgress_.size() + itemsInProgress_ > 0); +} +  } diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h index f09a801..6caee54 100644 --- a/Swift/Controllers/Chat/MUCSearchController.h +++ b/Swift/Controllers/Chat/MUCSearchController.h @@ -27,6 +27,8 @@ namespace Swift {  	class MUCSearchWindow;  	class MUCSearchWindowFactory;  	class IQRouter; +	class DiscoServiceWalker; +	class NickResolver;  	class MUCService {  		public: @@ -86,28 +88,32 @@ namespace Swift {  	class MUCSearchController {  		public: -			MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings); +			MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings, NickResolver* nickResolver);  			~MUCSearchController();  		private:  			void handleUIEvent(boost::shared_ptr<UIEvent> event); -			void handleAddService(const JID& jid, bool userTriggered=false); -			void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, const JID& jid); +			void handleAddService(const JID& jid);  			void handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid); -			void handleServerItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid);  			void handleDiscoError(const JID& jid, ErrorPayload::ref error); +			void handleDiscoServiceFound(const JID&, boost::shared_ptr<DiscoInfo>); +			void handleDiscoWalkFinished(DiscoServiceWalker* walker);  			void removeService(const JID& jid);  			void refreshView();  			void loadServices();  			void addAndSaveServices(const JID& jid); +			void updateInProgressness();  			UIEventStream* uiEventStream_;  			MUCSearchWindow* window_;  			MUCSearchWindowFactory* factory_;  			SettingsProvider* settings_; +			NickResolver* nickResolver_;  			boost::bsignals::scoped_connection uiEventConnection_;  			std::vector<JID> services_;  			std::vector<JID> savedServices_;  			std::map<JID, MUCService> serviceDetails_; +			std::vector<DiscoServiceWalker*> walksInProgress_;  			IQRouter* iqRouter_;  			JID jid_; +			int itemsInProgress_;  	};  } diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp new file mode 100644 index 0000000..c3e40c8 --- /dev/null +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/Controllers/Chat/UserSearchController.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> + +#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> + +namespace Swift { +UserSearchController::UserSearchController(const JID& jid, UIEventStream* uiEventStream, UserSearchWindowFactory* factory, IQRouter* iqRouter) : jid_(jid) { +	iqRouter_ = iqRouter; +	uiEventStream_ = uiEventStream; +	uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); +	window_ = NULL; +	factory_ = factory; +	discoWalker_ = NULL; +} + +UserSearchController::~UserSearchController() { +	delete window_; +	delete discoWalker_; +} + +void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) { +	boost::shared_ptr<RequestUserSearchUIEvent> searchEvent = boost::dynamic_pointer_cast<RequestUserSearchUIEvent>(event); +	if (searchEvent) { +		if (!window_) { +			window_ = factory_->createUserSearchWindow(uiEventStream_); +			window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); +			window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); +			window_->setSelectedService(JID(jid_.getDomain())); +			window_->clear(); +		} +		window_->show(); +		return; +	} +} + +void UserSearchController::handleFormRequested(const JID& service) { +	window_->setSearchError(false); +	window_->setServerSupportsSearch(true); +	//Abort a previous search if is active +	delete discoWalker_; +	discoWalker_ = new DiscoServiceWalker(service, iqRouter_); +	discoWalker_->onServiceFound.connect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2, discoWalker_)); +	discoWalker_->onWalkComplete.connect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this, discoWalker_)); +	discoWalker_->beginWalk(); +} + +void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info, DiscoServiceWalker* walker) { +	bool isUserDirectory = false; +	bool supports55 = false; +	foreach (DiscoInfo::Identity identity, info->getIdentities()) { +		if ((identity.getCategory() == "directory" +			&& identity.getType() == "user")) { +			isUserDirectory = true; +		} +	} +	std::vector<String> features = info->getFeatures(); +	supports55 = std::find(features.begin(), features.end(), DiscoInfo::JabberSearchFeature) != features.end(); +	if (/*isUserDirectory && */supports55) { //FIXME: once M-Link correctly advertises directoryness. +		/* Abort further searches.*/ +		delete discoWalker_; +		discoWalker_ = NULL; +		boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Get, jid, boost::shared_ptr<SearchPayload>(new SearchPayload()), iqRouter_)); +		searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleFormResponse, this, _1, _2, jid)); +		searchRequest->send(); +	} +} + +void UserSearchController::handleFormResponse(boost::shared_ptr<SearchPayload> fields, ErrorPayload::ref error, const JID& jid) { +	if (error || !fields) { +		window_->setServerSupportsSearch(false); +		return; +	} +	window_->setSearchFields(fields); +} + +void UserSearchController::handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid) { +	boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Set, jid, fields, iqRouter_)); +	searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleSearchResponse, this, _1, _2, jid)); +	searchRequest->send(); +} + +void UserSearchController::handleSearchResponse(boost::shared_ptr<SearchPayload> resultsPayload, ErrorPayload::ref error, const JID& jid) { +	if (error || !resultsPayload) { +		window_->setSearchError(true); +		return; +	} +	std::vector<UserSearchResult> results; +	foreach (SearchPayload::Item item, resultsPayload->getItems()) { +		JID jid(item.jid); +		std::map<String, 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); +} + +void UserSearchController::handleDiscoWalkFinished(DiscoServiceWalker* walker) { +	window_->setServerSupportsSearch(false); +	delete discoWalker_; +	discoWalker_ = NULL; +} + +} diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h new file mode 100644 index 0000000..3ba3352 --- /dev/null +++ b/Swift/Controllers/Chat/UserSearchController.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> +#include <vector> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/Elements/SearchPayload.h> +#include <Swiften/Base/String.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/Elements/ErrorPayload.h> + +namespace Swift { +	class UIEventStream; +	class UIEvent; +	class UserSearchWindow; +	class UserSearchWindowFactory; +	class IQRouter; +	class DiscoServiceWalker; + +	class UserSearchResult { +		public: +			UserSearchResult(const JID& jid, const std::map<String, String>& fields) : jid_(jid), fields_(fields) {} +			const JID& getJID() const {return jid_;} +			const std::map<String, String>& getFields() const {return fields_;} +		private: +			JID jid_; +			std::map<String, String> fields_; +	}; + +	class UserSearchController { +		public: +			UserSearchController(const JID& jid, UIEventStream* uiEventStream, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter); +			~UserSearchController(); +		private: +			void handleUIEvent(boost::shared_ptr<UIEvent> event); +			void handleFormRequested(const JID& service); +			void handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info, DiscoServiceWalker* walker); +			void handleDiscoWalkFinished(DiscoServiceWalker* walker); +			void handleFormResponse(boost::shared_ptr<SearchPayload> items, ErrorPayload::ref error, const JID& jid); +			void handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid); +			void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error, const JID& jid); +			UIEventStream* uiEventStream_; +			UserSearchWindow* window_; +			UserSearchWindowFactory* factory_; +			boost::bsignals::scoped_connection uiEventConnection_; +			IQRouter* iqRouter_; +			JID jid_; +			DiscoServiceWalker* discoWalker_; +	}; +} diff --git a/Swift/Controllers/DiscoServiceWalker.cpp b/Swift/Controllers/DiscoServiceWalker.cpp new file mode 100644 index 0000000..95ce23b --- /dev/null +++ b/Swift/Controllers/DiscoServiceWalker.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/DiscoServiceWalker.h> + +#include <boost/bind.hpp> + +#include "Swiften/Disco/GetDiscoInfoRequest.h" +#include "Swiften/Disco/GetDiscoItemsRequest.h" + +namespace Swift { + +DiscoServiceWalker::DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps) : service_(service), iqRouter_(iqRouter), maxSteps_(maxSteps) { + +} + +void DiscoServiceWalker::beginWalk() { +	assert(servicesBeingSearched_.size() == 0); +	walkNode(service_); +} + +void DiscoServiceWalker::walkNode(const JID& jid) { +	servicesBeingSearched_.push_back(jid); +	searchedServices_.push_back(jid); +	GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_); +	discoInfoRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, jid)); +	discoInfoRequest->send(); +} + +void DiscoServiceWalker::handleReceivedDiscoItem(const JID& item) { +	if (std::find(searchedServices_.begin(), searchedServices_.end(), item) != searchedServices_.end()) { +		/* Don't recurse infinitely */ +		return; +	} +	walkNode(item); +} + +void DiscoServiceWalker::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, const JID& jid) { +	if (error) { +		handleDiscoError(jid, error); +		return; +	} + +	bool couldContainServices = false; +	foreach (DiscoInfo::Identity identity, info->getIdentities()) { +		if (identity.getCategory() == "server") { +			couldContainServices = true; +		} +	} +	bool completed = false; +	if (couldContainServices) { +		GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); +		discoItemsRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, jid)); +		discoItemsRequest->send(); +	} else { +		completed = true; +	} +	onServiceFound(jid, info); +	if (completed) { +		markNodeCompleted(jid); +	} +} + +void DiscoServiceWalker::handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { +	if (error) { +		handleDiscoError(jid, error); +		return; +	} +	foreach (DiscoItems::Item item, items->getItems()) { +		if (item.getNode().isEmpty()) { +			/* Don't look at noded items. It's possible that this will exclude some services, +			 * but I've never seen one in the wild, and it's an easy fix for not looping. +			 */ +			handleReceivedDiscoItem(item.getJID()); +		} +	} +	markNodeCompleted(jid); +} + +void DiscoServiceWalker::handleDiscoError(const JID& jid, ErrorPayload::ref /*error*/) { +	markNodeCompleted(jid); +} + +void DiscoServiceWalker::markNodeCompleted(const JID& jid) { +	servicesBeingSearched_.erase(std::remove(servicesBeingSearched_.begin(), servicesBeingSearched_.end(), jid), servicesBeingSearched_.end()); +	/* All results are in */ +	if (servicesBeingSearched_.size() == 0) { +		onWalkComplete(); +	} +	/* Check if we're on a rampage */ +	if (searchedServices_.size() >= maxSteps_) { +		onWalkComplete(); +	} +} + +} diff --git a/Swift/Controllers/DiscoServiceWalker.h b/Swift/Controllers/DiscoServiceWalker.h new file mode 100644 index 0000000..5b2a47e --- /dev/null +++ b/Swift/Controllers/DiscoServiceWalker.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Base/String.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/Elements/ErrorPayload.h> + +namespace Swift { +	class IQRouter; +	/** +	 * Recursively walk service discovery trees to find all services offered. +	 * This stops on any disco item that's not reporting itself as a server. +	 */ +	class DiscoServiceWalker : public boost::signals::trackable { +		public: +			DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps = 200); +			/** Start the walk. Call this exactly once.*/ +			void beginWalk(); +			/** Emitted for each service found. */ +			boost::signal<void(const JID&, boost::shared_ptr<DiscoInfo>)> onServiceFound; +			/** Emitted when walking is complete.*/ +			boost::signal<void()> onWalkComplete; +		private: +			void handleReceivedDiscoItem(const JID& item); +			void walkNode(const JID& jid); +			void markNodeCompleted(const JID& jid); +			void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, const JID& jid); +			void handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid); +			void handleDiscoError(const JID& jid, ErrorPayload::ref error); +			JID service_; +			IQRouter* iqRouter_; +			size_t maxSteps_; +			std::vector<JID> servicesBeingSearched_; +			std::vector<JID> searchedServices_; +	}; +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 6e57a8f..b925857 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -18,6 +18,7 @@  #include "Swiften/Client/Storages.h"  #include "Swiften/VCards/VCardManager.h"  #include "Swift/Controllers/Chat/MUCSearchController.h" +#include "Swift/Controllers/Chat/UserSearchController.h"  #include "Swift/Controllers/Chat/ChatsManager.h"  #include "Swift/Controllers/XMPPEvents/EventController.h"  #include "Swift/Controllers/EventWindowController.h" @@ -98,6 +99,7 @@ MainController::MainController(  	chatsManager_ = NULL;  	eventWindowController_ = NULL;  	mucSearchController_ = NULL; +	userSearchController_ = NULL;  	quitRequested_ = false;  	timeBeforeNextReconnect_ = -1; @@ -188,6 +190,8 @@ void MainController::resetClient() {  	statusTracker_ = NULL;  	delete profileSettings_;  	profileSettings_ = NULL; +	delete userSearchController_; +	userSearchController_ = NULL;  }  void MainController::handleUIEvent(boost::shared_ptr<UIEvent> event) { @@ -242,7 +246,9 @@ void MainController::handleConnected() {  		client_->getDiscoManager()->setCapsNode(CLIENT_NODE);  		client_->getDiscoManager()->setDiscoInfo(discoInfo); -		mucSearchController_ = new MUCSearchController(jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), settings_); + +		mucSearchController_ = new MUCSearchController(jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), settings_, client_->getNickResolver()); +		userSearchController_ = new UserSearchController(jid_, uiEventStream_, uiFactory_, client_->getIQRouter());  	}  	client_->requestRoster(); diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 2f101a5..aece80f 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -54,6 +54,7 @@ namespace Swift {  	class EventWindowFactory;  	class EventWindowController;  	class MUCSearchController; +	class UserSearchController;  	class StatusTracker;  	class Dock;  	class Storages; @@ -137,6 +138,7 @@ namespace Swift {  			boost::shared_ptr<ErrorEvent> lastDisconnectError_;  			bool useDelayForLatency_;  			MUCSearchController* mucSearchController_; +			UserSearchController* userSearchController_;  			int timeBeforeNextReconnect_;  			Timer::ref reconnectTimer_;  			StatusTracker* statusTracker_; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 9cd2be4..96674bd 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -26,6 +26,8 @@ if env["SCONS_STAGE"] == "build" :  			"Chat/ChatsManager.cpp",  			"Chat/MUCController.cpp",  			"Chat/MUCSearchController.cpp", +			"Chat/UserSearchController.cpp", +			"DiscoServiceWalker.cpp",  			"MainController.cpp",  			"RosterController.cpp",  			"RosterGroupExpandinessPersister.cpp", diff --git a/Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h b/Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h new file mode 100644 index 0000000..1312a84 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIEvents/UIEvent.h" + +namespace Swift { +	class RequestUserSearchUIEvent : public UIEvent { + +	}; +} diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h index da54ded..3c0ab12 100644 --- a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h @@ -26,6 +26,7 @@ namespace Swift {  			virtual void clearList() = 0;  			virtual void addService(const MUCService& service) = 0;  			virtual void addSavedServices(const std::vector<JID>& services) = 0; +			virtual void setSearchInProgress(bool searching) = 0;  			virtual void show() = 0; diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 4e15b27..acb7638 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -12,10 +12,11 @@  #include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/MainWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>  #include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h>  namespace Swift { -	class UIFactory : public ChatListWindowFactory, public ChatWindowFactory, public EventWindowFactory, public LoginWindowFactory, public MainWindowFactory, public MUCSearchWindowFactory, public XMLConsoleWidgetFactory { +	class UIFactory : public ChatListWindowFactory, public ChatWindowFactory, public EventWindowFactory, public LoginWindowFactory, public MainWindowFactory, public MUCSearchWindowFactory, public XMLConsoleWidgetFactory, public UserSearchWindowFactory {  		public:  			virtual ~UIFactory() {}  	}; diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h new file mode 100644 index 0000000..dda3409 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swiften/Base/boost_bsignals.h" + +#include <vector> + +#include "Swiften/Base/String.h" +#include "Swiften/JID/JID.h" +#include "Swift/Controllers/Chat/UserSearchController.h" + +namespace Swift { + +	class UserSearchWindow { +		public: +			virtual ~UserSearchWindow() {}; + +			virtual void clear() = 0; +			virtual void setResults(const std::vector<UserSearchResult>& results) = 0; +			virtual void addSavedServices(const std::vector<JID>& services) = 0; +			virtual void setSelectedService(const JID& service) = 0; +			virtual void setServerSupportsSearch(bool support) = 0; +			virtual void setSearchError(bool support) = 0; +			virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields) = 0; +			virtual void show() = 0; + +			boost::signal<void (const JID&)> onFormRequested; +			boost::signal<void (boost::shared_ptr<SearchPayload>, const JID&)> onSearchRequested; +	}; +} diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h b/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h new file mode 100644 index 0000000..808c1db --- /dev/null +++ b/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/UserSearchWindow.h" + +namespace Swift { +	class UIEventStream; +	class UserSearchWindowFactory { +		public: +			virtual ~UserSearchWindowFactory() {}; + +			virtual UserSearchWindow* createUserSearchWindow(UIEventStream* eventStream) = 0; +	}; +} diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp index c31230c..7d2caba 100644 --- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp +++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp @@ -7,6 +7,9 @@  #include "Swift/QtUI/MUCSearch/QtMUCSearchWindow.h"  #include <qdebug.h> +#include <QMovie> +#include <QScrollBar> +#include <QTimer>  #include "Swift/Controllers/UIEvents/UIEventStream.h"  #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" @@ -41,12 +44,41 @@ QtMUCSearchWindow::QtMUCSearchWindow(UIEventStream* eventStream) {  	connect(joinButton_, SIGNAL(clicked()), this, SLOT(handleJoin()));  	connect(results_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleSelected(const QModelIndex&)));  	connect(results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&))); +	throbber_ = new QLabel("Searching", results_); +	throbber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), throbber_)); +	throbber_->setToolTip("Searching"); +	hasHadScrollBars_ = false; +	updateThrobberPosition(); +	setSearchInProgress(false);  }  QtMUCSearchWindow::~QtMUCSearchWindow() {  } +void QtMUCSearchWindow::resizeEvent(QResizeEvent* /*event*/) { +	updateThrobberPosition(); +} + +void QtMUCSearchWindow::updateThrobberPosition() { +	bool isShown = throbber_->isVisible(); +	int resultWidth = results_->width(); +	int resultHeight = results_->height(); +	//throbberWidth = throbber_->movie()->scaledSize().width(); +	//throbberHeight = throbber_->movie()->scaledSize().height(); +	int throbberWidth = 16; /* This is nasty, but the above doesn't work! */ +	int throbberHeight = 16; +	/* It's difficult (or I spent a while trying) to work out whether the scrollbars are currently shown and their appropriate size, +	 * because if you listen for the expanded/collapsed signals, you seem to get them before the scrollbars are updated. +	 * This seems an acceptable workaround. +	 */ +	hasHadScrollBars_ |= results_->verticalScrollBar()->isVisible(); +	int hMargin = hasHadScrollBars_ ? results_->verticalScrollBar()->width() + 2 : 2; +	int vMargin = 2; /* We don't get horizontal scrollbars */ +	throbber_->setGeometry(QRect(resultWidth - throbberWidth - hMargin, resultHeight - throbberHeight - vMargin, throbberWidth, throbberHeight)); /* include margins */ +	throbber_->setVisible(isShown); +} +  void QtMUCSearchWindow::addSavedServices(const std::vector<JID>& services) {  	service_->clear();  	foreach (JID jid, services) { @@ -145,6 +177,7 @@ void QtMUCSearchWindow::clearList() {  }  void QtMUCSearchWindow::addService(const MUCService& service) { +	updateThrobberPosition();  	MUCSearchServiceItem* serviceItem = new MUCSearchServiceItem(P2QSTRING(service.getJID().toString()));  	foreach (MUCService::MUCRoom room, service.getRooms()) {  		new MUCSearchRoomItem(P2QSTRING(room.getNode()), serviceItem); @@ -153,4 +186,13 @@ void QtMUCSearchWindow::addService(const MUCService& service) {  	results_->expandAll();  } +void QtMUCSearchWindow::setSearchInProgress(bool searching) { +	if (searching) { +		throbber_->movie()->start(); +	} else { +		throbber_->movie()->stop(); +	} +	throbber_->setVisible(searching); +} +  } diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h index 27ccdcb..b8cf953 100644 --- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h +++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h @@ -25,19 +25,25 @@ namespace Swift {  			virtual void clearList();  			virtual void addService(const MUCService& service);  			virtual void addSavedServices(const std::vector<JID>& services); +			virtual void setSearchInProgress(bool searching);  			virtual void show(); +		protected: +			virtual void resizeEvent(QResizeEvent* event);  		private slots:  			void handleSearch(const QString& text);  			void handleSearch();  			void handleJoin();  			void handleSelected(const QModelIndex& current);  			void handleActivated(const QModelIndex& index); +			void updateThrobberPosition();  		private:  			void createAutoJoin(const JID& room, boost::optional<String> passedNick);  			MUCSearchModel* model_;  			MUCSearchDelegate* delegate_;  			UIEventStream* eventStream_; +			QLabel* throbber_;  			String lastSetNick_; +			bool hasHadScrollBars_;  	};  } diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index ee83f4d..6e53258 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -24,7 +24,7 @@  #include "QtSwiftUtil.h"  #include "QtTabWidget.h"  #include "Roster/QtTreeWidget.h" -#include "Swift/Controllers/UIEvents/AddContactUIEvent.h" +#include "Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h"  #include "Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h"  #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"  #include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" @@ -84,9 +84,9 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS  	QAction* joinMUCAction = new QAction("Join Room", this);  	connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction()));  	actionsMenu->addAction(joinMUCAction); -	addAction_ = new QAction("Add Contact", this); -	connect(addAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddActionTriggered(bool))); -	actionsMenu->addAction(addAction_); +	otherUserAction_ = new QAction("Find Other Contact", this); +	connect(otherUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleOtherUserActionTriggered(bool))); +	actionsMenu->addAction(otherUserAction_);  	QAction* signOutAction = new QAction("Sign Out", this);  	connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction()));  	actionsMenu->addAction(signOutAction); @@ -123,22 +123,15 @@ void QtMainWindow::handleEventCountUpdated(int count) {  	tabs_->setTabText(eventIndex, text);  } -void QtMainWindow::handleAddActionTriggered(bool checked) { -	Q_UNUSED(checked); -	QtAddContactDialog* addContact = new QtAddContactDialog(this); -	connect(addContact, SIGNAL(onAddCommand(const JID&, const QString&)), SLOT(handleAddContactDialogComplete(const JID&, const QString&))); -	addContact->show(); +void QtMainWindow::handleOtherUserActionTriggered(bool /*checked*/) { +	boost::shared_ptr<UIEvent> event(new RequestUserSearchUIEvent()); +	uiEventStream_->send(event);  }  void QtMainWindow::handleSignOutAction() {  	onSignOutRequest();  } -void QtMainWindow::handleAddContactDialogComplete(const JID& contact, const QString& name) { -	boost::shared_ptr<UIEvent> event(new AddContactUIEvent(contact, Q2PSTRING(name))); -	uiEventStream_->send(event); -} -  void QtMainWindow::handleJoinMUCAction() {  	uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestMUCSearchUIEvent()));  } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index f494fd0..10cad66 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -54,15 +54,14 @@ namespace Swift {  			void handleShowOfflineToggled(bool);  			void handleJoinMUCAction();  			void handleSignOutAction(); -			void handleAddContactDialogComplete(const JID& contact, const QString& name); -			void handleAddActionTriggered(bool checked); +			void handleOtherUserActionTriggered(bool checked);  			void handleEventCountUpdated(int count);  		private:  			std::vector<QMenu*> menus_;  			QtTreeWidget* treeWidget_;  			QtRosterHeader* meView_; -			QAction* addAction_; +			QAction* otherUserAction_;  			QAction* showOfflineAction_;  			QtTabWidget* tabs_;  			QWidget* contactsTabWidget_; diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 32e261c..ca6e126 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -42,6 +42,7 @@ namespace Swift {  	class QtChatWindowFactory;  	class QtSoundPlayer;  	class QtMUCSearchWindowFactory; +	class QtUserSearchWindowFactory;  	class EventLoop;  	class QtSwift : public QObject { diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 55ed370..4f1bef5 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -19,6 +19,7 @@  #include "QtChatWindowFactory.h"  #include "QtSwiftUtil.h"  #include "MUCSearch/QtMUCSearchWindow.h" +#include "UserSearch/QtUserSearchWindow.h"  namespace Swift { @@ -77,4 +78,8 @@ ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eve  	return chatWindowFactory->createChatWindow(contact, eventStream);  } +UserSearchWindow* QtUIFactory::createUserSearchWindow(UIEventStream* eventStream) { +	return new QtUserSearchWindow(eventStream); +}; +  } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 7f610a2..5282b31 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -33,6 +33,7 @@ namespace Swift {  			virtual ChatListWindow* createChatListWindow(UIEventStream*);  			virtual MUCSearchWindow* createMUCSearchWindow(UIEventStream* eventStream);  			virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); +			virtual UserSearchWindow* createUserSearchWindow(UIEventStream* eventStream);  		private slots:  			void handleLoginWindowGeometryChanged(); diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index b435e34..0f08556 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -111,6 +111,9 @@ sources = [      "MUCSearch/MUCSearchModel.cpp",      "MUCSearch/MUCSearchRoomItem.cpp",      "MUCSearch/MUCSearchDelegate.cpp", +    "UserSearch/QtUserSearchWindow.cpp", +    "UserSearch/UserSearchModel.cpp", +    "UserSearch/UserSearchDelegate.cpp",      "ContextMenus/QtRosterContextMenu.cpp",      "ContextMenus/QtContextMenu.cpp",      "QtSubscriptionRequestWindow.cpp", @@ -138,6 +141,7 @@ else :    swiftProgram = myenv.Program("swift", sources)  myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui") +myenv.Uic4("UserSearch/QtUserSearchWindow.ui")  myenv.Uic4("QtAddContactDialog.ui")  myenv.Uic4("QtBookmarkDetailWindow.ui")  myenv.Qrc("DefaultTheme.qrc") diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp new file mode 100644 index 0000000..c3038d8 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/QtUI/UserSearch/QtUserSearchWindow.h" + +#include <qdebug.h> +#include <QModelIndex> + +#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" + +namespace Swift { + +QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream) { +#ifndef Q_WS_MAC +	setWindowIcon(QIcon(":/logo-icon-16.png")); +#endif +	eventStream_ = eventStream; +	setupUi(this); +	model_ = new UserSearchModel(); +	delegate_ = new UserSearchDelegate(); +	results_->setModel(model_); +	results_->setItemDelegate(delegate_); +	results_->setHeaderHidden(true); +#ifdef SWIFT_PLATFORM_MACOSX +	results_->setAlternatingRowColors(true); +#endif +	connect(service_, SIGNAL(activated(const QString&)), this, SLOT(handleGetForm())); +	connect(getSearchForm_, SIGNAL(clicked()), this, SLOT(handleGetForm())); +	//connect(user_, SIGNAL(returnPressed()), this, SLOT(handleJoin())); +	//connect(nickName_, SIGNAL(returnPressed()), room_, SLOT(setFocus())); +	connect(search_, SIGNAL(clicked()), this, SLOT(handleSearch())); + +	connect(results_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleSelected(const QModelIndex&))); +	connect(results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&))); +	connect(buttonBox_, SIGNAL(accepted()), this, SLOT(handleOkClicked())); +	connect(buttonBox_, SIGNAL(rejected()), this, SLOT(handleCancelClicked())); +	/* When inputs change, check if OK is enabled */ +	connect(jid_, SIGNAL(textChanged(const QString&)), this, SLOT(enableCorrectButtons())); +	connect(nickName_, SIGNAL(textChanged(const QString&)), this, SLOT(enableCorrectButtons())); +	connect(startChat_, SIGNAL(stateChanged(int)), this, SLOT(enableCorrectButtons())); +	connect(addToRoster_, SIGNAL(stateChanged(int)), this, SLOT(enableCorrectButtons())); +	enableCorrectButtons(); +} + +QtUserSearchWindow::~QtUserSearchWindow() { + +} + +void QtUserSearchWindow::handleGetForm() { +	lastServiceJID_ = JID(Q2PSTRING(service_->currentText())); +	onFormRequested(lastServiceJID_); +} + +void QtUserSearchWindow::handleSearch() { +	boost::shared_ptr<SearchPayload> search(new SearchPayload()); +	if (nickInput_->isEnabled()) { +		search->setNick(Q2PSTRING(nickInput_->text())); +	} +	if (firstInput_->isEnabled()) { +		search->setFirst(Q2PSTRING(firstInput_->text())); +	} +	if (lastInput_->isEnabled()) { +		search->setLast(Q2PSTRING(lastInput_->text())); +	} +	if (emailInput_->isEnabled()) { +		search->setEMail(Q2PSTRING(emailInput_->text())); +	} +	onSearchRequested(search, lastServiceJID_); +} + +void QtUserSearchWindow::show() { +	clear(); +	enableCorrectButtons(); +	QWidget::show(); +} + +void QtUserSearchWindow::enableCorrectButtons() { +	bool enable = !jid_->text().isEmpty() && (startChat_->isChecked() || (addToRoster_->isChecked() && !nickName_->text().isEmpty())); +	buttonBox_->button(QDialogButtonBox::Ok)->setEnabled(enable); +} + +void QtUserSearchWindow::handleOkClicked() { +	JID contact = JID(Q2PSTRING(jid_->text())); +	String nick = Q2PSTRING(nickName_->text()); +	if (addToRoster_->isChecked()) { +		boost::shared_ptr<UIEvent> event(new AddContactUIEvent(contact, nick)); +		eventStream_->send(event); +	} +	if (startChat_->isChecked()) { +		boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(contact)); +		eventStream_->send(event); +	} +	hide(); +} + +void QtUserSearchWindow::handleCancelClicked() { +	hide(); +} + +void QtUserSearchWindow::addSavedServices(const std::vector<JID>& services) { +	service_->clear(); +	foreach (JID jid, services) { +		service_->addItem(P2QSTRING(jid.toString())); +	} +	service_->clearEditText(); +} + +void QtUserSearchWindow::setServerSupportsSearch(bool support) { +	if (!support) { +		stack_->setCurrentIndex(0); +		messageLabel_->setText("This service doesn't support searching for users."); +		search_->setEnabled(false); +	} +} + +void QtUserSearchWindow::setSearchFields(boost::shared_ptr<SearchPayload> fields) { +	bool enabled[8] = {fields->getNick(), fields->getNick(), fields->getFirst(), fields->getFirst(), fields->getLast(), fields->getLast(), fields->getEMail(), fields->getEMail()}; +	QWidget* widget[8] = {nickInputLabel_, nickInput_, firstInputLabel_, firstInput_, lastInputLabel_, lastInput_, emailInputLabel_, emailInput_}; +	for (int i = 0; i < 8; i++) { +		widget[i]->setVisible(enabled[i]); +		widget[i]->setEnabled(enabled[i]); +	} +	stack_->setCurrentIndex(1); +	search_->setEnabled(true); +} + +void QtUserSearchWindow::handleActivated(const QModelIndex& index) { +	if (!index.isValid()) { +		return; +	} +	UserSearchResult* userItem = static_cast<UserSearchResult*>(index.internalPointer()); +	if (userItem) { /* static cast, so always will be, but if we change to be like mucsearch, remember the check.*/ +		handleSelected(index); +		//handleJoin(); /* Don't do anything automatically on selection.*/ +	} +} + +void QtUserSearchWindow::handleSelected(const QModelIndex& current) { +	if (!current.isValid()) { +		return; +	} +	UserSearchResult* userItem = static_cast<UserSearchResult*>(current.internalPointer()); +	if (userItem) { /* Remember to leave this if we change to dynamic cast */ +		jid_->setText(P2QSTRING(userItem->getJID().toString())); +	} +} + +void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results) { +	model_->setResults(results); +} + +void QtUserSearchWindow::setSelectedService(const JID& jid) { +	service_->setEditText(P2QSTRING(jid.toString())); +} + +void QtUserSearchWindow::clear() { +	stack_->setCurrentIndex(0); +	messageLabel_->setText("Please choose a service to search, above"); +	model_->clear(); +	search_->setEnabled(false); +} + +void QtUserSearchWindow::setSearchError(bool error) { +	//FIXME +} + +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h new file mode 100644 index 0000000..dca321a --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/QtUI/UserSearch/ui_QtUserSearchWindow.h" + +#include "Swift/Controllers/UIInterfaces/UserSearchWindow.h" + + +namespace Swift { +	class UserSearchModel; +	class UserSearchDelegate; +	class UserSearchResult; +	class UIEventStream; +	class QtUserSearchWindow : public QWidget, public UserSearchWindow, private Ui::QtUserSearchWindow { +		Q_OBJECT +		public: +			QtUserSearchWindow(UIEventStream* eventStream); +			virtual ~QtUserSearchWindow(); + +			virtual void addSavedServices(const std::vector<JID>& services); + +			virtual void clear(); +			virtual void show(); +			virtual void setResults(const std::vector<UserSearchResult>& results); +			virtual void setSelectedService(const JID& jid); +			virtual void setServerSupportsSearch(bool error); +			virtual void setSearchError(bool error); +			virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields) ; +		private slots: +			void handleGetForm(); +			void handleSelected(const QModelIndex& current); +			void handleSearch(); +			void handleActivated(const QModelIndex& index); +			void handleOkClicked(); +			void handleCancelClicked(); +			void enableCorrectButtons(); +		private: +			UserSearchModel* model_; +			UserSearchDelegate* delegate_; +			UIEventStream* eventStream_; +			JID lastServiceJID_; +	}; +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.ui b/Swift/QtUI/UserSearch/QtUserSearchWindow.ui new file mode 100644 index 0000000..56047ce --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.ui @@ -0,0 +1,292 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtUserSearchWindow</class> + <widget class="QWidget" name="QtUserSearchWindow"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>698</width> +    <height>569</height> +   </rect> +  </property> +  <property name="sizePolicy"> +   <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> +    <horstretch>0</horstretch> +    <verstretch>0</verstretch> +   </sizepolicy> +  </property> +  <property name="windowTitle"> +   <string>Find other users</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <item> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <property name="sizeConstraint"> +      <enum>QLayout::SetNoConstraint</enum> +     </property> +     <item> +      <widget class="QFrame" name="frame"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="frameShape"> +        <enum>QFrame::StyledPanel</enum> +       </property> +       <property name="frameShadow"> +        <enum>QFrame::Raised</enum> +       </property> +       <layout class="QVBoxLayout" name="verticalLayout_2"> +        <item> +         <widget class="QLabel" name="label"> +          <property name="text"> +           <string>Service to search:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QComboBox" name="service_"> +          <property name="editable"> +           <bool>true</bool> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_2"> +          <item> +           <spacer name="horizontalSpacer"> +            <property name="orientation"> +             <enum>Qt::Horizontal</enum> +            </property> +            <property name="sizeHint" stdset="0"> +             <size> +              <width>40</width> +              <height>20</height> +             </size> +            </property> +           </spacer> +          </item> +          <item> +           <widget class="QPushButton" name="getSearchForm_"> +            <property name="text"> +             <string>Get Search Form</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +        <item> +         <widget class="QStackedWidget" name="stack_"> +          <property name="currentIndex"> +           <number>1</number> +          </property> +          <widget class="QWidget" name="display"> +           <layout class="QHBoxLayout" name="horizontalLayout_6"> +            <item> +             <widget class="QLabel" name="messageLabel_"> +              <property name="text"> +               <string>TextLabel</string> +              </property> +              <property name="alignment"> +               <set>Qt::AlignHCenter|Qt::AlignTop</set> +              </property> +              <property name="wordWrap"> +               <bool>true</bool> +              </property> +             </widget> +            </item> +           </layout> +          </widget> +          <widget class="QWidget" name="legacy"> +           <layout class="QVBoxLayout" name="verticalLayout_4"> +            <item> +             <layout class="QGridLayout" name="gridLayout"> +              <item row="0" column="0" colspan="2"> +               <widget class="QLabel" name="label_4"> +                <property name="text"> +                 <string>Enter search terms</string> +                </property> +               </widget> +              </item> +              <item row="1" column="0"> +               <widget class="QLabel" name="nickInputLabel_"> +                <property name="text"> +                 <string>Nickname:</string> +                </property> +               </widget> +              </item> +              <item row="1" column="1"> +               <widget class="QLineEdit" name="nickInput_"/> +              </item> +              <item row="2" column="0"> +               <widget class="QLabel" name="firstInputLabel_"> +                <property name="text"> +                 <string>First name:</string> +                </property> +               </widget> +              </item> +              <item row="2" column="1"> +               <widget class="QLineEdit" name="firstInput_"/> +              </item> +              <item row="3" column="0"> +               <widget class="QLabel" name="lastInputLabel_"> +                <property name="text"> +                 <string>Last name:</string> +                </property> +               </widget> +              </item> +              <item row="3" column="1"> +               <widget class="QLineEdit" name="lastInput_"/> +              </item> +              <item row="4" column="0"> +               <widget class="QLabel" name="emailInputLabel_"> +                <property name="text"> +                 <string>E-Mail:</string> +                </property> +               </widget> +              </item> +              <item row="4" column="1"> +               <widget class="QLineEdit" name="emailInput_"/> +              </item> +             </layout> +            </item> +            <item> +             <spacer name="verticalSpacer"> +              <property name="orientation"> +               <enum>Qt::Vertical</enum> +              </property> +              <property name="sizeHint" stdset="0"> +               <size> +                <width>20</width> +                <height>40</height> +               </size> +              </property> +             </spacer> +            </item> +           </layout> +          </widget> +         </widget> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_3"> +          <item> +           <spacer name="horizontalSpacer_2"> +            <property name="orientation"> +             <enum>Qt::Horizontal</enum> +            </property> +            <property name="sizeHint" stdset="0"> +             <size> +              <width>40</width> +              <height>20</height> +             </size> +            </property> +           </spacer> +          </item> +          <item> +           <widget class="QPushButton" name="search_"> +            <property name="text"> +             <string>Search</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <widget class="QFrame" name="frame_2"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="frameShape"> +        <enum>QFrame::StyledPanel</enum> +       </property> +       <property name="frameShadow"> +        <enum>QFrame::Raised</enum> +       </property> +       <layout class="QVBoxLayout" name="verticalLayout_3"> +        <item> +         <widget class="QLabel" name="label_2"> +          <property name="text"> +           <string>Results:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QTreeView" name="results_"/> +        </item> +        <item> +         <widget class="QLabel" name="label_3"> +          <property name="text"> +           <string>Address:</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QLineEdit" name="jid_"/> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_4"> +          <item> +           <widget class="QCheckBox" name="addToRoster_"> +            <property name="text"> +             <string>Add to Roster. Nickname:</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QLineEdit" name="nickName_"/> +          </item> +         </layout> +        </item> +        <item> +         <layout class="QHBoxLayout" name="horizontalLayout_5"> +          <item> +           <widget class="QCheckBox" name="startChat_"> +            <property name="text"> +             <string>Start Chat With Contact</string> +            </property> +           </widget> +          </item> +          <item> +           <spacer name="horizontalSpacer_4"> +            <property name="orientation"> +             <enum>Qt::Horizontal</enum> +            </property> +            <property name="sizeHint" stdset="0"> +             <size> +              <width>40</width> +              <height>20</height> +             </size> +            </property> +           </spacer> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </item> +   <item> +    <widget class="QDialogButtonBox" name="buttonBox_"> +     <property name="orientation"> +      <enum>Qt::Horizontal</enum> +     </property> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/UserSearch/UserSearchDelegate.cpp b/Swift/QtUI/UserSearch/UserSearchDelegate.cpp new file mode 100644 index 0000000..ff3e766 --- /dev/null +++ b/Swift/QtUI/UserSearch/UserSearchDelegate.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <QPen> +#include <QPainter> + +#include "Swift/QtUI/UserSearch/UserSearchDelegate.h" +//#include "Swift/QtUI/Roster/GroupItemDelegate.h" +//#include "Swift/QtUI/MUCSearch/MUCSearchItem.h" +//#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h" +//#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h" + +namespace Swift { + +UserSearchDelegate::UserSearchDelegate() { + +} + +UserSearchDelegate::~UserSearchDelegate() { + +} + +// QSize MUCSearchDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { +// 	// MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); +// 	// if (item && dynamic_cast<MUCSearchMUCItem*>(item)) { +// 	// 	return mucSizeHint(option, index); +// 	// } else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) { +// 	// 	return groupDelegate_->sizeHint(option, index); +// 	// } +// 	return QStyledItemDelegate::sizeHint(option, index); +// } + +// QSize MUCSearchDelegate::mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const { +// 	QFontMetrics nameMetrics(common_.nameFont); +// 	QFontMetrics statusMetrics(common_.detailFont); +// 	int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); +// 	return QSize(150, sizeByText); +// } + +// void MUCSearchDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { +// 	MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); +// 	if (item && dynamic_cast<MUCSearchMUCItem*>(item)) { +// 		paintMUC(painter, option, dynamic_cast<MUCSearchMUCItem*>(item)); +// 	} else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) { +// 		MUCSearchGroupItem* group = dynamic_cast<MUCSearchGroupItem*>(item); +// 		groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); +// 	} else { +// 		QStyledItemDelegate::paint(painter, option, index); +// 	} +// } + +// void MUCSearchDelegate::paintMUC(QPainter* painter, const QStyleOptionViewItem& option, MUCSearchMUCItem* item) const { +// 	painter->save(); +// 	QRect fullRegion(option.rect); +// 	if ( option.state & QStyle::State_Selected ) { +// 		painter->fillRect(fullRegion, option.palette.highlight()); +// 		painter->setPen(option.palette.highlightedText().color()); +// 	} else { +// 		QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); +// 		painter->setPen(QPen(nameColor)); +// 	} + +// 	QFontMetrics nameMetrics(common_.nameFont); +// 	painter->setFont(common_.nameFont); +// 	int extraFontWidth = nameMetrics.width("H"); +// 	int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; +// 	QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); + +// 	int nameHeight = nameMetrics.height() + common_.verticalMargin; +// 	QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); + +// 	painter->drawText(nameRegion, Qt::AlignTop, item->data(Qt::DisplayRole).toString()); + +// 	painter->setFont(common_.detailFont); +// 	painter->setPen(QPen(QColor(160,160,160))); + +// 	QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); +// 	painter->drawText(detailRegion, Qt::AlignTop, item->data(DetailTextRole).toString()); + +// 	painter->restore(); +// } + +} diff --git a/Swift/QtUI/UserSearch/UserSearchDelegate.h b/Swift/QtUI/UserSearch/UserSearchDelegate.h new file mode 100644 index 0000000..d046d62 --- /dev/null +++ b/Swift/QtUI/UserSearch/UserSearchDelegate.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QStyledItemDelegate> + +#include "Swift/QtUI/Roster/DelegateCommons.h" + +namespace Swift { +	class UserSearchDelegate : public QStyledItemDelegate { +		public: +			UserSearchDelegate(); +			~UserSearchDelegate(); +	}; + +} + diff --git a/Swift/QtUI/UserSearch/UserSearchModel.cpp b/Swift/QtUI/UserSearch/UserSearchModel.cpp new file mode 100644 index 0000000..782d2d0 --- /dev/null +++ b/Swift/QtUI/UserSearch/UserSearchModel.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/QtUI/UserSearch/UserSearchModel.h" + +#include "Swift/QtUI/QtSwiftUtil.h" + +namespace Swift { + +UserSearchModel::UserSearchModel() { +} + +void UserSearchModel::clear() { +	emit layoutAboutToBeChanged(); +	results_.clear(); +	emit layoutChanged(); +} + +void UserSearchModel::setResults(const std::vector<UserSearchResult>& results) { +	clear(); +	emit layoutAboutToBeChanged(); +	results_ = results; +	emit layoutChanged(); +} + +int UserSearchModel::columnCount(const QModelIndex& /*parent*/) const { +	return 1; +} + +QVariant UserSearchModel::data(const QModelIndex& index, int role) const { +	if (!index.isValid()) return QVariant(); +	UserSearchResult* result = static_cast<UserSearchResult*>(index.internalPointer()); +	switch (role) { +		case Qt::DisplayRole: return QVariant(P2QSTRING(result->getJID().toString())); +		default: return QVariant(); +	} +} + +QModelIndex UserSearchModel::index(int row, int column, const QModelIndex & parent) const { +	if (!hasIndex(row, column, parent)) { +		return QModelIndex(); +	} +	return row < (int)results_.size() ? createIndex(row, column, (void*)&(results_[row])) : QModelIndex(); +} + +QModelIndex UserSearchModel::parent(const QModelIndex& /*index*/) const { +	return QModelIndex(); +} + +int UserSearchModel::rowCount(const QModelIndex& parentIndex) const { +	if (!parentIndex.isValid()) { +		return results_.size(); +	} +	return 0; +} + +} diff --git a/Swift/QtUI/UserSearch/UserSearchModel.h b/Swift/QtUI/UserSearch/UserSearchModel.h new file mode 100644 index 0000000..d766d9a --- /dev/null +++ b/Swift/QtUI/UserSearch/UserSearchModel.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <QAbstractItemModel> +#include <QList> + +#include "Swift/Controllers/Chat/UserSearchController.h" + +namespace Swift { +	class UserSearchModel : public QAbstractItemModel { +		Q_OBJECT +		public: +			UserSearchModel(); +			void clear(); +			void setResults(const std::vector<UserSearchResult>& results); +			int columnCount(const QModelIndex& parent = QModelIndex()) const; +			QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; +			QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; +			QModelIndex parent(const QModelIndex& index) const; +			int rowCount(const QModelIndex& parent = QModelIndex()) const; +		private: +			std::vector<UserSearchResult> results_; +	}; + +} diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp index 276b341..a939d48 100644 --- a/Swiften/Elements/DiscoInfo.cpp +++ b/Swiften/Elements/DiscoInfo.cpp @@ -10,6 +10,7 @@ namespace Swift {  const String DiscoInfo::ChatStatesFeature = String("http://jabber.org/protocol/chatstates");  const String DiscoInfo::SecurityLabelsFeature = String("urn:xmpp:sec-label:0"); +const String DiscoInfo::JabberSearchFeature = String("jabber:iq:search");  bool DiscoInfo::Identity::operator<(const Identity& other) const { diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h index 038a2f1..41bf6bf 100644 --- a/Swiften/Elements/DiscoInfo.h +++ b/Swiften/Elements/DiscoInfo.h @@ -21,6 +21,7 @@ namespace Swift {  			static const String ChatStatesFeature;  			static const String SecurityLabelsFeature; +			static const String JabberSearchFeature;  			const static std::string SecurityLabels;  			class Identity { diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index f57411b..1bbcbf2 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -39,6 +39,7 @@  #include "Swiften/Serializer/PayloadSerializers/CommandSerializer.h"  #include "Swiften/Serializer/PayloadSerializers/InBandRegistrationPayloadSerializer.h"  #include "Swiften/Serializer/PayloadSerializers/NicknameSerializer.h" +#include "Swiften/Serializer/PayloadSerializers/SearchPayloadSerializer.h"  namespace Swift { @@ -75,6 +76,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {  	serializers_.push_back(new CommandSerializer());  	serializers_.push_back(new InBandRegistrationPayloadSerializer());  	serializers_.push_back(new NicknameSerializer()); +	serializers_.push_back(new SearchPayloadSerializer());  	foreach(PayloadSerializer* serializer, serializers_) {  		addSerializer(serializer);  	} | 
 Swift
 Swift