summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/AdHocManager.cpp69
-rw-r--r--Swift/Controllers/AdHocManager.h40
-rw-r--r--Swift/Controllers/CertificateMemoryStorageFactory.h28
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp10
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp5
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h10
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp54
-rw-r--r--Swift/Controllers/Chat/MUCController.h18
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.cpp1
-rw-r--r--Swift/Controllers/Chat/UserSearchController.cpp2
-rw-r--r--Swift/Controllers/ChatMessageSummarizer.cpp45
-rw-r--r--Swift/Controllers/ChatMessageSummarizer.h20
-rw-r--r--Swift/Controllers/DiscoServiceWalker.cpp1
-rw-r--r--Swift/Controllers/MainController.cpp49
-rw-r--r--Swift/Controllers/MainController.h8
-rw-r--r--Swift/Controllers/ProfileSettingsProvider.h1
-rw-r--r--Swift/Controllers/Roster/ContactRosterItem.cpp2
-rw-r--r--Swift/Controllers/Roster/GroupRosterItem.cpp15
-rw-r--r--Swift/Controllers/Roster/GroupRosterItem.h4
-rw-r--r--Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp1
-rw-r--r--Swift/Controllers/Roster/RosterItem.cpp4
-rw-r--r--Swift/Controllers/Roster/RosterItem.h4
-rw-r--r--Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp1
-rw-r--r--Swift/Controllers/SConscript16
-rw-r--r--Swift/Controllers/Storages/AvatarFileStorage.cpp105
-rw-r--r--Swift/Controllers/Storages/AvatarFileStorage.h41
-rw-r--r--Swift/Controllers/Storages/CapsFileStorage.cpp34
-rw-r--r--Swift/Controllers/Storages/CapsFileStorage.h28
-rw-r--r--Swift/Controllers/Storages/CertificateFileStorage.cpp (renamed from Swift/Controllers/CertificateFileStorage.cpp)2
-rw-r--r--Swift/Controllers/Storages/CertificateFileStorage.h (renamed from Swift/Controllers/CertificateFileStorage.h)2
-rw-r--r--Swift/Controllers/Storages/CertificateFileStorageFactory.h (renamed from Swift/Controllers/CertificateFileStorageFactory.h)4
-rw-r--r--Swift/Controllers/Storages/CertificateMemoryStorage.cpp27
-rw-r--r--Swift/Controllers/Storages/CertificateMemoryStorage.h25
-rw-r--r--Swift/Controllers/Storages/CertificateStorage.cpp (renamed from Swift/Controllers/CertificateStorage.cpp)2
-rw-r--r--Swift/Controllers/Storages/CertificateStorage.h (renamed from Swift/Controllers/CertificateStorage.h)0
-rw-r--r--Swift/Controllers/Storages/CertificateStorageFactory.cpp (renamed from Swift/Controllers/CertificateStorageFactory.cpp)2
-rw-r--r--Swift/Controllers/Storages/CertificateStorageFactory.h (renamed from Swift/Controllers/CertificateStorageFactory.h)0
-rw-r--r--Swift/Controllers/Storages/CertificateStorageTrustChecker.h (renamed from Swift/Controllers/CertificateStorageTrustChecker.h)2
-rw-r--r--Swift/Controllers/Storages/FileStorages.cpp46
-rw-r--r--Swift/Controllers/Storages/FileStorages.h53
-rw-r--r--Swift/Controllers/Storages/FileStoragesFactory.h (renamed from Swift/Controllers/FileStoragesFactory.h)4
-rw-r--r--Swift/Controllers/Storages/MemoryStoragesFactory.h (renamed from Swift/Controllers/MemoryStoragesFactory.h)4
-rw-r--r--Swift/Controllers/Storages/RosterFileStorage.cpp26
-rw-r--r--Swift/Controllers/Storages/RosterFileStorage.h24
-rw-r--r--Swift/Controllers/Storages/StoragesFactory.h (renamed from Swift/Controllers/StoragesFactory.h)1
-rw-r--r--Swift/Controllers/Storages/VCardFileStorage.cpp107
-rw-r--r--Swift/Controllers/Storages/VCardFileStorage.h38
-rw-r--r--Swift/Controllers/UIEvents/RequestAdHocUIEvent.h21
-rw-r--r--Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h13
-rw-r--r--Swift/Controllers/UIInterfaces/AdHocCommandWindow.h14
-rw-r--r--Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h22
-rw-r--r--Swift/Controllers/UIInterfaces/MainWindow.h2
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h4
-rw-r--r--Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp122
-rw-r--r--Swift/Controllers/UnitTest/MockMainWindow.h1
-rw-r--r--Swift/Controllers/XMPPEvents/EventController.cpp1
-rw-r--r--Swift/Controllers/XMPPURIController.cpp38
-rw-r--r--Swift/Controllers/XMPPURIController.h32
-rw-r--r--Swift/QtUI/.gitignore1
-rw-r--r--Swift/QtUI/QtAdHocCommandWindow.cpp137
-rw-r--r--Swift/QtUI/QtAdHocCommandWindow.h48
-rw-r--r--Swift/QtUI/QtChatTabs.cpp14
-rw-r--r--Swift/QtUI/QtChatView.cpp13
-rw-r--r--Swift/QtUI/QtChatView.h3
-rw-r--r--Swift/QtUI/QtChatWindow.cpp22
-rw-r--r--Swift/QtUI/QtChatWindow.h8
-rw-r--r--Swift/QtUI/QtDBUSURIHandler.cpp41
-rw-r--r--Swift/QtUI/QtDBUSURIHandler.h17
-rw-r--r--Swift/QtUI/QtFormWidget.cpp242
-rw-r--r--Swift/QtUI/QtFormWidget.h31
-rw-r--r--Swift/QtUI/QtLoginWindow.cpp19
-rw-r--r--Swift/QtUI/QtLoginWindow.h8
-rw-r--r--Swift/QtUI/QtMainWindow.cpp36
-rw-r--r--Swift/QtUI/QtMainWindow.h8
-rw-r--r--Swift/QtUI/QtSwift.cpp32
-rw-r--r--Swift/QtUI/QtSwift.h2
-rw-r--r--Swift/QtUI/QtUIFactory.cpp4
-rw-r--r--Swift/QtUI/QtUIFactory.h1
-rw-r--r--Swift/QtUI/QtURIHandler.cpp36
-rw-r--r--Swift/QtUI/QtURIHandler.h22
-rw-r--r--Swift/QtUI/SConscript22
-rw-r--r--Swift/QtUI/swift-open-uri.cpp30
82 files changed, 1934 insertions, 118 deletions
diff --git a/Swift/Controllers/AdHocManager.cpp b/Swift/Controllers/AdHocManager.cpp
new file mode 100644
index 0000000..0fa63a1
--- /dev/null
+++ b/Swift/Controllers/AdHocManager.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/AdHocManager.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h>
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
+
+namespace Swift {
+
+AdHocManager::AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow) : jid_(jid) {
+ iqRouter_ = iqRouter;
+ uiEventStream_ = uiEventStream;
+ mainWindow_ = mainWindow;
+ factory_ = factory;
+
+ uiEventStream_->onUIEvent.connect(boost::bind(&AdHocManager::handleUIEvent, this, _1));
+}
+
+AdHocManager::~AdHocManager() {
+ uiEventStream_->onUIEvent.disconnect(boost::bind(&AdHocManager::handleUIEvent, this, _1));
+}
+
+void AdHocManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) {
+ if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::CommandsFeature)) {
+ if (discoItemsRequest_) {
+ discoItemsRequest_->onResponse.disconnect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2));
+ discoItemsRequest_.reset();
+ }
+ discoItemsRequest_ = GetDiscoItemsRequest::create(JID(jid_.getDomain()), DiscoInfo::CommandsFeature, iqRouter_);
+ discoItemsRequest_->onResponse.connect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2));
+ discoItemsRequest_->send();
+ } else {
+ mainWindow_->setAvailableAdHocCommands(std::vector<DiscoItems::Item>());
+ }
+
+}
+
+void AdHocManager::handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error) {
+ std::vector<DiscoItems::Item> commands;
+ if (!error) {
+ foreach (DiscoItems::Item item, items->getItems()) {
+ if (item.getNode() != "http://isode.com/xmpp/commands#test") {
+ commands.push_back(item);
+ }
+ }
+ }
+ mainWindow_->setAvailableAdHocCommands(commands);
+}
+
+void AdHocManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+ boost::shared_ptr<RequestAdHocUIEvent> adHocEvent = boost::dynamic_pointer_cast<RequestAdHocUIEvent>(event);
+ if (adHocEvent) {
+ factory_->createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession>(new OutgoingAdHocCommandSession(adHocEvent->getCommand(), factory_, iqRouter_)));
+ }
+}
+
+}
diff --git a/Swift/Controllers/AdHocManager.h b/Swift/Controllers/AdHocManager.h
new file mode 100644
index 0000000..47b03cd
--- /dev/null
+++ b/Swift/Controllers/AdHocManager.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2011 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 <vector>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/DiscoItems.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Disco/GetDiscoItemsRequest.h>
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+ class IQRouter;
+ class MainWindow;
+ class UIEventStream;
+ class AdHocCommandWindowFactory;
+ class AdHocManager {
+ public:
+ AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow);
+ ~AdHocManager();
+ void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
+ private:
+ void handleUIEvent(boost::shared_ptr<UIEvent> event);
+ void handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems>, ErrorPayload::ref error);
+ JID jid_;
+ IQRouter* iqRouter_;
+ UIEventStream* uiEventStream_;
+ MainWindow* mainWindow_;
+ AdHocCommandWindowFactory* factory_;
+ GetDiscoItemsRequest::ref discoItemsRequest_;
+ };
+}
diff --git a/Swift/Controllers/CertificateMemoryStorageFactory.h b/Swift/Controllers/CertificateMemoryStorageFactory.h
new file mode 100644
index 0000000..adbce80
--- /dev/null
+++ b/Swift/Controllers/CertificateMemoryStorageFactory.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateMemoryStorage.h>
+
+namespace Swift {
+ class CertificateFactory;
+
+ class CertificateMemoryStorageFactory : public CertificateStorageFactory {
+ public:
+ CertificateMemoryStorageFactory() {
+ }
+
+ virtual CertificateStorage* createCertificateStorage(const JID&) const {
+ return new CertificateMemoryStorage();
+ }
+
+ private:
+ boost::filesystem::path basePath;
+ CertificateFactory* certificateFactory;
+ };
+}
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 22ef68d..911f3e2 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -117,7 +117,7 @@ void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
std::string id = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
- if (stanzaChannel_->getStreamManagementEnabled()) {
+ if (stanzaChannel_->getStreamManagementEnabled() && !id.empty()) {
chatWindow_->setAckState(id, ChatWindow::Pending);
unackedStanzas_[sentStanza] = id;
}
@@ -126,11 +126,11 @@ void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<
}
void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
- std::string id = unackedStanzas_[stanza];
- if (id != "") {
- chatWindow_->setAckState(id, ChatWindow::Received);
+ std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza);
+ if (unackedStanza != unackedStanzas_.end()) {
+ chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received);
+ unackedStanzas_.erase(unackedStanza);
}
- unackedStanzas_.erase(unackedStanzas_.find(stanza));
}
void ChatController::setOnline(bool online) {
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 94d4b9a..972501f 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -8,6 +8,7 @@
#include <boost/bind.hpp>
+#include <Swiften/Base/foreach.h>
#include "Swift/Controllers/Chat/ChatController.h"
#include "Swift/Controllers/Chat/MUCSearchController.h"
#include "Swift/Controllers/XMPPEvents/EventController.h"
@@ -157,13 +158,13 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false);
}
- else if (boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
+ else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
if (!joinMUCWindow_) {
joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow();
joinMUCWindow_->onJoinMUC.connect(boost::bind(&ChatsManager::handleJoinMUCRequest, this, _1, _2, _3));
joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
}
- joinMUCWindow_->setMUC("");
+ joinMUCWindow_->setMUC(joinEvent->getRoom());
joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
joinMUCWindow_->show();
}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 3740186..34cd1bf 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -52,6 +52,12 @@ namespace Swift {
void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
void handleIncomingMessage(boost::shared_ptr<Message> message);
private:
+ class Chat {
+ public: Chat(const JID& jid, bool isMUC, const std::string& nick) : jid(jid), isMUC(isMUC), nick(nick) {}
+ JID jid;
+ bool isMUC;
+ std::string nick;
+ };
void handleChatRequest(const std::string& contact);
void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool autoJoin);
void handleSearchMUCRequest();
@@ -64,6 +70,9 @@ namespace Swift {
void handleUserLeftMUC(MUCController* mucController);
void handleBookmarksReady();
void setupBookmarks();
+ void loadRecents();
+ void saveRecents();
+ void handleChatMadeRecent();
ChatController* getChatControllerOrFindAnother(const JID &contact);
ChatController* createNewChatController(const JID &contact);
ChatController* getChatControllerOrCreate(const JID &contact);
@@ -92,5 +101,6 @@ namespace Swift {
EntityCapsProvider* entityCapsProvider_;
MUCManager* mucManager;
MUCSearchController* mucSearchController_;
+ std::vector<Chat> recentChats_;
};
}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 2914116..0604dee 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "Swift/Controllers/Chat/MUCController.h"
+#include <Swift/Controllers/Chat/MUCController.h>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
@@ -12,23 +12,24 @@
#include <Swift/Controllers/Intl.h>
#include <Swiften/Base/format.h>
-#include "Swiften/Network/Timer.h"
-#include "Swiften/Network/TimerFactory.h"
-#include "Swiften/Base/foreach.h"
-#include "SwifTools/TabComplete.h"
-#include "Swiften/Base/foreach.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Elements/Delay.h"
-#include "Swiften/MUC/MUC.h"
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swift/Controllers/Roster/Roster.h"
-#include "Swift/Controllers/Roster/SetAvatar.h"
-#include "Swift/Controllers/Roster/SetPresence.h"
+#include <Swiften/Network/Timer.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Base/foreach.h>
+#include <SwifTools/TabComplete.h>
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Elements/Delay.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/SetAvatar.h>
+#include <Swift/Controllers/Roster/SetPresence.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
@@ -206,7 +207,12 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
currentOccupants_.insert(occupant.getNick());
NickJoinPart event(occupant.getNick(), Join);
appendToJoinParts(joinParts_, event);
- roster_->addContact(jid, realJID, occupant.getNick(), roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string());
+ std::string groupName(roleToGroupName(occupant.getRole()));
+ roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid).string());
+ if (addedRosterGroups_.count(groupName) == 0) {
+ roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole()));
+ addedRosterGroups_.insert(groupName);
+ }
if (joined_) {
std::string joinString;
MUCOccupant::Role role = occupant.getRole();
@@ -248,6 +254,16 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) {
return "";
}
+std::string MUCController::roleToSortName(MUCOccupant::Role role) {
+ switch (role) {
+ case MUCOccupant::Moderator: return "1";
+ case MUCOccupant::Participant: return "2";
+ case MUCOccupant::Visitor: return "3";
+ case MUCOccupant::NoRole: return "4";
+ }
+ return "5";
+}
+
JID MUCController::nickToJID(const std::string& nick) {
return JID(toJID_.getNode(), toJID_.getDomain(), nick);
}
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index ebdd6cd..e876791 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -7,18 +7,18 @@
#pragma once
#include <boost/shared_ptr.hpp>
-#include "Swiften/Base/boost_bsignals.h"
+#include <Swiften/Base/boost_bsignals.h>
#include <boost/signals/connection.hpp>
#include <set>
#include <string>
-#include "Swiften/Network/Timer.h"
-#include "Swift/Controllers/Chat/ChatControllerBase.h"
-#include "Swiften/Elements/Message.h"
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/JID/JID.h"
-#include "Swiften/MUC/MUC.h"
-#include "Swiften/Elements/MUCOccupant.h"
+#include <Swiften/Network/Timer.h>
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/Elements/MUCOccupant.h>
namespace Swift {
class StanzaChannel;
@@ -71,6 +71,7 @@ namespace Swift {
void handleJoinFailed(boost::shared_ptr<ErrorPayload> error);
void handleJoinTimeoutTick();
std::string roleToGroupName(MUCOccupant::Role role);
+ std::string roleToSortName(MUCOccupant::Role role);
JID nickToJID(const std::string& nick);
std::string roleToFriendlyName(MUCOccupant::Role role);
void receivedActivity();
@@ -97,6 +98,7 @@ namespace Swift {
std::set<std::string> currentOccupants_;
std::vector<NickJoinPart> joinParts_;
boost::posix_time::ptime lastActivity_;
+ std::set<std::string> addedRosterGroups_;
};
}
diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp
index 743aabb..2d5c7e7 100644
--- a/Swift/Controllers/Chat/MUCSearchController.cpp
+++ b/Swift/Controllers/Chat/MUCSearchController.cpp
@@ -11,6 +11,7 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/GetDiscoItemsRequest.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index b3403d7..deac2f9 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -9,9 +9,9 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/foreach.h>
#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/RequestChatWithUserDialogUIEvent.h>
diff --git a/Swift/Controllers/ChatMessageSummarizer.cpp b/Swift/Controllers/ChatMessageSummarizer.cpp
new file mode 100644
index 0000000..682d88b
--- /dev/null
+++ b/Swift/Controllers/ChatMessageSummarizer.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/ChatMessageSummarizer.h>
+
+#include <Swiften/Base/format.h>
+#include <Swift/Controllers/Intl.h>
+#include <Swiften/Base/foreach.h>
+
+using namespace Swift;
+using namespace std;
+
+string ChatMessageSummarizer::getSummary(const string& current, const vector<UnreadPair>& unreads) {
+ vector<UnreadPair> others;
+ int currentUnread = 0;
+ int otherCount = 0;
+ foreach (UnreadPair unread, unreads) {
+ if (unread.first == current) {
+ currentUnread += unread.second;
+ } else {
+ if (unread.second > 0) {
+ otherCount += unread.second;
+ others.push_back(unread);
+ }
+ }
+ }
+ string myString(current);
+
+ if (currentUnread > 0) {
+ string result(QT_TRANSLATE_NOOP("", "%1% (%2%)"));
+ myString = str(format(result) % current % currentUnread);
+ }
+
+ if (others.size() > 1) {
+ string result(QT_TRANSLATE_NOOP("", "%1% and %2% others (%3%)"));
+ myString = str(format(result) % myString % others.size() % otherCount);
+ } else if (others.size() > 0) {
+ string result(QT_TRANSLATE_NOOP("", "%1%, %2% (%3%)"));
+ myString = str(format(result) % myString % others[0].first % otherCount);
+ }
+ return myString;
+}
diff --git a/Swift/Controllers/ChatMessageSummarizer.h b/Swift/Controllers/ChatMessageSummarizer.h
new file mode 100644
index 0000000..d4ff188
--- /dev/null
+++ b/Swift/Controllers/ChatMessageSummarizer.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <utility>
+#include <string>
+
+namespace Swift {
+typedef std::pair<std::string, int> UnreadPair;
+
+ class ChatMessageSummarizer {
+ public:
+ std::string getSummary(const std::string& current, const std::vector<UnreadPair>& unreads);
+ };
+}
diff --git a/Swift/Controllers/DiscoServiceWalker.cpp b/Swift/Controllers/DiscoServiceWalker.cpp
index ce29927..6aed6eb 100644
--- a/Swift/Controllers/DiscoServiceWalker.cpp
+++ b/Swift/Controllers/DiscoServiceWalker.cpp
@@ -6,6 +6,7 @@
#include <Swift/Controllers/DiscoServiceWalker.h>
#include <Swiften/Base/Log.h>
+#include <Swiften/Base/foreach.h>
#include <boost/bind.hpp>
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 9a35cc1..6133ffa 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -18,10 +18,8 @@
#include <Swift/Controllers/UIInterfaces/UIFactory.h>
#include "Swiften/Network/TimerFactory.h"
#include "Swift/Controllers/BuildVersion.h"
-#include "Swift/Controllers/StoragesFactory.h"
#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"
@@ -41,6 +39,7 @@
#include "Swift/Controllers/UIEvents/UIEventStream.h"
#include "Swift/Controllers/PresenceNotifier.h"
#include "Swift/Controllers/EventNotifier.h"
+#include "Swift/Controllers/Storages/StoragesFactory.h"
#include "SwifTools/Dock/Dock.h"
#include "SwifTools/Notifier/TogglableNotifier.h"
#include "Swiften/Base/foreach.h"
@@ -60,11 +59,13 @@
#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
#include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
-#include "Swift/Controllers/CertificateStorageFactory.h"
-#include "Swift/Controllers/CertificateStorageTrustChecker.h"
+#include "Swift/Controllers/Storages/CertificateStorageFactory.h"
+#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h"
#include "Swiften/Network/NetworkFactories.h"
#include <Swift/Controllers/ProfileController.h>
#include <Swift/Controllers/ContactEditController.h>
+#include <Swift/Controllers/XMPPURIController.h>
+#include "Swift/Controllers/AdHocManager.h"
namespace Swift {
@@ -84,6 +85,7 @@ MainController::MainController(
CertificateStorageFactory* certificateStorageFactory,
Dock* dock,
Notifier* notifier,
+ URIHandler* uriHandler,
bool useDelayForLatency) :
eventLoop_(eventLoop),
networkFactories_(networkFactories),
@@ -92,6 +94,7 @@ MainController::MainController(
storagesFactory_(storagesFactory),
certificateStorageFactory_(certificateStorageFactory),
settings_(settings),
+ uriHandler_(uriHandler),
loginWindow_(NULL) ,
useDelayForLatency_(useDelayForLatency) {
storages_ = NULL;
@@ -121,6 +124,8 @@ MainController::MainController(
loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_);
soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, uiEventStream_);
+ xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_);
+
std::string selectedLoginJID = settings_->getStringSetting("lastLoginJID");
bool loginAutomatically = settings_->getBoolSetting("loginAutomatically", false);
std::string cachedPassword;
@@ -167,6 +172,7 @@ MainController::~MainController() {
resetClient();
delete xmlConsoleController_;
+ delete xmppURIController_;
delete soundEventController_;
delete systemTrayController_;
delete eventController_;
@@ -262,14 +268,14 @@ void MainController::handleConnected() {
client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
client_->getDiscoManager()->setDiscoInfo(discoInfo);
-
userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_);
userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_);
+ adHocManager_ = new AdHocManager(boundJID_, uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow());
}
client_->requestRoster();
- GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(), client_->getIQRouter());
+ GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(boundJID_.toBare(), client_->getIQRouter());
discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2));
discoInfoRequest->send();
@@ -356,19 +362,25 @@ void MainController::handleInputIdleChanged(bool idle) {
}
void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificateFile, bool remember, bool loginAutomatically) {
- loginWindow_->setMessage("");
- loginWindow_->setIsLoggingIn(true);
- profileSettings_ = new ProfileSettingsProvider(username, settings_);
- profileSettings_->storeString("jid", username);
- profileSettings_->storeString("certificate", certificateFile);
- profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : "");
- settings_->storeString("lastLoginJID", username);
- settings_->storeBool("loginAutomatically", loginAutomatically);
- loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"));
jid_ = JID(username);
- password_ = password;
- certificateFile_ = certificateFile;
- performLoginFromCachedCredentials();
+ if (!jid_.isValid() || jid_.getNode().empty()) {
+ loginWindow_->setMessage(QT_TRANSLATE_NOOP("", "Username not a valid format"));
+ loginWindow_->setIsLoggingIn(false);
+ } else {
+ loginWindow_->setMessage("");
+ loginWindow_->setIsLoggingIn(true);
+ profileSettings_ = new ProfileSettingsProvider(username, settings_);
+ profileSettings_->storeString("jid", username);
+ profileSettings_->storeString("certificate", certificateFile);
+ profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : "");
+ settings_->storeString("lastLoginJID", username);
+ settings_->storeBool("loginAutomatically", loginAutomatically);
+ loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"));
+
+ password_ = password;
+ certificateFile_ = certificateFile;
+ performLoginFromCachedCredentials();
+ }
}
void MainController::handlePurgeSavedLoginRequest(const std::string& username) {
@@ -554,6 +566,7 @@ void MainController::setManagersOffline() {
void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error) {
if (!error) {
chatsManager_->setServerDiscoInfo(info);
+ adHocManager_->setServerDiscoInfo(info);
}
}
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index f402f8f..ae69e70 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -62,6 +62,10 @@ namespace Swift {
class Storages;
class StoragesFactory;
class NetworkFactories;
+ class URIHandler;
+ class XMPPURIController;
+ class AdHocManager;
+ class AdHocCommandWindowFactory;
class MainController {
public:
@@ -76,6 +80,7 @@ namespace Swift {
CertificateStorageFactory* certificateStorageFactory,
Dock* dock,
Notifier* notifier,
+ URIHandler* uriHandler,
bool useDelayForLatency);
~MainController();
@@ -123,12 +128,14 @@ namespace Swift {
SettingsProvider *settings_;
ProfileSettingsProvider* profileSettings_;
Dock* dock_;
+ URIHandler* uriHandler_;
TogglableNotifier* notifier_;
PresenceNotifier* presenceNotifier_;
EventNotifier* eventNotifier_;
RosterController* rosterController_;
EventController* eventController_;
EventWindowController* eventWindowController_;
+ AdHocManager* adHocManager_;
LoginWindow* loginWindow_;
UIEventStream* uiEventStream_;
XMLConsoleController* xmlConsoleController_;
@@ -139,6 +146,7 @@ namespace Swift {
JID boundJID_;
SystemTrayController* systemTrayController_;
SoundEventController* soundEventController_;
+ XMPPURIController* xmppURIController_;
std::string vCardPhotoHash_;
std::string password_;
std::string certificateFile_;
diff --git a/Swift/Controllers/ProfileSettingsProvider.h b/Swift/Controllers/ProfileSettingsProvider.h
index 74bcd12..8ba250c 100644
--- a/Swift/Controllers/ProfileSettingsProvider.h
+++ b/Swift/Controllers/ProfileSettingsProvider.h
@@ -7,6 +7,7 @@
#pragma once
#include "Swift/Controllers/Settings/SettingsProvider.h"
+#include <Swiften/Base/foreach.h>
namespace Swift {
diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp
index 0fe88aa..894f64b 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.cpp
+++ b/Swift/Controllers/Roster/ContactRosterItem.cpp
@@ -7,6 +7,8 @@
#include "Swift/Controllers/Roster/ContactRosterItem.h"
#include "Swift/Controllers/Roster/GroupRosterItem.h"
+#include <Swiften/Base/foreach.h>
+
namespace Swift {
diff --git a/Swift/Controllers/Roster/GroupRosterItem.cpp b/Swift/Controllers/Roster/GroupRosterItem.cpp
index f0a377a..b8fad07 100644
--- a/Swift/Controllers/Roster/GroupRosterItem.cpp
+++ b/Swift/Controllers/Roster/GroupRosterItem.cpp
@@ -12,7 +12,7 @@
namespace Swift {
-GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus) {
+GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus), manualSort_(false) {
expanded_ = true;
}
@@ -20,6 +20,19 @@ GroupRosterItem::~GroupRosterItem() {
}
+void GroupRosterItem::setManualSort(const std::string& manualSortValue) {
+ manualSort_ = true;
+ bool changed = manualSortValue_ != manualSortValue;
+ manualSortValue_ = manualSortValue;
+ if (changed) {
+ onDataChanged();
+ }
+}
+
+const std::string& GroupRosterItem::getSortableDisplayName() const {
+ return manualSort_ ? manualSortValue_ : RosterItem::getSortableDisplayName();
+}
+
bool GroupRosterItem::isExpanded() const {
return expanded_;
}
diff --git a/Swift/Controllers/Roster/GroupRosterItem.h b/Swift/Controllers/Roster/GroupRosterItem.h
index 57fa9fa..beb7705 100644
--- a/Swift/Controllers/Roster/GroupRosterItem.h
+++ b/Swift/Controllers/Roster/GroupRosterItem.h
@@ -31,6 +31,8 @@ class GroupRosterItem : public RosterItem {
void setExpanded(bool expanded);
bool isExpanded() const;
boost::signal<void (bool)> onExpandedChanged;
+ void setManualSort(const std::string& manualSortValue);
+ virtual const std::string& getSortableDisplayName() const;
private:
void handleChildrenChanged(GroupRosterItem* group);
void handleDataChanged(RosterItem* item);
@@ -40,6 +42,8 @@ class GroupRosterItem : public RosterItem {
std::vector<RosterItem*> children_;
std::vector<RosterItem*> displayedChildren_;
bool sortByStatus_;
+ bool manualSort_;
+ std::string manualSortValue_;
};
}
diff --git a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp
index c1045ee..0a242ae 100644
--- a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp
+++ b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp
@@ -9,6 +9,7 @@
#include <boost/bind.hpp>
#include <vector>
+#include <Swiften/Base/foreach.h>
#include "Swiften/Base/String.h"
#include "Swift/Controllers/Roster/GroupRosterItem.h"
diff --git a/Swift/Controllers/Roster/RosterItem.cpp b/Swift/Controllers/Roster/RosterItem.cpp
index 3f130bb..77db8a3 100644
--- a/Swift/Controllers/Roster/RosterItem.cpp
+++ b/Swift/Controllers/Roster/RosterItem.cpp
@@ -33,11 +33,11 @@ void RosterItem::setDisplayName(const std::string& name) {
onDataChanged();
}
-std::string RosterItem::getDisplayName() const {
+const std::string& RosterItem::getDisplayName() const {
return name_;
}
-std::string RosterItem::getSortableDisplayName() const {
+const std::string& RosterItem::getSortableDisplayName() const {
return sortableDisplayName_;
}
diff --git a/Swift/Controllers/Roster/RosterItem.h b/Swift/Controllers/Roster/RosterItem.h
index ed8cb16..769f72d 100644
--- a/Swift/Controllers/Roster/RosterItem.h
+++ b/Swift/Controllers/Roster/RosterItem.h
@@ -20,8 +20,8 @@ class RosterItem {
boost::signal<void ()> onDataChanged;
GroupRosterItem* getParent() const;
void setDisplayName(const std::string& name);
- std::string getDisplayName() const;
- std::string getSortableDisplayName() const;
+ const std::string& getDisplayName() const;
+ virtual const std::string& getSortableDisplayName() const;
private:
std::string name_;
std::string sortableDisplayName_;
diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
index 16f2745..ca74dbb 100644
--- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
+++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
@@ -8,6 +8,7 @@
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <Swiften/Base/foreach.h>
#include "Swift/Controllers/Roster/RosterController.h"
#include "Swift/Controllers/UnitTest/MockMainWindowFactory.h"
// #include "Swiften/Elements/Payload.h"
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 61da9fb..eed1f4d 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -44,16 +44,25 @@ if env["SCONS_STAGE"] == "build" :
"StatusTracker.cpp",
"PresenceNotifier.cpp",
"EventNotifier.cpp",
+ "AdHocManager.cpp",
"XMPPEvents/EventController.cpp",
"UIEvents/UIEvent.cpp",
"UIInterfaces/XMLConsoleWidget.cpp",
"UIInterfaces/ChatListWindow.cpp",
"PreviousStatusStore.cpp",
- "CertificateStorageFactory.cpp",
- "CertificateStorage.cpp",
- "CertificateFileStorage.cpp",
+ "Storages/CertificateStorageFactory.cpp",
+ "Storages/CertificateStorage.cpp",
+ "Storages/CertificateFileStorage.cpp",
+ "Storages/CertificateMemoryStorage.cpp",
+ "Storages/AvatarFileStorage.cpp",
+ "Storages/FileStorages.cpp",
+ "Storages/RosterFileStorage.cpp",
+ "Storages/CapsFileStorage.cpp",
+ "Storages/VCardFileStorage.cpp",
"StatusUtil.cpp",
"Translator.cpp",
+ "XMPPURIController.cpp",
+ "ChatMessageSummarizer.cpp",
])
env.Append(UNITTEST_SOURCES = [
@@ -64,4 +73,5 @@ if env["SCONS_STAGE"] == "build" :
File("Chat/UnitTest/ChatsManagerTest.cpp"),
File("Chat/UnitTest/MUCControllerTest.cpp"),
File("UnitTest/MockChatWindow.cpp"),
+ File("UnitTest/ChatMessageSummarizerTest.cpp"),
])
diff --git a/Swift/Controllers/Storages/AvatarFileStorage.cpp b/Swift/Controllers/Storages/AvatarFileStorage.cpp
new file mode 100644
index 0000000..288f6fd
--- /dev/null
+++ b/Swift/Controllers/Storages/AvatarFileStorage.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Storages/AvatarFileStorage.h>
+
+#include <iostream>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/String.h>
+#include <Swiften/StringCodecs/SHA1.h>
+#include <Swiften/StringCodecs/Hexify.h>
+
+namespace Swift {
+
+AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile) : avatarsDir(avatarsDir), avatarsFile(avatarsFile) {
+ if (boost::filesystem::exists(avatarsFile)) {
+ try {
+ boost::filesystem::ifstream file(avatarsFile);
+ std::string line;
+ if (file.is_open()) {
+ while (!file.eof()) {
+ getline(file, line);
+ std::pair<std::string, std::string> r = String::getSplittedAtFirst(line, ' ');
+ JID jid(r.second);
+ if (jid.isValid()) {
+ jidAvatars.insert(std::make_pair(jid, r.first));
+ }
+ else if (!r.first.empty() || !r.second.empty()) {
+ std::cerr << "Invalid entry in avatars file: " << r.second << std::endl;
+ }
+ }
+ }
+ }
+ catch (...) {
+ std::cerr << "Error reading avatars file" << std::endl;
+ }
+ }
+}
+
+bool AvatarFileStorage::hasAvatar(const std::string& hash) const {
+ return boost::filesystem::exists(getAvatarPath(hash));
+}
+
+void AvatarFileStorage::addAvatar(const std::string& hash, const ByteArray& avatar) {
+ assert(Hexify::hexify(SHA1::getHash(avatar)) == hash);
+
+ boost::filesystem::path avatarPath = getAvatarPath(hash);
+ if (!boost::filesystem::exists(avatarPath.parent_path())) {
+ try {
+ boost::filesystem::create_directories(avatarPath.parent_path());
+ }
+ catch (const boost::filesystem::filesystem_error& e) {
+ std::cerr << "ERROR: " << e.what() << std::endl;
+ }
+ }
+ boost::filesystem::ofstream file(avatarPath, boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out);
+ file.write(reinterpret_cast<const char*>(avatar.getData()), static_cast<std::streamsize>(avatar.getSize()));
+ file.close();
+}
+
+boost::filesystem::path AvatarFileStorage::getAvatarPath(const std::string& hash) const {
+ return avatarsDir / hash;
+}
+
+ByteArray AvatarFileStorage::getAvatar(const std::string& hash) const {
+ ByteArray data;
+ data.readFromFile(getAvatarPath(hash).string());
+ return data;
+}
+
+void AvatarFileStorage::setAvatarForJID(const JID& jid, const std::string& hash) {
+ std::pair<JIDAvatarMap::iterator, bool> r = jidAvatars.insert(std::make_pair(jid, hash));
+ if (r.second) {
+ saveJIDAvatars();
+ }
+ else if (r.first->second != hash) {
+ r.first->second = hash;
+ saveJIDAvatars();
+ }
+}
+
+std::string AvatarFileStorage::getAvatarForJID(const JID& jid) const {
+ JIDAvatarMap::const_iterator i = jidAvatars.find(jid);
+ return i == jidAvatars.end() ? "" : i->second;
+}
+
+void AvatarFileStorage::saveJIDAvatars() {
+ try {
+ boost::filesystem::ofstream file(avatarsFile);
+ for (JIDAvatarMap::const_iterator i = jidAvatars.begin(); i != jidAvatars.end(); ++i) {
+ file << i->second << " " << i->first.toString() << std::endl;
+ }
+ file.close();
+ }
+ catch (...) {
+ std::cerr << "Error writing avatars file" << std::endl;
+ }
+}
+
+}
diff --git a/Swift/Controllers/Storages/AvatarFileStorage.h b/Swift/Controllers/Storages/AvatarFileStorage.h
new file mode 100644
index 0000000..b7e73f5
--- /dev/null
+++ b/Swift/Controllers/Storages/AvatarFileStorage.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/JID/JID.h>
+#include "Swiften/Base/ByteArray.h"
+#include "Swiften/Avatars/AvatarStorage.h"
+
+namespace Swift {
+ class AvatarFileStorage : public AvatarStorage {
+ public:
+ AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile);
+
+ virtual bool hasAvatar(const std::string& hash) const;
+ virtual void addAvatar(const std::string& hash, const ByteArray& avatar);
+ virtual ByteArray getAvatar(const std::string& hash) const;
+
+ virtual boost::filesystem::path getAvatarPath(const std::string& hash) const;
+
+ virtual void setAvatarForJID(const JID& jid, const std::string& hash);
+ virtual std::string getAvatarForJID(const JID& jid) const;
+
+ private:
+ void saveJIDAvatars();
+
+ private:
+ boost::filesystem::path avatarsDir;
+ boost::filesystem::path avatarsFile;
+ typedef std::map<JID, std::string> JIDAvatarMap;
+ JIDAvatarMap jidAvatars;
+ };
+
+}
diff --git a/Swift/Controllers/Storages/CapsFileStorage.cpp b/Swift/Controllers/Storages/CapsFileStorage.cpp
new file mode 100644
index 0000000..b7593fd
--- /dev/null
+++ b/Swift/Controllers/Storages/CapsFileStorage.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Storages/CapsFileStorage.h"
+
+#include <Swiften/Entity/GenericPayloadPersister.h>
+#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h"
+#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h"
+#include "Swiften/StringCodecs/Hexify.h"
+#include "Swiften/StringCodecs/Base64.h"
+
+using namespace Swift;
+
+typedef GenericPayloadPersister<DiscoInfo, DiscoInfoParser, DiscoInfoSerializer> DiscoInfoPersister;
+
+CapsFileStorage::CapsFileStorage(const boost::filesystem::path& path) : path(path) {
+}
+
+DiscoInfo::ref CapsFileStorage::getDiscoInfo(const std::string& hash) const {
+ return DiscoInfoPersister().loadPayloadGeneric(getCapsPath(hash));
+}
+
+void CapsFileStorage::setDiscoInfo(const std::string& hash, DiscoInfo::ref discoInfo) {
+ DiscoInfo::ref bareDiscoInfo(new DiscoInfo(*discoInfo.get()));
+ bareDiscoInfo->setNode("");
+ DiscoInfoPersister().savePayload(bareDiscoInfo, getCapsPath(hash));
+}
+
+boost::filesystem::path CapsFileStorage::getCapsPath(const std::string& hash) const {
+ return path / (Hexify::hexify(Base64::decode(hash)) + ".xml");
+}
diff --git a/Swift/Controllers/Storages/CapsFileStorage.h b/Swift/Controllers/Storages/CapsFileStorage.h
new file mode 100644
index 0000000..b3757e0
--- /dev/null
+++ b/Swift/Controllers/Storages/CapsFileStorage.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include "Swiften/Disco/CapsStorage.h"
+#include <string>
+
+namespace Swift {
+ class CapsFileStorage : public CapsStorage {
+ public:
+ CapsFileStorage(const boost::filesystem::path& path);
+
+ virtual DiscoInfo::ref getDiscoInfo(const std::string& hash) const;
+ virtual void setDiscoInfo(const std::string& hash, DiscoInfo::ref discoInfo);
+
+ private:
+ boost::filesystem::path getCapsPath(const std::string& hash) const;
+
+ private:
+ boost::filesystem::path path;
+ };
+}
diff --git a/Swift/Controllers/CertificateFileStorage.cpp b/Swift/Controllers/Storages/CertificateFileStorage.cpp
index cf924ee..31af949 100644
--- a/Swift/Controllers/CertificateFileStorage.cpp
+++ b/Swift/Controllers/Storages/CertificateFileStorage.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include <Swift/Controllers/CertificateFileStorage.h>
+#include <Swift/Controllers/Storages/CertificateFileStorage.h>
#include <iostream>
#include <boost/filesystem/fstream.hpp>
diff --git a/Swift/Controllers/CertificateFileStorage.h b/Swift/Controllers/Storages/CertificateFileStorage.h
index 2b853ed..f7a60b9 100644
--- a/Swift/Controllers/CertificateFileStorage.h
+++ b/Swift/Controllers/Storages/CertificateFileStorage.h
@@ -8,7 +8,7 @@
#include <boost/filesystem.hpp>
-#include "Swift/Controllers/CertificateStorage.h"
+#include "Swift/Controllers/Storages/CertificateStorage.h"
namespace Swift {
class CertificateFactory;
diff --git a/Swift/Controllers/CertificateFileStorageFactory.h b/Swift/Controllers/Storages/CertificateFileStorageFactory.h
index 7ed8287..b215165 100644
--- a/Swift/Controllers/CertificateFileStorageFactory.h
+++ b/Swift/Controllers/Storages/CertificateFileStorageFactory.h
@@ -6,8 +6,8 @@
#pragma once
-#include <Swift/Controllers/CertificateStorageFactory.h>
-#include <Swift/Controllers/CertificateFileStorage.h>
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateFileStorage.h>
namespace Swift {
class CertificateFactory;
diff --git a/Swift/Controllers/Storages/CertificateMemoryStorage.cpp b/Swift/Controllers/Storages/CertificateMemoryStorage.cpp
new file mode 100644
index 0000000..71d7c4a
--- /dev/null
+++ b/Swift/Controllers/Storages/CertificateMemoryStorage.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Storages/CertificateMemoryStorage.h>
+
+#include <Swiften/Base/foreach.h>
+
+using namespace Swift;
+
+CertificateMemoryStorage::CertificateMemoryStorage() {
+}
+
+bool CertificateMemoryStorage::hasCertificate(Certificate::ref certificate) const {
+ foreach(Certificate::ref storedCert, certificates) {
+ if (storedCert->toDER() == certificate->toDER()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void CertificateMemoryStorage::addCertificate(Certificate::ref certificate) {
+ certificates.push_back(certificate);
+}
diff --git a/Swift/Controllers/Storages/CertificateMemoryStorage.h b/Swift/Controllers/Storages/CertificateMemoryStorage.h
new file mode 100644
index 0000000..5c0333d
--- /dev/null
+++ b/Swift/Controllers/Storages/CertificateMemoryStorage.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <Swift/Controllers/Storages/CertificateStorage.h>
+
+namespace Swift {
+ class CertificateMemoryStorage : public CertificateStorage {
+ public:
+ CertificateMemoryStorage();
+
+ virtual bool hasCertificate(Certificate::ref certificate) const;
+ virtual void addCertificate(Certificate::ref certificate);
+
+ private:
+ std::vector<Certificate::ref> certificates;
+ };
+
+}
diff --git a/Swift/Controllers/CertificateStorage.cpp b/Swift/Controllers/Storages/CertificateStorage.cpp
index 343fccd..ee942c0 100644
--- a/Swift/Controllers/CertificateStorage.cpp
+++ b/Swift/Controllers/Storages/CertificateStorage.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "Swift/Controllers/CertificateStorage.h"
+#include "Swift/Controllers/Storages/CertificateStorage.h"
namespace Swift {
diff --git a/Swift/Controllers/CertificateStorage.h b/Swift/Controllers/Storages/CertificateStorage.h
index f8c6fb5..f8c6fb5 100644
--- a/Swift/Controllers/CertificateStorage.h
+++ b/Swift/Controllers/Storages/CertificateStorage.h
diff --git a/Swift/Controllers/CertificateStorageFactory.cpp b/Swift/Controllers/Storages/CertificateStorageFactory.cpp
index 613a8c3..ba0179a 100644
--- a/Swift/Controllers/CertificateStorageFactory.cpp
+++ b/Swift/Controllers/Storages/CertificateStorageFactory.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include <Swift/Controllers/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
namespace Swift {
diff --git a/Swift/Controllers/CertificateStorageFactory.h b/Swift/Controllers/Storages/CertificateStorageFactory.h
index 5b85757..5b85757 100644
--- a/Swift/Controllers/CertificateStorageFactory.h
+++ b/Swift/Controllers/Storages/CertificateStorageFactory.h
diff --git a/Swift/Controllers/CertificateStorageTrustChecker.h b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h
index f33287c..40838dd 100644
--- a/Swift/Controllers/CertificateStorageTrustChecker.h
+++ b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h
@@ -7,7 +7,7 @@
#pragma once
#include <Swiften/TLS/CertificateTrustChecker.h>
-#include <Swift/Controllers/CertificateStorage.h>
+#include <Swift/Controllers/Storages/CertificateStorage.h>
namespace Swift {
/**
diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp
new file mode 100644
index 0000000..6447099
--- /dev/null
+++ b/Swift/Controllers/Storages/FileStorages.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Storages/FileStorages.h"
+#include "Swift/Controllers/Storages/VCardFileStorage.h"
+#include "Swift/Controllers/Storages/AvatarFileStorage.h"
+#include "Swift/Controllers/Storages/CapsFileStorage.h"
+#include "Swift/Controllers/Storages/RosterFileStorage.h"
+
+namespace Swift {
+
+FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid) {
+ std::string profile = jid.toBare();
+ vcardStorage = new VCardFileStorage(baseDir / profile / "vcards");
+ capsStorage = new CapsFileStorage(baseDir / "caps");
+ avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars");
+ rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml");
+}
+
+FileStorages::~FileStorages() {
+ delete rosterStorage;
+ delete avatarStorage;
+ delete capsStorage;
+ delete vcardStorage;
+}
+
+VCardStorage* FileStorages::getVCardStorage() const {
+ return vcardStorage;
+}
+
+CapsStorage* FileStorages::getCapsStorage() const {
+ return capsStorage;
+}
+
+AvatarStorage* FileStorages::getAvatarStorage() const {
+ return avatarStorage;
+}
+
+RosterStorage* FileStorages::getRosterStorage() const {
+ return rosterStorage;
+}
+
+}
diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h
new file mode 100644
index 0000000..28df314
--- /dev/null
+++ b/Swift/Controllers/Storages/FileStorages.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include "Swiften/Client/Storages.h"
+
+namespace Swift {
+ class VCardFileStorage;
+ class AvatarFileStorage;
+ class CapsFileStorage;
+ class RosterFileStorage;
+ class JID;
+
+ /**
+ * A storages implementation that stores all controller data on disk.
+ */
+ class FileStorages : public Storages {
+ public:
+ /**
+ * Creates the storages interface.
+ *
+ * All data will be stored relative to a base directory, and
+ * for some controllers, in a subdirectory for the given profile.
+ * The data is stored in the following places:
+ * - Avatars: <basedir>/avatars
+ * - VCards: <basedir>/<profile>/vcards
+ * - Entity capabilities: <basedir>/caps
+ *
+ * \param baseDir the base dir to store data relative to
+ * \param jid the subdir in which profile-specific data will be stored.
+ * The bare JID will be used as the subdir name.
+ */
+ FileStorages(const boost::filesystem::path& baseDir, const JID& jid);
+ ~FileStorages();
+
+ virtual VCardStorage* getVCardStorage() const;
+ virtual AvatarStorage* getAvatarStorage() const;
+ virtual CapsStorage* getCapsStorage() const;
+ virtual RosterStorage* getRosterStorage() const;
+
+ private:
+ VCardFileStorage* vcardStorage;
+ AvatarFileStorage* avatarStorage;
+ CapsFileStorage* capsStorage;
+ RosterFileStorage* rosterStorage;
+ };
+}
diff --git a/Swift/Controllers/FileStoragesFactory.h b/Swift/Controllers/Storages/FileStoragesFactory.h
index bd7cdfb..0676bc3 100644
--- a/Swift/Controllers/FileStoragesFactory.h
+++ b/Swift/Controllers/Storages/FileStoragesFactory.h
@@ -6,8 +6,8 @@
#pragma once
-#include "Swift/Controllers/StoragesFactory.h"
-#include "Swiften/Client/FileStorages.h"
+#include "Swift/Controllers/Storages/StoragesFactory.h"
+#include "Swift/Controllers/Storages/FileStorages.h"
namespace Swift {
class FileStoragesFactory : public StoragesFactory {
diff --git a/Swift/Controllers/MemoryStoragesFactory.h b/Swift/Controllers/Storages/MemoryStoragesFactory.h
index 8408e10..0dea349 100644
--- a/Swift/Controllers/MemoryStoragesFactory.h
+++ b/Swift/Controllers/Storages/MemoryStoragesFactory.h
@@ -6,10 +6,12 @@
#pragma once
-#include "Swift/Controllers/StoragesFactory.h"
+#include "Swift/Controllers/Storages/StoragesFactory.h"
#include "Swiften/Client/MemoryStorages.h"
namespace Swift {
+ class JID;
+
class MemoryStoragesFactory : public StoragesFactory {
public:
MemoryStoragesFactory() {}
diff --git a/Swift/Controllers/Storages/RosterFileStorage.cpp b/Swift/Controllers/Storages/RosterFileStorage.cpp
new file mode 100644
index 0000000..73e582f
--- /dev/null
+++ b/Swift/Controllers/Storages/RosterFileStorage.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Storages/RosterFileStorage.h>
+
+#include <Swiften/Entity/GenericPayloadPersister.h>
+#include <Swiften/Serializer/PayloadSerializers/RosterSerializer.h>
+#include <Swiften/Parser/PayloadParsers/RosterParser.h>
+
+using namespace Swift;
+
+typedef GenericPayloadPersister<RosterPayload, RosterParser, RosterSerializer> RosterPersister;
+
+RosterFileStorage::RosterFileStorage(const boost::filesystem::path& path) : path(path) {
+}
+
+boost::shared_ptr<RosterPayload> RosterFileStorage::getRoster() const {
+ return RosterPersister().loadPayloadGeneric(path);
+}
+
+void RosterFileStorage::setRoster(boost::shared_ptr<RosterPayload> roster) {
+ RosterPersister().savePayload(roster, path);
+}
diff --git a/Swift/Controllers/Storages/RosterFileStorage.h b/Swift/Controllers/Storages/RosterFileStorage.h
new file mode 100644
index 0000000..cb00969
--- /dev/null
+++ b/Swift/Controllers/Storages/RosterFileStorage.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/Roster/RosterStorage.h>
+
+namespace Swift {
+ class RosterFileStorage : public RosterStorage {
+ public:
+ RosterFileStorage(const boost::filesystem::path& path);
+
+ virtual boost::shared_ptr<RosterPayload> getRoster() const;
+ virtual void setRoster(boost::shared_ptr<RosterPayload>);
+
+ private:
+ boost::filesystem::path path;
+ };
+}
diff --git a/Swift/Controllers/StoragesFactory.h b/Swift/Controllers/Storages/StoragesFactory.h
index 441a4e9..203f9c9 100644
--- a/Swift/Controllers/StoragesFactory.h
+++ b/Swift/Controllers/Storages/StoragesFactory.h
@@ -8,6 +8,7 @@
namespace Swift {
class Storages;
+ class JID;
class StoragesFactory {
public:
diff --git a/Swift/Controllers/Storages/VCardFileStorage.cpp b/Swift/Controllers/Storages/VCardFileStorage.cpp
new file mode 100644
index 0000000..4933d0c
--- /dev/null
+++ b/Swift/Controllers/Storages/VCardFileStorage.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Storages/VCardFileStorage.h"
+
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem.hpp>
+#include <iostream>
+
+#include <Swiften/Entity/GenericPayloadPersister.h>
+#include <Swiften/Base/String.h>
+#include <Swiften/StringCodecs/Hexify.h>
+#include <Swiften/StringCodecs/SHA1.h>
+#include <Swiften/Base/foreach.h>
+#include "Swiften/JID/JID.h"
+#include "Swiften/Elements/VCard.h"
+#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h"
+#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h"
+#include "Swiften/Parser/PayloadParsers/VCardParser.h"
+
+using namespace Swift;
+
+typedef GenericPayloadPersister<VCard, VCardParser, VCardSerializer> VCardPersister;
+
+VCardFileStorage::VCardFileStorage(boost::filesystem::path dir) : vcardsPath(dir) {
+ cacheFile = vcardsPath / "phashes";
+ if (boost::filesystem::exists(cacheFile)) {
+ try {
+ boost::filesystem::ifstream file(cacheFile);
+ std::string line;
+ if (file.is_open()) {
+ while (!file.eof()) {
+ getline(file, line);
+ std::pair<std::string, std::string> r = String::getSplittedAtFirst(line, ' ');
+ JID jid(r.second);
+ if (jid.isValid()) {
+ photoHashes.insert(std::make_pair(jid, r.first));
+ }
+ else if (!r.first.empty() || !r.second.empty()) {
+ std::cerr << "Invalid entry in phashes file" << std::endl;
+ }
+ }
+ }
+ }
+ catch (...) {
+ std::cerr << "Error reading phashes file" << std::endl;
+ }
+ }
+}
+
+boost::shared_ptr<VCard> VCardFileStorage::getVCard(const JID& jid) const {
+ return VCardPersister().loadPayloadGeneric(getVCardPath(jid));
+}
+
+void VCardFileStorage::setVCard(const JID& jid, VCard::ref v) {
+ VCardPersister().savePayload(v, getVCardPath(jid));
+ getAndUpdatePhotoHash(jid, v);
+}
+
+boost::filesystem::path VCardFileStorage::getVCardPath(const JID& jid) const {
+ std::string file(jid.toString());
+ String::replaceAll(file, '/', "%2f");
+ return boost::filesystem::path(vcardsPath / (file + ".xml"));
+}
+
+std::string VCardFileStorage::getPhotoHash(const JID& jid) const {
+ PhotoHashMap::const_iterator i = photoHashes.find(jid);
+ if (i != photoHashes.end()) {
+ return i->second;
+ }
+ else {
+ VCard::ref vCard = getVCard(jid);
+ return getAndUpdatePhotoHash(jid, vCard);
+ }
+}
+
+std::string VCardFileStorage::getAndUpdatePhotoHash(const JID& jid, VCard::ref vCard) const {
+ std::string hash;
+ if (vCard && !vCard->getPhoto().isEmpty()) {
+ hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
+ }
+ std::pair<PhotoHashMap::iterator, bool> r = photoHashes.insert(std::make_pair(jid, hash));
+ if (r.second) {
+ savePhotoHashes();
+ }
+ else if (r.first->second != hash) {
+ r.first->second = hash;
+ savePhotoHashes();
+ }
+ return hash;
+}
+
+void VCardFileStorage::savePhotoHashes() const {
+ try {
+ boost::filesystem::ofstream file(cacheFile);
+ for (PhotoHashMap::const_iterator i = photoHashes.begin(); i != photoHashes.end(); ++i) {
+ file << i->second << " " << i->first.toString() << std::endl;
+ }
+ file.close();
+ }
+ catch (...) {
+ std::cerr << "Error writing vcards file" << std::endl;
+ }
+}
diff --git a/Swift/Controllers/Storages/VCardFileStorage.h b/Swift/Controllers/Storages/VCardFileStorage.h
new file mode 100644
index 0000000..ba422f4
--- /dev/null
+++ b/Swift/Controllers/Storages/VCardFileStorage.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <boost/filesystem/path.hpp>
+#include <string>
+#include <map>
+
+#include "Swiften/VCards/VCardStorage.h"
+
+namespace Swift {
+ class VCardFileStorage : public VCardStorage {
+ public:
+ VCardFileStorage(boost::filesystem::path dir);
+
+ virtual VCard::ref getVCard(const JID& jid) const;
+ virtual void setVCard(const JID& jid, VCard::ref v);
+
+ virtual std::string getPhotoHash(const JID&) const;
+
+ private:
+ boost::filesystem::path getVCardPath(const JID&) const;
+
+ std::string getAndUpdatePhotoHash(const JID& jid, VCard::ref vcard) const;
+ void savePhotoHashes() const;
+
+ private:
+ boost::filesystem::path vcardsPath;
+ boost::filesystem::path cacheFile;
+ typedef std::map<JID, std::string> PhotoHashMap;
+ mutable PhotoHashMap photoHashes;
+ };
+}
diff --git a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h
new file mode 100644
index 0000000..c3b4b49
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.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 <Swift/Controllers/UIInterfaces/MainWindow.h>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+ class RequestAdHocUIEvent : public UIEvent {
+ public:
+ RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {};
+ const DiscoItems::Item& getCommand() const {return command_;}
+ private:
+ DiscoItems::Item command_;
+ };
+}
diff --git a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
index dd2ff6c..2c7b105 100644
--- a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
+++ b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
@@ -6,18 +6,25 @@
#pragma once
-#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
-
#include <string>
+
#include <Swift/Controllers/UIEvents/UIEvent.h>
+#include <Swiften/JID/JID.h>
namespace Swift {
class RequestJoinMUCUIEvent : public UIEvent {
public:
typedef boost::shared_ptr<RequestJoinMUCUIEvent> ref;
- RequestJoinMUCUIEvent() {
+ RequestJoinMUCUIEvent(const JID& room = JID()) : room(room) {
}
+
+ const JID& getRoom() const {
+ return room;
+ }
+
+ private:
+ JID room;
};
}
diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h
new file mode 100644
index 0000000..f7a5d39
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+ class AdHocCommandWindow {
+ public:
+ virtual ~AdHocCommandWindow() {};
+ };
+}
diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h
new file mode 100644
index 0000000..ae77180
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h>
+#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h>
+
+namespace Swift {
+ class AdHocCommandWindowFactory {
+ public:
+ virtual ~AdHocCommandWindowFactory() {}
+ /**
+ * The UI should deal with the lifetime of this window (i.e. DeleteOnClose),
+ * so the result isn't returned.
+ */
+ virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0;
+ };
+}
diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h
index 2fd463b..93584e7 100644
--- a/Swift/Controllers/UIInterfaces/MainWindow.h
+++ b/Swift/Controllers/UIInterfaces/MainWindow.h
@@ -9,6 +9,7 @@
#include <string>
#include "Swiften/JID/JID.h"
#include "Swiften/Elements/StatusShow.h"
+#include "Swiften/Elements/DiscoItems.h"
#include "Swiften/Base/boost_bsignals.h"
#include <boost/shared_ptr.hpp>
@@ -33,6 +34,7 @@ namespace Swift {
/** Must be able to cope with NULL to clear the roster */
virtual void setRosterModel(Roster* roster) = 0;
virtual void setConnecting() = 0;
+ virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) = 0;
boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest;
boost::signal<void ()> onSignOutRequest;
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index 9b36ac5..57f55d0 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -17,6 +17,7 @@
#include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h>
#include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>
namespace Swift {
class UIFactory :
@@ -30,7 +31,8 @@ namespace Swift {
public UserSearchWindowFactory,
public JoinMUCWindowFactory,
public ProfileWindowFactory,
- public ContactEditWindowFactory {
+ public ContactEditWindowFactory,
+ public AdHocCommandWindowFactory {
public:
virtual ~UIFactory() {}
};
diff --git a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp
new file mode 100644
index 0000000..ee0ee9f
--- /dev/null
+++ b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "Swift/Controllers/ChatMessageSummarizer.h"
+
+using namespace Swift;
+using namespace std;
+
+class ChatMessageSummarizerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(ChatMessageSummarizerTest);
+ CPPUNIT_TEST(testEmpty);
+ CPPUNIT_TEST(testCurrentNone);
+ CPPUNIT_TEST(testCurrentCount);
+ CPPUNIT_TEST(testCurrentCountOthersNone);
+ CPPUNIT_TEST(testCurrentCountOtherCount);
+ CPPUNIT_TEST(testCurrentNoneOtherCount);
+ CPPUNIT_TEST(testCurrentCountOthersCount);
+ CPPUNIT_TEST(testCurrentNoneOthersCount);
+ CPPUNIT_TEST(testCurrentCountSomeOthersCount);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ChatMessageSummarizerTest() {};
+
+ void setUp() {
+
+ }
+
+ void testEmpty() {
+ string current("");
+ vector<UnreadPair> unreads;
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(current, summary.getSummary(current, unreads));
+ }
+
+ void testCurrentNone() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bob", 0));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(current, summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bob", 3));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (3)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountOthersNone() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 0));
+ unreads.push_back(UnreadPair("Bob", 3));
+ unreads.push_back(UnreadPair("Betty", 0));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (3)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountOtherCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 0));
+ unreads.push_back(UnreadPair("Bob", 3));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (3), Betty (7)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentNoneOtherCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 0));
+ unreads.push_back(UnreadPair("Bob", 0));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob, Betty (7)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentNoneOthersCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 2));
+ unreads.push_back(UnreadPair("Bob", 0));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob and 2 others (9)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountOthersCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 2));
+ unreads.push_back(UnreadPair("Bob", 11));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (11) and 2 others (9)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountSomeOthersCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 2));
+ unreads.push_back(UnreadPair("Beverly", 0));
+ unreads.push_back(UnreadPair("Bob", 11));
+ unreads.push_back(UnreadPair("Beatrice", 0));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (11) and 2 others (9)"), summary.getSummary(current, unreads));
+ }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageSummarizerTest);
diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h
index afa5c2a..f773062 100644
--- a/Swift/Controllers/UnitTest/MockMainWindow.h
+++ b/Swift/Controllers/UnitTest/MockMainWindow.h
@@ -20,6 +20,7 @@ namespace Swift {
virtual void setMyAvatarPath(const std::string& /*path*/) {};
virtual void setMyStatusText(const std::string& /*status*/) {};
virtual void setMyStatusType(StatusShow::Type /*type*/) {};
+ virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& /*commands*/) {};
virtual void setConnecting() {};
Roster* roster;
diff --git a/Swift/Controllers/XMPPEvents/EventController.cpp b/Swift/Controllers/XMPPEvents/EventController.cpp
index 7f8f216..98fd634 100644
--- a/Swift/Controllers/XMPPEvents/EventController.cpp
+++ b/Swift/Controllers/XMPPEvents/EventController.cpp
@@ -9,6 +9,7 @@
#include <boost/bind.hpp>
#include <algorithm>
+#include <Swiften/Base/foreach.h>
#include "Swift/Controllers/XMPPEvents/MessageEvent.h"
#include "Swift/Controllers/XMPPEvents/ErrorEvent.h"
#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h"
diff --git a/Swift/Controllers/XMPPURIController.cpp b/Swift/Controllers/XMPPURIController.cpp
new file mode 100644
index 0000000..00759b8
--- /dev/null
+++ b/Swift/Controllers/XMPPURIController.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/XMPPURIController.h>
+
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <SwifTools/URIHandler/URIHandler.h>
+#include <SwifTools/URIHandler/XMPPURI.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+
+using namespace Swift;
+
+XMPPURIController::XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream) : uriHandler(uriHandler), uiEventStream(uiEventStream) {
+ uriHandler->onURI.connect(boost::bind(&XMPPURIController::handleURI, this, _1));
+}
+
+XMPPURIController::~XMPPURIController() {
+ uriHandler->onURI.disconnect(boost::bind(&XMPPURIController::handleURI, this, _1));
+}
+
+void XMPPURIController::handleURI(const std::string& s) {
+ XMPPURI uri = XMPPURI::fromString(s);
+ if (!uri.isNull()) {
+ if (uri.getQueryType() == "join") {
+ uiEventStream->send(boost::make_shared<RequestJoinMUCUIEvent>(uri.getPath()));
+ }
+ else {
+ uiEventStream->send(boost::make_shared<RequestChatUIEvent>(uri.getPath()));
+ }
+ }
+}
diff --git a/Swift/Controllers/XMPPURIController.h b/Swift/Controllers/XMPPURIController.h
new file mode 100644
index 0000000..54534d4
--- /dev/null
+++ b/Swift/Controllers/XMPPURIController.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <Swiften/Base/boost_bsignals.h>
+
+namespace Swift {
+ class URIHandler;
+ class JID;
+ class UIEventStream;
+
+ class XMPPURIController {
+ public:
+ XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream);
+ ~XMPPURIController();
+
+ boost::signal<void (const JID&)> onStartChat;
+ boost::signal<void (const JID&)> onJoinMUC;
+
+ private:
+ void handleURI(const std::string&);
+
+ private:
+ URIHandler* uriHandler;
+ UIEventStream* uiEventStream;
+ };
+}
diff --git a/Swift/QtUI/.gitignore b/Swift/QtUI/.gitignore
index f539e86..53acb9f 100644
--- a/Swift/QtUI/.gitignore
+++ b/Swift/QtUI/.gitignore
@@ -1,3 +1,4 @@
Swift
BuildVersion.h
*.dmg
+swift-open-uri
diff --git a/Swift/QtUI/QtAdHocCommandWindow.cpp b/Swift/QtUI/QtAdHocCommandWindow.cpp
new file mode 100644
index 0000000..a3bb077
--- /dev/null
+++ b/Swift/QtUI/QtAdHocCommandWindow.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/QtAdHocCommandWindow.h>
+
+#include <boost/bind.hpp>
+#include <QBoxLayout>
+#include <Swift/QtUI/QtFormWidget.h>
+#include <Swiften/Elements/Command.h>
+
+namespace Swift {
+QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) : command_(command) {
+
+ formWidget_ = NULL;
+
+ setAttribute(Qt::WA_DeleteOnClose);
+ command->onNextStageReceived.connect(boost::bind(&QtAdHocCommandWindow::handleNextStageReceived, this, _1));
+ command->onError.connect(boost::bind(&QtAdHocCommandWindow::handleError, this, _1));
+ command->start();
+
+ QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
+ layout->setContentsMargins(0,0,0,0);
+ layout->setSpacing(2);
+ label_ = new QLabel(this);
+ layout->addWidget(label_);
+ QWidget* formContainer = new QWidget(this);
+ layout->addWidget(formContainer);
+ formLayout_ = new QBoxLayout(QBoxLayout::TopToBottom, formContainer);
+ QWidget* buttonsWidget = new QWidget(this);
+ layout->addWidget(buttonsWidget);
+
+ QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget);
+ cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget);
+ buttonsLayout->addWidget(cancelButton_);
+ connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked()));
+ backButton_ = new QPushButton(tr("Back"), buttonsWidget);
+ buttonsLayout->addWidget(backButton_);
+ connect(backButton_, SIGNAL(clicked()), this, SLOT(handlePrevClicked()));
+ nextButton_ = new QPushButton(tr("Next"), buttonsWidget);
+ buttonsLayout->addWidget(nextButton_);
+ connect(nextButton_, SIGNAL(clicked()), this, SLOT(handleNextClicked()));
+ completeButton_ = new QPushButton(tr("Complete"), buttonsWidget);
+ buttonsLayout->addWidget(completeButton_);
+ connect(completeButton_, SIGNAL(clicked()), this, SLOT(handleCompleteClicked()));
+ nextButton_->setEnabled(false);
+ backButton_->setEnabled(false);
+ completeButton_->setEnabled(false);
+ actions_[Command::Next] = nextButton_;
+ actions_[Command::Prev] = backButton_;
+ actions_[Command::Complete] = completeButton_;
+ actions_[Command::Cancel] = cancelButton_;
+ show();
+}
+
+QtAdHocCommandWindow::~QtAdHocCommandWindow() {
+
+}
+
+void QtAdHocCommandWindow::handleCancelClicked() {
+ command_->cancel();
+}
+
+void QtAdHocCommandWindow::handlePrevClicked() {
+ command_->goBack();
+}
+
+void QtAdHocCommandWindow::handleNextClicked() {
+ command_->goNext(formWidget_ ? formWidget_->getCompletedForm() : Form::ref());
+}
+
+void QtAdHocCommandWindow::handleCompleteClicked() {
+ command_->complete(formWidget_ ? formWidget_->getCompletedForm() : Form::ref());
+}
+
+void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) {
+ if (command->getForm()) {
+ setForm(command->getForm());
+ } else {
+ setNoForm();
+ }
+ QString notes;
+ foreach (Command::Note note, command->getNotes()) {
+ if (!notes.isEmpty()) {
+ notes += "\n";
+ QString qNote(note.note.c_str());
+ switch (note.type) {
+ case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break;
+ case Command::Note::Warn: notes += tr("Warning: %1").arg(qNote); break;
+ case Command::Note::Info: notes += qNote; break;
+ }
+ }
+ }
+ label_->setText(notes);
+ setAvailableActions(command);
+}
+
+void QtAdHocCommandWindow::handleError(ErrorPayload::ref /*error*/) {
+ nextButton_->setEnabled(false);
+ backButton_->setEnabled(false);
+ completeButton_->setEnabled(false);
+ label_->setText(tr("Error executing command"));
+}
+
+void QtAdHocCommandWindow::setForm(Form::ref form) {
+ delete formWidget_;
+ formWidget_ = new QtFormWidget(form, this);
+ formLayout_->addWidget(formWidget_);
+}
+
+void QtAdHocCommandWindow::setNoForm() {
+ delete formWidget_;
+}
+
+typedef std::pair<Command::Action, QPushButton*> ActionButton;
+
+void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) {
+ foreach (ActionButton pair, actions_) {
+ OutgoingAdHocCommandSession::ActionState state = command_->getActionState(pair.first);
+ if (state & OutgoingAdHocCommandSession::Present) {
+ pair.second->show();
+ }
+ else {
+ pair.second->hide();
+ }
+ if (state & OutgoingAdHocCommandSession::Enabled) {
+ pair.second->setEnabled(true);
+ }
+ else {
+ pair.second->setEnabled(false);
+ }
+ }
+}
+
+}
diff --git a/Swift/QtUI/QtAdHocCommandWindow.h b/Swift/QtUI/QtAdHocCommandWindow.h
new file mode 100644
index 0000000..adeb3e6
--- /dev/null
+++ b/Swift/QtUI/QtAdHocCommandWindow.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+#include <QPushButton>
+#include <QLabel>
+
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h>
+#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h>
+
+class QBoxLayout;
+
+namespace Swift {
+ class QtFormWidget;
+ class QtAdHocCommandWindow : public QWidget, public AdHocCommandWindow {
+ Q_OBJECT
+ public:
+ QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
+ virtual ~QtAdHocCommandWindow();
+ private:
+ void handleNextStageReceived(Command::ref command);
+ void handleError(ErrorPayload::ref error);
+ void setForm(Form::ref);
+ void setNoForm();
+ void setAvailableActions(Command::ref commandResult);
+ private slots:
+ void handleCancelClicked();
+ void handlePrevClicked();
+ void handleNextClicked();
+ void handleCompleteClicked();
+ private:
+ boost::shared_ptr<OutgoingAdHocCommandSession> command_;
+ QtFormWidget* formWidget_;
+ QBoxLayout* formLayout_;
+ Form::ref form_;
+ QLabel* label_;
+ QPushButton* backButton_;
+ QPushButton* nextButton_;
+ QPushButton* completeButton_;
+ QPushButton* cancelButton_;
+ std::map<Command::Action, QPushButton*> actions_;
+ };
+}
diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp
index 25c7ca2..249080b 100644
--- a/Swift/QtUI/QtChatTabs.cpp
+++ b/Swift/QtUI/QtChatTabs.cpp
@@ -7,6 +7,10 @@
#include "QtChatTabs.h"
#include <algorithm>
+#include <vector>
+
+#include <Swift/Controllers/ChatMessageSummarizer.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
#include <QCloseEvent>
#include <QDesktopWidget>
@@ -236,16 +240,18 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
default : tabTextColor = QColor();
}
tabs_->tabBar()->setTabTextColor(index, tabTextColor);
- int unread = 0;
+
+ std::vector<std::pair<std::string, int> > unreads;
for (int i = 0; i < tabs_->count(); i++) {
QtTabbable* tab = qobject_cast<QtTabbable*>(tabs_->widget(i));
if (tab) {
- unread += tab->getCount();
+ unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount()));
}
}
- QtTabbable* current = qobject_cast<QtTabbable*>(tabs_->currentWidget());
- setWindowTitle(unread > 0 ? QString("(%1) %2").arg(unread).arg(current->windowTitle()) : current->windowTitle());
+ std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(tabs_->currentWidget())->windowTitle()));
+ ChatMessageSummarizer summary;
+ setWindowTitle(summary.getSummary(current, unreads).c_str());
}
void QtChatTabs::flash() {
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 521b072..4846af6 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -106,6 +106,19 @@ void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
//qApp->processEvents();
}
+void QtChatView::addLastSeenLine() {
+ if (lineSeparator_.isNull()) {
+ lineSeparator_ = newInsertPoint_.clone();
+ lineSeparator_.setInnerXml(QString("<hr/>"));
+ newInsertPoint_.prependOutside(lineSeparator_);
+ }
+ else {
+ QWebElement lineSeparatorC = lineSeparator_.clone();
+ lineSeparatorC.removeFromDocument();
+ }
+ newInsertPoint_.prependOutside(lineSeparator_);
+}
+
void QtChatView::replaceLastMessage(const QString& newMessage) {
assert(viewReady_);
/* FIXME: must be queued? */
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index 58b33df..ce12ca8 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -26,8 +26,8 @@ namespace Swift {
Q_OBJECT
public:
QtChatView(QtChatTheme* theme, QWidget* parent);
-
void addMessage(boost::shared_ptr<ChatSnippet> snippet);
+ void addLastSeenLine();
void replaceLastMessage(const QString& newMessage);
void replaceLastMessage(const QString& newMessage, const QString& note);
void rememberScrolledToBottom();
@@ -63,6 +63,7 @@ namespace Swift {
QtChatTheme* theme_;
QWebElement newInsertPoint_;
+ QWebElement lineSeparator_;
QWebElement lastElement_;
QWebElement document_;
};
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 1a909fd..312ec65 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -40,7 +40,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(2);
-
+
QSplitter *logRosterSplitter = new QSplitter(this);
logRosterSplitter->setAutoFillBackground(true);
@@ -72,7 +72,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
input_ = new QtTextEdit(this);
input_->setAcceptRichText(false);
layout->addWidget(input_);
-
+
inputClearing_ = false;
contactIsTyping_ = false;
@@ -150,7 +150,7 @@ void QtChatWindow::tabComplete() {
}
void QtChatWindow::setRosterModel(Roster* roster) {
- treeWidget_->setRosterModel(roster);
+ treeWidget_->setRosterModel(roster);
}
void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {
@@ -204,10 +204,13 @@ void QtChatWindow::qAppFocusChanged(QWidget *old, QWidget *now) {
Q_UNUSED(old);
Q_UNUSED(now);
if (isWidgetSelected()) {
+ lastLineTracker_.setHasFocus(true);
input_->setFocus();
onAllMessagesRead();
}
-
+ else {
+ lastLineTracker_.setHasFocus(false);
+ }
}
void QtChatWindow::setInputEnabled(bool enabled) {
@@ -236,7 +239,7 @@ void QtChatWindow::setContactChatState(ChatState::ChatStateType state) {
QtTabbable::AlertType QtChatWindow::getWidgetAlertState() {
if (contactIsTyping_) {
return ImpendingActivity;
- }
+ }
if (unreadCount_ > 0) {
return WaitingActivity;
}
@@ -265,7 +268,6 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri
if (isWidgetSelected()) {
onAllMessagesRead();
}
-
QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
QString htmlString;
@@ -281,6 +283,12 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri
htmlString += styleSpanStart + messageHTML + styleSpanEnd;
bool appendToPrevious = !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName)));
+ if (lastLineTracker_.getShouldMoveLastLine()) {
+ /* should this be queued? */
+ messageLog_->addLastSeenLine();
+ /* if the line is added we should break the snippet */
+ appendToPrevious = false;
+ }
QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
std::string id = id_.generateID();
messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
@@ -399,7 +407,7 @@ void QtChatWindow::resizeEvent(QResizeEvent*) {
}
void QtChatWindow::moveEvent(QMoveEvent*) {
- emit geometryChanged();
+ emit geometryChanged();
}
void QtChatWindow::replaceLastMessage(const std::string& message) {
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 910019b..9e3aeb3 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -4,13 +4,14 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFT_QtChatWindow_H
-#define SWIFT_QtChatWindow_H
+#pragma once
#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
#include "QtTabbable.h"
+#include "SwifTools/LastLineTracker.h"
+
#include "Swiften/Base/IDGenerator.h"
class QTextEdit;
@@ -79,6 +80,7 @@ namespace Swift {
int unreadCount_;
bool contactIsTyping_;
+ LastLineTracker lastLineTracker_;
QString contact_;
QtChatView* messageLog_;
QtChatTheme* theme_;
@@ -97,5 +99,3 @@ namespace Swift {
IDGenerator id_;
};
}
-
-#endif
diff --git a/Swift/QtUI/QtDBUSURIHandler.cpp b/Swift/QtUI/QtDBUSURIHandler.cpp
new file mode 100644
index 0000000..9b69ca6
--- /dev/null
+++ b/Swift/QtUI/QtDBUSURIHandler.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtDBUSURIHandler.h"
+
+#include <QDBusAbstractAdaptor>
+#include <QDBusConnection>
+
+#include "QtSwiftUtil.h"
+
+using namespace Swift;
+
+namespace {
+ class DBUSAdaptor: public QDBusAbstractAdaptor {
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler");
+ public:
+ DBUSAdaptor(QtDBUSURIHandler* uriHandler) : QDBusAbstractAdaptor(uriHandler), uriHandler(uriHandler) {
+ }
+
+ public slots:
+ void openURI(const QString& uri) {
+ uriHandler->onURI(Q2PSTRING(uri));
+ }
+
+ private:
+ QtDBUSURIHandler* uriHandler;
+ };
+}
+
+QtDBUSURIHandler::QtDBUSURIHandler() {
+ new DBUSAdaptor(this);
+ QDBusConnection connection = QDBusConnection::sessionBus();
+ connection.registerService("im.swift.Swift.URIHandler");
+ connection.registerObject("/", this);
+}
+
+#include "QtDBUSURIHandler.moc"
diff --git a/Swift/QtUI/QtDBUSURIHandler.h b/Swift/QtUI/QtDBUSURIHandler.h
new file mode 100644
index 0000000..be1872e
--- /dev/null
+++ b/Swift/QtUI/QtDBUSURIHandler.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <SwifTools/URIHandler/URIHandler.h>
+
+namespace Swift {
+ class QtDBUSURIHandler : public QObject, public URIHandler {
+ public:
+ QtDBUSURIHandler();
+ };
+}
diff --git a/Swift/QtUI/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp
new file mode 100644
index 0000000..050ff27
--- /dev/null
+++ b/Swift/QtUI/QtFormWidget.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/QtFormWidget.h>
+
+#include <QGridLayout>
+#include <QLabel>
+#include <QListWidget>
+#include <QLineEdit>
+#include <QTextEdit>
+#include <QCheckBox>
+#include <QScrollArea>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swiften/Base/foreach.h>
+
+namespace Swift {
+
+QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), form_(form) {
+ QGridLayout* thisLayout = new QGridLayout(this);
+ int row = 0;
+ if (!form->getTitle().empty()) {
+ QLabel* instructions = new QLabel(("<b>" + form->getTitle() + "</b>").c_str(), this);
+ thisLayout->addWidget(instructions, row++, 0, 1, 2);
+ }
+ if (!form->getInstructions().empty()) {
+ QLabel* instructions = new QLabel(form->getInstructions().c_str(), this);
+ thisLayout->addWidget(instructions, row++, 0, 1, 2);
+ }
+ QScrollArea* scrollArea = new QScrollArea(this);
+ thisLayout->addWidget(scrollArea);
+ QWidget* scroll = new QWidget(this);
+ QGridLayout* layout = new QGridLayout(scroll);
+ foreach (boost::shared_ptr<FormField> field, form->getFields()) {
+ QWidget* widget = createWidget(field);
+ if (widget) {
+ layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0);
+ layout->addWidget(widget, row++, 1);
+ }
+ }
+ scrollArea->setWidget(scroll);
+ scrollArea->setWidgetResizable(true);
+}
+
+QtFormWidget::~QtFormWidget() {
+
+}
+
+QListWidget* QtFormWidget::createList(FormField::ref field) {
+ QListWidget* listWidget = new QListWidget(this);
+ listWidget->setSortingEnabled(false);
+ listWidget->setSelectionMode(boost::dynamic_pointer_cast<ListMultiFormField>(field) ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection);
+ foreach (FormField::Option option, field->getOptions()) {
+ listWidget->addItem(option.label.c_str());
+ }
+ boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field);
+ boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field);
+ for (int i = 0; i < listWidget->count(); i++) {
+ QListWidgetItem* item = listWidget->item(i);
+ bool selected = false;
+ if (listSingleField) {
+ selected = (item->text() == QString(listSingleField->getValue().c_str()));
+ }
+ else if (listMultiField) {
+ std::string text = Q2PSTRING(item->text());
+ selected = (std::find(listMultiField->getValue().begin(), listMultiField->getValue().end(), text) != listMultiField->getValue().end());
+ }
+ item->setSelected(selected);
+ }
+ return listWidget;
+}
+
+QWidget* QtFormWidget::createWidget(FormField::ref field) {
+ QWidget* widget = NULL;
+ boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field);
+ if (booleanField) {
+ QCheckBox* checkWidget = new QCheckBox(this);
+ checkWidget->setCheckState(booleanField->getValue() ? Qt::Checked : Qt::Unchecked);
+ widget = checkWidget;
+ }
+ boost::shared_ptr<FixedFormField> fixedField = boost::dynamic_pointer_cast<FixedFormField>(field);
+ if (fixedField) {
+ QString value = fixedField->getValue().c_str();
+ widget = new QLabel(value, this);
+ }
+ boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field);
+ if (listSingleField) {
+ widget = createList(field);
+ }
+ boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field);
+ if (textMultiField) {
+ QString value = textMultiField->getValue().c_str();
+ widget = new QTextEdit(value, this);
+ }
+ boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field);
+ if (textPrivateField) {
+ QString value = textPrivateField->getValue().c_str();
+ QLineEdit* lineWidget = new QLineEdit(value, this);
+ lineWidget->setEchoMode(QLineEdit::Password);
+ widget = lineWidget;
+ }
+ boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field);
+ if (textSingleField) {
+ QString value = textSingleField->getValue().c_str();
+ widget = new QLineEdit(value, this);
+ }
+ boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field);
+ if (jidSingleField) {
+ QString value = jidSingleField->getValue().toString().c_str();
+ widget = new QLineEdit(value, this);
+ }
+ boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field);
+ if (jidMultiField) {
+ QString text;
+ bool prev = false;
+ foreach (JID line, jidMultiField->getValue()) {
+ if (prev) {
+ text += "\n";
+ }
+ prev = true;
+ text += line.toString().c_str();
+ }
+ widget = new QTextEdit(text, this);
+ }
+ boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field);
+ if (listMultiField) {
+ widget = createList(field);
+ }
+ boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field);
+ if (hiddenField) {
+ }
+ fields_[field->getName()] = widget;
+ return widget;
+}
+
+Form::ref QtFormWidget::getCompletedForm() {
+ Form::ref result(new Form(Form::SubmitType));
+ foreach (boost::shared_ptr<FormField> field, form_->getFields()) {
+ boost::shared_ptr<FormField> resultField;
+ boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field);
+ if (booleanField) {
+ resultField = FormField::ref(BooleanFormField::create(qobject_cast<QCheckBox*>(fields_[field->getName()])->checkState() == Qt::Checked));
+ }
+ boost::shared_ptr<FixedFormField> fixedField = boost::dynamic_pointer_cast<FixedFormField>(field);
+ if (fixedField) {
+ resultField = FormField::ref(FixedFormField::create(fixedField->getValue()));
+ }
+ boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field);
+ if (listSingleField) {
+ QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]);
+ if (listWidget->selectedItems().size() > 0) {
+ int i = listWidget->row(listWidget->selectedItems()[0]);
+ resultField = FormField::ref(ListSingleFormField::create(field->getOptions()[i].value));
+ }
+ else {
+ resultField = FormField::ref(ListSingleFormField::create());
+ }
+ }
+ boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field);
+ if (textMultiField) {
+ QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]);
+ QString string = widget->toPlainText();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(TextMultiFormField::create());
+ }
+ else {
+ resultField = FormField::ref(TextMultiFormField::create(Q2PSTRING(string)));
+ }
+ }
+ boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field);
+ if (textPrivateField) {
+ QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]);
+ QString string = widget->text();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(TextPrivateFormField::create());
+ }
+ else {
+ resultField = FormField::ref(TextPrivateFormField::create(Q2PSTRING(string)));
+ }
+ }
+ boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field);
+ if (textSingleField) {
+ QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]);
+ QString string = widget->text();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(TextSingleFormField::create());
+ }
+ else {
+ resultField = FormField::ref(TextSingleFormField::create(Q2PSTRING(string)));
+ }
+ }
+ boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field);
+ if (jidSingleField) {
+ QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]);
+ QString string = widget->text();
+ JID jid(Q2PSTRING(string));
+ if (string.isEmpty()) {
+ resultField = FormField::ref(JIDSingleFormField::create());
+ }
+ else {
+ resultField = FormField::ref(JIDSingleFormField::create(jid));
+ }
+ }
+ boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field);
+ if (jidMultiField) {
+ QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]);
+ QString string = widget->toPlainText();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(JIDMultiFormField::create());
+ }
+ else {
+ QStringList lines = string.split("\n");
+ std::vector<JID> value;
+ foreach (QString line, lines) {
+ value.push_back(JID(Q2PSTRING(line)));
+ }
+ resultField = FormField::ref(JIDMultiFormField::create(value));
+ }
+ }
+ boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field);
+ if (listMultiField) {
+ QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]);
+ std::vector<std::string> values;
+ foreach (QListWidgetItem* item, listWidget->selectedItems()) {
+ values.push_back(field->getOptions()[listWidget->row(item)].value);
+ }
+ resultField = FormField::ref(ListMultiFormField::create(values));
+ }
+ boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field);
+ if (hiddenField) {
+ resultField = FormField::ref(HiddenFormField::create(hiddenField->getValue()));
+ }
+ resultField->setName(field->getName());
+ result->addField(resultField);
+ }
+ return result;
+}
+
+}
diff --git a/Swift/QtUI/QtFormWidget.h b/Swift/QtUI/QtFormWidget.h
new file mode 100644
index 0000000..2fb7b98
--- /dev/null
+++ b/Swift/QtUI/QtFormWidget.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+
+#include <map>
+#include <Swiften/Elements/Form.h>
+
+class QListWidget;
+
+namespace Swift {
+
+class QtFormWidget : public QWidget {
+ Q_OBJECT
+ public:
+ QtFormWidget(Form::ref form, QWidget* parent = NULL);
+ virtual ~QtFormWidget();
+ Form::ref getCompletedForm();
+ private:
+ QWidget* createWidget(FormField::ref field);
+ QListWidget* createList(FormField::ref field);
+ std::map<std::string, QWidget*> fields_;
+ Form::ref form_;
+};
+
+}
diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp
index d0ab61e..4302c10 100644
--- a/Swift/QtUI/QtLoginWindow.cpp
+++ b/Swift/QtUI/QtLoginWindow.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -56,9 +56,9 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
stack_ = new QStackedWidget(centralWidget);
topLayout->addWidget(stack_);
topLayout->setMargin(0);
- QWidget *wrapperWidget = new QWidget(this);
- wrapperWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
- QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, wrapperWidget);
+ loginWidgetWrapper_ = new QWidget(this);
+ loginWidgetWrapper_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
+ QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, loginWidgetWrapper_);
layout->addStretch(2);
QLabel* logo = new QLabel(this);
@@ -139,7 +139,7 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
layout->addWidget(loginAutomatically_);
connect(loginButton_, SIGNAL(clicked()), SLOT(loginClicked()));
- stack_->addWidget(wrapperWidget);
+ stack_->addWidget(loginWidgetWrapper_);
#ifdef SWIFTEN_PLATFORM_MACOSX
menuBar_ = new QMenuBar(NULL);
#else
@@ -284,11 +284,9 @@ void QtLoginWindow::handleUsernameTextChanged() {
}
void QtLoginWindow::loggedOut() {
- if (stack_->count() > 1) {
- QWidget* current = stack_->currentWidget();
- stack_->setCurrentIndex(0);
- stack_->removeWidget(current);
- }
+ stack_->removeWidget(stack_->currentWidget());
+ stack_->addWidget(loginWidgetWrapper_);
+ stack_->setCurrentWidget(loginWidgetWrapper_);
setInitialMenus();
setIsLoggingIn(false);
}
@@ -370,6 +368,7 @@ void QtLoginWindow::setInitialMenus() {
void QtLoginWindow::morphInto(MainWindow *mainWindow) {
QtMainWindow *qtMainWindow = dynamic_cast<QtMainWindow*>(mainWindow);
assert(qtMainWindow);
+ stack_->removeWidget(loginWidgetWrapper_);
stack_->addWidget(qtMainWindow);
stack_->setCurrentWidget(qtMainWindow);
setEnabled(true);
diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h
index 3f3b5f8..b667a4b 100644
--- a/Swift/QtUI/QtLoginWindow.h
+++ b/Swift/QtUI/QtLoginWindow.h
@@ -1,11 +1,10 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFT_QtLoginWindow_H
-#define SWIFT_QtLoginWindow_H
+#pragma once
#include <QMainWindow>
#include <QPointer>
@@ -65,6 +64,7 @@ namespace Swift {
private:
void setInitialMenus();
+ QWidget* loginWidgetWrapper_;
QStringList usernames_;
QStringList passwords_;
QStringList certificateFiles_;
@@ -87,5 +87,3 @@ namespace Swift {
QPointer<QtAboutWidget> aboutDialog_;
};
}
-
-#endif
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 6391961..f7dba58 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -30,6 +30,7 @@
#include "Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
#include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h"
+#include "Swift/Controllers/UIEvents/RequestAdHocUIEvent.h"
namespace Swift {
@@ -99,6 +100,8 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
chatUserAction_ = new QAction(tr("Start &Chat"), this);
connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool)));
actionsMenu->addAction(chatUserAction_);
+ serverAdHocMenu_ = new QMenu("Server Commands", this);
+ actionsMenu->addMenu(serverAdHocMenu_);
actionsMenu->addSeparator();
QAction* signOutAction = new QAction(tr("&Sign Out"), this);
connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction()));
@@ -106,6 +109,12 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
connect(treeWidget_, SIGNAL(onSomethingSelectedChanged(bool)), editUserAction_, SLOT(setEnabled(bool)));
+ setAvailableAdHocCommands(std::vector<DiscoItems::Item>());
+ QAction* adHocAction = new QAction("Populating commands..", this);
+ adHocAction->setEnabled(false);
+ serverAdHocMenu_->addAction(adHocAction);
+ serverAdHocCommandActions_.append(adHocAction);
+
lastOfflineState_ = false;
uiEventStream_->onUIEvent.connect(boost::bind(&QtMainWindow::handleUIEvent, this, _1));
}
@@ -206,6 +215,33 @@ void QtMainWindow::setConnecting() {
meView_->setConnecting();
}
+void QtMainWindow::handleAdHocActionTriggered(bool /*checked*/) {
+ QAction* action = qobject_cast<QAction*>(sender());
+ assert(action);
+ DiscoItems::Item command = serverAdHocCommands_[serverAdHocCommandActions_.indexOf(action)];
+ uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestAdHocUIEvent(command)));
+}
+
+void QtMainWindow::setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) {
+ serverAdHocCommands_ = commands;
+ foreach (QAction* action, serverAdHocCommandActions_) {
+ delete action;
+ }
+ serverAdHocMenu_->clear();
+ serverAdHocCommandActions_.clear();
+ foreach (DiscoItems::Item command, commands) {
+ QAction* action = new QAction(P2QSTRING(command.getName()), this);
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(handleAdHocActionTriggered(bool)));
+ serverAdHocMenu_->addAction(action);
+ serverAdHocCommandActions_.append(action);
+ }
+ if (serverAdHocCommandActions_.isEmpty()) {
+ QAction* action = new QAction("No available commands", this);
+ action->setEnabled(false);
+ serverAdHocMenu_->addAction(action);
+ serverAdHocCommandActions_.append(action);
+ }
+}
}
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index 3462bb0..e20d773 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -8,6 +8,7 @@
#include <QWidget>
#include <QMenu>
+#include <QList>
#include "Swift/Controllers/UIInterfaces/MainWindow.h"
#include "Swift/QtUI/QtRosterHeader.h"
#include "Swift/QtUI/EventViewer/QtEventWindow.h"
@@ -20,7 +21,7 @@ class QLineEdit;
class QPushButton;
class QToolBar;
class QAction;
-
+class QMenu;
class QTabWidget;
namespace Swift {
@@ -46,6 +47,7 @@ namespace Swift {
QtEventWindow* getEventWindow();
QtChatListWindow* getChatListWindow();
void setRosterModel(Roster* roster);
+ void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands);
private slots:
void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage);
void handleUIEvent(boost::shared_ptr<UIEvent> event);
@@ -55,6 +57,7 @@ namespace Swift {
void handleEditProfileAction();
void handleAddUserActionTriggered(bool checked);
void handleChatUserActionTriggered(bool checked);
+ void handleAdHocActionTriggered(bool checked);
void handleEventCountUpdated(int count);
void handleEditProfileRequest();
@@ -66,6 +69,7 @@ namespace Swift {
QAction* editUserAction_;
QAction* chatUserAction_;
QAction* showOfflineAction_;
+ QMenu* serverAdHocMenu_;
QtTabWidget* tabs_;
QWidget* contactsTabWidget_;
QWidget* eventsTabWidget_;
@@ -73,5 +77,7 @@ namespace Swift {
QtChatListWindow* chatListWindow_;
UIEventStream* uiEventStream_;
bool lastOfflineState_;
+ std::vector<DiscoItems::Item> serverAdHocCommands_;
+ QList<QAction*> serverAdHocCommandActions_;
};
}
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index d4c306f..d7a1f78 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -18,13 +18,11 @@
#include "QtUIFactory.h"
#include "QtChatWindowFactory.h"
#include <Swiften/Base/Log.h>
-#include <Swift/Controllers/CertificateFileStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h>
+#include "Swift/Controllers/Storages/FileStoragesFactory.h"
#include "SwifTools/Application/PlatformApplicationPathProvider.h"
-#include "Swiften/Avatars/AvatarFileStorage.h"
-#include "Swiften/Disco/CapsFileStorage.h"
#include <string>
#include "Swiften/Base/Platform.h"
-#include "Swift/Controllers/FileStoragesFactory.h"
#include "Swiften/Elements/Presence.h"
#include "Swiften/Client/Client.h"
#include "Swift/Controllers/MainController.h"
@@ -32,20 +30,30 @@
#include "Swift/Controllers/BuildVersion.h"
#include "SwifTools/AutoUpdater/AutoUpdater.h"
#include "SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h"
+
#if defined(SWIFTEN_PLATFORM_WINDOWS)
#include "WindowsNotifier.h"
-#endif
-#if defined(HAVE_GROWL)
+#elif defined(HAVE_GROWL)
#include "SwifTools/Notifier/GrowlNotifier.h"
#elif defined(SWIFTEN_PLATFORM_LINUX)
#include "FreeDesktopNotifier.h"
#else
#include "SwifTools/Notifier/NullNotifier.h"
#endif
+
#if defined(SWIFTEN_PLATFORM_MACOSX)
#include "SwifTools/Dock/MacOSXDock.h"
-#endif
+#else
#include "SwifTools/Dock/NullDock.h"
+#endif
+
+#if defined(SWIFTEN_PLATFORM_MACOSX)
+#include "QtURIHandler.h"
+#elif defined(SWIFTEN_PLATFORM_WIN32)
+#include <SwifTools/URIHandler/NullURIHandler.h>
+#else
+#include "QtDBUSURIHandler.h"
+#endif
namespace Swift{
@@ -123,6 +131,14 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
dock_ = new NullDock();
#endif
+#if defined(SWIFTEN_PLATFORM_MACOSX)
+ uriHandler_ = new QtURIHandler();
+#elif defined(SWIFTEN_PLATFORM_WIN32)
+ uriHandler_ = new NullURIHandler();
+#else
+ uriHandler_ = new QtDBUSURIHandler();
+#endif
+
if (splitter_) {
splitter_->show();
}
@@ -145,6 +161,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
certificateStorageFactory_,
dock_,
notifier_,
+ uriHandler_,
options.count("latency-debug") > 0);
mainControllers_.push_back(mainController);
}
@@ -172,6 +189,7 @@ QtSwift::~QtSwift() {
}
delete tabs_;
delete splitter_;
+ delete uriHandler_;
delete dock_;
delete soundPlayer_;
delete chatWindowFactory_;
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index 978fa14..4bf5c97 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -44,6 +44,7 @@ namespace Swift {
class QtMUCSearchWindowFactory;
class QtUserSearchWindowFactory;
class EventLoop;
+ class URIHandler;
class QtSwift : public QObject {
Q_OBJECT
@@ -63,6 +64,7 @@ namespace Swift {
QSplitter* splitter_;
QtSoundPlayer* soundPlayer_;
Dock* dock_;
+ URIHandler* uriHandler_;
QtChatTabs* tabs_;
ApplicationPathProvider* applicationPathProvider_;
StoragesFactory* storagesFactory_;
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 35fbfcd..5c1f78d 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -23,6 +23,7 @@
#include "UserSearch/QtUserSearchWindow.h"
#include "QtProfileWindow.h"
#include "QtContactEditWindow.h"
+#include "QtAdHocCommandWindow.h"
namespace Swift {
@@ -99,5 +100,8 @@ ContactEditWindow* QtUIFactory::createContactEditWindow() {
return new QtContactEditWindow();
}
+void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) {
+ new QtAdHocCommandWindow(command);
+}
}
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index ddaaf6e..9ef228a 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -37,6 +37,7 @@ namespace Swift {
virtual JoinMUCWindow* createJoinMUCWindow();
virtual ProfileWindow* createProfileWindow();
virtual ContactEditWindow* createContactEditWindow();
+ virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
private slots:
void handleLoginWindowGeometryChanged();
diff --git a/Swift/QtUI/QtURIHandler.cpp b/Swift/QtUI/QtURIHandler.cpp
new file mode 100644
index 0000000..43f3ed1
--- /dev/null
+++ b/Swift/QtUI/QtURIHandler.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtURIHandler.h"
+
+#include <QCoreApplication>
+#include <QFileOpenEvent>
+#include <QUrl>
+
+#include "QtSwiftUtil.h"
+#ifdef Q_WS_MAC
+#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h>
+#endif
+
+using namespace Swift;
+
+QtURIHandler::QtURIHandler() {
+ qApp->installEventFilter(this);
+#ifdef Q_WS_MAC
+ registerAppAsDefaultXMPPURIHandler();
+#endif
+}
+
+bool QtURIHandler::eventFilter(QObject*, QEvent* event) {
+ if (event->type() == QEvent::FileOpen) {
+ QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event);
+ if (fileOpenEvent->url().scheme() == "xmpp") {
+ onURI(Q2PSTRING(fileOpenEvent->url().toString()));
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/Swift/QtUI/QtURIHandler.h b/Swift/QtUI/QtURIHandler.h
new file mode 100644
index 0000000..a02114a
--- /dev/null
+++ b/Swift/QtUI/QtURIHandler.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <SwifTools/URIHandler/URIHandler.h>
+
+class QUrl;
+
+namespace Swift {
+ class QtURIHandler : public QObject, public URIHandler {
+ public:
+ QtURIHandler();
+
+ private:
+ bool eventFilter(QObject* obj, QEvent* event);
+ };
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 494731c..2f138cd 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -77,6 +77,7 @@ sources = [
"QtStatusWidget.cpp",
"QtScaledAvatarCache.cpp",
"QtSwift.cpp",
+ "QtURIHandler.cpp",
"QtChatView.cpp",
"QtChatTheme.cpp",
"QtChatTabs.cpp",
@@ -87,6 +88,7 @@ sources = [
"QtTabWidget.cpp",
"QtTextEdit.cpp",
"QtXMLConsoleWidget.cpp",
+ "QtAdHocCommandWindow.cpp",
"QtUtilities.cpp",
"QtBookmarkDetailWindow.cpp",
"QtAddBookmarkWindow.cpp",
@@ -97,6 +99,7 @@ sources = [
"MessageSnippet.cpp",
"SystemMessageSnippet.cpp",
"QtElidingLabel.cpp",
+ "QtFormWidget.cpp",
"QtLineEdit.cpp",
"QtJoinMUCWindow.cpp",
"Roster/RosterModel.cpp",
@@ -136,20 +139,31 @@ sources = [
myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift")
if env["PLATFORM"] == "win32" :
- myenv.RES("../resources/Windows/Swift.rc")
+ res = myenv.RES("../resources/Windows/Swift.rc")
+ # For some reason, SCons isn't picking up the dependency correctly
+ # Adding it explicitly until i figure out why
+ myenv.Depends(res, "../Controllers/BuildVersion.h")
sources += [
"WindowsNotifier.cpp",
"../resources/Windows/Swift.res"
]
if env["PLATFORM"] == "posix" :
- sources += ["FreeDesktopNotifier.cpp"]
+ sources += [
+ "FreeDesktopNotifier.cpp",
+ "QtDBUSURIHandler.cpp",
+ ]
if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" :
swiftProgram = myenv.Program("Swift", sources)
else :
swiftProgram = myenv.Program("swift", sources)
+if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" :
+ openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp")
+else :
+ openURIProgram = []
+
myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui")
myenv.Uic4("UserSearch/QtUserSearchWizard.ui")
myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui")
@@ -215,12 +229,12 @@ if env["PLATFORM"] == "darwin" :
if env["HAVE_GROWL"] :
frameworks.append(env["GROWL_FRAMEWORK"])
commonResources[""] = commonResources.get("", []) + ["../resources/MacOSX/Swift.icns"]
- app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks)
+ app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True)
if env["DIST"] :
myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"])
if env.get("SWIFT_INSTALLDIR", "") :
- env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram)
+ env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram + openURIProgram)
env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "../resources/logo/logo-icon-32.xpm")
icons_path = os.path.join(env["SWIFT_INSTALLDIR"], "share", "icons", "hicolor")
env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "../resources/logo/logo-icon-32.xpm")
diff --git a/Swift/QtUI/swift-open-uri.cpp b/Swift/QtUI/swift-open-uri.cpp
new file mode 100644
index 0000000..2d5ef19
--- /dev/null
+++ b/Swift/QtUI/swift-open-uri.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <QCoreApplication>
+#include <QDBusConnection>
+#include <QDBusMessage>
+#include <iostream>
+
+int main(int argc, char* argv[]) {
+ QCoreApplication app(argc, argv);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ if (!bus.isConnected()) {
+ return -1;
+ }
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " uri" << std::endl;
+ return -1;
+ }
+
+ QDBusMessage msg = QDBusMessage::createMethodCall("im.swift.Swift.URIHandler", "/", "im.swift.Swift.URIHandler", "openURI");
+ msg << argv[1];
+
+ bus.call(msg);
+
+ return 0;
+}