diff options
Diffstat (limited to 'Swift')
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; +} |