summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Smith <git@kismith.co.uk>2010-11-18 12:09:08 (GMT)
committerKevin Smith <git@kismith.co.uk>2010-12-22 20:19:07 (GMT)
commit9f04a7ec3429303118f12607703b877d8ba43888 (patch)
tree3e868395fa3c4f9cbbf84614094bb0251b425c15
parent04b99ce8430109d0467c29c85cb6bacb85c54c44 (diff)
downloadswift-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.
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.cpp144
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.h14
-rw-r--r--Swift/Controllers/Chat/UserSearchController.cpp122
-rw-r--r--Swift/Controllers/Chat/UserSearchController.h59
-rw-r--r--Swift/Controllers/DiscoServiceWalker.cpp99
-rw-r--r--Swift/Controllers/DiscoServiceWalker.h47
-rw-r--r--Swift/Controllers/MainController.cpp8
-rw-r--r--Swift/Controllers/MainController.h2
-rw-r--r--Swift/Controllers/SConscript2
-rw-r--r--Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h15
-rw-r--r--Swift/Controllers/UIInterfaces/MUCSearchWindow.h1
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h3
-rw-r--r--Swift/Controllers/UIInterfaces/UserSearchWindow.h35
-rw-r--r--Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h19
-rw-r--r--Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp42
-rw-r--r--Swift/QtUI/MUCSearch/QtMUCSearchWindow.h6
-rw-r--r--Swift/QtUI/QtMainWindow.cpp21
-rw-r--r--Swift/QtUI/QtMainWindow.h5
-rw-r--r--Swift/QtUI/QtSwift.h1
-rw-r--r--Swift/QtUI/QtUIFactory.cpp5
-rw-r--r--Swift/QtUI/QtUIFactory.h1
-rw-r--r--Swift/QtUI/SConscript4
-rw-r--r--Swift/QtUI/UserSearch/QtUserSearchWindow.cpp175
-rw-r--r--Swift/QtUI/UserSearch/QtUserSearchWindow.h48
-rw-r--r--Swift/QtUI/UserSearch/QtUserSearchWindow.ui292
-rw-r--r--Swift/QtUI/UserSearch/UserSearchDelegate.cpp86
-rw-r--r--Swift/QtUI/UserSearch/UserSearchDelegate.h21
-rw-r--r--Swift/QtUI/UserSearch/UserSearchModel.cpp60
-rw-r--r--Swift/QtUI/UserSearch/UserSearchModel.h32
-rw-r--r--Swiften/Elements/DiscoInfo.cpp1
-rw-r--r--Swiften/Elements/DiscoInfo.h1
-rw-r--r--Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp2
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);
}