diff options
Diffstat (limited to 'Swift')
54 files changed, 1540 insertions, 31 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index f4aa745..a3d9fb5 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -20,8 +20,13 @@ #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swiften/Client/NickResolver.h> #include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> #include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> + namespace Swift { @@ -29,7 +34,7 @@ namespace Swift { * The controller does not gain ownership of the stanzaChannel, nor the factory. */ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider) { + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -60,6 +65,10 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatWindow_->addSystemMessage(startMessage); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); + chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2)); + chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); + chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); handleBareJIDCapsChanged(toJID_); } @@ -171,6 +180,45 @@ void ChatController::setOnline(bool online) { ChatControllerBase::setOnline(online); } +void ChatController::handleNewFileTransferController(FileTransferController* ftc) { + std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty()); + std::string ftID = ftc->setChatWindow(chatWindow_, nick); + + ftControllers[ftID] = ftc; +} + +void ChatController::handleFileTransferCancel(std::string id) { + std::cout << "handleFileTransferCancel(" << id << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->cancel(); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleFileTransferStart(std::string id, std::string description) { + std::cout << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->start(description); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleFileTransferAccept(std::string id, std::string filename) { + std::cout << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->accept(filename); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleSendFileRequest(std::string filename) { + std::cout << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; + eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename)); +} + std::string ChatController::senderDisplayNameFromMessage(const JID& from) { return nickResolver_->jidToNick(from); } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index f6b8763..2531adb 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -8,12 +8,16 @@ #include "Swift/Controllers/Chat/ChatControllerBase.h" +#include <map> +#include <string> + namespace Swift { class AvatarManager; class ChatStateNotifier; class ChatStateTracker; class NickResolver; class EntityCapsProvider; + class FileTransferController; class ChatController : public ChatControllerBase { public: @@ -21,6 +25,7 @@ namespace Swift { virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setOnline(bool online); + virtual void handleNewFileTransferController(FileTransferController* ftc); private: void handlePresenceChange(boost::shared_ptr<Presence> newPresence); @@ -37,6 +42,11 @@ namespace Swift { void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); void handleBareJIDCapsChanged(const JID& jid); + void handleFileTransferCancel(std::string /* id */); + void handleFileTransferStart(std::string /* id */, std::string /* description */); + void handleFileTransferAccept(std::string /* id */, std::string /* filename */); + void handleSendFileRequest(std::string filename); + private: NickResolver* nickResolver_; ChatStateNotifier* chatStateNotifier_; @@ -47,6 +57,9 @@ namespace Swift { std::string lastStatusChangeString_; std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_; StatusShow::Type lastShownStatus_; + UIEventStream* eventStream_; + + std::map<std::string, FileTransferController*> ftControllers; }; } diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index d631494..c61479c 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -29,6 +29,8 @@ #include <Swiften/MUC/MUCManager.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/MUC/MUCBookmarkManager.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swiften/Avatars/AvatarManager.h> @@ -56,13 +58,15 @@ ChatsManager::ChatsManager( EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, - ProfileSettingsProvider* settings) : + ProfileSettingsProvider* settings, + FileTransferOverview* ftOverview) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry), entityCapsProvider_(entityCapsProvider), - mucManager(mucManager) { + mucManager(mucManager), + ftOverview_(ftOverview) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -86,6 +90,7 @@ ChatsManager::ChatsManager( joinMUCWindow_ = NULL; mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); + ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); setupBookmarks(); loadRecents(); } @@ -535,6 +540,12 @@ void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) { uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getNick())); } +void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) { + ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty()); + chatController->handleNewFileTransferController(ftc); + chatController->activateChatWindow(); +} + void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { if (chat.isMUC) { uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, chat.nick)); diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index b4db523..46c104d 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -43,10 +43,12 @@ namespace Swift { class MUCSearchWindowFactory; class ProfileSettingsProvider; class MUCSearchController; - + class FileTransferOverview; + class FileTransferController; + class ChatsManager { public: - ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings); + ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings, FileTransferOverview* ftOverview); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); @@ -67,6 +69,7 @@ namespace Swift { void handleUserLeftMUC(MUCController* mucController); void handleBookmarksReady(); void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); + void handleNewFileTransferController(FileTransferController*); void appendRecent(const ChatListWindow::Chat& chat); void prependRecent(const ChatListWindow::Chat& chat); void setupBookmarks(); @@ -110,5 +113,6 @@ namespace Swift { MUCSearchController* mucSearchController_; std::list<ChatListWindow::Chat> recentChats_; ProfileSettingsProvider* profileSettings_; + FileTransferOverview* ftOverview_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index b8cb368..5339703 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -8,6 +8,8 @@ #include <cppunit/extensions/TestFactoryRegistry.h> #include "3rdParty/hippomocks.h" +#include <boost/bind.hpp> + #include "Swift/Controllers/Chat/ChatsManager.h" #include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h" @@ -36,11 +38,14 @@ #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" #include "Swiften/Presence/PresenceOracle.h" +#include "Swiften/Jingle/JingleSessionManager.h" +#include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include <Swift/Controllers/ProfileSettingsProvider.h> - +#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" +#include <Swiften/Base/Algorithm.h> using namespace Swift; @@ -86,19 +91,23 @@ public: settings_ = new DummySettingsProvider(); profileSettings_ = new ProfileSettingsProvider("a", settings_); chatListWindow_ = new MockChatListWindow(); + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_); avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); }; void tearDown() { - //delete chatListWindowFactory_; + //delete chatListWindowFactory delete settings_; delete profileSettings_; delete avatarManager_; delete manager_; + delete ftOverview_; + delete ftManager_; delete directedPresenceSender_; delete presenceSender_; delete presenceOracle_; @@ -354,6 +363,8 @@ private: DummySettingsProvider* settings_; ProfileSettingsProvider* profileSettings_; ChatListWindow* chatListWindow_; + FileTransferOverview* ftOverview_; + FileTransferManager* ftManager_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/FileTransfer/FileTransferController.cpp b/Swift/Controllers/FileTransfer/FileTransferController.cpp new file mode 100644 index 0000000..afa907d --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferController.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferController.h" +#include "Swiften/FileTransfer/OutgoingJingleFileTransfer.h" +#include "Swiften/FileTransfer/FileTransferManager.h" +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/Base/boost_bsignals.h> +#include <boost/bind.hpp> +#include "Swift/Controllers/UIInterfaces/ChatWindow.h" + +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + +FileTransferController::FileTransferController(const JID& receipient, const std::string& filename, FileTransferManager* fileTransferManager) : + sending(true), otherParty(receipient), filename(filename), ftManager(fileTransferManager), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + +} + +FileTransferController::FileTransferController(IncomingFileTransfer::ref transfer) : + sending(false), otherParty(transfer->getSender()), filename(transfer->filename), transfer(transfer), ftManager(0), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + +} + +FileTransferController::~FileTransferController() { + delete ftProgressInfo; +} + +const JID &FileTransferController::getOtherParty() const { + return otherParty; +} + +std::string FileTransferController::setChatWindow(ChatWindow* wnd, std::string nickname) { + chatWindow = wnd; + if (sending) { + uiID = wnd->addFileTransfer("me", true, filename, boost::filesystem::file_size(boost::filesystem::path(filename))); + } else { + uiID = wnd->addFileTransfer(nickname, false, filename, transfer->fileSizeInBytes); + } + return uiID; +} + +void FileTransferController::setReceipient(const JID& receipient) { + this->otherParty = receipient; +} + +bool FileTransferController::isIncoming() const { + return !sending; +} + +FileTransfer::State FileTransferController::getState() const { + return currentState; +} + +int FileTransferController::getProgress() const { + return ftProgressInfo ? ftProgressInfo->getPercentage() : 0; +} + +boost::uintmax_t FileTransferController::getSize() const { + if (transfer) { + return transfer->fileSizeInBytes; + } else { + return 0; + } +} + +void FileTransferController::start(std::string& description) { + std::cout << "FileTransferController::start" << std::endl; + fileReadStream = boost::make_shared<FileReadBytestream>(boost::filesystem::path(filename)); + OutgoingFileTransfer::ref outgoingTransfer = ftManager->createOutgoingFileTransfer(otherParty, boost::filesystem::path(filename), description, fileReadStream); + if (outgoingTransfer) { + ftProgressInfo = new FileTransferProgressInfo(outgoingTransfer->fileSizeInBytes); + ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); + outgoingTransfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + outgoingTransfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); + outgoingTransfer->start(); + transfer = outgoingTransfer; + } else { + std::cerr << "File transfer not supported!" << std::endl; + } +} + +void FileTransferController::accept(std::string& file) { + std::cout << "FileTransferController::accept" << std::endl; + IncomingFileTransfer::ref incomingTransfer = boost::dynamic_pointer_cast<IncomingFileTransfer>(transfer); + if (incomingTransfer) { + fileWriteStream = boost::make_shared<FileWriteBytestream>(boost::filesystem::path(file)); + + ftProgressInfo = new FileTransferProgressInfo(transfer->fileSizeInBytes); + ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); + transfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + transfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); + incomingTransfer->accept(fileWriteStream); + } else { + std::cerr << "Expected an incoming transfer in this situation!" << std::endl; + } +} + +void FileTransferController::cancel() { + if (transfer) { + transfer->cancel(); + } else { + chatWindow->setFileTransferStatus(uiID, ChatWindow::Canceled); + } +} + +void FileTransferController::handleFileTransferStateChange(FileTransfer::State state) { + currentState = state; + onStateChage(); + switch(state.state) { + case FileTransfer::State::Negotiating: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Negotiating); + return; + case FileTransfer::State::Transferring: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Transferring); + return; + case FileTransfer::State::Canceled: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Canceled); + return; + case FileTransfer::State::Finished: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Finished); + if (fileWriteStream) { + fileWriteStream->close(); + } + return; + case FileTransfer::State::Failed: + chatWindow->setFileTransferStatus(uiID, ChatWindow::FTFailed); + return; + case FileTransfer::State::WaitingForAccept: + chatWindow->setFileTransferStatus(uiID, ChatWindow::WaitingForAccept); + return; + case FileTransfer::State::WaitingForStart: + return; + } + std::cerr << "Unhandled FileTransfer::State!" << std::endl; +} + +void FileTransferController::handleProgressPercentageChange(int percentage) { + onProgressChange(); + chatWindow->setFileTransferProgress(uiID, percentage); +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferController.h b/Swift/Controllers/FileTransfer/FileTransferController.h new file mode 100644 index 0000000..5d98468 --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferController.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/FileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/FileTransfer/FileWriteBytestream.h> +#include <Swift/Controllers/FileTransfer/FileTransferProgressInfo.h> + +namespace Swift { + +class FileTransferManager; +class ChatWindow; + +class FileTransferController { +public: + /** + * For outgoing file transfers. It'll create a file transfer via FileTransferManager as soon as the descriptive information is available. + */ + FileTransferController(const JID&, const std::string&, FileTransferManager*); + + /** + * For incoming file transfers. + */ + FileTransferController(IncomingFileTransfer::ref transfer); + ~FileTransferController(); + + std::string setChatWindow(ChatWindow*, std::string nickname); + void setReceipient(const JID& otherParty); + + void start(std::string& description); + void accept(std::string& file); + void cancel(); + + const JID &getOtherParty() const; + bool isIncoming() const; + FileTransfer::State getState() const; + int getProgress() const; + boost::uintmax_t getSize() const; + + boost::signal<void ()> onStateChage; + boost::signal<void ()> onProgressChange; + +private: + void handleFileTransferStateChange(FileTransfer::State); + void handleProgressPercentageChange(int percentage); + +private: + bool sending; + JID otherParty; + std::string filename; + FileTransfer::ref transfer; + boost::shared_ptr<FileReadBytestream> fileReadStream; + boost::shared_ptr<FileWriteBytestream> fileWriteStream; + FileTransferManager* ftManager; + FileTransferProgressInfo* ftProgressInfo; + ChatWindow* chatWindow; + std::string uiID; + FileTransfer::State currentState; +}; + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferOverview.cpp b/Swift/Controllers/FileTransfer/FileTransferOverview.cpp new file mode 100644 index 0000000..c3ffc5c --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferOverview.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferOverview.h" + +#include <boost/bind.hpp> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + +FileTransferOverview::FileTransferOverview(FileTransferManager* ftm) : fileTransferManager(ftm) { + fileTransferManager->onIncomingFileTransfer.connect(boost::bind(&FileTransferOverview::handleIncomingFileTransfer, this, _1)); +} + +FileTransferOverview::~FileTransferOverview() { + +} + +void FileTransferOverview::sendFile(const JID& jid, const std::string& filename) { + FileTransferController* controller = new FileTransferController(jid, filename, fileTransferManager); + fileTransfers.push_back(controller); + + onNewFileTransferController(controller); +} + +void FileTransferOverview::handleIncomingFileTransfer(IncomingFileTransfer::ref transfer) { + FileTransferController* controller = new FileTransferController(transfer); + fileTransfers.push_back(controller); + onNewFileTransferController(controller); +} + +const std::vector<FileTransferController*>& FileTransferOverview::getFileTransfers() const { + return fileTransfers; +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferOverview.h b/Swift/Controllers/FileTransfer/FileTransferOverview.h new file mode 100644 index 0000000..716666a --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferOverview.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include "Swift/Controllers/FileTransfer/FileTransferController.h" + +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + +class ChatsManager; +class FileTransferManager; + +class FileTransferOverview { +public: + FileTransferOverview(FileTransferManager*); + ~FileTransferOverview(); + + void sendFile(const JID&, const std::string&); + const std::vector<FileTransferController*>& getFileTransfers() const; + + boost::signal<void (FileTransferController*)> onNewFileTransferController; + +private: + void handleIncomingFileTransfer(IncomingFileTransfer::ref transfer); + +private: + std::vector<FileTransferController*> fileTransfers; + FileTransferManager *fileTransferManager; +}; + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp new file mode 100644 index 0000000..6d19fa1 --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferProgressInfo.h" + +#include <Swiften/Base/Log.h> + +namespace Swift { + +FileTransferProgressInfo::FileTransferProgressInfo(boost::uintmax_t completeBytes) : completeBytes(completeBytes), completedBytes(0), percentage(0) { + onProgressPercentage(0); +} + +void FileTransferProgressInfo::setBytesProcessed(int processedBytes) { + int oldPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); + completedBytes += processedBytes; + int newPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); + if (oldPercentage != newPercentage) { + onProgressPercentage(newPercentage); + } + percentage = newPercentage; +} + +int FileTransferProgressInfo::getPercentage() const { + return percentage; +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h new file mode 100644 index 0000000..bb3c0fc --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + +class FileTransferProgressInfo { +public: + FileTransferProgressInfo(boost::uintmax_t completeBytes); + +public: + void setBytesProcessed(int processedBytes); + + int getPercentage() const; + boost::signal<void (int)> onProgressPercentage; + +private: + boost::uintmax_t completeBytes; + boost::uintmax_t completedBytes; + int percentage; +}; + +} diff --git a/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp new file mode 100644 index 0000000..da0606c --- /dev/null +++ b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "SOCKS5BytestreamProxyFinder.h" + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/Log.h> +#include <Swiften/Elements/S5BProxyRequest.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Queries/IQRouter.h> + +namespace Swift { + +SOCKS5BytestreamProxyFinder::SOCKS5BytestreamProxyFinder(const JID& service, IQRouter *iqRouter) : iqRouter(iqRouter) { + serviceWalker = boost::make_shared<DiscoServiceWalker>(service, iqRouter); + serviceWalker->onServiceFound.connect(boost::bind(&SOCKS5BytestreamProxyFinder::handleServiceFound, this, _1, _2)); +} + +void SOCKS5BytestreamProxyFinder::start() { + serviceWalker->beginWalk(); +} + +void SOCKS5BytestreamProxyFinder::sendBytestreamQuery(const JID& jid) { + S5BProxyRequest::ref proxyRequest = boost::make_shared<S5BProxyRequest>(); + boost::shared_ptr<GenericRequest<S5BProxyRequest> > request = boost::make_shared<GenericRequest<S5BProxyRequest> >(IQ::Get, jid, proxyRequest, iqRouter); + request->onResponse.connect(boost::bind(&SOCKS5BytestreamProxyFinder::handleProxyResponse, this, _1, _2)); + request->send(); +} + +void SOCKS5BytestreamProxyFinder::handleServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> discoInfo) { + if (discoInfo->hasFeature(DiscoInfo::Bytestream)) { + sendBytestreamQuery(jid); + } +} + +void SOCKS5BytestreamProxyFinder::handleProxyResponse(boost::shared_ptr<S5BProxyRequest> request, ErrorPayload::ref error) { + if (error) { + SWIFT_LOG(debug) << "ERROR" << std::endl; + } else { + if (request) { + onProxyFound(request); + } else { + //assert(false); + } + } +} + +} diff --git a/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h new file mode 100644 index 0000000..1727a63 --- /dev/null +++ b/Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swiften/Network/HostAddressPort.h> +#include <Swiften/Elements/S5BProxyRequest.h> + +namespace Swift { + +class JID; +class IQRouter; + +class SOCKS5BytestreamProxyFinder { +public: + SOCKS5BytestreamProxyFinder(const JID& service, IQRouter *iqRouter); + void start(); + + boost::signal<void(boost::shared_ptr<S5BProxyRequest>)> onProxyFound; + +private: + void sendBytestreamQuery(const JID&); + + void handleServiceFound(const JID&, boost::shared_ptr<DiscoInfo>); + void handleProxyResponse(boost::shared_ptr<S5BProxyRequest>, ErrorPayload::ref); +private: + boost::shared_ptr<DiscoServiceWalker> serviceWalker; + IQRouter* iqRouter; + std::vector<boost::shared_ptr<GenericRequest<S5BProxyRequest> > > requests; +}; + +} diff --git a/Swift/Controllers/FileTransferListController.cpp b/Swift/Controllers/FileTransferListController.cpp new file mode 100644 index 0000000..093a3c4 --- /dev/null +++ b/Swift/Controllers/FileTransferListController.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swift/Controllers/FileTransferListController.h" + +#include <boost/bind.hpp> + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h" +#include "Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h" + +namespace Swift { + +FileTransferListController::FileTransferListController(UIEventStream* uiEventStream, FileTransferListWidgetFactory* fileTransferListWidgetFactory) : fileTransferListWidgetFactory(fileTransferListWidgetFactory), fileTransferListWidget(NULL), fileTransferOverview(0) { + uiEventStream->onUIEvent.connect(boost::bind(&FileTransferListController::handleUIEvent, this, _1)); +} + +FileTransferListController::~FileTransferListController() { + delete fileTransferListWidget; +} + +void FileTransferListController::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + if (fileTransferListWidget) { + fileTransferListWidget->setFileTransferOverview(fileTransferOverview); + } +} + +void FileTransferListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + boost::shared_ptr<RequestFileTransferListUIEvent> event = boost::dynamic_pointer_cast<RequestFileTransferListUIEvent>(rawEvent); + if (event != NULL) { + if (fileTransferListWidget == NULL) { + fileTransferListWidget = fileTransferListWidgetFactory->createFileTransferListWidget(); + if (fileTransferOverview) { + fileTransferListWidget->setFileTransferOverview(fileTransferOverview); + } + } + fileTransferListWidget->show(); + fileTransferListWidget->activate(); + } +} + +} diff --git a/Swift/Controllers/FileTransferListController.h b/Swift/Controllers/FileTransferListController.h new file mode 100644 index 0000000..c5c8893 --- /dev/null +++ b/Swift/Controllers/FileTransferListController.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swift/Controllers/UIEvents/UIEventStream.h" + +namespace Swift { + +class FileTransferListWidgetFactory; +class FileTransferListWidget; +class FileTransferOverview; + +class FileTransferListController { +public: + FileTransferListController(UIEventStream* uiEventStream, FileTransferListWidgetFactory* fileTransferListWidgetFactory); + ~FileTransferListController(); + + void setFileTransferOverview(FileTransferOverview* overview); + +private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + +private: + FileTransferListWidgetFactory* fileTransferListWidgetFactory; + FileTransferListWidget* fileTransferListWidget; + FileTransferOverview* fileTransferOverview; +}; + +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 24c5303..b84e7d6 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -37,6 +37,7 @@ #include "Swift/Controllers/SystemTray.h" #include "Swift/Controllers/SystemTrayController.h" #include "Swift/Controllers/XMLConsoleController.h" +#include "Swift/Controllers/FileTransferListController.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/PresenceNotifier.h" #include "Swift/Controllers/EventNotifier.h" @@ -68,6 +69,9 @@ #include <Swift/Controllers/XMPPURIController.h> #include "Swift/Controllers/AdHocManager.h" #include <SwifTools/Idle/IdleDetector.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Client/ClientXMLTracer.h> namespace Swift { @@ -101,7 +105,8 @@ MainController::MainController( idleDetector_(idleDetector), loginWindow_(NULL) , useDelayForLatency_(useDelayForLatency), - eagleMode_(eagleMode) { + eagleMode_(eagleMode), + ftOverview_(NULL) { storages_ = NULL; certificateStorage_ = NULL; statusTracker_ = NULL; @@ -164,6 +169,8 @@ MainController::MainController( xmlConsoleController_ = new XMLConsoleController(uiEventStream_, uiFactory_); + fileTransferListController_ = new FileTransferListController(uiEventStream_, uiFactory_); + uiEventStream_->onUIEvent.connect(boost::bind(&MainController::handleUIEvent, this, _1)); bool enabled = settings_->getBoolSetting(SHOW_NOTIFICATIONS, true); uiEventStream_->send(boost::shared_ptr<ToggleNotificationsUIEvent>(new ToggleNotificationsUIEvent(enabled))); @@ -184,7 +191,7 @@ MainController::~MainController() { eventController_->disconnectAll(); resetClient(); - + delete fileTransferListController_; delete xmlConsoleController_; delete xmppURIController_; delete soundEventController_; @@ -211,6 +218,10 @@ void MainController::resetClient() { eventWindowController_ = NULL; delete chatsManager_; chatsManager_ = NULL; + delete s5bProxyFinder_; + s5bProxyFinder_ = NULL; + delete ftOverview_; + ftOverview_ = NULL; delete rosterController_; rosterController_ = NULL; delete eventNotifier_; @@ -270,13 +281,22 @@ void MainController::handleConnected() { myStatusLooksOnline_ = true; if (freshLogin) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); - rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_); + srand(time(NULL)); + int randomPort = 10000 + rand() % 10000; + client_->getFileTransferManager()->startListeningOnPort(randomPort); + s5bProxyFinder_ = new SOCKS5BytestreamProxyFinder(client_->getJID().getDomain(), client_->getIQRouter()); + s5bProxyFinder_->onProxyFound.connect(boost::bind(&FileTransferManager::addS5BProxy, client_->getFileTransferManager(), _1)); + s5bProxyFinder_->start(); + ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); + fileTransferListController_->setFileTransferOverview(ftOverview_); + rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); contactEditController_ = new ContactEditController(rosterController_, uiFactory_, uiEventStream_); - chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_); + chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_); + client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); @@ -289,6 +309,10 @@ void MainController::handleConnected() { discoInfo.addFeature(DiscoInfo::ChatStatesFeature); discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature); discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature); + discoInfo.addFeature(DiscoInfo::JingleFeature); + discoInfo.addFeature(DiscoInfo::JingleFTFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 7ac6648..2aaa542 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -23,6 +23,8 @@ #include "Swiften/Elements/CapsInfo.h" #include "Swift/Controllers/XMPPEvents/ErrorEvent.h" #include "Swift/Controllers/UIEvents/UIEvent.h" +#include "Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h" +#include "Swiften/Client/ClientXMLTracer.h" namespace Swift { class IdleDetector; @@ -51,6 +53,7 @@ namespace Swift { class SoundEventController; class SoundPlayer; class XMLConsoleController; + class FileTransferListController; class UIEventStream; class EventWindowFactory; class EventWindowController; @@ -65,6 +68,7 @@ namespace Swift { class XMPPURIController; class AdHocManager; class AdHocCommandWindowFactory; + class FileTransferOverview; class MainController { public: @@ -140,6 +144,7 @@ namespace Swift { LoginWindow* loginWindow_; UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; + FileTransferListController* fileTransferListController_; ChatsManager* chatsManager_; ProfileController* profileController_; ContactEditController* contactEditController_; @@ -162,5 +167,7 @@ namespace Swift { bool quitRequested_; static const int SecondsToWaitBeforeForceQuitting; bool eagleMode_; + FileTransferOverview* ftOverview_; + SOCKS5BytestreamProxyFinder* s5bProxyFinder_; }; } diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index bbe81e6..6d707bb 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -113,6 +113,14 @@ void ContactRosterItem::removeGroup(const std::string& group) { groups_.erase(std::remove(groups_.begin(), groups_.end(), group), groups_.end()); } +void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) { + features_ = features; +} + +bool ContactRosterItem::supportsFeature(const Feature feat) const { + return features_.find(feat) != features_.end(); +} + } diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 7aa948c..67ffaa9 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -13,6 +13,7 @@ #include "Swiften/Elements/Presence.h" #include <map> +#include <set> #include <boost/bind.hpp> #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -22,6 +23,11 @@ namespace Swift { class GroupRosterItem; class ContactRosterItem : public RosterItem { public: + enum Feature { + FileTransferFeature, + }; + + public: ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent); virtual ~ContactRosterItem(); @@ -40,6 +46,9 @@ class ContactRosterItem : public RosterItem { /** Only used so a contact can know about the groups it's in*/ void addGroup(const std::string& group); void removeGroup(const std::string& group); + + void setSupportedFeatures(const std::set<Feature>& features); + bool supportsFeature(Feature feat) const; private: JID jid_; JID displayJID_; @@ -48,6 +57,7 @@ class ContactRosterItem : public RosterItem { boost::shared_ptr<Presence> offlinePresence_; boost::shared_ptr<Presence> shownPresence_; std::vector<std::string> groups_; + std::set<Feature> features_; }; } diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index a62a18a..f3d058e 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -17,6 +17,7 @@ #include <boost/bind.hpp> #include <iostream> +#include <set> #include <deque> namespace Swift { @@ -60,6 +61,13 @@ GroupRosterItem* Roster::getGroup(const std::string& groupName) { return group; } +void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features) { + if (itemMap_[fullJIDMapping_ ? jid : jid.toBare()].empty()) return; + foreach(ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) { + item->setSupportedFeatures(features); + } +} + void Roster::removeGroup(const std::string& group) { root_->removeGroupChild(group); } @@ -74,7 +82,7 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) { void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const std::string& avatarPath) { GroupRosterItem* group(getGroup(groupName)); - ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); + ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); item->setAvatarPath(avatarPath); group->addChild(item); if (itemMap_[fullJIDMapping_ ? jid : jid.toBare()].size() > 0) { diff --git a/Swift/Controllers/Roster/Roster.h b/Swift/Controllers/Roster/Roster.h index 53161a8..2b4dd27 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -10,6 +10,7 @@ #include "Swiften/JID/JID.h" #include "Swift/Controllers/Roster/RosterItemOperation.h" #include "Swift/Controllers/Roster/RosterFilter.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> #include <vector> #include <map> @@ -43,6 +44,8 @@ class Roster { boost::signal<void (GroupRosterItem*)> onGroupAdded; boost::signal<void (RosterItem*)> onDataChanged; GroupRosterItem* getGroup(const std::string& groupName); + void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features); + private: void handleDataChanged(RosterItem* item); void handleChildrenChanged(GroupRosterItem* item); diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index 5b61abf..66948c1 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -36,9 +36,14 @@ #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" #include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" +#include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/Client/NickManager.h> #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> namespace Swift { @@ -47,8 +52,9 @@ static const std::string SHOW_OFFLINE = "showOffline"; /** * The controller does not gain ownership of these parameters. */ -RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream) { +RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview) { + assert(fileTransferOverview); iqRouter_ = iqRouter; presenceOracle_ = presenceOracle; subscriptionManager_ = subscriptionManager; @@ -74,15 +80,17 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); mainWindow_->setMyJID(jid); mainWindow_->setMyNick(nickManager_->getOwnNick()); + + entityCapsManager_->onCapsChanged.connect(boost::bind(&RosterController::handleOnCapsChanged, this, _1)); if (settings->getBoolSetting(SHOW_OFFLINE, false)) { uiEventStream->onUIEvent(boost::shared_ptr<UIEvent>(new ToggleShowOfflineUIEvent(true))); } } -RosterController::~RosterController() { +RosterController::~RosterController() { nickManager_->onOwnNickChanged.disconnect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); - + delete offlineFilter_; delete expandiness_; @@ -91,6 +99,7 @@ RosterController::~RosterController() { delete mainWindow_; } delete roster_; + } void RosterController::setEnabled(bool enabled) { @@ -226,6 +235,10 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { } } } + else if (boost::shared_ptr<SendFileUIEvent> sendFileEvent = boost::dynamic_pointer_cast<SendFileUIEvent>(event)) { + //TODO add send file dialog to ChatView of receipient jid + ftOverview_->sendFile(sendFileEvent->getJID(), sendFileEvent->getFilename()); + } } void RosterController::setContactGroups(const JID& jid, const std::vector<std::string>& groups) { @@ -302,4 +315,15 @@ std::set<std::string> RosterController::getGroups() const { return xmppRoster_->getGroups(); } +void RosterController::handleOnCapsChanged(const JID& jid) { + DiscoInfo::ref info = entityCapsManager_->getCaps(jid); + if (info) { + std::set<ContactRosterItem::Feature> features; + if (info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { + features.insert(ContactRosterItem::FileTransferFeature); + } + roster_->setAvailableFeatures(jid, features); + } +} + } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 0a2b818..66748ca 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -8,12 +8,14 @@ #include "Swiften/JID/JID.h" #include <string> +#include <set> #include "Swiften/Elements/Presence.h" #include "Swiften/Elements/ErrorPayload.h" #include "Swiften/Elements/RosterPayload.h" #include "Swiften/Avatars/AvatarManager.h" #include "Swift/Controllers/UIEvents/UIEvent.h" #include "RosterGroupExpandinessPersister.h" +#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -35,10 +37,12 @@ namespace Swift { class IQRouter; class SettingsProvider; class NickManager; - + class EntityCapsProvider; + class FileTransferManager; + class RosterController { public: - RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings); + RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview); ~RosterController(); void showRosterWindow(); MainWindow* getWindow() {return mainWindow_;}; @@ -69,6 +73,7 @@ namespace Swift { void handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void applyAllPresenceTo(const JID& jid); void handleEditProfileRequest(); + void handleOnCapsChanged(const JID& jid); JID myJID_; XMPPRoster* xmppRoster_; @@ -86,6 +91,9 @@ namespace Swift { IQRouter* iqRouter_; SettingsProvider* settings_; UIEventStream* uiEventStream_; + EntityCapsProvider* entityCapsManager_; + FileTransferOverview* ftOverview_; + boost::bsignals::scoped_connection changeStatusConnection_; boost::bsignals::scoped_connection signOutConnection_; boost::bsignals::scoped_connection uiEventConnection_; diff --git a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp index 3acab12..963c5cd 100644 --- a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp @@ -4,12 +4,12 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include <cppunit/extensions/HelperMacros.h> -#include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/assign/list_of.hpp> #include <functional> #include <QA/Checker/IO.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> #include <Swift/Controllers/Roster/LeastCommonSubsequence.h> using namespace Swift; diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index ca74dbb..fbee894 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -31,11 +31,21 @@ #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" #include "Swiften/MUC/MUCRegistry.h" #include <Swiften/Client/DummyNickManager.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Base/Algorithm.h> +#include <Swiften/EventLoop/DummyEventLoop.h> using namespace Swift; #define CHILDREN mainWindow_->roster->getRoot()->getChildren() +class DummyCapsProvider : public CapsProvider { + DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());} +}; + class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RosterControllerTest); CPPUNIT_TEST(testAdd); @@ -66,12 +76,21 @@ class RosterControllerTest : public CppUnit::TestFixture { uiEventStream_ = new UIEventStream(); settings_ = new DummySettingsProvider(); nickManager_ = new DummyNickManager(); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_); + capsProvider_ = new DummyCapsProvider(); + entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); + jingleSessionManager_ = new JingleSessionManager(router_); + + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_); mainWindow_ = mainWindowFactory_->last; }; void tearDown() { delete rosterController_; + delete ftManager_; + delete jingleSessionManager_; + delete nickManager_; delete nickResolver_; delete mucRegistry_; @@ -313,6 +332,11 @@ class RosterControllerTest : public CppUnit::TestFixture { UIEventStream* uiEventStream_; MockMainWindow* mainWindow_; DummySettingsProvider* settings_; + DummyCapsProvider* capsProvider_; + EntityCapsManager* entityCapsManager_; + JingleSessionManager* jingleSessionManager_; + FileTransferManager* ftManager_; + FileTransferOverview* ftOverview_; }; CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest); diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 031e93a..289f055 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -31,6 +31,10 @@ if env["SCONS_STAGE"] == "build" : "MainController.cpp", "ProfileController.cpp", "ContactEditController.cpp", + "FileTransfer/FileTransferController.cpp", + "FileTransfer/FileTransferOverview.cpp", + "FileTransfer/FileTransferProgressInfo.cpp", + "FileTransfer/SOCKS5BytestreamProxyFinder.cpp", "Roster/RosterController.cpp", "Roster/RosterGroupExpandinessPersister.cpp", "Roster/ContactRosterItem.cpp", @@ -42,6 +46,7 @@ if env["SCONS_STAGE"] == "build" : "SoundEventController.cpp", "SystemTrayController.cpp", "XMLConsoleController.cpp", + "FileTransferListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", diff --git a/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h b/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h new file mode 100644 index 0000000..aff6909 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + +class RequestFileTransferListUIEvent : public UIEvent { +}; + +} diff --git a/Swift/Controllers/UIEvents/SendFileUIEvent.h b/Swift/Controllers/UIEvents/SendFileUIEvent.h new file mode 100644 index 0000000..3bfa69d --- /dev/null +++ b/Swift/Controllers/UIEvents/SendFileUIEvent.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swiften/JID/JID.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class SendFileUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<SendFileUIEvent> ref; + + SendFileUIEvent(const JID& jid, const std::string& filename) : jid(jid), filename(filename) { + } + + const JID& getJID() const { + return jid; + } + + const std::string& getFilename() const { + return filename; + } + + private: + JID jid; + std::string filename; + }; +} diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index faef5c8..b90efd9 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -24,12 +24,14 @@ namespace Swift { class TabComplete; class RosterItem; class ContactRosterItem; + class FileTransferController; class ChatWindow { public: enum AckState {Pending, Received, Failed}; enum Tristate {Yes, No, Maybe}; enum OccupantAction {Kick}; + enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; ChatWindow() {} virtual ~ChatWindow() {}; @@ -45,6 +47,11 @@ namespace Swift { virtual void addPresenceMessage(const std::string& message) = 0; virtual void addErrorMessage(const std::string& message) = 0; virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + + // File transfer related stuff + virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; + virtual void setFileTransferProgress(std::string, const int percentageDone) = 0; + virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0; virtual void setContactChatState(ChatState::ChatStateType state) = 0; virtual void setName(const std::string& name) = 0; @@ -88,6 +95,12 @@ namespace Swift { boost::signal<void ()> onAlertButtonClicked; boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged; boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; + + // File transfer related + boost::signal<void (std::string /* id */)> onFileTransferCancel; + boost::signal<void (std::string /* id */, std::string /* description */)> onFileTransferStart; + boost::signal<void (std::string /* id */, std::string /* path */)> onFileTransferAccept; + boost::signal<void (std::string /* path */)> onSendFileRequest; }; } #endif diff --git a/Swift/Controllers/UIInterfaces/FileTransferListWidget.h b/Swift/Controllers/UIInterfaces/FileTransferListWidget.h new file mode 100644 index 0000000..01dcfd3 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/FileTransferListWidget.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + +class FileTransferOverview; + +class FileTransferListWidget { +public: + virtual ~FileTransferListWidget() {} + + virtual void show() = 0; + virtual void activate() = 0; + + virtual void setFileTransferOverview(FileTransferOverview*) = 0; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h b/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h new file mode 100644 index 0000000..0b08fb3 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidget.h" + +namespace Swift { + +class FileTransferListWidgetFactory { +public: + virtual ~FileTransferListWidgetFactory() {} + + virtual FileTransferListWidget* createFileTransferListWidget() = 0; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 57f55d0..cf89dab 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -18,6 +18,7 @@ #include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h> #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> namespace Swift { class UIFactory : @@ -27,12 +28,13 @@ namespace Swift { public LoginWindowFactory, public MainWindowFactory, public MUCSearchWindowFactory, - public XMLConsoleWidgetFactory, + public XMLConsoleWidgetFactory, public UserSearchWindowFactory, public JoinMUCWindowFactory, public ProfileWindowFactory, public ContactEditWindowFactory, - public AdHocCommandWindowFactory { + public AdHocCommandWindowFactory, + public FileTransferListWidgetFactory { public: virtual ~UIFactory() {} }; diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 574248f..b410c69 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -20,6 +20,11 @@ namespace Swift { virtual void addErrorMessage(const std::string& /*message*/) {}; virtual void addPresenceMessage(const std::string& /*message*/) {}; + // File transfer related stuff + virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }; + virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { }; + virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { }; + virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}; virtual void setName(const std::string& name) {name_ = name;}; virtual void show() {}; @@ -40,7 +45,7 @@ namespace Swift { virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}; virtual void cancelAlert() {}; virtual void setCorrectionEnabled(Tristate /*enabled*/) {} - void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {} + void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {} boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 4c6a729..d51f74c 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -18,6 +18,8 @@ #include <QMessageBox> #include <QApplication> +#include <Swiften/Base/Log.h> + #include "QtWebView.h" #include "QtChatTheme.h" @@ -47,9 +49,11 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), f #else mainLayout->addWidget(webView_); #endif + setAcceptDrops(true); webPage_ = new QWebPage(this); webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); + webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); webView_->setPage(webPage_); connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); @@ -151,6 +155,10 @@ QString QtChatView::getLastSentMessage() { return lastElement_.toPlainText(); } +void QtChatView::addToJSEnvironment(const QString& name, QObject* obj) { + webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj); +} + void QtChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) { rememberScrolledToBottom(); QWebElement message = document_.findFirst("#" + id); @@ -263,4 +271,69 @@ void QtChatView::resetView() { connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); } +QWebElement findDivElementWithID(QWebElement document, QString id) { + QWebElementCollection divs = document.findAll("div"); + foreach(QWebElement div, divs) { + if (div.attribute("id") == id) { + return div; + } + } + return QWebElement(); +} + +void QtChatView::setFileTransferProgress(QString id, const int percentageDone) { + QWebElement ftElement = findDivElementWithID(document_, id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl; + return; + } + QWebElement progressBar = ftElement.findFirst("div.progressbar"); + progressBar.setStyleProperty("width", QString::number(percentageDone) + "%"); + + QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value"); + progressBarValue.setInnerXml(QString::number(percentageDone) + " %"); +} + +void QtChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) { + QWebElement ftElement = findDivElementWithID(document_, id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << id.toStdString() << std::endl; + return; + } + + QString newInnerHTML = ""; + if (state == ChatWindow::WaitingForAccept) { + newInnerHTML = "Waiting for other side to accept the transfer.<br/>" + "<input id=\"discard\" type=\"submit\" value=\"Cancel\" onclick=\"filetransfer.cancel(\'" + id + "\');\">"; + } + if (state == ChatWindow::Negotiating) { + // replace with text "Negotiaging" + Cancel button + newInnerHTML = "Negotiating...<br/>" + "<input id=\"discard\" type=\"submit\" value=\"Cancel\" onclick=\"filetransfer.cancel(\'" + id + "\');\">"; + } + else if (state == ChatWindow::Transferring) { + // progress bar + Cancel Button + newInnerHTML = "<div style=\"position: relative; width: 90%; height: 20px; border: 2px solid grey; -webkit-border-radius: 10px;\">" + "<div class=\"progressbar\" style=\"width: 0%; height: 100%; background: #AAA; -webkit-border-radius: 6px;\">" + "<div class=\"progressbar-value\" style=\"position: absolute; top: 0px; left: 0px; width: 100%; text-align: center; padding-top: 2px;\">" + "0%" + "</div>" + "</div>" + "</div>" + "<input id=\"discard\" type=\"submit\" value=\"Cancel\" onclick=\"filetransfer.cancel(\'" + id + "\');\">"; + } + else if (state == ChatWindow::Canceled) { + newInnerHTML = "Transfer has been canceled!"; + } + else if (state == ChatWindow::Finished) { + // text "Successful transfer" + newInnerHTML = "Transfer completed successfully."; + } + else if (state == ChatWindow::FTFailed) { + newInnerHTML = "Transfer failed."; + } + + ftElement.setInnerXml(newInnerHTML); +} + } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index eda7e42..cf47029 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -16,6 +16,8 @@ #include "ChatSnippet.h" +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + class QWebPage; class QUrl; @@ -34,6 +36,9 @@ namespace Swift { void rememberScrolledToBottom(); void setAckXML(const QString& id, const QString& xml); QString getLastSentMessage(); + void addToJSEnvironment(const QString&, QObject*); + void setFileTransferProgress(QString id, const int percentageDone); + void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); signals: void gotFocus(); diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index a36bc32..238100a 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -19,8 +19,15 @@ #include "QtScaledAvatarCache.h" #include "SwifTools/TabComplete.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include "QtFileTransferJSBridge.h" + +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> #include <QLabel> +#include <QInputDialog> #include <QApplication> #include <QBoxLayout> #include <QCloseEvent> @@ -33,9 +40,12 @@ #include <QTime> #include <QUrl> #include <QPushButton> +#include <QFileDialog> + +#include <QDebug> namespace Swift { -QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), eventStream_(eventStream) { +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), previousMessageWasFileTransfer_(false), eventStream_(eventStream) { unreadCount_ = 0; inputEnabled_ = true; completer_ = NULL; @@ -45,6 +55,8 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt updateTitleWithUnreadCount(); QtSettingsProvider settings; + setAcceptDrops(true); + alertStyleSheet_ = "background: rgb(255, 255, 153); color: black"; QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); @@ -117,15 +129,23 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus())); resize(400,300); connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1)); treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2)); + fileTransferJS = new QtFileTransferJSBridge(); + messageLog_->addToJSEnvironment("filetransfer", fileTransferJS); + connect(fileTransferJS, SIGNAL(setDescription(QString)), this, SLOT(handleFileTransferSetDescription(QString))); + connect(fileTransferJS, SIGNAL(sendRequest(QString)), this, SLOT(handleFileTransferStart(QString))); + connect(fileTransferJS, SIGNAL(acceptRequest(QString, QString)), this, SLOT(handleFileTransferAccept(QString, QString))); + connect(fileTransferJS, SIGNAL(cancel(QString)), this, SLOT(handleFileTransferCancel(QString))); } QtChatWindow::~QtChatWindow() { - + delete fileTransferJS; } + void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); } @@ -306,6 +326,7 @@ void QtChatWindow::closeEvent(QCloseEvent* event) { } void QtChatWindow::convertToMUC() { + setAcceptDrops(false); treeWidget_->show(); } @@ -391,7 +412,7 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri QString styleSpanEnd = style == "" ? "" : "</span>"; htmlString += styleSpanStart + messageHTML + styleSpanEnd; - bool appendToPrevious = !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); + bool appendToPrevious = !previousMessageWasFileTransfer_ && !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); if (lastLineTracker_.getShouldMoveLastLine()) { /* should this be queued? */ messageLog_->addLastSeenLine(); @@ -406,6 +427,7 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri previousSenderName_ = P2QSTRING(senderName); previousMessageWasSystem_ = false; previousMessageWasPresence_ = false; + previousMessageWasFileTransfer_ = false; return id; } @@ -431,6 +453,94 @@ std::string QtChatWindow::addAction(const std::string &message, const std::strin return addMessage(" *" + message + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time); } +std::string formatSize(const uintmax_t bytes) { + static const char *siPrefix[] = {"k", "M", "G", "T", "P", "E", "Z", "Y", NULL}; + int power = 0; + double engBytes = bytes; + while (engBytes >= 1000) { + ++power; + engBytes = engBytes / 1000.0; + } + return str( boost::format("%.1lf %sB") % engBytes % (power > 0 ? siPrefix[power-1] : "") ); +} + +std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { + qDebug() << "addFileTransfer"; + std::string ft_id = id_.generateID(); + + std::string htmlString; + if (senderIsSelf) { + // outgoing + htmlString = "Send file: " + filename + " ( " + formatSize(sizeInBytes) + ") </br>" + + "<div id='" + ft_id + "'>" + + "<input id='discard' type='submit' value='Cancel' onclick='filetransfer.cancel(\"" + ft_id + "\");' />" + + "<input id='description' type='submit' value='Set Description' onclick='filetransfer.setDescription(\"" + ft_id + "\");' />" + + "<input id='send' type='submit' value='Send' onclick='filetransfer.sendRequest(\"" + ft_id + "\");' />" + + "</div>"; + } else { + // incoming + htmlString = "Receiving file: " + filename + " ( " + formatSize(sizeInBytes) + ") </br>" + + "<div id='" + ft_id + "'>" + + "<input id='discard' type='submit' value='Cancel' onclick='filetransfer.cancel(\"" + ft_id + "\");' />" + + "<input id='accept' type='submit' value='Accept' onclick='filetransfer.acceptRequest(\"" + ft_id + "\", \"" + filename + "\");' />" + + "</div>"; + } + + //addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time()); + + bool appendToPrevious = !previousMessageWasFileTransfer_ && !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 = "qrc:/icons/avatar.png"; + std::string id = id_.generateID(); + messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(QString::fromStdString(htmlString), Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + + + return ft_id; +} + +void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) { + messageLog_->setFileTransferProgress(QString::fromStdString(id), percentageDone); +} + +void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) { + messageLog_->setFileTransferStatus(QString::fromStdString(id), state, QString::fromStdString(msg)); +} + +void QtChatWindow::handleFileTransferCancel(QString id) { + qDebug() << "QtChatWindow::handleFileTransferCancel(" << id << ")"; + onFileTransferCancel(id.toStdString()); +} + +void QtChatWindow::handleFileTransferSetDescription(QString id) { + bool ok = false; + QString text = QInputDialog::getText(this, tr("File transfer description"), + tr("Description:"), QLineEdit::Normal, "", &ok); + if (ok) { + descriptions[id] = text; + } +} + +void QtChatWindow::handleFileTransferStart(QString id) { + qDebug() << "QtChatWindow::handleFileTransferStart(" << id << ")"; + + QString text = descriptions.find(id) == descriptions.end() ? QString() : descriptions[id]; + onFileTransferStart(id.toStdString(), text.toStdString()); +} + +void QtChatWindow::handleFileTransferAccept(QString id, QString filename) { + qDebug() << "QtChatWindow::handleFileTransferAccept(" << id << ", " << filename << ")"; + + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename); + if (!path.isEmpty()) { + onFileTransferAccept(id.toStdString(), path.toStdString()); + } +} + void QtChatWindow::addErrorMessage(const std::string& errorMessage) { if (isWidgetSelected()) { onAllMessagesRead(); @@ -443,6 +553,7 @@ void QtChatWindow::addErrorMessage(const std::string& errorMessage) { previousMessageWasSelf_ = false; previousMessageWasSystem_ = true; previousMessageWasPresence_ = false; + previousMessageWasFileTransfer_ = false; } void QtChatWindow::addSystemMessage(const std::string& message) { @@ -458,6 +569,7 @@ void QtChatWindow::addSystemMessage(const std::string& message) { previousMessageWasSelf_ = false; previousMessageWasSystem_ = true; previousMessageWasPresence_ = false; + previousMessageWasFileTransfer_ = false; } void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { @@ -532,6 +644,21 @@ void QtChatWindow::moveEvent(QMoveEvent*) { emit geometryChanged(); } +void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + // TODO: check whether contact actually supports file transfer + event->acceptProposedAction(); + } +} + +void QtChatWindow::dropEvent(QDropEvent *event) { + if (event->mimeData()->urls().size() == 1) { + onSendFileRequest(event->mimeData()->urls().at(0).toLocalFile().toStdString()); + } else { + addSystemMessage("Sending of multiple files at once isn't supported at this time."); + } +} + void QtChatWindow::replaceLastMessage(const std::string& message) { messageLog_->replaceLastMessage(P2QSTRING(Linkify::linkify(message))); } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index d38e9c6..f0c078c 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -14,6 +14,8 @@ #include "Swiften/Base/IDGenerator.h" +#include <map> + class QTextEdit; class QLineEdit; class QComboBox; @@ -28,6 +30,7 @@ namespace Swift { class TreeWidget; class QtTextEdit; class UIEventStream; + class QtFileTransferJSBridge; class QtChatWindow : public QtTabbable, public ChatWindow { Q_OBJECT public: @@ -39,6 +42,11 @@ namespace Swift { void addPresenceMessage(const std::string& message); void addErrorMessage(const std::string& errorMessage); void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + // File transfer related stuff + std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes); + void setFileTransferProgress(std::string id, const int percentageDone); + void setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg); + void show(); void activate(); void setUnreadMessageCount(int count); @@ -79,6 +87,9 @@ namespace Swift { void resizeEvent(QResizeEvent* event); void moveEvent(QMoveEvent* event); + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + protected: void showEvent(QShowEvent* event); @@ -88,6 +99,13 @@ namespace Swift { void handleKeyPressEvent(QKeyEvent* event); void handleSplitterMoved(int pos, int index); void handleAlertButtonClicked(); + + + void handleFileTransferCancel(QString id); + void handleFileTransferSetDescription(QString id); + void handleFileTransferStart(QString id); + void handleFileTransferAccept(QString id, QString filename); + private: void updateTitleWithUnreadCount(); void tabComplete(); @@ -116,6 +134,7 @@ namespace Swift { bool previousMessageWasSelf_; bool previousMessageWasSystem_; bool previousMessageWasPresence_; + bool previousMessageWasFileTransfer_; QString previousSenderName_; bool inputClearing_; UIEventStream* eventStream_; @@ -124,5 +143,8 @@ namespace Swift { QSplitter *logRosterSplitter_; Tristate correctionEnabled_; QString alertStyleSheet_; + + std::map<QString, QString> descriptions; + QtFileTransferJSBridge* fileTransferJS; }; } diff --git a/Swift/QtUI/QtFileTransferJSBridge.cpp b/Swift/QtUI/QtFileTransferJSBridge.cpp new file mode 100644 index 0000000..76c1509 --- /dev/null +++ b/Swift/QtUI/QtFileTransferJSBridge.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFileTransferJSBridge.h" + +namespace Swift { + +QtFileTransferJSBridge::QtFileTransferJSBridge() { + +} + +QtFileTransferJSBridge::~QtFileTransferJSBridge() { + +} + +}
\ No newline at end of file diff --git a/Swift/QtUI/QtFileTransferJSBridge.h b/Swift/QtUI/QtFileTransferJSBridge.h new file mode 100644 index 0000000..bd884e5 --- /dev/null +++ b/Swift/QtUI/QtFileTransferJSBridge.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QObject> + +#include <map> + +namespace Swift { + +class FileTransferController; + +class QtFileTransferJSBridge : public QObject { + Q_OBJECT +public: + QtFileTransferJSBridge(); + virtual ~QtFileTransferJSBridge(); +signals: + void discard(QString id); + void sendRequest(QString id); + void setDescription(QString id); + void acceptRequest(QString id, QString filename); + void cancel(QString id); +}; + +} diff --git a/Swift/QtUI/QtFileTransferListItemModel.cpp b/Swift/QtUI/QtFileTransferListItemModel.cpp new file mode 100644 index 0000000..1b8ec51 --- /dev/null +++ b/Swift/QtUI/QtFileTransferListItemModel.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFileTransferListItemModel.h" + +#include <boost/bind.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> + +namespace Swift { + +extern std::string formatSize(const uintmax_t bytes); + +QtFileTransferListItemModel::QtFileTransferListItemModel(QObject *parent) : QAbstractItemModel(parent), fileTransferOverview(0) { +} + +void QtFileTransferListItemModel::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + fileTransferOverview->onNewFileTransferController.connect(boost::bind(&QtFileTransferListItemModel::handleNewFileTransferController, this, _1)); +} + +void QtFileTransferListItemModel::handleNewFileTransferController(FileTransferController* newController) { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + dataChanged(createIndex(0,0), createIndex(fileTransferOverview->getFileTransfers().size(),4)); + newController->onStateChage.connect(boost::bind(&QtFileTransferListItemModel::handleStateChange, this, fileTransferOverview->getFileTransfers().size() - 1)); + newController->onProgressChange.connect(boost::bind(&QtFileTransferListItemModel::handleProgressChange, this, fileTransferOverview->getFileTransfers().size() - 1)); +} + +void QtFileTransferListItemModel::handleStateChange(int index) { + emit dataChanged(createIndex(index, 2), createIndex(index, 2)); +} + +void QtFileTransferListItemModel::handleProgressChange(int index) { + emit dataChanged(createIndex(index, 3), createIndex(index, 3)); +} + +QVariant QtFileTransferListItemModel::headerData(int section, Qt::Orientation /* orientation */, int role) const { + if (role != Qt::DisplayRole) return QVariant(); + if (section == Direction) return QVariant("Direction"); + if (section == OtherParty) return QVariant("Other Party"); + if (section == State) return QVariant("State"); + if (section == Progress) return QVariant("Progress"); + if (section == OverallSize) return QVariant("Size"); + return QVariant(); +} + +int QtFileTransferListItemModel::columnCount(const QModelIndex& /* parent */) const { + return NoOfColumns; +} + +QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) const { + if (role != Qt::DisplayRole || !index.isValid() || + !fileTransferOverview || static_cast<size_t>(index.row()) >= fileTransferOverview->getFileTransfers().size()) { + return QVariant(); + } + FileTransferController* controller = fileTransferOverview->getFileTransfers().at(index.row()); + if (index.column() == Direction) { + return controller->isIncoming() ? QVariant("Incoming") : QVariant("Outgoing"); + } + if (index.column() == OtherParty) { + return QVariant(QString::fromStdString(controller->getOtherParty().toString())); + } + if (index.column() == State) { + FileTransfer::State state = controller->getState(); + switch(state.state) { + case FileTransfer::State::WaitingForStart: + return QVariant("Waiting for start"); + case FileTransfer::State::WaitingForAccept: + return QVariant("Waiting for other side to accept"); + case FileTransfer::State::Negotiating: + return QVariant("Negotiating"); + case FileTransfer::State::Transferring: + return QVariant("Transferring"); + case FileTransfer::State::Finished: + return QVariant("Finished"); + case FileTransfer::State::Failed: + return QVariant("Failed"); + case FileTransfer::State::Canceled: + return QVariant("Canceled"); + } + } + + if (index.column() == Progress) { + return QVariant(QString::number(controller->getProgress())); + } + if (index.column() == OverallSize) { + return QVariant(QString::fromStdString(formatSize((controller->getSize())))); + } + return QVariant(); +} + +QModelIndex QtFileTransferListItemModel::parent(const QModelIndex& /* child */) const { + return createIndex(0,0); +} + +int QtFileTransferListItemModel::rowCount(const QModelIndex& /* parent */) const { + return fileTransferOverview ? fileTransferOverview->getFileTransfers().size() : 0; +} + +QModelIndex QtFileTransferListItemModel::index(int row, int column, const QModelIndex& /* parent */) const { + return createIndex(row, column, 0); +} + +} diff --git a/Swift/QtUI/QtFileTransferListItemModel.h b/Swift/QtUI/QtFileTransferListItemModel.h new file mode 100644 index 0000000..1d892a5 --- /dev/null +++ b/Swift/QtUI/QtFileTransferListItemModel.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QAbstractItemModel> + +namespace Swift { + +class FileTransferController; +class FileTransferOverview; + +class QtFileTransferListItemModel : public QAbstractItemModel { + Q_OBJECT +public: + explicit QtFileTransferListItemModel(QObject *parent = 0); + + void setFileTransferOverview(FileTransferOverview*); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + +private: + enum Columns { + Direction = 0, + OtherParty, + State, + Progress, + OverallSize, + NoOfColumns, + }; + +private: + void handleNewFileTransferController(FileTransferController*); + void handleStateChange(int index); + void handleProgressChange(int index); + +signals: + +public slots: + +private: + FileTransferOverview *fileTransferOverview; + +}; + +} diff --git a/Swift/QtUI/QtFileTransferListWidget.cpp b/Swift/QtUI/QtFileTransferListWidget.cpp new file mode 100644 index 0000000..01c632f --- /dev/null +++ b/Swift/QtUI/QtFileTransferListWidget.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtFileTransferListWidget.h" + +#include <Swift/QtUI/QtFileTransferListItemModel.h> + +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QWidget> +#include <QPushButton> + +namespace Swift { + +QtFileTransferListWidget::QtFileTransferListWidget() : fileTransferOverview(0) { + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(0,0,0,0); + + treeView = new QTreeView(this); + treeView->setRootIsDecorated(false); + treeView->setItemsExpandable(false); + layout->addWidget(treeView); + + itemModel = new QtFileTransferListItemModel(); + treeView->setModel(itemModel); + + QWidget* bottom = new QWidget(this); + layout->addWidget(bottom); + bottom->setAutoFillBackground(true); + + QHBoxLayout* buttonLayout = new QHBoxLayout(bottom); + buttonLayout->setContentsMargins(10,0,20,0); + buttonLayout->setSpacing(0); + + QPushButton* clearFinished = new QPushButton(tr("Clear Finished Transfers"), bottom); + clearFinished->setEnabled(false); + //connect(clearButton, SIGNAL(clicked()), textEdit, SLOT(clear())); + buttonLayout->addWidget(clearFinished); + + setWindowTitle(tr("File Transfer List")); + emit titleUpdated(); +} + +QtFileTransferListWidget::~QtFileTransferListWidget() { + delete itemModel; +} + +void QtFileTransferListWidget::showEvent(QShowEvent* event) { + emit windowOpening(); + emit titleUpdated(); /* This just needs to be somewhere after construction */ + QWidget::showEvent(event); +} + +void QtFileTransferListWidget::show() { + QWidget::show(); + emit windowOpening(); +} + +void QtFileTransferListWidget::activate() { + emit wantsToActivate(); +} + +void QtFileTransferListWidget::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + itemModel->setFileTransferOverview(overview); +} + +void QtFileTransferListWidget::closeEvent(QCloseEvent* event) { + emit windowClosing(); + event->accept(); +} + +} diff --git a/Swift/QtUI/QtFileTransferListWidget.h b/Swift/QtUI/QtFileTransferListWidget.h new file mode 100644 index 0000000..c828d4e --- /dev/null +++ b/Swift/QtUI/QtFileTransferListWidget.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidget.h" + +#include "QtTabbable.h" + +#include <QCloseEvent> +#include <QShowEvent> +#include <QTreeView> + +namespace Swift { + +class FileTransferOverview; +class QtFileTransferListItemModel; + +class QtFileTransferListWidget : public QtTabbable, public FileTransferListWidget { + Q_OBJECT + +public: + QtFileTransferListWidget(); + virtual ~QtFileTransferListWidget(); + + void show(); + void activate(); + + void setFileTransferOverview(FileTransferOverview *); + +private: + virtual void closeEvent(QCloseEvent* event); + virtual void showEvent(QShowEvent* event); + +private: + QTreeView* treeView; + + QtFileTransferListItemModel* itemModel; + FileTransferOverview* fileTransferOverview; +}; + +} diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index 475863c..543af65 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -7,6 +7,7 @@ #include "QtLoginWindow.h" #include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> #include <algorithm> #include <cassert> @@ -28,6 +29,7 @@ #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UIEvents/RequestXMLConsoleUIEvent.h" +#include "Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleSoundsUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h" #include "Swiften/Base/Platform.h" @@ -166,6 +168,10 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow(), forg connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(handleShowXMLConsole())); generalMenu_->addAction(xmlConsoleAction_); + fileTransferOverviewAction_ = new QAction(tr("Show &File Transfer Overview"), this); + connect(fileTransferOverviewAction_, SIGNAL(triggered()), SLOT(handleShowFileTransferOverview())); + generalMenu_->addAction(fileTransferOverviewAction_); + toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this); toggleSoundsAction_->setCheckable(true); toggleSoundsAction_->setChecked(true); @@ -356,6 +362,10 @@ void QtLoginWindow::handleShowXMLConsole() { uiEventStream_->send(boost::shared_ptr<RequestXMLConsoleUIEvent>(new RequestXMLConsoleUIEvent())); } +void QtLoginWindow::handleShowFileTransferOverview() { + uiEventStream_->send(boost::make_shared<RequestFileTransferListUIEvent>()); +} + void QtLoginWindow::handleToggleSounds(bool enabled) { uiEventStream_->send(boost::shared_ptr<ToggleSoundsUIEvent>(new ToggleSoundsUIEvent(enabled))); } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 4628af7..414bb20 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -51,6 +51,7 @@ namespace Swift { void handleCertficateChecked(bool); void handleQuit(); void handleShowXMLConsole(); + void handleShowFileTransferOverview(); void handleToggleSounds(bool enabled); void handleToggleNotifications(bool enabled); void handleAbout(); @@ -88,5 +89,6 @@ namespace Swift { QPointer<QtAboutWidget> aboutDialog_; bool forgetful_; QAction* xmlConsoleAction_; + QAction* fileTransferOverviewAction_; }; } diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index bd936d4..9de700c 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -24,6 +24,7 @@ #include "QtProfileWindow.h" #include "QtContactEditWindow.h" #include "QtAdHocCommandWindow.h" +#include "QtFileTransferListWidget.h" #define CHATWINDOW_FONT_SIZE "chatWindowFontSize" @@ -43,6 +44,15 @@ XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { return widget; } +FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { + QtFileTransferListWidget* widget = new QtFileTransferListWidget(); + tabs->addTab(widget); + if (!tabs->isVisible()) { + tabs->show(); + } + widget->show(); + return widget; +} MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { lastMainWindow = new QtMainWindow(settings, eventStream); diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index e7ea6c6..8fc5395 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -39,6 +39,7 @@ namespace Swift { virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream); virtual ProfileWindow* createProfileWindow(); virtual ContactEditWindow* createContactEditWindow(); + virtual FileTransferListWidget* createFileTransferListWidget(); virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); private slots: diff --git a/Swift/QtUI/QtWebView.cpp b/Swift/QtUI/QtWebView.cpp index 5f9539e..d849ce7 100644 --- a/Swift/QtUI/QtWebView.cpp +++ b/Swift/QtUI/QtWebView.cpp @@ -45,7 +45,9 @@ void QtWebView::setFontSizeIsMinimal(bool minimum) { void QtWebView::contextMenuEvent(QContextMenuEvent* ev) { // Filter out the relevant actions from the standard actions + QMenu* menu = page()->createStandardContextMenu(); + /* QList<QAction*> actions(menu->actions()); for (int i = 0; i < actions.size(); ++i) { QAction* action = actions.at(i); @@ -59,7 +61,7 @@ void QtWebView::contextMenuEvent(QContextMenuEvent* ev) { if (removeAction) { menu->removeAction(action); } - } + }*/ // Add our own custom actions menu->addAction(tr("Clear"), this, SIGNAL(clearRequested())); diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp index b0c0385..0c88ce6 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.cpp +++ b/Swift/QtUI/QtXMLConsoleWidget.cpp @@ -6,6 +6,8 @@ #include "QtXMLConsoleWidget.h" +#include <Swiften/Client/XMLBeautifier.h> + #include <QCloseEvent> #include <QTextEdit> #include <QVBoxLayout> @@ -49,6 +51,11 @@ QtXMLConsoleWidget::QtXMLConsoleWidget() { setWindowTitle(tr("Debug Console")); emit titleUpdated(); + beautifier = new XMLBeautifier(true, false); +} + +QtXMLConsoleWidget::~QtXMLConsoleWidget() { + delete beautifier; } void QtXMLConsoleWidget::showEvent(QShowEvent* event) { @@ -72,11 +79,11 @@ void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) { } void QtXMLConsoleWidget::handleDataRead(const SafeByteArray& data) { - appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(33,98,33)); + appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + beautifier->beautify(safeByteArrayToString(data)) + "\n", QColor(33,98,33)); } void QtXMLConsoleWidget::handleDataWritten(const SafeByteArray& data) { - appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(155,1,0)); + appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + beautifier->beautify(safeByteArrayToString(data)) + "\n", QColor(155,1,0)); } void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QColor& color) { diff --git a/Swift/QtUI/QtXMLConsoleWidget.h b/Swift/QtUI/QtXMLConsoleWidget.h index 73a3bad..c22251f 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.h +++ b/Swift/QtUI/QtXMLConsoleWidget.h @@ -14,11 +14,14 @@ class QCheckBox; class QColor; namespace Swift { + class XMLBeautifier; + class QtXMLConsoleWidget : public QtTabbable, public XMLConsoleWidget { Q_OBJECT public: QtXMLConsoleWidget(); + ~QtXMLConsoleWidget(); void show(); void activate(); @@ -35,5 +38,6 @@ namespace Swift { private: QTextEdit* textEdit; QCheckBox* enabled; + XMLBeautifier* beautifier; }; } diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index 923f977..f3b0441 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -9,10 +9,12 @@ #include <QContextMenuEvent> #include <QMenu> #include <QInputDialog> +#include <QFileDialog> #include "Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h" #include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" #include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" #include "QtContactEditWindow.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" @@ -54,6 +56,10 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { QAction* editContact = contextMenu.addAction(tr("Edit")); QAction* removeContact = contextMenu.addAction(tr("Remove")); + QAction* sendFile = NULL; + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + sendFile = contextMenu.addAction(tr("Send File")); + } QAction* result = contextMenu.exec(event->globalPos()); if (result == editContact) { eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID())); @@ -63,6 +69,12 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID())); } } + else if (result == sendFile) { + QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;")); + if (!fileName.isEmpty()) { + eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), fileName.toStdString())); + } + } } else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) { QAction* renameGroupAction = contextMenu.addAction(tr("Rename")); diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 96a078b..79dd6a2 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -8,11 +8,14 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <QUrl> + #include "Swiften/Base/Platform.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" #include "QtSwiftUtil.h" namespace Swift { @@ -31,6 +34,7 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, QWidget* parent) : QTreeV expandAll(); setAnimated(true); setIndentation(0); + setAcceptDrops(true); setRootIsDecorated(true); connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); connect(model_, SIGNAL(itemExpanded(const QModelIndex&, bool)), this, SLOT(handleModelItemExpanded(const QModelIndex&, bool))); @@ -104,6 +108,41 @@ void QtTreeWidget::handleItemActivated(const QModelIndex& index) { } } +void QtTreeWidget::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + event->acceptProposedAction(); + } +} + +void QtTreeWidget::dropEvent(QDropEvent *event) { + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + QString filename = event->mimeData()->urls().at(0).toLocalFile(); + if (!filename.isEmpty()) { + eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), filename.toStdString())); + } + } + } + } +} + +void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) { + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + event->accept(); + return; + } + } + } + event->ignore(); +} + void QtTreeWidget::handleExpanded(const QModelIndex& index) { GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); if (item) { diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h index 1ad56d6..271fbd5 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.h +++ b/Swift/QtUI/Roster/QtTreeWidget.h @@ -8,6 +8,9 @@ #include <QTreeView> #include <QModelIndex> +#include <QDragEnterEvent> +#include <QDropEvent> +#include <QDragMoveEvent> #include "Swift/QtUI/Roster/RosterModel.h" #include "Swift/QtUI/Roster/RosterDelegate.h" @@ -31,6 +34,10 @@ class QtTreeWidget : public QTreeView{ void handleExpanded(const QModelIndex&); void handleCollapsed(const QModelIndex&); void handleClicked(const QModelIndex&); + protected: + void dragEnterEvent(QDragEnterEvent* event); + void dropEvent(QDropEvent* event); + void dragMoveEvent(QDragMoveEvent* event); protected: QModelIndexList getSelectedIndexes() const; diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 33450ed..1d7ab78 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -84,6 +84,8 @@ sources = [ "QtTabWidget.cpp", "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", + "QtFileTransferListWidget.cpp", + "QtFileTransferListItemModel.cpp", "QtAdHocCommandWindow.cpp", "QtUtilities.cpp", "QtBookmarkDetailWindow.cpp", @@ -133,6 +135,7 @@ sources = [ "QtWebView.cpp", "qrc_DefaultTheme.cc", "qrc_Swift.cc", + "QtFileTransferJSBridge.cpp", ] myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") |