summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
authorTobias Markmann <tm@ayena.de>2011-05-26 18:46:49 (GMT)
committerRemko Tronçon <git@el-tramo.be>2011-09-25 17:42:32 (GMT)
commit4f62e5ec4b42929fe3c1a68667e63cb1b7a35509 (patch)
tree0d19fac3f578dec00ccf3e58930312951e38de89 /Swift
parentde660b763459cdd707876ec244b6866abca07fa2 (diff)
downloadswift-contrib-4f62e5ec4b42929fe3c1a68667e63cb1b7a35509.zip
swift-contrib-4f62e5ec4b42929fe3c1a68667e63cb1b7a35509.tar.bz2
Google Summer of Code 2011 Project: Adding support for Jingle File Transfers (XEP-0234), Jingle SOCKS5 Bytestreams Transport Method (XEP-0260), Jingle In-Band Bytestreams Transport Method (XEP-0261) and SOCKS5 Bytestreams (XEP-0065).
License: This patch is BSD-licensed, see http://www.opensource.org/licenses/bsd-license.php
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp50
-rw-r--r--Swift/Controllers/Chat/ChatController.h13
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp15
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h8
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp17
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferController.cpp147
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferController.h72
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferOverview.cpp41
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferOverview.h38
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp31
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferProgressInfo.h29
-rw-r--r--Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.cpp53
-rw-r--r--Swift/Controllers/FileTransfer/SOCKS5BytestreamProxyFinder.h38
-rw-r--r--Swift/Controllers/FileTransferListController.cpp45
-rw-r--r--Swift/Controllers/FileTransferListController.h35
-rw-r--r--Swift/Controllers/MainController.cpp32
-rw-r--r--Swift/Controllers/MainController.h7
-rw-r--r--Swift/Controllers/Roster/ContactRosterItem.cpp8
-rw-r--r--Swift/Controllers/Roster/ContactRosterItem.h10
-rw-r--r--Swift/Controllers/Roster/Roster.cpp10
-rw-r--r--Swift/Controllers/Roster/Roster.h3
-rw-r--r--Swift/Controllers/Roster/RosterController.cpp32
-rw-r--r--Swift/Controllers/Roster/RosterController.h12
-rw-r--r--Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp4
-rw-r--r--Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp26
-rw-r--r--Swift/Controllers/SConscript5
-rw-r--r--Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h16
-rw-r--r--Swift/Controllers/UIEvents/SendFileUIEvent.h34
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h13
-rw-r--r--Swift/Controllers/UIInterfaces/FileTransferListWidget.h23
-rw-r--r--Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h20
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h6
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h7
-rw-r--r--Swift/QtUI/QtChatView.cpp73
-rw-r--r--Swift/QtUI/QtChatView.h5
-rw-r--r--Swift/QtUI/QtChatWindow.cpp133
-rw-r--r--Swift/QtUI/QtChatWindow.h22
-rw-r--r--Swift/QtUI/QtFileTransferJSBridge.cpp19
-rw-r--r--Swift/QtUI/QtFileTransferJSBridge.h30
-rw-r--r--Swift/QtUI/QtFileTransferListItemModel.cpp110
-rw-r--r--Swift/QtUI/QtFileTransferListItemModel.h54
-rw-r--r--Swift/QtUI/QtFileTransferListWidget.cpp77
-rw-r--r--Swift/QtUI/QtFileTransferListWidget.h45
-rw-r--r--Swift/QtUI/QtLoginWindow.cpp10
-rw-r--r--Swift/QtUI/QtLoginWindow.h2
-rw-r--r--Swift/QtUI/QtUIFactory.cpp10
-rw-r--r--Swift/QtUI/QtUIFactory.h1
-rw-r--r--Swift/QtUI/QtWebView.cpp4
-rw-r--r--Swift/QtUI/QtXMLConsoleWidget.cpp11
-rw-r--r--Swift/QtUI/QtXMLConsoleWidget.h4
-rw-r--r--Swift/QtUI/Roster/QtRosterWidget.cpp12
-rw-r--r--Swift/QtUI/Roster/QtTreeWidget.cpp39
-rw-r--r--Swift/QtUI/Roster/QtTreeWidget.h7
-rw-r--r--Swift/QtUI/SConscript3
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")