summaryrefslogtreecommitdiffstats
path: root/Swift
diff options
context:
space:
mode:
Diffstat (limited to 'Swift')
-rw-r--r--Swift/Controllers/AdHocManager.cpp70
-rw-r--r--Swift/Controllers/AdHocManager.h40
-rw-r--r--Swift/Controllers/CertificateMemoryStorageFactory.h28
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp110
-rw-r--r--Swift/Controllers/Chat/ChatController.h15
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp57
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h24
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp298
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h51
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp145
-rw-r--r--Swift/Controllers/Chat/MUCController.h35
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.cpp7
-rw-r--r--Swift/Controllers/Chat/MUCSearchController.h8
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp33
-rw-r--r--Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp6
-rw-r--r--Swift/Controllers/Chat/UnitTest/MockChatListWindow.h25
-rw-r--r--Swift/Controllers/Chat/UserSearchController.cpp8
-rw-r--r--Swift/Controllers/ChatMessageSummarizer.cpp45
-rw-r--r--Swift/Controllers/ChatMessageSummarizer.h20
-rw-r--r--Swift/Controllers/DiscoServiceWalker.cpp154
-rw-r--r--Swift/Controllers/DiscoServiceWalker.h72
-rw-r--r--Swift/Controllers/EventWindowController.cpp7
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferController.cpp147
-rw-r--r--Swift/Controllers/FileTransfer/FileTransferController.h73
-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/FileTransferListController.cpp45
-rw-r--r--Swift/Controllers/FileTransferListController.h35
-rw-r--r--Swift/Controllers/MainController.cpp186
-rw-r--r--Swift/Controllers/MainController.h27
-rw-r--r--Swift/Controllers/PreviousStatusStore.cpp2
-rw-r--r--Swift/Controllers/ProfileSettingsProvider.h1
-rw-r--r--Swift/Controllers/Roster/ContactRosterItem.cpp12
-rw-r--r--Swift/Controllers/Roster/ContactRosterItem.h10
-rw-r--r--Swift/Controllers/Roster/GroupRosterItem.cpp25
-rw-r--r--Swift/Controllers/Roster/GroupRosterItem.h4
-rw-r--r--Swift/Controllers/Roster/LeastCommonSubsequence.h106
-rw-r--r--Swift/Controllers/Roster/Roster.cpp19
-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/RosterGroupExpandinessPersister.cpp1
-rw-r--r--Swift/Controllers/Roster/RosterItem.cpp4
-rw-r--r--Swift/Controllers/Roster/RosterItem.h4
-rw-r--r--Swift/Controllers/Roster/TableRoster.cpp184
-rw-r--r--Swift/Controllers/Roster/TableRoster.h83
-rw-r--r--Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp308
-rw-r--r--Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp27
-rw-r--r--Swift/Controllers/Roster/UnitTest/RosterTest.cpp17
-rw-r--r--Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp91
-rw-r--r--Swift/Controllers/SConscript24
-rw-r--r--Swift/Controllers/Storages/AvatarFileStorage.cpp105
-rw-r--r--Swift/Controllers/Storages/AvatarFileStorage.h41
-rw-r--r--Swift/Controllers/Storages/CapsFileStorage.cpp34
-rw-r--r--Swift/Controllers/Storages/CapsFileStorage.h28
-rw-r--r--Swift/Controllers/Storages/CertificateFileStorage.cpp (renamed from Swift/Controllers/CertificateFileStorage.cpp)6
-rw-r--r--Swift/Controllers/Storages/CertificateFileStorage.h (renamed from Swift/Controllers/CertificateFileStorage.h)2
-rw-r--r--Swift/Controllers/Storages/CertificateFileStorageFactory.h (renamed from Swift/Controllers/CertificateFileStorageFactory.h)4
-rw-r--r--Swift/Controllers/Storages/CertificateMemoryStorage.cpp27
-rw-r--r--Swift/Controllers/Storages/CertificateMemoryStorage.h25
-rw-r--r--Swift/Controllers/Storages/CertificateStorage.cpp (renamed from Swift/Controllers/CertificateStorage.cpp)2
-rw-r--r--Swift/Controllers/Storages/CertificateStorage.h (renamed from Swift/Controllers/CertificateStorage.h)0
-rw-r--r--Swift/Controllers/Storages/CertificateStorageFactory.cpp (renamed from Swift/Controllers/CertificateStorageFactory.cpp)2
-rw-r--r--Swift/Controllers/Storages/CertificateStorageFactory.h (renamed from Swift/Controllers/CertificateStorageFactory.h)0
-rw-r--r--Swift/Controllers/Storages/CertificateStorageTrustChecker.h (renamed from Swift/Controllers/CertificateStorageTrustChecker.h)2
-rw-r--r--Swift/Controllers/Storages/FileStorages.cpp46
-rw-r--r--Swift/Controllers/Storages/FileStorages.h53
-rw-r--r--Swift/Controllers/Storages/FileStoragesFactory.h (renamed from Swift/Controllers/FileStoragesFactory.h)4
-rw-r--r--Swift/Controllers/Storages/MemoryStoragesFactory.h (renamed from Swift/Controllers/MemoryStoragesFactory.h)4
-rw-r--r--Swift/Controllers/Storages/RosterFileStorage.cpp26
-rw-r--r--Swift/Controllers/Storages/RosterFileStorage.h24
-rw-r--r--Swift/Controllers/Storages/StoragesFactory.h (renamed from Swift/Controllers/StoragesFactory.h)1
-rw-r--r--Swift/Controllers/Storages/VCardFileStorage.cpp115
-rw-r--r--Swift/Controllers/Storages/VCardFileStorage.h38
-rw-r--r--Swift/Controllers/SystemTrayController.cpp2
-rw-r--r--Swift/Controllers/UIEvents/JoinMUCUIEvent.h6
-rw-r--r--Swift/Controllers/UIEvents/RequestAdHocUIEvent.h21
-rw-r--r--Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h16
-rw-r--r--Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h13
-rw-r--r--Swift/Controllers/UIEvents/SendFileUIEvent.h34
-rw-r--r--Swift/Controllers/UIInterfaces/AdHocCommandWindow.h14
-rw-r--r--Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h22
-rw-r--r--Swift/Controllers/UIInterfaces/ChatListWindow.h43
-rw-r--r--Swift/Controllers/UIInterfaces/ChatWindow.h51
-rw-r--r--Swift/Controllers/UIInterfaces/EventWindow.h3
-rw-r--r--Swift/Controllers/UIInterfaces/FileTransferListWidget.h23
-rw-r--r--Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h20
-rw-r--r--Swift/Controllers/UIInterfaces/JoinMUCWindow.h1
-rw-r--r--Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h3
-rw-r--r--Swift/Controllers/UIInterfaces/LoginWindow.h2
-rw-r--r--Swift/Controllers/UIInterfaces/MainWindow.h2
-rw-r--r--Swift/Controllers/UIInterfaces/UIFactory.h8
-rw-r--r--Swift/Controllers/UIInterfaces/XMLConsoleWidget.h6
-rw-r--r--Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp122
-rw-r--r--Swift/Controllers/UnitTest/MockChatWindow.h16
-rw-r--r--Swift/Controllers/UnitTest/MockMainWindow.h1
-rw-r--r--Swift/Controllers/UnitTest/PresenceNotifierTest.cpp44
-rw-r--r--Swift/Controllers/XMLConsoleController.cpp4
-rw-r--r--Swift/Controllers/XMLConsoleController.h5
-rw-r--r--Swift/Controllers/XMPPEvents/EventController.cpp1
-rw-r--r--Swift/Controllers/XMPPURIController.cpp38
-rw-r--r--Swift/Controllers/XMPPURIController.h32
-rw-r--r--Swift/QtUI/.gitignore1
-rw-r--r--Swift/QtUI/ChatList/ChatListDelegate.cpp39
-rw-r--r--Swift/QtUI/ChatList/ChatListDelegate.h5
-rw-r--r--Swift/QtUI/ChatList/ChatListGroupItem.h7
-rw-r--r--Swift/QtUI/ChatList/ChatListMUCItem.cpp2
-rw-r--r--Swift/QtUI/ChatList/ChatListMUCItem.h14
-rw-r--r--Swift/QtUI/ChatList/ChatListModel.cpp22
-rw-r--r--Swift/QtUI/ChatList/ChatListModel.h11
-rw-r--r--Swift/QtUI/ChatList/ChatListRecentItem.cpp47
-rw-r--r--Swift/QtUI/ChatList/ChatListRecentItem.h35
-rw-r--r--Swift/QtUI/ChatList/QtChatListWindow.cpp47
-rw-r--r--Swift/QtUI/ChatList/QtChatListWindow.h9
-rw-r--r--Swift/QtUI/ChatSnippet.h9
-rw-r--r--Swift/QtUI/EventViewer/QtEventWindow.h2
-rw-r--r--Swift/QtUI/MUCSearch/MUCSearchRoomItem.h2
-rw-r--r--Swift/QtUI/MUCSearch/MUCSearchServiceItem.h2
-rw-r--r--Swift/QtUI/QtAdHocCommandWindow.cpp137
-rw-r--r--Swift/QtUI/QtAdHocCommandWindow.h48
-rw-r--r--Swift/QtUI/QtAvatarWidget.cpp8
-rw-r--r--Swift/QtUI/QtBookmarkDetailWindow.ui2
-rw-r--r--Swift/QtUI/QtChatTabs.cpp14
-rw-r--r--Swift/QtUI/QtChatTheme.cpp2
-rw-r--r--Swift/QtUI/QtChatTheme.h34
-rw-r--r--Swift/QtUI/QtChatView.cpp164
-rw-r--r--Swift/QtUI/QtChatView.h18
-rw-r--r--Swift/QtUI/QtChatWindow.cpp377
-rw-r--r--Swift/QtUI/QtChatWindow.h78
-rw-r--r--Swift/QtUI/QtChatWindowFactory.cpp25
-rw-r--r--Swift/QtUI/QtChatWindowFactory.h3
-rw-r--r--Swift/QtUI/QtContactEditWindow.cpp1
-rw-r--r--Swift/QtUI/QtDBUSURIHandler.cpp41
-rw-r--r--Swift/QtUI/QtDBUSURIHandler.h17
-rw-r--r--Swift/QtUI/QtFileTransferJSBridge.cpp19
-rw-r--r--Swift/QtUI/QtFileTransferJSBridge.h30
-rw-r--r--Swift/QtUI/QtFileTransferListItemModel.cpp111
-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/QtFormWidget.cpp244
-rw-r--r--Swift/QtUI/QtFormWidget.h31
-rw-r--r--Swift/QtUI/QtJoinMUCWindow.cpp7
-rw-r--r--Swift/QtUI/QtJoinMUCWindow.h4
-rw-r--r--Swift/QtUI/QtLoginWindow.cpp54
-rw-r--r--Swift/QtUI/QtLoginWindow.h13
-rw-r--r--Swift/QtUI/QtMUCConfigurationWindow.cpp56
-rw-r--r--Swift/QtUI/QtMUCConfigurationWindow.h34
-rw-r--r--Swift/QtUI/QtMainWindow.cpp89
-rw-r--r--Swift/QtUI/QtMainWindow.h20
-rw-r--r--Swift/QtUI/QtSubscriptionRequestWindow.cpp2
-rw-r--r--Swift/QtUI/QtSwift.cpp42
-rw-r--r--Swift/QtUI/QtSwift.h6
-rw-r--r--Swift/QtUI/QtTextEdit.cpp16
-rw-r--r--Swift/QtUI/QtUIFactory.cpp44
-rw-r--r--Swift/QtUI/QtUIFactory.h9
-rw-r--r--Swift/QtUI/QtURIHandler.cpp36
-rw-r--r--Swift/QtUI/QtURIHandler.h22
-rw-r--r--Swift/QtUI/QtWebView.cpp13
-rw-r--r--Swift/QtUI/QtWebView.h4
-rw-r--r--Swift/QtUI/QtXMLConsoleWidget.cpp11
-rw-r--r--Swift/QtUI/QtXMLConsoleWidget.h5
-rw-r--r--Swift/QtUI/Roster/DelegateCommons.cpp88
-rw-r--r--Swift/QtUI/Roster/DelegateCommons.h11
-rw-r--r--Swift/QtUI/Roster/QtOccupantListWidget.cpp60
-rw-r--r--Swift/QtUI/Roster/QtOccupantListWidget.h30
-rw-r--r--Swift/QtUI/Roster/QtRosterWidget.cpp100
-rw-r--r--Swift/QtUI/Roster/QtRosterWidget.h25
-rw-r--r--Swift/QtUI/Roster/QtTreeWidget.cpp97
-rw-r--r--Swift/QtUI/Roster/QtTreeWidget.h29
-rw-r--r--Swift/QtUI/Roster/QtTreeWidgetItem.cpp242
-rw-r--r--Swift/QtUI/Roster/QtTreeWidgetItem.h86
-rw-r--r--Swift/QtUI/Roster/RosterDelegate.cpp81
-rw-r--r--Swift/QtUI/Roster/RosterDelegate.h4
-rw-r--r--Swift/QtUI/SConscript67
-rw-r--r--Swift/QtUI/main.cpp2
-rw-r--r--Swift/QtUI/swift-open-uri.cpp30
-rw-r--r--Swift/Translations/swift_nl.ts63
-rw-r--r--Swift/resources/logo/dmg.pngbin0 -> 39414 bytes
181 files changed, 6131 insertions, 1226 deletions
diff --git a/Swift/Controllers/AdHocManager.cpp b/Swift/Controllers/AdHocManager.cpp
new file mode 100644
index 0000000..e926138
--- /dev/null
+++ b/Swift/Controllers/AdHocManager.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/AdHocManager.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h>
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
+
+namespace Swift {
+
+AdHocManager::AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow) : jid_(jid) {
+ iqRouter_ = iqRouter;
+ uiEventStream_ = uiEventStream;
+ mainWindow_ = mainWindow;
+ factory_ = factory;
+
+ uiEventStream_->onUIEvent.connect(boost::bind(&AdHocManager::handleUIEvent, this, _1));
+}
+
+AdHocManager::~AdHocManager() {
+ uiEventStream_->onUIEvent.disconnect(boost::bind(&AdHocManager::handleUIEvent, this, _1));
+}
+
+void AdHocManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) {
+ if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::CommandsFeature)) {
+ if (discoItemsRequest_) {
+ discoItemsRequest_->onResponse.disconnect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2));
+ discoItemsRequest_.reset();
+ }
+ discoItemsRequest_ = GetDiscoItemsRequest::create(JID(jid_.getDomain()), DiscoInfo::CommandsFeature, iqRouter_);
+ discoItemsRequest_->onResponse.connect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2));
+ discoItemsRequest_->send();
+ } else {
+ mainWindow_->setAvailableAdHocCommands(std::vector<DiscoItems::Item>());
+ }
+
+}
+
+void AdHocManager::handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error) {
+ std::vector<DiscoItems::Item> commands;
+ if (!error) {
+ foreach (DiscoItems::Item item, items->getItems()) {
+ if (item.getNode() != "http://isode.com/xmpp/commands#test") {
+ commands.push_back(item);
+ }
+ }
+ }
+ mainWindow_->setAvailableAdHocCommands(commands);
+}
+
+void AdHocManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+ boost::shared_ptr<RequestAdHocUIEvent> adHocEvent = boost::dynamic_pointer_cast<RequestAdHocUIEvent>(event);
+ if (adHocEvent) {
+ factory_->createAdHocCommandWindow(boost::make_shared<OutgoingAdHocCommandSession>(adHocEvent->getCommand().getJID(), adHocEvent->getCommand().getNode(), iqRouter_));
+ }
+}
+
+}
diff --git a/Swift/Controllers/AdHocManager.h b/Swift/Controllers/AdHocManager.h
new file mode 100644
index 0000000..47b03cd
--- /dev/null
+++ b/Swift/Controllers/AdHocManager.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <vector>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/DiscoItems.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Disco/GetDiscoItemsRequest.h>
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+ class IQRouter;
+ class MainWindow;
+ class UIEventStream;
+ class AdHocCommandWindowFactory;
+ class AdHocManager {
+ public:
+ AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow);
+ ~AdHocManager();
+ void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
+ private:
+ void handleUIEvent(boost::shared_ptr<UIEvent> event);
+ void handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems>, ErrorPayload::ref error);
+ JID jid_;
+ IQRouter* iqRouter_;
+ UIEventStream* uiEventStream_;
+ MainWindow* mainWindow_;
+ AdHocCommandWindowFactory* factory_;
+ GetDiscoItemsRequest::ref discoItemsRequest_;
+ };
+}
diff --git a/Swift/Controllers/CertificateMemoryStorageFactory.h b/Swift/Controllers/CertificateMemoryStorageFactory.h
new file mode 100644
index 0000000..adbce80
--- /dev/null
+++ b/Swift/Controllers/CertificateMemoryStorageFactory.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateMemoryStorage.h>
+
+namespace Swift {
+ class CertificateFactory;
+
+ class CertificateMemoryStorageFactory : public CertificateStorageFactory {
+ public:
+ CertificateMemoryStorageFactory() {
+ }
+
+ virtual CertificateStorage* createCertificateStorage(const JID&) const {
+ return new CertificateMemoryStorage();
+ }
+
+ private:
+ boost::filesystem::path basePath;
+ CertificateFactory* certificateFactory;
+ };
+}
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 22ef68d..a3d9fb5 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -11,16 +11,22 @@
#include <Swift/Controllers/Intl.h>
#include <Swiften/Base/format.h>
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Chat/ChatStateNotifier.h"
-#include "Swiften/Chat/ChatStateTracker.h"
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swiften/Disco/EntityCapsProvider.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
+#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Chat/ChatStateNotifier.h>
+#include <Swiften/Chat/ChatStateTracker.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#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 {
@@ -28,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) {
+ : 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);
@@ -59,6 +65,11 @@ 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_);
}
@@ -74,6 +85,19 @@ ChatController::~ChatController() {
delete chatStateTracker_;
}
+void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) {
+ DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_);
+ if (disco) {
+ if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
+ chatWindow_->setCorrectionEnabled(ChatWindow::Yes);
+ } else {
+ chatWindow_->setCorrectionEnabled(ChatWindow::No);
+ }
+ } else {
+ chatWindow_->setCorrectionEnabled(ChatWindow::Maybe);
+ }
+}
+
void ChatController::setToJID(const JID& jid) {
chatStateNotifier_->setContact(jid);
ChatControllerBase::setToJID(jid);
@@ -84,6 +108,7 @@ void ChatController::setToJID(const JID& jid) {
presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid);
}
chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available);
+ handleBareJIDCapsChanged(toJID_);
}
bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
@@ -102,8 +127,8 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me
setToJID(from);
}
}
- chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
chatStateTracker_->handleMessageReceived(message);
+ chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>());
}
void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
@@ -116,27 +141,33 @@ void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) {
}
void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
- std::string id = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
- if (stanzaChannel_->getStreamManagementEnabled()) {
- chatWindow_->setAckState(id, ChatWindow::Pending);
- unackedStanzas_[sentStanza] = id;
+ boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
+ if (replace) {
+ eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_));
+ chatWindow_->replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time());
+ } else {
+ myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time());
+ }
+ if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) {
+ chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending);
+ unackedStanzas_[sentStanza] = myLastMessageUIID_;
}
lastWasPresence_ = false;
chatStateNotifier_->userSentMessage();
}
void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) {
- std::string id = unackedStanzas_[stanza];
- if (id != "") {
- chatWindow_->setAckState(id, ChatWindow::Received);
+ std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza);
+ if (unackedStanza != unackedStanzas_.end()) {
+ chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received);
+ unackedStanzas_.erase(unackedStanza);
}
- unackedStanzas_.erase(unackedStanzas_.find(stanza));
}
void ChatController::setOnline(bool online) {
if (!online) {
std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin();
- for ( ; it != unackedStanzas_.end(); it++) {
+ for ( ; it != unackedStanzas_.end(); ++it) {
chatWindow_->setAckState(it->second, ChatWindow::Failed);
}
unackedStanzas_.clear();
@@ -149,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 dd4bf90..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);
@@ -35,16 +40,26 @@ namespace Swift {
void handleStanzaAcked(boost::shared_ptr<Stanza> stanza);
void dayTicked() {lastWasPresence_ = false;}
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_;
ChatStateTracker* chatStateTracker_;
+ std::string myLastMessageUIID_;
bool isInMUC_;
bool lastWasPresence_;
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/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 281d968..df59c2f 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -7,6 +7,7 @@
#include "Swift/Controllers/Chat/ChatControllerBase.h"
#include <sstream>
+#include <map>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
@@ -20,6 +21,7 @@
#include "Swiften/Elements/Delay.h"
#include "Swiften/Base/foreach.h"
#include "Swift/Controllers/XMPPEvents/EventController.h"
+#include "Swiften/Disco/EntityCapsProvider.h"
#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
#include "Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h"
@@ -27,10 +29,11 @@
namespace Swift {
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) {
chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
- chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1));
+ chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
+ entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1));
setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable());
createDayChangeTimer();
}
@@ -39,6 +42,12 @@ ChatControllerBase::~ChatControllerBase() {
delete chatWindow_;
}
+void ChatControllerBase::handleCapsChanged(const JID& jid) {
+ if (jid.compare(toJID_, JID::WithoutResource) == 0) {
+ handleBareJIDCapsChanged(jid);
+ }
+}
+
void ChatControllerBase::createDayChangeTimer() {
if (timerFactory_) {
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
@@ -81,14 +90,21 @@ void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo>
}
void ChatControllerBase::handleAllMessagesRead() {
- foreach (boost::shared_ptr<MessageEvent> messageEvent, unreadMessages_) {
- messageEvent->read();
+ if (!unreadMessages_.empty()) {
+ foreach (boost::shared_ptr<MessageEvent> messageEvent, unreadMessages_) {
+ messageEvent->read();
+ }
+ unreadMessages_.clear();
+ chatWindow_->setUnreadMessageCount(0);
+ onUnreadCountChanged();
}
- unreadMessages_.clear();
- chatWindow_->setUnreadMessageCount(0);
}
-void ChatControllerBase::handleSendMessageRequest(const std::string &body) {
+int ChatControllerBase::getUnreadCount() {
+ return unreadMessages_.size();
+}
+
+void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) {
if (!stanzaChannel_->isAvailable() || body.empty()) {
return;
}
@@ -104,8 +120,13 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body) {
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
message->addPayload(boost::shared_ptr<Delay>(new Delay(now, selfJID_)));
}
+ if (isCorrectionMessage) {
+ message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_)));
+ }
+ message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID());
stanzaChannel_->sendMessage(message);
postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message));
+ onActivity(message->getBody());
}
void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) {
@@ -152,7 +173,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
boost::shared_ptr<Message> message = messageEvent->getStanza();
std::string body = message->getBody();
if (message->isError()) {
- std::string errorMessage = getErrorMessage(message->getPayload<ErrorPayload>());
+ std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
chatWindow_->addErrorMessage(errorMessage);
}
else {
@@ -163,7 +184,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
JID from = message->getFrom();
std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>();
for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) {
- if (!delayPayloads[i]->getFrom()) {
+ if (!delayPayloads[i]->getFrom()) {
continue;
}
boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
@@ -179,11 +200,25 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
if (messageTimeStamp) {
timeStamp = *messageTimeStamp;
}
+ onActivity(body);
- addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
+ boost::shared_ptr<Replace> replace = message->getPayload<Replace>();
+ if (replace) {
+ std::string body = message->getBody();
+ // Should check if the user has a previous message
+ std::map<JID, std::string>::iterator lastMessage;
+ lastMessage = lastMessagesUIID_.find(from);
+ if (lastMessage != lastMessagesUIID_.end()) {
+ chatWindow_->replaceMessage(body, lastMessagesUIID_[from], timeStamp);
+ }
+ }
+ else {
+ lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp);
+ }
}
chatWindow_->show();
chatWindow_->setUnreadMessageCount(unreadMessages_.size());
+ onUnreadCountChanged();
postHandleIncomingMessage(messageEvent);
}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 9573b1b..67bd74f 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -4,8 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFTEN_ChatControllerBase_H
-#define SWIFTEN_ChatControllerBase_H
+#pragma once
#include <map>
#include <vector>
@@ -26,6 +25,7 @@
#include "Swiften/Elements/ErrorPayload.h"
#include "Swiften/Presence/PresenceOracle.h"
#include "Swiften/Queries/IQRouter.h"
+#include "Swiften/Base/IDGenerator.h"
namespace Swift {
class IQRouter;
@@ -35,6 +35,7 @@ namespace Swift {
class AvatarManager;
class UIEventStream;
class EventController;
+ class EntityCapsProvider;
class ChatControllerBase : public boost::bsignals::trackable {
public:
@@ -47,8 +48,14 @@ namespace Swift {
virtual void setOnline(bool online);
virtual void setEnabled(bool enabled);
virtual void setToJID(const JID& jid) {toJID_ = jid;};
+ /** Used for determining when something is recent.*/
+ boost::signal<void (const std::string& /*activity*/)> onActivity;
+ boost::signal<void ()> onUnreadCountChanged;
+ int getUnreadCount();
+ const JID& getToJID() {return toJID_;}
+ void handleCapsChanged(const JID& jid);
protected:
- ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory);
+ ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider);
/**
* Pass the Message appended, and the stanza used to send it.
@@ -62,13 +69,16 @@ namespace Swift {
virtual bool isFromContact(const JID& from);
virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0;
virtual void dayTicked() {};
+ virtual void handleBareJIDCapsChanged(const JID& jid) = 0;
+ std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
private:
+ IDGenerator idGenerator_;
+ std::string lastSentMessageStanzaID_;
void createDayChangeTimer();
- void handleSendMessageRequest(const std::string &body);
+ void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage);
void handleAllMessagesRead();
void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error);
- std::string getErrorMessage(boost::shared_ptr<ErrorPayload>);
void handleDayChangeTick();
protected:
@@ -80,13 +90,13 @@ namespace Swift {
ChatWindow* chatWindow_;
JID toJID_;
bool labelsEnabled_;
+ std::map<JID, std::string> lastMessagesUIID_;
PresenceOracle* presenceOracle_;
AvatarManager* avatarManager_;
bool useDelayForLatency_;
EventController* eventController_;
boost::shared_ptr<Timer> dateChangeTimer_;
TimerFactory* timerFactory_;
+ EntityCapsProvider* entityCapsProvider_;
};
}
-
-#endif
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 94d4b9a..edc1a79 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -7,31 +7,40 @@
#include "Swift/Controllers/Chat/ChatsManager.h"
#include <boost/bind.hpp>
-
-#include "Swift/Controllers/Chat/ChatController.h"
-#include "Swift/Controllers/Chat/MUCSearchController.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swift/Controllers/Chat/MUCController.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
-#include "Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h"
-#include "Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h"
-#include "Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h"
-#include "Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h"
-#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/JoinMUCWindow.h"
-#include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h"
-#include "Swiften/Presence/PresenceSender.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swiften/MUC/MUCManager.h"
-#include "Swiften/Elements/ChatState.h"
-#include "Swiften/MUC/MUCBookmarkManager.h"
+#include <boost/algorithm/string.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/Chat/ChatController.h>
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
+#include <Swift/Controllers/Chat/MUCSearchController.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/Chat/MUCController.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
+#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
+#include <Swiften/Presence/PresenceSender.h>
+#include <Swiften/Client/NickResolver.h>
+#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>
namespace Swift {
typedef std::pair<JID, ChatController*> JIDChatControllerPair;
typedef std::pair<JID, MUCController*> JIDMUCControllerPair;
+#define RECENT_CHATS "recent_chats"
+
ChatsManager::ChatsManager(
JID jid, StanzaChannel* stanzaChannel,
IQRouter* iqRouter,
@@ -49,13 +58,15 @@ ChatsManager::ChatsManager(
EntityCapsProvider* entityCapsProvider,
MUCManager* mucManager,
MUCSearchWindowFactory* mucSearchWindowFactory,
- SettingsProvider* 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;
@@ -68,13 +79,21 @@ ChatsManager::ChatsManager(
presenceSender_ = presenceSender;
uiEventStream_ = uiEventStream;
mucBookmarkManager_ = NULL;
+ profileSettings_ = settings;
presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1));
uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1));
+
chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_);
+ chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1));
+ chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1));
+ chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this));
+
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();
}
ChatsManager::~ChatsManager() {
@@ -89,6 +108,72 @@ ChatsManager::~ChatsManager() {
delete mucSearchController_;
}
+void ChatsManager::saveRecents() {
+ std::string recents;
+ int i = 1;
+ foreach (ChatListWindow::Chat chat, recentChats_) {
+ std::vector<std::string> activity;
+ boost::split(activity, chat.activity, boost::is_any_of("\t\n"));
+ if (activity.size() == 0) {
+ /* Work around Boost bug https://svn.boost.org/trac/boost/ticket/4751 */
+ activity.push_back("");
+ }
+ std::string recent = chat.jid.toString() + "\t" + activity[0] + "\t" + (chat.isMUC ? "true" : "false") + "\t" + chat.nick;
+ recents += recent + "\n";
+ if (i++ > 25) {
+ break;
+ }
+ }
+ profileSettings_->storeString(RECENT_CHATS, recents);
+}
+
+void ChatsManager::handleClearRecentsRequested() {
+ recentChats_.clear();
+ saveRecents();
+ handleUnreadCountChanged(NULL);
+}
+
+void ChatsManager::loadRecents() {
+ std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
+ std::vector<std::string> recents;
+ boost::split(recents, recentsString, boost::is_any_of("\n"));
+ int i = 0;
+ foreach (std::string recentString, recents) {
+ if (i++ > 30) {
+ break;
+ }
+ std::vector<std::string> recent;
+ boost::split(recent, recentString, boost::is_any_of("\t"));
+ if (recent.size() < 4) {
+ continue;
+ }
+ JID jid(recent[0]);
+ if (!jid.isValid()) {
+ continue;
+ }
+ std::string activity(recent[1]);
+ bool isMUC = recent[2] == "true";
+ std::string nick(recent[3]);
+ StatusShow::Type type = StatusShow::None;
+ boost::filesystem::path path;
+ if (isMUC) {
+ if (mucControllers_.find(jid.toBare()) != mucControllers_.end()) {
+ type = StatusShow::Online;
+ }
+ } else {
+ if (avatarManager_) {
+ path = avatarManager_->getAvatarPath(jid);
+ }
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare());
+ type = presence ? presence->getShow() : StatusShow::None;
+ }
+
+ ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick);
+ prependRecent(chat);
+ }
+ handleUnreadCountChanged(NULL);
+}
+
void ChatsManager::setupBookmarks() {
if (!mucBookmarkManager_) {
mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
@@ -98,7 +183,7 @@ void ChatsManager::setupBookmarks() {
if (chatListWindow_) {
chatListWindow_->setBookmarksEnabled(false);
- chatListWindow_->clear();
+ chatListWindow_->clearBookmarks();
}
}
}
@@ -122,10 +207,90 @@ void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) {
chatListWindow_->removeMUCBookmark(bookmark);
}
+ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) {
+ int unreadCount = 0;
+ if (mucRegistry_->isMUC(jid)) {
+ MUCController* controller = mucControllers_[jid.toBare()];
+ StatusShow::Type type = StatusShow::None;
+ std::string nick = "";
+ if (controller) {
+ unreadCount = controller->getUnreadCount();
+ if (controller->isJoined()) {
+ type = StatusShow::Online;
+ }
+ nick = controller->getNick();
+ }
+ return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick);
+
+ } else {
+ ChatController* controller = getChatControllerIfExists(jid, false);
+ if (controller) {
+ unreadCount = controller->getUnreadCount();
+ }
+ JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare();
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(bareishJID);
+ StatusShow::Type type = presence ? presence->getShow() : StatusShow::None;
+ boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path();
+ return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false);
+ }
+}
+
+void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) {
+ if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) {
+ /* Don't include PMs in MUC rooms.*/
+ return;
+ }
+ ChatListWindow::Chat chat = createChatListChatItem(jid, activity);
+ /* FIXME: handle nick changes */
+ appendRecent(chat);
+ handleUnreadCountChanged(NULL);
+ saveRecents();
+}
+
+void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) {
+ int unreadTotal = 0;
+ bool controllerIsMUC = dynamic_cast<MUCController*>(controller);
+ bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare());
+ foreach (ChatListWindow::Chat& chatItem, recentChats_) {
+ bool match = false;
+ if (controller) {
+ /* Matching MUC item */
+ match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
+ /* Matching PM */
+ match |= isPM && chatItem.jid == controller->getToJID();
+ /* Matching non-PM */
+ match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare();
+ }
+ if (match) {
+ chatItem.setUnreadCount(controller->getUnreadCount());
+ }
+ unreadTotal += chatItem.unreadCount;
+ }
+ chatListWindow_->setRecents(recentChats_);
+ chatListWindow_->setUnreadCount(unreadTotal);
+}
+
+void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) {
+ recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
+ recentChats_.push_front(chat);
+}
+
+void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) {
+ recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
+ recentChats_.push_back(chat);
+}
+
void ChatsManager::handleUserLeftMUC(MUCController* mucController) {
std::map<JID, MUCController*>::iterator it;
- for (it = mucControllers_.begin(); it != mucControllers_.end(); it++) {
+ for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) {
if ((*it).second == mucController) {
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (chat.isMUC && chat.jid == (*it).first) {
+ chat.statusType = StatusShow::None;
+ chatListWindow_->setRecents(recentChats_);
+ break;
+ }
+ }
mucControllers_.erase(it);
delete mucController;
return;
@@ -155,15 +320,15 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark());
}
else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
- handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false);
+ handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically());
+ mucControllers_[joinEvent->getJID()]->activateChatWindow();
}
- else if (boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
+ else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
if (!joinMUCWindow_) {
- joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow();
- joinMUCWindow_->onJoinMUC.connect(boost::bind(&ChatsManager::handleJoinMUCRequest, this, _1, _2, _3));
+ joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_);
joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this));
}
- joinMUCWindow_->setMUC("");
+ joinMUCWindow_->setMUC(joinEvent->getRoom());
joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_));
joinMUCWindow_->show();
}
@@ -174,6 +339,16 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
*/
void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) {
if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return;
+
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (newPresence->getFrom().toBare() == chat.jid.toBare() && !chat.isMUC) {
+ Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare());
+ chat.setStatusType(presence ? presence->getShow() : StatusShow::None);
+ chatListWindow_->setRecents(recentChats_);
+ break;
+ }
+ }
+
//if (newPresence->getType() != Presence::Unavailable) return;
JID fullJID(newPresence->getFrom());
std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
@@ -185,7 +360,25 @@ void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence)
}
void ChatsManager::setAvatarManager(AvatarManager* avatarManager) {
+ if (avatarManager_) {
+ avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
+ }
avatarManager_ = avatarManager;
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (!chat.isMUC) {
+ chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid));
+ }
+ }
+ avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1));
+}
+
+void ChatsManager::handleAvatarChanged(const JID& jid) {
+ foreach (ChatListWindow::Chat& chat, recentChats_) {
+ if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) {
+ chat.setAvatarPath(avatarManager_->getAvatarPath(jid));
+ break;
+ }
+ }
}
void ChatsManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) {
@@ -244,6 +437,8 @@ ChatController* ChatsManager::createNewChatController(const JID& contact) {
ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_);
chatControllers_[contact] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
+ controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
+ controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
return controller;
}
@@ -252,7 +447,7 @@ ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) {
return controller ? controller : createNewChatController(contact);
}
-ChatController* ChatsManager::getChatControllerIfExists(const JID &contact) {
+ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) {
if (chatControllers_.find(contact) == chatControllers_.end()) {
if (mucRegistry_->isMUC(contact.toBare())) {
return NULL;
@@ -264,8 +459,13 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact) {
} else {
foreach (JIDChatControllerPair pair, chatControllers_) {
if (pair.first.toBare() == contact.toBare()) {
- rebindControllerJID(pair.first, contact);
- return chatControllers_[contact];
+ if (rebindIfNeeded) {
+ rebindControllerJID(pair.first, contact);
+ return chatControllers_[contact];
+ } else {
+ return pair.second;
+ }
+
}
}
return NULL;
@@ -280,8 +480,8 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
chatControllers_[to]->setToJID(to);
}
-void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& nickMaybe, bool autoJoin) {
- if (autoJoin) {
+void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& nickMaybe, bool addAutoJoin) {
+ if (addAutoJoin) {
MUCBookmark bookmark(mucJID, mucJID.getNode());
bookmark.setAutojoin(true);
if (nickMaybe) {
@@ -296,12 +496,17 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
} else {
std::string nick = nickMaybe ? nickMaybe.get() : jid_.getNode();
MUC::ref muc = mucManager->createMUC(mucJID);
- MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_);
+ MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_);
mucControllers_[mucJID] = controller;
controller->setAvailableServerFeatures(serverDiscoInfo_);
controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
+ controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true));
+ controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true));
+ controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+ handleChatActivity(mucJID.toBare(), "", true);
}
- mucControllers_[mucJID]->activateChatWindow();
+
+ mucControllers_[mucJID]->showChatWindow();
}
void ChatsManager::handleSearchMUCRequest() {
@@ -311,7 +516,7 @@ void ChatsManager::handleSearchMUCRequest() {
void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
JID jid = message->getFrom();
boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
- if (!event->isReadable() && !message->getPayload<ChatState>() && message->getSubject().empty()) {
+ if (!event->isReadable() && !message->getPayload<ChatState>() && !message->hasSubject()) {
return;
}
@@ -338,5 +543,24 @@ void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) {
}
}
+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));
+ }
+ else {
+ uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(chat.jid));
+ }
+}
+
}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 3740186..57643eb 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,17 +11,19 @@
#include <boost/shared_ptr.hpp>
#include <string>
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/Elements/Message.h"
-#include "Swiften/Elements/Presence.h"
-#include "Swiften/JID/JID.h"
-#include "Swiften/MUC/MUCRegistry.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swiften/MUC/MUCBookmark.h"
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/MUC/MUCRegistry.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+#include <Swiften/MUC/MUCBookmark.h>
namespace Swift {
class EventController;
class ChatController;
+ class ChatControllerBase;
class MUCController;
class MUCManager;
class ChatWindowFactory;
@@ -34,26 +36,29 @@ namespace Swift {
class IQRouter;
class PresenceSender;
class MUCBookmarkManager;
- class ChatListWindow;
class ChatListWindowFactory;
class TimerFactory;
class EntityCapsProvider;
class DirectedPresenceSender;
class MUCSearchWindowFactory;
- class SettingsProvider;
+ 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, SettingsProvider* 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);
void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
void handleIncomingMessage(boost::shared_ptr<Message> message);
+
private:
+ ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity);
void handleChatRequest(const std::string& contact);
- void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool autoJoin);
+ void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool addAutoJoin);
void handleSearchMUCRequest();
void handleMUCSelectedAfterSearch(const JID&);
void rebindControllerJID(const JID& from, const JID& to);
@@ -63,11 +68,26 @@ namespace Swift {
void handleMUCBookmarkRemoved(const MUCBookmark& bookmark);
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();
+ void loadRecents();
+ void saveRecents();
+ void handleChatMadeRecent();
+ void handleMUCBookmarkActivated(const MUCBookmark&);
+ void handleRecentActivated(const ChatListWindow::Chat&);
+ void handleUnreadCountChanged(ChatControllerBase* controller);
+ void handleAvatarChanged(const JID& jid);
+ void handleClearRecentsRequested();
+
ChatController* getChatControllerOrFindAnother(const JID &contact);
ChatController* createNewChatController(const JID &contact);
ChatController* getChatControllerOrCreate(const JID &contact);
- ChatController* getChatControllerIfExists(const JID &contact);
+ ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true);
+
+ private:
std::map<JID, MUCController*> mucControllers_;
std::map<JID, ChatController*> chatControllers_;
EventController* eventController_;
@@ -92,5 +112,8 @@ namespace Swift {
EntityCapsProvider* entityCapsProvider_;
MUCManager* mucManager;
MUCSearchController* mucSearchController_;
+ std::list<ChatListWindow::Chat> recentChats_;
+ ProfileSettingsProvider* profileSettings_;
+ FileTransferOverview* ftOverview_;
};
}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 2914116..e1d02ae 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "Swift/Controllers/Chat/MUCController.h"
+#include <Swift/Controllers/Chat/MUCController.h>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
@@ -12,23 +12,26 @@
#include <Swift/Controllers/Intl.h>
#include <Swiften/Base/format.h>
-#include "Swiften/Network/Timer.h"
-#include "Swiften/Network/TimerFactory.h"
-#include "Swiften/Base/foreach.h"
-#include "SwifTools/TabComplete.h"
-#include "Swiften/Base/foreach.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swiften/Avatars/AvatarManager.h"
-#include "Swiften/Elements/Delay.h"
-#include "Swiften/MUC/MUC.h"
-#include "Swiften/Client/StanzaChannel.h"
-#include "Swift/Controllers/Roster/Roster.h"
-#include "Swift/Controllers/Roster/SetAvatar.h"
-#include "Swift/Controllers/Roster/SetPresence.h"
+#include <Swiften/Network/Timer.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Base/foreach.h>
+#include <SwifTools/TabComplete.h>
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Elements/Delay.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/SetAvatar.h>
+#include <Swift/Controllers/Roster/SetPresence.h>
+#include <Swiften/Disco/EntityCapsProvider.h>
#define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
@@ -50,8 +53,9 @@ MUCController::MUCController (
UIEventStream* uiEventStream,
bool useDelayForLatency,
TimerFactory* timerFactory,
- EventController* eventController) :
- ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory), muc_(muc), nick_(nick), desiredNick_(nick) {
+ EventController* eventController,
+ EntityCapsProvider* entityCapsProvider) :
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick) {
parting_ = true;
joined_ = false;
lastWasPresence_ = false;
@@ -65,12 +69,19 @@ MUCController::MUCController (
chatWindow_->setTabComplete(completer_);
chatWindow_->setName(muc->getJID().getNode());
chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
+ chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1));
+ chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2));
+ chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1));
+ chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1));
+ chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this));
muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
+ muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1));
+ muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
if (timerFactory) {
loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
@@ -81,6 +92,7 @@ MUCController::MUCController (
if (avatarManager_ != NULL) {
avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
}
+ handleBareJIDCapsChanged(muc->getJID());
}
MUCController::~MUCController() {
@@ -93,6 +105,38 @@ MUCController::~MUCController() {
delete completer_;
}
+void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) {
+ std::vector<ChatWindow::OccupantAction> actions;
+ //FIXME
+ if (item) {
+ actions.push_back(ChatWindow::Kick);
+ }
+ chatWindow_->setAvailableOccupantActions(actions);
+}
+
+void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) {
+ switch (action) {
+ case ChatWindow::Kick: muc_->kickUser(item->getJID());break;
+ }
+}
+
+void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) {
+ ChatWindow::Tristate support = ChatWindow::Yes;
+ bool any = false;
+ foreach (const std::string& nick, currentOccupants_) {
+ DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick);
+ if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) {
+ any = true;
+ } else {
+ support = ChatWindow::Maybe;
+ }
+ }
+ if (!any) {
+ support = ChatWindow::No;
+ }
+ chatWindow_->setCorrectionEnabled(support);
+}
+
/**
* Join the MUC if not already in it.
*/
@@ -109,6 +153,14 @@ void MUCController::rejoin() {
}
}
+bool MUCController::isJoined() {
+ return joined_;
+}
+
+const std::string& MUCController::getNick() {
+ return nick_;
+}
+
void MUCController::handleJoinTimeoutTick() {
receivedActivity();
chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString()));
@@ -158,7 +210,7 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
default: break;
}
}
- errorMessage += ".";
+ errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage);
chatWindow_->addErrorMessage(errorMessage);
if (!rejoinNick.empty()) {
nick_ = rejoinNick;
@@ -176,6 +228,7 @@ void MUCController::handleJoinComplete(const std::string& nick) {
clearPresenceQueue();
shouldJoinOnReconnect_ = true;
setEnabled(true);
+ onUserJoined();
}
void MUCController::handleAvatarChanged(const JID& jid) {
@@ -206,7 +259,9 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
currentOccupants_.insert(occupant.getNick());
NickJoinPart event(occupant.getNick(), Join);
appendToJoinParts(joinParts_, event);
- roster_->addContact(jid, realJID, occupant.getNick(), roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string());
+ std::string groupName(roleToGroupName(occupant.getRole()));
+ roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid).string());
+ roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole()));
if (joined_) {
std::string joinString;
MUCOccupant::Role role = occupant.getRole();
@@ -248,6 +303,16 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) {
return "";
}
+std::string MUCController::roleToSortName(MUCOccupant::Role role) {
+ switch (role) {
+ case MUCOccupant::Moderator: return "1";
+ case MUCOccupant::Participant: return "2";
+ case MUCOccupant::Visitor: return "3";
+ case MUCOccupant::NoRole: return "4";
+ }
+ return "5";
+}
+
JID MUCController::nickToJID(const std::string& nick) {
return JID(toJID_.getNode(), toJID_.getDomain(), nick);
}
@@ -279,8 +344,9 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes
receivedActivity();
joined_ = true;
- if (!message->getSubject().empty() && message->getBody().empty()) {
+ if (message->hasSubject() && message->getBody().empty()) {
chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject()));;
+ chatWindow_->setSubject(message->getSubject());
doneGettingHistory_ = true;
}
@@ -309,7 +375,9 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC
if (occupant.getRealJID()) {
realJID = occupant.getRealJID().get();
}
- roster_->addContact(jid, realJID, nick, roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string());
+ std::string group(roleToGroupName(occupant.getRole()));
+ roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid).string());
+ roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole())));
}
@@ -411,7 +479,7 @@ void MUCController::updateJoinParts() {
void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) {
std::vector<NickJoinPart>::iterator it = joinParts.begin();
bool matched = false;
- for (; it != joinParts.end(); it++) {
+ for (; it != joinParts.end(); ++it) {
if ((*it).nick == newEvent.nick) {
matched = true;
JoinPart type = (*it).type;
@@ -508,4 +576,31 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart
return result;
}
+void MUCController::handleChangeSubjectRequest(const std::string& subject) {
+ muc_->changeSubject(subject);
+}
+
+void MUCController::handleConfigureRequest(Form::ref form) {
+ if (form) {
+ muc_->configureRoom(form);
+ }
+ else {
+ muc_->requestConfigurationForm();
+ }
+}
+
+void MUCController::handleConfigurationFailed(ErrorPayload::ref error) {
+ std::string errorMessage = getErrorMessage(error);
+ errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage);
+ chatWindow_->addErrorMessage(errorMessage);
+}
+
+void MUCController::handleConfigurationFormReceived(Form::ref form) {
+ chatWindow_->showRoomConfigurationForm(form);
+}
+
+void MUCController::handleDestroyRoomRequest() {
+ muc_->destroyRoom();
+}
+
}
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index ebdd6cd..7a7461b 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -7,23 +7,24 @@
#pragma once
#include <boost/shared_ptr.hpp>
-#include "Swiften/Base/boost_bsignals.h"
+#include <Swiften/Base/boost_bsignals.h>
#include <boost/signals/connection.hpp>
-#include <set>
+#include <set>
#include <string>
-#include "Swiften/Network/Timer.h"
-#include "Swift/Controllers/Chat/ChatControllerBase.h"
-#include "Swiften/Elements/Message.h"
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/JID/JID.h"
-#include "Swiften/MUC/MUC.h"
-#include "Swiften/Elements/MUCOccupant.h"
+#include <Swiften/Network/Timer.h>
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/Elements/MUCOccupant.h>
+#include <Swift/Controllers/Roster/RosterItem.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
namespace Swift {
class StanzaChannel;
class IQRouter;
- class ChatWindow;
class ChatWindowFactory;
class Roster;
class AvatarManager;
@@ -41,14 +42,17 @@ namespace Swift {
class MUCController : public ChatControllerBase {
public:
- MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController);
+ MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider);
~MUCController();
boost::signal<void ()> onUserLeft;
+ boost::signal<void ()> onUserJoined;
virtual void setOnline(bool online);
void rejoin();
static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts);
static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts);
+ bool isJoined();
+ const std::string& getNick();
protected:
void preSendMessageRequest(boost::shared_ptr<Message> message);
@@ -61,6 +65,8 @@ namespace Swift {
private:
void clearPresenceQueue();
void addPresenceMessage(const std::string& message);
+ void handleWindowOccupantSelectionChanged(ContactRosterItem* item);
+ void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item);
void handleWindowClosed();
void handleAvatarChanged(const JID& jid);
void handleOccupantJoined(const MUCOccupant& occupant);
@@ -70,7 +76,9 @@ namespace Swift {
void handleJoinComplete(const std::string& nick);
void handleJoinFailed(boost::shared_ptr<ErrorPayload> error);
void handleJoinTimeoutTick();
+ void handleChangeSubjectRequest(const std::string&);
std::string roleToGroupName(MUCOccupant::Role role);
+ std::string roleToSortName(MUCOccupant::Role role);
JID nickToJID(const std::string& nick);
std::string roleToFriendlyName(MUCOccupant::Role role);
void receivedActivity();
@@ -79,6 +87,11 @@ namespace Swift {
bool shouldUpdateJoinParts();
void dayTicked() {lastWasPresence_ = false;}
void processUserPart();
+ void handleBareJIDCapsChanged(const JID& jid);
+ void handleConfigureRequest(Form::ref);
+ void handleConfigurationFailed(ErrorPayload::ref);
+ void handleConfigurationFormReceived(Form::ref);
+ void handleDestroyRoomRequest();
private:
MUC::ref muc_;
diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp
index 743aabb..5312fa7 100644
--- a/Swift/Controllers/Chat/MUCSearchController.cpp
+++ b/Swift/Controllers/Chat/MUCSearchController.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,19 +11,20 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/GetDiscoItemsRequest.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Base/String.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h>
-#include <Swift/Controllers/DiscoServiceWalker.h>
+#include <Swiften/Disco/DiscoServiceWalker.h>
#include <Swiften/Client/NickResolver.h>
namespace Swift {
static const std::string SEARCHED_SERVICES = "searchedServices";
-MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) {
+MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) {
itemsInProgress_ = 0;
loadSavedServices();
}
diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h
index c8040ed..f90e4a7 100644
--- a/Swift/Controllers/Chat/MUCSearchController.h
+++ b/Swift/Controllers/Chat/MUCSearchController.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -16,7 +16,7 @@
#include "Swiften/JID/JID.h"
#include "Swift/Controllers/UIEvents/UIEvent.h"
-#include "Swift/Controllers/Settings/SettingsProvider.h"
+#include "Swift/Controllers/ProfileSettingsProvider.h"
#include "Swiften/Elements/DiscoInfo.h"
#include "Swiften/Elements/DiscoItems.h"
#include "Swiften/Elements/ErrorPayload.h"
@@ -87,7 +87,7 @@ namespace Swift {
class MUCSearchController {
public:
- MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings);
+ MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings);
~MUCSearchController();
void openSearchWindow();
@@ -112,7 +112,7 @@ namespace Swift {
JID jid_;
MUCSearchWindowFactory* factory_;
IQRouter* iqRouter_;
- SettingsProvider* settings_;
+ ProfileSettingsProvider* settings_;
MUCSearchWindow* window_;
DiscoServiceWalker* walker_;
std::list<JID> services_;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 40f7445..5339703 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -8,8 +8,11 @@
#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"
#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
#include "Swift/Controllers/Settings/DummySettingsProvider.h"
#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
@@ -35,10 +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;
@@ -59,7 +66,7 @@ class ChatsManagerTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE_END();
public:
- void setUp() {
+ void setUp() {
mocks_ = new MockRepository();
jid_ = JID("test@test.com/resource");
stanzaChannel_ = new DummyStanzaChannel();
@@ -82,19 +89,25 @@ public:
chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>();
settings_ = new DummySettingsProvider();
- mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(NULL);
- manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, settings_);
+ 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_, ftOverview_);
avatarManager_ = new NullAvatarManager();
manager_->setAvatarManager(avatarManager_);
};
void tearDown() {
- //delete chatListWindowFactory_;
+ //delete chatListWindowFactory
delete settings_;
- delete mocks_;
+ delete profileSettings_;
delete avatarManager_;
delete manager_;
+ delete ftOverview_;
+ delete ftManager_;
delete directedPresenceSender_;
delete presenceSender_;
delete presenceOracle_;
@@ -109,6 +122,8 @@ public:
delete xmppRoster_;
delete entityCapsManager_;
delete capsProvider_;
+ delete chatListWindow_;
+ delete mocks_;
}
void testFirstOpenWindowIncoming() {
@@ -346,6 +361,10 @@ private:
CapsProvider* capsProvider_;
MUCManager* mucManager_;
DummySettingsProvider* settings_;
+ ProfileSettingsProvider* profileSettings_;
+ ChatListWindow* chatListWindow_;
+ FileTransferOverview* ftOverview_;
+ FileTransferManager* ftManager_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 5f5e44d..ad5ceac 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -25,6 +25,7 @@
#include "Swiften/Presence/PresenceOracle.h"
#include "Swiften/Network/TimerFactory.h"
#include "Swiften/Elements/MUCUserPayload.h"
+#include "Swiften/Disco/DummyEntityCapsProvider.h"
using namespace Swift;
@@ -56,12 +57,14 @@ public:
TimerFactory* timerFactory = NULL;
window_ = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
mucRegistry_ = new MUCRegistry();
+ entityCapsProvider_ = new DummyEntityCapsProvider();
muc_ = MUC::ref(new MUC(stanzaChannel_, iqRouter_, directedPresenceSender_, JID("teaparty@rooms.wonderland.lit"), mucRegistry_));
mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
- controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_);
+ controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_);
};
void tearDown() {
+ delete entityCapsProvider_;
delete controller_;
delete eventController_;
delete presenceOracle_;
@@ -240,6 +243,7 @@ private:
UIEventStream* uiEventStream_;
MockChatWindow* window_;
MUCRegistry* mucRegistry_;
+ DummyEntityCapsProvider* entityCapsProvider_;
};
CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
new file mode 100644
index 0000000..6ac8d4a
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIInterfaces/ChatListWindow.h"
+
+namespace Swift {
+
+ class MockChatListWindow : public ChatListWindow {
+ public:
+ MockChatListWindow() {};
+ virtual ~MockChatListWindow() {};
+ void addMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+ void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {}
+ void setBookmarksEnabled(bool /*enabled*/) {}
+ void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {}
+ void setUnreadCount(int /*unread*/) {}
+ void clearBookmarks() {}
+ };
+
+}
diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index b3403d7..3e734df 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -9,10 +9,10 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/foreach.h>
#include <Swiften/Disco/GetDiscoInfoRequest.h>
#include <Swiften/Disco/GetDiscoItemsRequest.h>
-
-#include <Swift/Controllers/DiscoServiceWalker.h>
+#include <Swiften/Disco/DiscoServiceWalker.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
@@ -85,12 +85,12 @@ void UserSearchController::endDiscoWalker() {
void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) {
- bool isUserDirectory = false;
+ //bool isUserDirectory = false;
bool supports55 = false;
foreach (DiscoInfo::Identity identity, info->getIdentities()) {
if ((identity.getCategory() == "directory"
&& identity.getType() == "user")) {
- isUserDirectory = true;
+ //isUserDirectory = true;
}
}
std::vector<std::string> features = info->getFeatures();
diff --git a/Swift/Controllers/ChatMessageSummarizer.cpp b/Swift/Controllers/ChatMessageSummarizer.cpp
new file mode 100644
index 0000000..d95a177
--- /dev/null
+++ b/Swift/Controllers/ChatMessageSummarizer.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/ChatMessageSummarizer.h>
+
+#include <Swiften/Base/format.h>
+#include <Swift/Controllers/Intl.h>
+#include <Swiften/Base/foreach.h>
+
+using namespace Swift;
+using namespace std;
+
+string ChatMessageSummarizer::getSummary(const string& current, const vector<UnreadPair>& unreads) {
+ vector<UnreadPair> others;
+ int currentUnread = 0;
+ int otherCount = 0;
+ foreach (UnreadPair unread, unreads) {
+ if (unread.first == current) {
+ currentUnread += unread.second;
+ } else {
+ if (unread.second > 0) {
+ otherCount += unread.second;
+ others.push_back(unread);
+ }
+ }
+ }
+ string myString(current);
+
+ if (currentUnread > 0) {
+ string result(QT_TRANSLATE_NOOP("", "%1% (%2%)"));
+ myString = str(format(result) % current % currentUnread);
+ }
+
+ if (others.size() > 1) {
+ string result(QT_TRANSLATE_NOOP("", "%1% and %2% others (%3%)"));
+ myString = str(format(result) % myString % others.size() % otherCount);
+ } else if (!others.empty()) {
+ string result(QT_TRANSLATE_NOOP("", "%1%, %2% (%3%)"));
+ myString = str(format(result) % myString % others[0].first % otherCount);
+ }
+ return myString;
+}
diff --git a/Swift/Controllers/ChatMessageSummarizer.h b/Swift/Controllers/ChatMessageSummarizer.h
new file mode 100644
index 0000000..d4ff188
--- /dev/null
+++ b/Swift/Controllers/ChatMessageSummarizer.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <utility>
+#include <string>
+
+namespace Swift {
+typedef std::pair<std::string, int> UnreadPair;
+
+ class ChatMessageSummarizer {
+ public:
+ std::string getSummary(const std::string& current, const std::vector<UnreadPair>& unreads);
+ };
+}
diff --git a/Swift/Controllers/DiscoServiceWalker.cpp b/Swift/Controllers/DiscoServiceWalker.cpp
deleted file mode 100644
index ce29927..0000000
--- a/Swift/Controllers/DiscoServiceWalker.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (c) 2010 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include <Swift/Controllers/DiscoServiceWalker.h>
-#include <Swiften/Base/Log.h>
-
-#include <boost/bind.hpp>
-
-namespace Swift {
-
-DiscoServiceWalker::DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps) : service_(service), iqRouter_(iqRouter), maxSteps_(maxSteps), active_(false) {
-
-}
-
-void DiscoServiceWalker::beginWalk() {
- SWIFT_LOG(debug) << "Starting walk to " << service_ << std::endl;
- assert(!active_);
- assert(servicesBeingSearched_.empty());
- active_ = true;
- walkNode(service_);
-}
-
-void DiscoServiceWalker::endWalk() {
- if (active_) {
- SWIFT_LOG(debug) << "Ending walk to " << service_ << std::endl;
- foreach (GetDiscoInfoRequest::ref request, pendingDiscoInfoRequests_) {
- request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, request));
- }
- foreach (GetDiscoItemsRequest::ref request, pendingDiscoItemsRequests_) {
- request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, request));
- }
- active_ = false;
- }
-}
-
-void DiscoServiceWalker::walkNode(const JID& jid) {
- SWIFT_LOG(debug) << "Walking node " << jid << std::endl;
- servicesBeingSearched_.insert(jid);
- searchedServices_.insert(jid);
- GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_);
- discoInfoRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, discoInfoRequest));
- pendingDiscoInfoRequests_.insert(discoInfoRequest);
- discoInfoRequest->send();
-}
-
-void DiscoServiceWalker::handleReceivedDiscoItem(const JID& item) {
- SWIFT_LOG(debug) << "Received disco item " << item << std::endl;
-
- /* If we got canceled, don't do anything */
- if (!active_) {
- return;
- }
-
- if (std::find(searchedServices_.begin(), searchedServices_.end(), item) != searchedServices_.end()) {
- /* Don't recurse infinitely */
- return;
- }
- walkNode(item);
-}
-
-void DiscoServiceWalker::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, GetDiscoInfoRequest::ref request) {
- /* If we got canceled, don't do anything */
- if (!active_) {
- return;
- }
-
- SWIFT_LOG(debug) << "Disco info response from " << request->getReceiver() << std::endl;
-
- pendingDiscoInfoRequests_.erase(request);
- if (error) {
- handleDiscoError(request->getReceiver(), error);
- return;
- }
-
- bool couldContainServices = false;
- foreach (DiscoInfo::Identity identity, info->getIdentities()) {
- if (identity.getCategory() == "server") {
- couldContainServices = true;
- }
- }
- bool completed = false;
- if (couldContainServices) {
- GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(request->getReceiver(), iqRouter_);
- discoItemsRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, discoItemsRequest));
- pendingDiscoItemsRequests_.insert(discoItemsRequest);
- discoItemsRequest->send();
- } else {
- completed = true;
- }
- onServiceFound(request->getReceiver(), info);
- if (completed) {
- markNodeCompleted(request->getReceiver());
- }
-}
-
-void DiscoServiceWalker::handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, GetDiscoItemsRequest::ref request) {
- /* If we got canceled, don't do anything */
- if (!active_) {
- return;
- }
-
- SWIFT_LOG(debug) << "Received disco item from " << request->getReceiver() << std::endl;
-
- pendingDiscoItemsRequests_.erase(request);
- if (error) {
- handleDiscoError(request->getReceiver(), error);
- return;
- }
- foreach (DiscoItems::Item item, items->getItems()) {
- if (item.getNode().empty()) {
- /* Don't look at noded items. It's possible that this will exclude some services,
- * but I've never seen one in the wild, and it's an easy fix for not looping.
- */
- handleReceivedDiscoItem(item.getJID());
- }
- }
- markNodeCompleted(request->getReceiver());
-}
-
-void DiscoServiceWalker::handleDiscoError(const JID& jid, ErrorPayload::ref /*error*/) {
- /* If we got canceled, don't do anything */
- if (!active_) {
- return;
- }
-
- SWIFT_LOG(debug) << "Disco error from " << jid << std::endl;
-
- markNodeCompleted(jid);
-}
-
-void DiscoServiceWalker::markNodeCompleted(const JID& jid) {
- // Check whether we weren't canceled in between a 'emit result' and this call
- if (!active_) {
- return;
- }
- SWIFT_LOG(debug) << "Node completed " << jid << std::endl;
-
- servicesBeingSearched_.erase(jid);
- /* All results are in */
- if (servicesBeingSearched_.size() == 0) {
- active_ = false;
- onWalkComplete();
- }
- /* Check if we're on a rampage */
- else if (searchedServices_.size() >= maxSteps_) {
- active_ = false;
- onWalkComplete();
- }
-}
-
-}
diff --git a/Swift/Controllers/DiscoServiceWalker.h b/Swift/Controllers/DiscoServiceWalker.h
deleted file mode 100644
index 7982bbc..0000000
--- a/Swift/Controllers/DiscoServiceWalker.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2010 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include <vector>
-#include <set>
-
-#include <boost/shared_ptr.hpp>
-#include <Swiften/Base/boost_bsignals.h>
-#include <string>
-#include <Swiften/JID/JID.h>
-#include <Swiften/Elements/DiscoInfo.h>
-#include <Swiften/Elements/DiscoItems.h>
-#include <Swiften/Elements/ErrorPayload.h>
-#include <Swiften/Disco/GetDiscoInfoRequest.h>
-#include <Swiften/Disco/GetDiscoItemsRequest.h>
-
-namespace Swift {
- class IQRouter;
- /**
- * Recursively walk service discovery trees to find all services offered.
- * This stops on any disco item that's not reporting itself as a server.
- */
- class DiscoServiceWalker {
- public:
- DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps = 200);
-
- /**
- * Start the walk.
- *
- * Call this exactly once.
- */
- void beginWalk();
-
- /**
- * End the walk.
- */
- void endWalk();
-
- bool isActive() const {
- return active_;
- }
-
- /** Emitted for each service found. */
- boost::signal<void(const JID&, boost::shared_ptr<DiscoInfo>)> onServiceFound;
-
- /** Emitted when walking is complete.*/
- boost::signal<void()> onWalkComplete;
-
- private:
- void handleReceivedDiscoItem(const JID& item);
- void walkNode(const JID& jid);
- void markNodeCompleted(const JID& jid);
- void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, GetDiscoInfoRequest::ref request);
- void handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, GetDiscoItemsRequest::ref request);
- void handleDiscoError(const JID& jid, ErrorPayload::ref error);
-
- private:
- JID service_;
- IQRouter* iqRouter_;
- size_t maxSteps_;
- bool active_;
- std::set<JID> servicesBeingSearched_;
- std::set<JID> searchedServices_;
- std::set<GetDiscoInfoRequest::ref> pendingDiscoInfoRequests_;
- std::set<GetDiscoItemsRequest::ref> pendingDiscoItemsRequests_;
- };
-}
diff --git a/Swift/Controllers/EventWindowController.cpp b/Swift/Controllers/EventWindowController.cpp
index fbe9a8a..47554ce 100644
--- a/Swift/Controllers/EventWindowController.cpp
+++ b/Swift/Controllers/EventWindowController.cpp
@@ -27,8 +27,11 @@ void EventWindowController::handleEventQueueEventAdded(boost::shared_ptr<StanzaE
if (event->getConcluded()) {
handleEventConcluded(event);
} else {
- event->onConclusion.connect(boost::bind(&EventWindowController::handleEventConcluded, this, event));
- window_->addEvent(event, true);
+ boost::shared_ptr<MessageEvent> message = boost::dynamic_pointer_cast<MessageEvent>(event);
+ if (!(message && message->isReadable())) {
+ event->onConclusion.connect(boost::bind(&EventWindowController::handleEventConcluded, this, event));
+ window_->addEvent(event, true);
+ }
}
}
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..3d6f7d5
--- /dev/null
+++ b/Swift/Controllers/FileTransfer/FileTransferController.h
@@ -0,0 +1,73 @@
+/*
+ * 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 <boost/cstdint.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/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 9a35cc1..364dd57 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -14,14 +14,13 @@
#include <stdlib.h>
#include <Swiften/Base/format.h>
+#include <Swiften/Base/Algorithm.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/UIInterfaces/UIFactory.h>
#include "Swiften/Network/TimerFactory.h"
#include "Swift/Controllers/BuildVersion.h"
-#include "Swift/Controllers/StoragesFactory.h"
#include "Swiften/Client/Storages.h"
#include "Swiften/VCards/VCardManager.h"
-#include "Swift/Controllers/Chat/MUCSearchController.h"
#include "Swift/Controllers/Chat/UserSearchController.h"
#include "Swift/Controllers/Chat/ChatsManager.h"
#include "Swift/Controllers/XMPPEvents/EventController.h"
@@ -38,9 +37,11 @@
#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"
+#include "Swift/Controllers/Storages/StoragesFactory.h"
#include "SwifTools/Dock/Dock.h"
#include "SwifTools/Notifier/TogglableNotifier.h"
#include "Swiften/Base/foreach.h"
@@ -60,11 +61,17 @@
#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
#include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
-#include "Swift/Controllers/CertificateStorageFactory.h"
-#include "Swift/Controllers/CertificateStorageTrustChecker.h"
+#include "Swift/Controllers/Storages/CertificateStorageFactory.h"
+#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h"
#include "Swiften/Network/NetworkFactories.h"
#include <Swift/Controllers/ProfileController.h>
#include <Swift/Controllers/ContactEditController.h>
+#include <Swift/Controllers/XMPPURIController.h>
+#include "Swift/Controllers/AdHocManager.h"
+#include <SwifTools/Idle/IdleDetector.h>
+#include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
+#include <Swiften/FileTransfer/FileTransferManager.h>
+#include <Swiften/Client/ClientXMLTracer.h>
namespace Swift {
@@ -84,16 +91,22 @@ MainController::MainController(
CertificateStorageFactory* certificateStorageFactory,
Dock* dock,
Notifier* notifier,
- bool useDelayForLatency) :
+ URIHandler* uriHandler,
+ IdleDetector* idleDetector,
+ bool useDelayForLatency,
+ bool eagleMode) :
eventLoop_(eventLoop),
networkFactories_(networkFactories),
uiFactory_(uiFactories),
- idleDetector_(&idleQuerier_, networkFactories_->getTimerFactory(), 100),
storagesFactory_(storagesFactory),
certificateStorageFactory_(certificateStorageFactory),
settings_(settings),
+ uriHandler_(uriHandler),
+ idleDetector_(idleDetector),
loginWindow_(NULL) ,
- useDelayForLatency_(useDelayForLatency) {
+ useDelayForLatency_(useDelayForLatency),
+ eagleMode_(eagleMode),
+ ftOverview_(NULL) {
storages_ = NULL;
certificateStorage_ = NULL;
statusTracker_ = NULL;
@@ -121,33 +134,43 @@ MainController::MainController(
loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_);
soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, uiEventStream_);
+ xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_);
+
std::string selectedLoginJID = settings_->getStringSetting("lastLoginJID");
bool loginAutomatically = settings_->getBoolSetting("loginAutomatically", false);
std::string cachedPassword;
std::string cachedCertificate;
- foreach (std::string profile, settings->getAvailableProfiles()) {
- ProfileSettingsProvider profileSettings(profile, settings);
- std::string password = profileSettings.getStringSetting("pass");
- std::string certificate = profileSettings.getStringSetting("certificate");
- std::string jid = profileSettings.getStringSetting("jid");
- loginWindow_->addAvailableAccount(jid, password, certificate);
- if (jid == selectedLoginJID) {
- cachedPassword = password;
- cachedCertificate = certificate;
+ if (!eagleMode_) {
+ foreach (std::string profile, settings->getAvailableProfiles()) {
+ ProfileSettingsProvider profileSettings(profile, settings);
+ std::string password = eagleMode ? "" : profileSettings.getStringSetting("pass");
+ std::string certificate = profileSettings.getStringSetting("certificate");
+ std::string jid = profileSettings.getStringSetting("jid");
+ loginWindow_->addAvailableAccount(jid, password, certificate);
+ if (jid == selectedLoginJID) {
+ cachedPassword = password;
+ cachedCertificate = certificate;
+ }
}
+ loginWindow_->selectUser(selectedLoginJID);
+ loginWindow_->setLoginAutomatically(loginAutomatically);
+ } else {
+ loginWindow_->setRememberingAllowed(false);
}
- loginWindow_->selectUser(selectedLoginJID);
- loginWindow_->setLoginAutomatically(loginAutomatically);
+
+
loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5));
loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1));
loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this));
loginWindow_->onQuitRequest.connect(boost::bind(&MainController::handleQuitRequest, this));
- idleDetector_.setIdleTimeSeconds(600);
- idleDetector_.onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1));
+ idleDetector_->setIdleTimeSeconds(600);
+ idleDetector_->onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1));
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)));
@@ -161,12 +184,16 @@ MainController::MainController(
}
MainController::~MainController() {
+ idleDetector_->onIdleChanged.disconnect(boost::bind(&MainController::handleInputIdleChanged, this, _1));
+
+ purgeCachedCredentials();
//setManagersOffline();
eventController_->disconnectAll();
resetClient();
-
+ delete fileTransferListController_;
delete xmlConsoleController_;
+ delete xmppURIController_;
delete soundEventController_;
delete systemTrayController_;
delete eventController_;
@@ -174,7 +201,12 @@ MainController::~MainController() {
delete uiEventStream_;
}
+void MainController::purgeCachedCredentials() {
+ safeClear(password_);
+}
+
void MainController::resetClient() {
+ purgeCachedCredentials();
resetCurrentError();
resetPendingReconnects();
vCardPhotoHash_.clear();
@@ -186,6 +218,8 @@ void MainController::resetClient() {
eventWindowController_ = NULL;
delete chatsManager_;
chatsManager_ = NULL;
+ delete ftOverview_;
+ ftOverview_ = NULL;
delete rosterController_;
rosterController_ = NULL;
delete eventNotifier_;
@@ -234,20 +268,30 @@ void MainController::resetCurrentError() {
void MainController::handleConnected() {
boundJID_ = client_->getJID();
- loginWindow_->setIsLoggingIn(false);
resetCurrentError();
resetPendingReconnects();
+
+ if (eagleMode_) {
+ purgeCachedCredentials();
+ }
+
bool freshLogin = rosterController_ == NULL;
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);
+ 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_, settings_);
+ 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());
@@ -259,17 +303,25 @@ void MainController::handleConnected() {
discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc"));
discoInfo.addFeature(DiscoInfo::ChatStatesFeature);
discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature);
+ discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature);
+#ifdef SWIFT_EXPERIMENTAL_FT
+ discoInfo.addFeature(DiscoInfo::JingleFeature);
+ discoInfo.addFeature(DiscoInfo::JingleFTFeature);
+ discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature);
+ discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature);
+#endif
client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
client_->getDiscoManager()->setDiscoInfo(discoInfo);
-
userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_);
userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_);
+ adHocManager_ = new AdHocManager(boundJID_, uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow());
}
-
+ loginWindow_->setIsLoggingIn(false);
+
client_->requestRoster();
- GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(), client_->getIQRouter());
+ GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(boundJID_.toBare(), client_->getIQRouter());
discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2));
discoInfoRequest->send();
@@ -356,19 +408,27 @@ void MainController::handleInputIdleChanged(bool idle) {
}
void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificateFile, bool remember, bool loginAutomatically) {
- loginWindow_->setMessage("");
- loginWindow_->setIsLoggingIn(true);
- profileSettings_ = new ProfileSettingsProvider(username, settings_);
- profileSettings_->storeString("jid", username);
- profileSettings_->storeString("certificate", certificateFile);
- profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : "");
- settings_->storeString("lastLoginJID", username);
- settings_->storeBool("loginAutomatically", loginAutomatically);
- loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"));
jid_ = JID(username);
- password_ = password;
- certificateFile_ = certificateFile;
- performLoginFromCachedCredentials();
+ if (!jid_.isValid() || jid_.getNode().empty()) {
+ loginWindow_->setMessage(QT_TRANSLATE_NOOP("", "User address invalid. User address should be of the form 'alice@wonderland.lit'"));
+ loginWindow_->setIsLoggingIn(false);
+ } else {
+ loginWindow_->setMessage("");
+ loginWindow_->setIsLoggingIn(true);
+ profileSettings_ = new ProfileSettingsProvider(username, settings_);
+ if (!eagleMode_) {
+ profileSettings_->storeString("jid", username);
+ profileSettings_->storeString("certificate", certificateFile);
+ profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : "");
+ settings_->storeString("lastLoginJID", username);
+ settings_->storeBool("loginAutomatically", loginAutomatically);
+ loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"));
+ }
+
+ password_ = password;
+ certificateFile_ = certificateFile;
+ performLoginFromCachedCredentials();
+ }
}
void MainController::handlePurgeSavedLoginRequest(const std::string& username) {
@@ -377,6 +437,10 @@ void MainController::handlePurgeSavedLoginRequest(const std::string& username) {
}
void MainController::performLoginFromCachedCredentials() {
+ if (eagleMode_ && password_.empty()) {
+ /* Then we can't try to login again. */
+ return;
+ }
/* If we logged in with a bare JID, and we have a full bound JID, re-login with the
* bound JID to try and keep dynamically assigned resources */
JID clientJID = jid_;
@@ -392,7 +456,7 @@ void MainController::performLoginFromCachedCredentials() {
certificateStorage_ = certificateStorageFactory_->createCertificateStorage(jid_.toBare());
certificateTrustChecker_ = new CertificateStorageTrustChecker(certificateStorage_);
- client_ = boost::make_shared<Swift::Client>(clientJID, password_, networkFactories_, storages_);
+ client_ = boost::make_shared<Swift::Client>(clientJID, createSafeByteArray(password_.c_str()), networkFactories_, storages_);
clientInitialized_ = true;
client_->setCertificateTrustChecker(certificateTrustChecker_);
client_->onDataRead.connect(boost::bind(&XMLConsoleController::handleDataRead, xmlConsoleController_, _1));
@@ -422,11 +486,16 @@ void MainController::performLoginFromCachedCredentials() {
if (rosterController_) {
rosterController_->getWindow()->setConnecting();
}
-
- client_->connect();
+ ClientOptions clientOptions;
+ clientOptions.forgetPassword = eagleMode_;
+ clientOptions.useTLS = eagleMode_ ? ClientOptions::RequireTLS : ClientOptions::UseTLSWhenAvailable;
+ client_->connect(clientOptions);
}
void MainController::handleDisconnected(const boost::optional<ClientError>& error) {
+ if (eagleMode_) {
+ purgeCachedCredentials();
+ }
if (quitRequested_) {
resetClient();
loginWindow_->quit();
@@ -483,19 +552,27 @@ void MainController::handleDisconnected(const boost::optional<ClientError>& erro
else if (!rosterController_) { //hasn't been logged in yet
signOut();
loginWindow_->setMessage(message);
+ loginWindow_->setIsLoggingIn(false);
} else {
logout();
- setReconnectTimer();
- if (lastDisconnectError_) {
- message = str(format(QT_TRANSLATE_NOOP("", "Reconnect to %1% failed: %2%. Will retry in %3% seconds.")) % jid_.getDomain() % message % boost::lexical_cast<std::string>(timeBeforeNextReconnect_));
- lastDisconnectError_->conclude();
+ if (eagleMode_) {
+ message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.")) % jid_.getDomain() % message);
} else {
- message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%.")) % jid_.getDomain() % message);
+ setReconnectTimer();
+ if (lastDisconnectError_) {
+ message = str(format(QT_TRANSLATE_NOOP("", "Reconnect to %1% failed: %2%. Will retry in %3% seconds.")) % jid_.getDomain() % message % boost::lexical_cast<std::string>(timeBeforeNextReconnect_));
+ lastDisconnectError_->conclude();
+ } else {
+ message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%.")) % jid_.getDomain() % message);
+ }
+ lastDisconnectError_ = boost::shared_ptr<ErrorEvent>(new ErrorEvent(JID(jid_.getDomain()), message));
+ eventController_->handleIncomingEvent(lastDisconnectError_);
}
- lastDisconnectError_ = boost::shared_ptr<ErrorEvent>(new ErrorEvent(JID(jid_.getDomain()), message));
- eventController_->handleIncomingEvent(lastDisconnectError_);
}
}
+ else if (!rosterController_) { //hasn't been logged in yet
+ loginWindow_->setIsLoggingIn(false);
+ }
}
void MainController::setReconnectTimer() {
@@ -517,6 +594,9 @@ void MainController::handleCancelLoginRequest() {
}
void MainController::signOut() {
+ if (eagleMode_) {
+ purgeCachedCredentials();
+ }
eventController_->clear();
logout();
loginWindow_->loggedOut();
@@ -524,6 +604,9 @@ void MainController::signOut() {
}
void MainController::logout() {
+ if (eagleMode_) {
+ purgeCachedCredentials();
+ }
systemTrayController_->setMyStatusType(StatusShow::None);
if (clientInitialized_ /*&& client_->isAvailable()*/) {
client_->disconnect();
@@ -554,11 +637,12 @@ void MainController::setManagersOffline() {
void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error) {
if (!error) {
chatsManager_->setServerDiscoInfo(info);
+ adHocManager_->setServerDiscoInfo(info);
}
}
void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) {
- if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().isEmpty()) {
+ if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().empty()) {
return;
}
std::string hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index f402f8f..12028d7 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,8 +11,6 @@
#include <vector>
#include "Swiften/Network/Timer.h"
-#include "SwifTools/Idle/PlatformIdleQuerier.h"
-#include "SwifTools/Idle/ActualIdleDetector.h"
#include <string>
#include "Swiften/Client/ClientError.h"
#include "Swiften/JID/JID.h"
@@ -25,8 +23,10 @@
#include "Swiften/Elements/CapsInfo.h"
#include "Swift/Controllers/XMPPEvents/ErrorEvent.h"
#include "Swift/Controllers/UIEvents/UIEvent.h"
+#include "Swiften/Client/ClientXMLTracer.h"
namespace Swift {
+ class IdleDetector;
class UIFactory;
class EventLoop;
class Client;
@@ -52,6 +52,7 @@ namespace Swift {
class SoundEventController;
class SoundPlayer;
class XMLConsoleController;
+ class FileTransferListController;
class UIEventStream;
class EventWindowFactory;
class EventWindowController;
@@ -62,6 +63,11 @@ namespace Swift {
class Storages;
class StoragesFactory;
class NetworkFactories;
+ class URIHandler;
+ class XMPPURIController;
+ class AdHocManager;
+ class AdHocCommandWindowFactory;
+ class FileTransferOverview;
class MainController {
public:
@@ -76,7 +82,10 @@ namespace Swift {
CertificateStorageFactory* certificateStorageFactory,
Dock* dock,
Notifier* notifier,
- bool useDelayForLatency);
+ URIHandler* uriHandler,
+ IdleDetector* idleDetector,
+ bool useDelayForLatency,
+ bool eagleMode);
~MainController();
@@ -106,13 +115,12 @@ namespace Swift {
void setManagersOffline();
void handleNotificationClicked(const JID& jid);
void handleForceQuit();
+ void purgeCachedCredentials();
private:
EventLoop* eventLoop_;
NetworkFactories* networkFactories_;
UIFactory* uiFactory_;
- PlatformIdleQuerier idleQuerier_;
- ActualIdleDetector idleDetector_;
StoragesFactory* storagesFactory_;
Storages* storages_;
CertificateStorageFactory* certificateStorageFactory_;
@@ -123,15 +131,19 @@ namespace Swift {
SettingsProvider *settings_;
ProfileSettingsProvider* profileSettings_;
Dock* dock_;
+ URIHandler* uriHandler_;
+ IdleDetector* idleDetector_;
TogglableNotifier* notifier_;
PresenceNotifier* presenceNotifier_;
EventNotifier* eventNotifier_;
RosterController* rosterController_;
EventController* eventController_;
EventWindowController* eventWindowController_;
+ AdHocManager* adHocManager_;
LoginWindow* loginWindow_;
UIEventStream* uiEventStream_;
XMLConsoleController* xmlConsoleController_;
+ FileTransferListController* fileTransferListController_;
ChatsManager* chatsManager_;
ProfileController* profileController_;
ContactEditController* contactEditController_;
@@ -139,6 +151,7 @@ namespace Swift {
JID boundJID_;
SystemTrayController* systemTrayController_;
SoundEventController* soundEventController_;
+ XMPPURIController* xmppURIController_;
std::string vCardPhotoHash_;
std::string password_;
std::string certificateFile_;
@@ -152,5 +165,7 @@ namespace Swift {
bool myStatusLooksOnline_;
bool quitRequested_;
static const int SecondsToWaitBeforeForceQuitting;
+ bool eagleMode_;
+ FileTransferOverview* ftOverview_;
};
}
diff --git a/Swift/Controllers/PreviousStatusStore.cpp b/Swift/Controllers/PreviousStatusStore.cpp
index 947cdc7..ca0a12e 100644
--- a/Swift/Controllers/PreviousStatusStore.cpp
+++ b/Swift/Controllers/PreviousStatusStore.cpp
@@ -40,7 +40,7 @@ std::vector<TypeStringPair> PreviousStatusStore::getSuggestions(const std::strin
suggestions.push_back(status);
}
}
- if (suggestions.size() == 0) {
+ if (suggestions.empty()) {
TypeStringPair suggestion(StatusShow::Online, message);
suggestions.push_back(suggestion);
}
diff --git a/Swift/Controllers/ProfileSettingsProvider.h b/Swift/Controllers/ProfileSettingsProvider.h
index 74bcd12..8ba250c 100644
--- a/Swift/Controllers/ProfileSettingsProvider.h
+++ b/Swift/Controllers/ProfileSettingsProvider.h
@@ -7,6 +7,7 @@
#pragma once
#include "Swift/Controllers/Settings/SettingsProvider.h"
+#include <Swiften/Base/foreach.h>
namespace Swift {
diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp
index 0fe88aa..8c388bf 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.cpp
+++ b/Swift/Controllers/Roster/ContactRosterItem.cpp
@@ -7,6 +7,8 @@
#include "Swift/Controllers/Roster/ContactRosterItem.h"
#include "Swift/Controllers/Roster/GroupRosterItem.h"
+#include <Swiften/Base/foreach.h>
+
namespace Swift {
@@ -89,7 +91,7 @@ void ContactRosterItem::applyPresence(const std::string& resource, boost::shared
presences_.erase(resource);
}
}
- if (presences_.size() == 0) {
+ if (presences_.empty()) {
offlinePresence_ = presence;
}
} else {
@@ -111,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 feature) const {
+ return features_.find(feature) != features_.end();
+}
+
}
diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h
index 7aa948c..9932dc4 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 feature) 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/GroupRosterItem.cpp b/Swift/Controllers/Roster/GroupRosterItem.cpp
index f0a377a..2a7bfa5 100644
--- a/Swift/Controllers/Roster/GroupRosterItem.cpp
+++ b/Swift/Controllers/Roster/GroupRosterItem.cpp
@@ -12,7 +12,7 @@
namespace Swift {
-GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus) {
+GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus), manualSort_(false) {
expanded_ = true;
}
@@ -20,6 +20,20 @@ GroupRosterItem::~GroupRosterItem() {
}
+void GroupRosterItem::setManualSort(const std::string& manualSortValue) {
+ manualSort_ = true;
+ bool changed = manualSortValue_ != manualSortValue;
+ manualSortValue_ = manualSortValue;
+ if (changed) {
+ onChildrenChanged();
+ onDataChanged();
+ }
+}
+
+const std::string& GroupRosterItem::getSortableDisplayName() const {
+ return manualSort_ ? manualSortValue_ : RosterItem::getSortableDisplayName();
+}
+
bool GroupRosterItem::isExpanded() const {
return expanded_;
}
@@ -75,7 +89,7 @@ void GroupRosterItem::removeAll() {
delete group;
}
}
- it++;
+ ++it;
}
children_.clear();
}
@@ -103,7 +117,7 @@ ContactRosterItem* GroupRosterItem::removeChild(const JID& jid) {
removed = groupRemoved;
}
}
- it++;
+ ++it;
}
onChildrenChanged();
onDataChanged();
@@ -122,7 +136,7 @@ GroupRosterItem* GroupRosterItem::removeGroupChild(const std::string& groupName)
it = children_.erase(it);
continue;
}
- it++;
+ ++it;
}
onChildrenChanged();
onDataChanged();
@@ -210,7 +224,8 @@ void GroupRosterItem::handleChildrenChanged(GroupRosterItem* group) {
} else {
displayedChildren_.erase(std::remove(displayedChildren_.begin(), displayedChildren_.end(), group), displayedChildren_.end());
}
- if (oldSize != getDisplayedChildren().size()) {
+
+ if (oldSize != getDisplayedChildren().size() || sortDisplayed()) {
onChildrenChanged();
onDataChanged();
}
diff --git a/Swift/Controllers/Roster/GroupRosterItem.h b/Swift/Controllers/Roster/GroupRosterItem.h
index 57fa9fa..beb7705 100644
--- a/Swift/Controllers/Roster/GroupRosterItem.h
+++ b/Swift/Controllers/Roster/GroupRosterItem.h
@@ -31,6 +31,8 @@ class GroupRosterItem : public RosterItem {
void setExpanded(bool expanded);
bool isExpanded() const;
boost::signal<void (bool)> onExpandedChanged;
+ void setManualSort(const std::string& manualSortValue);
+ virtual const std::string& getSortableDisplayName() const;
private:
void handleChildrenChanged(GroupRosterItem* group);
void handleDataChanged(RosterItem* item);
@@ -40,6 +42,8 @@ class GroupRosterItem : public RosterItem {
std::vector<RosterItem*> children_;
std::vector<RosterItem*> displayedChildren_;
bool sortByStatus_;
+ bool manualSort_;
+ std::string manualSortValue_;
};
}
diff --git a/Swift/Controllers/Roster/LeastCommonSubsequence.h b/Swift/Controllers/Roster/LeastCommonSubsequence.h
new file mode 100644
index 0000000..dd3c95a
--- /dev/null
+++ b/Swift/Controllers/Roster/LeastCommonSubsequence.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+namespace Swift {
+ using std::equal_to;
+
+ namespace Detail {
+ template<typename XIt, typename YIt, typename Length, typename Predicate>
+ void computeLeastCommonSubsequenceMatrix(XIt xBegin, XIt xEnd, YIt yBegin, YIt yEnd, std::vector<Length>& result) {
+ size_t width = std::distance(xBegin, xEnd) + 1;
+ size_t height = std::distance(yBegin, yEnd) + 1;
+ result.resize(width * height);
+
+ // Initialize first row & column
+ for (size_t i = 0; i < width; ++i) {
+ result[i] = 0;
+ }
+ for (size_t j = 0; j < height; ++j) {
+ result[j*width] = 0;
+ }
+
+ // Compute the LCS lengths for subsets
+ Predicate predicate;
+ for (size_t i = 1; i < width; ++i) {
+ for (size_t j = 1; j < height; ++j) {
+ result[i + j*width] = (predicate(*(xBegin + i-1), *(yBegin + j-1)) ? result[(i-1) + (j-1)*width] + 1 : std::max(result[i + (j-1)*width], result[i-1 + (j*width)]));
+ }
+ }
+ }
+ }
+
+ template<typename X, typename InsertRemovePredicate, typename UpdatePredicate>
+ void computeIndexDiff(const std::vector<X>& x, const std::vector<X>& y, std::vector<size_t>& updates, std::vector<size_t>& postUpdates, std::vector<size_t>& removes, std::vector<size_t>& inserts) {
+ InsertRemovePredicate insertRemovePredicate;
+ UpdatePredicate updatePredicate;
+
+ // Find & handle common prefix (Optimization to reduce LCS matrix size)
+ typename std::vector<X>::const_iterator xBegin = x.begin();
+ typename std::vector<X>::const_iterator yBegin = y.begin();
+ while (xBegin < x.end() && yBegin < y.end() && insertRemovePredicate(*xBegin, *yBegin)) {
+ if (updatePredicate(*xBegin, *yBegin)) {
+ updates.push_back(std::distance(x.begin(), xBegin));
+ postUpdates.push_back(std::distance(y.begin(), yBegin));
+ }
+ ++xBegin;
+ ++yBegin;
+ }
+ size_t prefixLength = std::distance(x.begin(), xBegin);
+
+ // Find & handle common suffix (Optimization to reduce LCS matrix size)
+ typename std::vector<X>::const_reverse_iterator xEnd = x.rbegin();
+ typename std::vector<X>::const_reverse_iterator yEnd = y.rbegin();
+ while (xEnd.base() > xBegin && yEnd.base() > yBegin && insertRemovePredicate(*xEnd, *yEnd)) {
+ if (updatePredicate(*xEnd, *yEnd)) {
+ updates.push_back(std::distance(x.begin(), xEnd.base()) - 1);
+ postUpdates.push_back(std::distance(y.begin(), yEnd.base()) - 1);
+ }
+ ++xEnd;
+ ++yEnd;
+ }
+
+ // Compute lengths
+ size_t xLength = std::distance(xBegin, xEnd.base());
+ size_t yLength = std::distance(yBegin, yEnd.base());
+
+ // Compute LCS matrix
+ std::vector<unsigned int> lcs;
+ Detail::computeLeastCommonSubsequenceMatrix<typename std::vector<X>::const_iterator, typename std::vector<X>::const_iterator, unsigned int, InsertRemovePredicate>(xBegin, xEnd.base(), yBegin, yEnd.base(), lcs);
+
+ // Process LCS matrix
+ size_t i = xLength;
+ size_t j = yLength;
+ const size_t width = xLength + 1;
+ while (true) {
+ if (i > 0 && j > 0 && insertRemovePredicate(x[prefixLength + i-1], y[prefixLength + j-1])) {
+ // x[i-1] same
+ if (updatePredicate(x[prefixLength + i - 1], y[prefixLength + j - 1])) {
+ updates.push_back(prefixLength + i-1);
+ postUpdates.push_back(prefixLength + j-1);
+ }
+ i -= 1;
+ j -= 1;
+ }
+ else if (j > 0 && (i == 0 || lcs[i + (j-1)*width] >= lcs[i-1 + j*width])) {
+ // y[j-1] added
+ inserts.push_back(prefixLength + j-1);
+ j -= 1;
+ }
+ else if (i > 0 && (j == 0 || lcs[i + (j-1)*width] < lcs[i-1 + j*width])) {
+ // x[i-1] removed
+ removes.push_back(prefixLength + i-1);
+ i -= 1;
+ }
+ else {
+ break;
+ }
+ }
+ }
+}
diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp
index 4e34105..83837b1 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,16 @@ GroupRosterItem* Roster::getGroup(const std::string& groupName) {
return group;
}
+void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features) {
+ JID actualJID = fullJIDMapping_ ? jid : jid.toBare();
+ if (itemMap_[actualJID].empty()) {
+ return;
+ }
+ foreach(ContactRosterItem* item, itemMap_[actualJID]) {
+ item->setSupportedFeatures(features);
+ }
+}
+
void Roster::removeGroup(const std::string& group) {
root_->removeGroupChild(group);
}
@@ -74,7 +85,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) {
@@ -107,7 +118,7 @@ void Roster::removeAll() {
void Roster::removeContact(const JID& jid) {
std::vector<ContactRosterItem*>* items = &itemMap_[fullJIDMapping_ ? jid : jid.toBare()];
items->erase(std::remove_if(items->begin(), items->end(), JIDEqualsTo(jid)), items->end());
- if (items->size() == 0) {
+ if (items->empty()) {
itemMap_.erase(fullJIDMapping_ ? jid : jid.toBare());
}
//Causes the delete
@@ -124,7 +135,7 @@ void Roster::removeContactFromGroup(const JID& jid, const std::string& groupName
std::vector<ContactRosterItem*>* items = &itemMap_[fullJIDMapping_ ? jid : jid.toBare()];
items->erase(std::remove(items->begin(), items->end(), deleted), items->end());
}
- it++;
+ ++it;
}
foreach (ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) {
item->removeGroup(groupName);
@@ -179,7 +190,7 @@ void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) {
foreach (RosterFilter *filter, filters_) {
hide &= (*filter)(contact);
}
- group->setDisplayed(contact, filters_.size() == 0 || !hide);
+ group->setDisplayed(contact, filters_.empty() || !hide);
int newDisplayedSize = group->getDisplayedChildren().size();
if (oldDisplayedSize == 0 && newDisplayedSize > 0) {
onGroupAdded(group);
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/RosterGroupExpandinessPersister.cpp b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp
index c1045ee..0a242ae 100644
--- a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp
+++ b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp
@@ -9,6 +9,7 @@
#include <boost/bind.hpp>
#include <vector>
+#include <Swiften/Base/foreach.h>
#include "Swiften/Base/String.h"
#include "Swift/Controllers/Roster/GroupRosterItem.h"
diff --git a/Swift/Controllers/Roster/RosterItem.cpp b/Swift/Controllers/Roster/RosterItem.cpp
index 3f130bb..77db8a3 100644
--- a/Swift/Controllers/Roster/RosterItem.cpp
+++ b/Swift/Controllers/Roster/RosterItem.cpp
@@ -33,11 +33,11 @@ void RosterItem::setDisplayName(const std::string& name) {
onDataChanged();
}
-std::string RosterItem::getDisplayName() const {
+const std::string& RosterItem::getDisplayName() const {
return name_;
}
-std::string RosterItem::getSortableDisplayName() const {
+const std::string& RosterItem::getSortableDisplayName() const {
return sortableDisplayName_;
}
diff --git a/Swift/Controllers/Roster/RosterItem.h b/Swift/Controllers/Roster/RosterItem.h
index ed8cb16..769f72d 100644
--- a/Swift/Controllers/Roster/RosterItem.h
+++ b/Swift/Controllers/Roster/RosterItem.h
@@ -20,8 +20,8 @@ class RosterItem {
boost::signal<void ()> onDataChanged;
GroupRosterItem* getParent() const;
void setDisplayName(const std::string& name);
- std::string getDisplayName() const;
- std::string getSortableDisplayName() const;
+ const std::string& getDisplayName() const;
+ virtual const std::string& getSortableDisplayName() const;
private:
std::string name_;
std::string sortableDisplayName_;
diff --git a/Swift/Controllers/Roster/TableRoster.cpp b/Swift/Controllers/Roster/TableRoster.cpp
new file mode 100644
index 0000000..c00bf4f
--- /dev/null
+++ b/Swift/Controllers/Roster/TableRoster.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Roster/TableRoster.h>
+
+#include <boost/cast.hpp>
+#include <cassert>
+#include <algorithm>
+#include <Swiften/Base/foreach.h>
+
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Network/Timer.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/LeastCommonSubsequence.h>
+
+namespace Swift {
+ struct SectionNameEquals {
+ bool operator()(const TableRoster::Section& s1, const TableRoster::Section& s2) const {
+ return s1.name == s2.name;
+ }
+ };
+
+ template<typename T>
+ struct True {
+ bool operator()(const T&, const T&) const {
+ return true;
+ }
+ };
+
+ struct ItemEquals {
+ bool operator()(const TableRoster::Item& i1, const TableRoster::Item& i2) const {
+ return i1.jid == i2.jid;
+ }
+ };
+
+
+ struct ItemNeedsUpdate {
+ bool operator()(const TableRoster::Item& i1, const TableRoster::Item& i2) const {
+ return i1.status != i2.status || i1.description != i2.description || i1.name != i2.name || i1.avatarPath.empty() != i2.avatarPath.empty();
+ }
+ };
+
+ struct CreateIndexForSection {
+ CreateIndexForSection(size_t section) : section(section) {
+ }
+
+ TableRoster::Index operator()(size_t row) const {
+ return TableRoster::Index(section, row);
+ }
+
+ size_t section;
+ };
+}
+
+using namespace Swift;
+
+TableRoster::TableRoster(Roster* model, TimerFactory* timerFactory, int updateDelay) : model(model), updatePending(false) {
+ updateTimer = timerFactory->createTimer(updateDelay);
+ updateTimer->onTick.connect(boost::bind(&TableRoster::handleUpdateTimerTick, this));
+ if (model) {
+ model->onChildrenChanged.connect(boost::bind(&TableRoster::scheduleUpdate, this));
+ model->onGroupAdded.connect(boost::bind(&TableRoster::scheduleUpdate, this));
+ model->onDataChanged.connect(boost::bind(&TableRoster::scheduleUpdate, this));
+ }
+}
+
+TableRoster::~TableRoster() {
+ updateTimer->stop();
+ updateTimer->onTick.disconnect(boost::bind(&TableRoster::handleUpdateTimerTick, this));
+ if (model) {
+ model->onDataChanged.disconnect(boost::bind(&TableRoster::scheduleUpdate, this));
+ model->onGroupAdded.disconnect(boost::bind(&TableRoster::scheduleUpdate, this));
+ model->onChildrenChanged.disconnect(boost::bind(&TableRoster::scheduleUpdate, this));
+ }
+}
+
+size_t TableRoster::getNumberOfSections() const {
+ return sections.size();
+}
+
+const std::string& TableRoster::getSectionTitle(size_t section) {
+ return sections[section].name;
+}
+
+size_t TableRoster::getNumberOfRowsInSection(size_t section) const {
+ return sections[section].items.size();
+}
+
+const TableRoster::Item& TableRoster::getItem(const Index& index) const {
+ return sections[index.section].items[index.row];
+}
+
+void TableRoster::handleUpdateTimerTick() {
+ updateTimer->stop();
+ updatePending = false;
+
+ // Get a model for the new roster
+ std::vector<Section> newSections;
+ if (model) {
+ foreach(RosterItem* item, model->getRoot()->getDisplayedChildren()) {
+ if (GroupRosterItem* groupItem = boost::polymorphic_downcast<GroupRosterItem*>(item)) {
+ //std::cerr << "* " << groupItem->getDisplayName() << std::endl;
+ Section section(groupItem->getDisplayName());
+ foreach(RosterItem* groupChildItem, groupItem->getDisplayedChildren()) {
+ if (ContactRosterItem* contact = boost::polymorphic_downcast<ContactRosterItem*>(groupChildItem)) {
+ //std::cerr << " - " << contact->getDisplayJID() << std::endl;
+ section.items.push_back(Item(contact->getDisplayName(), contact->getStatusText(), contact->getDisplayJID(), contact->getStatusShow(), contact->getAvatarPath()));
+ }
+ }
+ newSections.push_back(section);
+ }
+ }
+ }
+
+ // Do a diff with the previous roster
+ Update update;
+ std::vector<size_t> sectionUpdates;
+ std::vector<size_t> sectionPostUpdates;
+ computeIndexDiff<Section,SectionNameEquals,True<Section> >(sections, newSections, sectionUpdates, sectionPostUpdates, update.deletedSections, update.insertedSections);
+ assert(sectionUpdates.size() == sectionPostUpdates.size());
+ for (size_t i = 0; i < sectionUpdates.size(); ++i) {
+ assert(sectionUpdates[i] < sections.size());
+ assert(sectionPostUpdates[i] < newSections.size());
+ std::vector<size_t> itemUpdates;
+ std::vector<size_t> itemPostUpdates;
+ std::vector<size_t> itemRemoves;
+ std::vector<size_t> itemInserts;
+ computeIndexDiff<Item, ItemEquals, ItemNeedsUpdate >(sections[sectionUpdates[i]].items, newSections[sectionPostUpdates[i]].items, itemUpdates, itemPostUpdates, itemRemoves, itemInserts);
+ size_t end = update.insertedRows.size();
+ update.insertedRows.resize(update.insertedRows.size() + itemInserts.size());
+ std::transform(itemInserts.begin(), itemInserts.end(), update.insertedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i]));
+ end = update.deletedRows.size();
+ update.deletedRows.resize(update.deletedRows.size() + itemRemoves.size());
+ std::transform(itemRemoves.begin(), itemRemoves.end(), update.deletedRows.begin() + end, CreateIndexForSection(sectionUpdates[i]));
+ end = update.updatedRows.size();
+ update.updatedRows.resize(update.updatedRows.size() + itemUpdates.size());
+ std::transform(itemUpdates.begin(), itemUpdates.end(), update.updatedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i]));
+ }
+
+ // Switch the old model with the new
+ sections.swap(newSections);
+
+ /*
+ std::cerr << "-S: ";
+ for (size_t i = 0; i < update.deletedSections.size(); ++i) {
+ std::cerr << update.deletedSections[i] << " ";
+ }
+ std::cerr << std::endl;
+ std::cerr << "+S: ";
+ for (size_t i = 0; i < update.insertedSections.size(); ++i) {
+ std::cerr << update.insertedSections[i] << " ";
+ }
+ std::cerr << std::endl;
+ std::cerr << "-R: ";
+ for (size_t i = 0; i < update.deletedRows.size(); ++i) {
+ std::cerr << update.deletedRows[i].section << "," << update.deletedRows[i].row << " ";
+ }
+ std::cerr << std::endl;
+ std::cerr << "*R: ";
+ for (size_t i = 0; i < update.updatedRows.size(); ++i) {
+ std::cerr << update.updatedRows[i].section << "," << update.updatedRows[i].row << " ";
+ }
+ std::cerr << std::endl;
+ std::cerr << "+R: ";
+ for (size_t i = 0; i < update.insertedRows.size(); ++i) {
+ std::cerr << update.insertedRows[i].section << "," << update.insertedRows[i].row << " ";
+ }
+ std::cerr << std::endl;
+ */
+
+ // Emit the update
+ onUpdate(update);
+}
+
+void TableRoster::scheduleUpdate() {
+ if (!updatePending) {
+ updatePending = true;
+ updateTimer->start();
+ }
+}
diff --git a/Swift/Controllers/Roster/TableRoster.h b/Swift/Controllers/Roster/TableRoster.h
new file mode 100644
index 0000000..8ff16d0
--- /dev/null
+++ b/Swift/Controllers/Roster/TableRoster.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <Swiften/Base/boost_bsignals.h>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/StatusShow.h>
+
+namespace Swift {
+ class Roster;
+ class TimerFactory;
+ class Timer;
+
+ class TableRoster {
+ public:
+ struct Item {
+ Item(const std::string& name, const std::string& description, const JID& jid, StatusShow::Type status, const std::string& avatarPath) : name(name), description(description), jid(jid), status(status), avatarPath(avatarPath) {
+ }
+ std::string name;
+ std::string description;
+ JID jid;
+ StatusShow::Type status;
+ std::string avatarPath;
+ };
+
+ struct Index {
+ Index(size_t section = 0, size_t row = 0) : section(section), row(row) {
+ }
+ size_t section;
+ size_t row;
+
+ bool operator==(const Index& o) const {
+ return o.section == section && o.row == row;
+ }
+ };
+
+ struct Update {
+ std::vector<Index> updatedRows;
+ std::vector<Index> insertedRows;
+ std::vector<Index> deletedRows;
+ std::vector<size_t> insertedSections;
+ std::vector<size_t> deletedSections;
+ };
+
+ TableRoster(Roster* model, TimerFactory* timerFactory, int updateDelay);
+ ~TableRoster();
+
+ size_t getNumberOfSections() const;
+ size_t getNumberOfRowsInSection(size_t section) const;
+
+ const std::string& getSectionTitle(size_t);
+
+ const Item& getItem(const Index&) const;
+
+ boost::signal<void (const Update&)> onUpdate;
+
+ private:
+ void handleUpdateTimerTick();
+ void scheduleUpdate();
+
+ private:
+ friend class SectionNameEquals;
+ struct Section {
+ Section(const std::string& name) : name(name) {
+ }
+
+ std::string name;
+ std::vector<Item> items;
+ };
+
+ Roster* model;
+ std::vector<Section> sections;
+ bool updatePending;
+ boost::shared_ptr<Timer> updateTimer;
+ };
+}
diff --git a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp
new file mode 100644
index 0000000..963c5cd
--- /dev/null
+++ b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#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;
+
+struct IsBOrC {
+ bool operator()(char c, char c2) const {
+ CPPUNIT_ASSERT_EQUAL(c, c2);
+ return c == 'b' || c == 'c';
+ }
+};
+
+struct IsXOrY {
+ bool operator()(char c, char c2) const {
+ CPPUNIT_ASSERT_EQUAL(c, c2);
+ return c == 'x' || c == 'y';
+ }
+};
+
+struct IsArizonaOrNewJersey {
+ bool operator()(const std::string& s, const std::string& s2) const {
+ CPPUNIT_ASSERT_EQUAL(s, s2);
+ return s == "Arizona" || s == "New Jersey";
+ }
+};
+
+class LeastCommonSubsequenceTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(LeastCommonSubsequenceTest);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_1);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_2);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_Sequence1Empty);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_Sequence2Empty);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_BothSequencesEmpty);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_NoCommonSequence);
+ CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_SameSequences);
+ CPPUNIT_TEST(testComputeIndexDiff_1);
+ CPPUNIT_TEST(testComputeIndexDiff_2);
+ CPPUNIT_TEST(testComputeIndexDiff_Sequence1Empty);
+ CPPUNIT_TEST(testComputeIndexDiff_Sequence2Empty);
+ CPPUNIT_TEST(testComputeIndexDiff_BothSequencesEmpty);
+ CPPUNIT_TEST(testComputeIndexDiff_NoCommonSequence);
+ CPPUNIT_TEST(testComputeIndexDiff_SameSequences);
+ CPPUNIT_TEST(testComputeIndexDiff_CommonPrefixAndSuffix);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void testComputeLeastCommonSubsequenceMatrix_1() {
+ std::vector<char> x = boost::assign::list_of('x')('m')('j')('y')('a')('u')('z');
+ std::vector<char> y = boost::assign::list_of('m')('z')('j')('a')('w')('x')('u');
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of
+ (0)(0)(0)(0)(0)(0)(0)(0)
+ (0)(0)(1)(1)(1)(1)(1)(1)
+ (0)(0)(1)(1)(1)(1)(1)(2)
+ (0)(0)(1)(2)(2)(2)(2)(2)
+ (0)(0)(1)(2)(2)(3)(3)(3)
+ (0)(0)(1)(2)(2)(3)(3)(3)
+ (0)(1)(1)(2)(2)(3)(3)(3)
+ (0)(1)(1)(2)(2)(3)(4)(4);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeLeastCommonSubsequenceMatrix_2() {
+ std::vector<char> x = boost::assign::list_of('x')('x')('x')('m')('j')('y')('a')('u')('z');
+ std::vector<char> y = boost::assign::list_of('m')('z')('j')('a')('w')('x')('u');
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of
+ (0)(0)(0)(0)(0)(0)(0)(0)(0)(0)
+ (0)(0)(0)(0)(1)(1)(1)(1)(1)(1)
+ (0)(0)(0)(0)(1)(1)(1)(1)(1)(2)
+ (0)(0)(0)(0)(1)(2)(2)(2)(2)(2)
+ (0)(0)(0)(0)(1)(2)(2)(3)(3)(3)
+ (0)(0)(0)(0)(1)(2)(2)(3)(3)(3)
+ (0)(1)(1)(1)(1)(2)(2)(3)(3)(3)
+ (0)(1)(1)(1)(1)(2)(2)(3)(4)(4);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeLeastCommonSubsequenceMatrix_Sequence1Empty() {
+ std::vector<char> x;
+ std::vector<char> y = boost::assign::list_of('a')('b')('c');
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of
+ (0)
+ (0)
+ (0)
+ (0);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeLeastCommonSubsequenceMatrix_Sequence2Empty() {
+ std::vector<char> x = boost::assign::list_of('a')('b')('c');
+ std::vector<char> y;
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of
+ (0)(0)(0)(0);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeLeastCommonSubsequenceMatrix_BothSequencesEmpty() {
+ std::vector<char> x;
+ std::vector<char> y;
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of(0);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeLeastCommonSubsequenceMatrix_NoCommonSequence() {
+ std::vector<char> x = boost::assign::list_of('a')('b')('c');
+ std::vector<char> y = boost::assign::list_of('d')('e')('f')('g');
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of
+ (0)(0)(0)(0)
+ (0)(0)(0)(0)
+ (0)(0)(0)(0)
+ (0)(0)(0)(0)
+ (0)(0)(0)(0);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeLeastCommonSubsequenceMatrix_SameSequences() {
+ std::vector<char> x = boost::assign::list_of('a')('b')('c');
+ std::vector<char> y = boost::assign::list_of('a')('b')('c');
+
+ std::vector<int> result;
+ Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result);
+
+ std::vector<int> expected = boost::assign::list_of
+ (0)(0)(0)(0)
+ (0)(1)(1)(1)
+ (0)(1)(2)(2)
+ (0)(1)(2)(3);
+ CPPUNIT_ASSERT_EQUAL(expected, result);
+ }
+
+ void testComputeIndexDiff_1() {
+ std::vector<std::string> x = boost::assign::list_of("Arizona")("California")("Delaware")("New Jersey")("Washington");
+ std::vector<std::string> y = boost::assign::list_of("Alaska")("Arizona")("California")("Georgia")("New Jersey")("Virginia");
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<std::string, std::equal_to<std::string>, IsArizonaOrNewJersey >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedUpdates = boost::assign::list_of(3)(0);
+ std::vector<size_t> expectedPostUpdates = boost::assign::list_of(4)(1);
+ std::vector<size_t> expectedRemoves = boost::assign::list_of(4)(2);
+ std::vector<size_t> expectedInserts = boost::assign::list_of(5)(3)(0);
+ CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates);
+ CPPUNIT_ASSERT_EQUAL(expectedPostUpdates, postUpdates);
+ CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes);
+ CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts);
+ }
+
+ void testComputeIndexDiff_2() {
+ std::vector<char> x = boost::assign::list_of('x')('y');
+ std::vector<char> y = boost::assign::list_of('x');
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedRemoves = boost::assign::list_of(1);
+ CPPUNIT_ASSERT(updates.empty());
+ CPPUNIT_ASSERT(postUpdates.empty());
+ CPPUNIT_ASSERT(inserts.empty());
+ CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes);
+ }
+
+ void testComputeIndexDiff_Sequence1Empty() {
+ std::vector<char> x;
+ std::vector<char> y = boost::assign::list_of('a')('b')('c');
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedInserts = boost::assign::list_of(2)(1)(0);
+ CPPUNIT_ASSERT(updates.empty());
+ CPPUNIT_ASSERT(postUpdates.empty());
+ CPPUNIT_ASSERT(removes.empty());
+ CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts);
+ }
+
+ void testComputeIndexDiff_Sequence2Empty() {
+ std::vector<char> x = boost::assign::list_of('a')('b')('c');
+ std::vector<char> y;
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedRemoves = boost::assign::list_of(2)(1)(0);
+ CPPUNIT_ASSERT(updates.empty());
+ CPPUNIT_ASSERT(postUpdates.empty());
+ CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes);
+ CPPUNIT_ASSERT(inserts.empty());
+ }
+
+ void testComputeIndexDiff_BothSequencesEmpty() {
+ std::vector<char> x;
+ std::vector<char> y;
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts);
+
+ CPPUNIT_ASSERT(updates.empty());
+ CPPUNIT_ASSERT(postUpdates.empty());
+ CPPUNIT_ASSERT(removes.empty());
+ CPPUNIT_ASSERT(inserts.empty());
+ }
+
+ void testComputeIndexDiff_NoCommonSequence() {
+ std::vector<char> x = boost::assign::list_of('a')('b')('c');
+ std::vector<char> y = boost::assign::list_of('d')('e')('f')('g');
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedRemoves = boost::assign::list_of(2)(1)(0);
+ std::vector<size_t> expectedInserts = boost::assign::list_of(3)(2)(1)(0);
+ CPPUNIT_ASSERT(updates.empty());
+ CPPUNIT_ASSERT(postUpdates.empty());
+ CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes);
+ CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts);
+ }
+
+ void testComputeIndexDiff_SameSequences() {
+ std::vector<char> x = boost::assign::list_of('a')('b')('c');
+ std::vector<char> y = boost::assign::list_of('a')('b')('c');
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedUpdates = boost::assign::list_of(1)(2);
+ CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates);
+ CPPUNIT_ASSERT_EQUAL(expectedUpdates, postUpdates);
+ CPPUNIT_ASSERT(removes.empty());
+ CPPUNIT_ASSERT(inserts.empty());
+ }
+
+ void testComputeIndexDiff_CommonPrefixAndSuffix() {
+ std::vector<char> x = boost::assign::list_of('x')('x')('x')('x')('a')('b')('c')('d')('e')('y')('y')('y');
+ std::vector<char> y = boost::assign::list_of('x')('x')('x')('x')('e')('a')('b')('f')('d')('g')('y')('y')('y');
+
+ std::vector<size_t> updates;
+ std::vector<size_t> postUpdates;
+ std::vector<size_t> removes;
+ std::vector<size_t> inserts;
+ computeIndexDiff<char, std::equal_to<char>, IsXOrY >(x, y, updates, postUpdates, removes, inserts);
+
+ std::vector<size_t> expectedUpdates = boost::assign::list_of(0)(1)(2)(3)(11)(10)(9);
+ std::vector<size_t> expectedPostUpdates = boost::assign::list_of(0)(1)(2)(3)(12)(11)(10);
+ std::vector<size_t> expectedRemoves = boost::assign::list_of(8)(6);
+ std::vector<size_t> expectedInserts = boost::assign::list_of(9)(7)(4);
+ CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates);
+ CPPUNIT_ASSERT_EQUAL(expectedPostUpdates, postUpdates);
+ CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes);
+ CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts);
+ }
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(LeastCommonSubsequenceTest);
diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
index 16f2745..fbee894 100644
--- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
+++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp
@@ -8,6 +8,7 @@
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <Swiften/Base/foreach.h>
#include "Swift/Controllers/Roster/RosterController.h"
#include "Swift/Controllers/UnitTest/MockMainWindowFactory.h"
// #include "Swiften/Elements/Payload.h"
@@ -30,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);
@@ -65,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_;
@@ -312,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/Roster/UnitTest/RosterTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp
index cbef787..4444e8a 100644
--- a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp
+++ b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp
@@ -21,6 +21,7 @@ class RosterTest : public CppUnit::TestFixture {
CPPUNIT_TEST(testRemoveSecondContact);
CPPUNIT_TEST(testRemoveSecondContactSameBare);
CPPUNIT_TEST(testApplyPresenceLikeMUC);
+ CPPUNIT_TEST(testReSortLikeMUC);
CPPUNIT_TEST_SUITE_END();
public:
@@ -117,6 +118,22 @@ class RosterTest : public CppUnit::TestFixture {
}
+ void testReSortLikeMUC() {
+ JID jid4a("a@b/c");
+ JID jid4b("a@b/d");
+ JID jid4c("a@b/e");
+ roster_->addContact(jid4a, JID(), "Bird", "group1", "");
+ roster_->addContact(jid4b, JID(), "Cookie", "group2", "");
+ roster_->addContact(jid4b, JID(), "Ernie", "group1", "");
+ roster_->getGroup("group1")->setManualSort("2");
+ roster_->getGroup("group2")->setManualSort("1");
+ GroupRosterItem* root = roster_->getRoot();
+ const std::vector<RosterItem*> kids = root->getDisplayedChildren();
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), kids.size());
+ CPPUNIT_ASSERT_EQUAL(std::string("group2"), kids[0]->getDisplayName());
+ CPPUNIT_ASSERT_EQUAL(std::string("group1"), kids[1]->getDisplayName());
+ }
+
private:
Roster *roster_;
JID jid1_;
diff --git a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp
new file mode 100644
index 0000000..e433b50
--- /dev/null
+++ b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Roster/TableRoster.h>
+
+std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) {
+ os << "(" << i.section << ", " << i.row << ")";
+ return os;
+}
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/variant.hpp>
+
+#include <Swiften/Network/DummyTimerFactory.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
+#include <Swift/Controllers/Roster/SetPresence.h>
+
+using namespace Swift;
+
+class TableRosterTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TableRosterTest);
+ CPPUNIT_TEST(testAddContact_EmptyRoster);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void setUp() {
+ timerFactory = new DummyTimerFactory();
+ roster = new Roster();
+ jid1 = JID("jid1@example.com");
+ jid2 = JID("jid2@example.com");
+ }
+
+ void tearDown() {
+ delete roster;
+ delete timerFactory;
+ }
+
+ void testAddContact_EmptyRoster() {
+ /*
+ boost::shared_ptr<TableRoster> tableRoster(createTestling());
+
+ addContact(jid1, "1", "group1");
+
+ CPPUNIT_ASSERT_EQUAL(4, static_cast<int>(events.size()));
+ CPPUNIT_ASSERT(boost::get<BeginUpdatesEvent>(&events[0]));
+ CPPUNIT_ASSERT(boost::get<SectionsInsertedEvent>(&events[1]));
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(boost::get<SectionsInsertedEvent>(events[1]).sections.size()));
+ CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(boost::get<SectionsInsertedEvent>(events[1]).sections[0]));
+ CPPUNIT_ASSERT(boost::get<RowsInsertedEvent>(&events[2]));
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(boost::get<RowsInsertedEvent>(events[2]).rows.size()));
+ CPPUNIT_ASSERT_EQUAL(TableRoster::Index(0, 0), boost::get<RowsInsertedEvent>(events[2]).rows[0]);
+ CPPUNIT_ASSERT(boost::get<EndUpdatesEvent>(&events[3]));
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tableRoster->getNumberOfSections()));
+ CPPUNIT_ASSERT_EQUAL(std::string("group1"), tableRoster->getSectionTitle(0));
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tableRoster->getNumberOfRowsInSection(0)));
+ CPPUNIT_ASSERT_EQUAL(jid1, tableRoster->getItem(TableRoster::Index(0, 0)).jid);
+ */
+ }
+
+ private:
+ void addContact(const JID& jid, const std::string& name, const std::string& group) {
+ roster->addContact(jid, JID(), name, group, "");
+ }
+
+ TableRoster* createTestling() {
+ TableRoster* result = new TableRoster(roster, timerFactory, 10);
+ result->onUpdate.connect(boost::bind(&TableRosterTest::handleUpdate, this, _1));
+ return result;
+ }
+
+ void handleUpdate(const TableRoster::Update& update) {
+ updates.push_back(update);
+ }
+
+ private:
+ DummyTimerFactory* timerFactory;
+ Roster* roster;
+ JID jid1;
+ JID jid2;
+ std::vector<TableRoster::Update> updates;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TableRosterTest);
+
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 61da9fb..e48f382 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -27,41 +27,57 @@ if env["SCONS_STAGE"] == "build" :
"Chat/MUCController.cpp",
"Chat/MUCSearchController.cpp",
"Chat/UserSearchController.cpp",
- "DiscoServiceWalker.cpp",
"MainController.cpp",
"ProfileController.cpp",
"ContactEditController.cpp",
+ "FileTransfer/FileTransferController.cpp",
+ "FileTransfer/FileTransferOverview.cpp",
+ "FileTransfer/FileTransferProgressInfo.cpp",
"Roster/RosterController.cpp",
"Roster/RosterGroupExpandinessPersister.cpp",
"Roster/ContactRosterItem.cpp",
"Roster/GroupRosterItem.cpp",
"Roster/RosterItem.cpp",
"Roster/Roster.cpp",
+ "Roster/TableRoster.cpp",
"EventWindowController.cpp",
"SoundEventController.cpp",
"SystemTrayController.cpp",
"XMLConsoleController.cpp",
+ "FileTransferListController.cpp",
"StatusTracker.cpp",
"PresenceNotifier.cpp",
"EventNotifier.cpp",
+ "AdHocManager.cpp",
"XMPPEvents/EventController.cpp",
"UIEvents/UIEvent.cpp",
"UIInterfaces/XMLConsoleWidget.cpp",
"UIInterfaces/ChatListWindow.cpp",
"PreviousStatusStore.cpp",
- "CertificateStorageFactory.cpp",
- "CertificateStorage.cpp",
- "CertificateFileStorage.cpp",
+ "Storages/CertificateStorageFactory.cpp",
+ "Storages/CertificateStorage.cpp",
+ "Storages/CertificateFileStorage.cpp",
+ "Storages/CertificateMemoryStorage.cpp",
+ "Storages/AvatarFileStorage.cpp",
+ "Storages/FileStorages.cpp",
+ "Storages/RosterFileStorage.cpp",
+ "Storages/CapsFileStorage.cpp",
+ "Storages/VCardFileStorage.cpp",
"StatusUtil.cpp",
"Translator.cpp",
+ "XMPPURIController.cpp",
+ "ChatMessageSummarizer.cpp",
])
env.Append(UNITTEST_SOURCES = [
File("Roster/UnitTest/RosterControllerTest.cpp"),
File("Roster/UnitTest/RosterTest.cpp"),
+ File("Roster/UnitTest/LeastCommonSubsequenceTest.cpp"),
+ File("Roster/UnitTest/TableRosterTest.cpp"),
File("UnitTest/PreviousStatusStoreTest.cpp"),
File("UnitTest/PresenceNotifierTest.cpp"),
File("Chat/UnitTest/ChatsManagerTest.cpp"),
File("Chat/UnitTest/MUCControllerTest.cpp"),
File("UnitTest/MockChatWindow.cpp"),
+ File("UnitTest/ChatMessageSummarizerTest.cpp"),
])
diff --git a/Swift/Controllers/Storages/AvatarFileStorage.cpp b/Swift/Controllers/Storages/AvatarFileStorage.cpp
new file mode 100644
index 0000000..b39e586
--- /dev/null
+++ b/Swift/Controllers/Storages/AvatarFileStorage.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Storages/AvatarFileStorage.h>
+
+#include <iostream>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/String.h>
+#include <Swiften/StringCodecs/SHA1.h>
+#include <Swiften/StringCodecs/Hexify.h>
+
+namespace Swift {
+
+AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile) : avatarsDir(avatarsDir), avatarsFile(avatarsFile) {
+ if (boost::filesystem::exists(avatarsFile)) {
+ try {
+ boost::filesystem::ifstream file(avatarsFile);
+ std::string line;
+ if (file.is_open()) {
+ while (!file.eof()) {
+ getline(file, line);
+ std::pair<std::string, std::string> r = String::getSplittedAtFirst(line, ' ');
+ JID jid(r.second);
+ if (jid.isValid()) {
+ jidAvatars.insert(std::make_pair(jid, r.first));
+ }
+ else if (!r.first.empty() || !r.second.empty()) {
+ std::cerr << "Invalid entry in avatars file: " << r.second << std::endl;
+ }
+ }
+ }
+ }
+ catch (...) {
+ std::cerr << "Error reading avatars file" << std::endl;
+ }
+ }
+}
+
+bool AvatarFileStorage::hasAvatar(const std::string& hash) const {
+ return boost::filesystem::exists(getAvatarPath(hash));
+}
+
+void AvatarFileStorage::addAvatar(const std::string& hash, const ByteArray& avatar) {
+ assert(Hexify::hexify(SHA1::getHash(avatar)) == hash);
+
+ boost::filesystem::path avatarPath = getAvatarPath(hash);
+ if (!boost::filesystem::exists(avatarPath.parent_path())) {
+ try {
+ boost::filesystem::create_directories(avatarPath.parent_path());
+ }
+ catch (const boost::filesystem::filesystem_error& e) {
+ std::cerr << "ERROR: " << e.what() << std::endl;
+ }
+ }
+ boost::filesystem::ofstream file(avatarPath, boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out);
+ file.write(reinterpret_cast<const char*>(vecptr(avatar)), static_cast<std::streamsize>(avatar.size()));
+ file.close();
+}
+
+boost::filesystem::path AvatarFileStorage::getAvatarPath(const std::string& hash) const {
+ return avatarsDir / hash;
+}
+
+ByteArray AvatarFileStorage::getAvatar(const std::string& hash) const {
+ ByteArray data;
+ readByteArrayFromFile(data, getAvatarPath(hash).string());
+ return data;
+}
+
+void AvatarFileStorage::setAvatarForJID(const JID& jid, const std::string& hash) {
+ std::pair<JIDAvatarMap::iterator, bool> r = jidAvatars.insert(std::make_pair(jid, hash));
+ if (r.second) {
+ saveJIDAvatars();
+ }
+ else if (r.first->second != hash) {
+ r.first->second = hash;
+ saveJIDAvatars();
+ }
+}
+
+std::string AvatarFileStorage::getAvatarForJID(const JID& jid) const {
+ JIDAvatarMap::const_iterator i = jidAvatars.find(jid);
+ return i == jidAvatars.end() ? "" : i->second;
+}
+
+void AvatarFileStorage::saveJIDAvatars() {
+ try {
+ boost::filesystem::ofstream file(avatarsFile);
+ for (JIDAvatarMap::const_iterator i = jidAvatars.begin(); i != jidAvatars.end(); ++i) {
+ file << i->second << " " << i->first.toString() << std::endl;
+ }
+ file.close();
+ }
+ catch (...) {
+ std::cerr << "Error writing avatars file" << std::endl;
+ }
+}
+
+}
diff --git a/Swift/Controllers/Storages/AvatarFileStorage.h b/Swift/Controllers/Storages/AvatarFileStorage.h
new file mode 100644
index 0000000..b7e73f5
--- /dev/null
+++ b/Swift/Controllers/Storages/AvatarFileStorage.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/JID/JID.h>
+#include "Swiften/Base/ByteArray.h"
+#include "Swiften/Avatars/AvatarStorage.h"
+
+namespace Swift {
+ class AvatarFileStorage : public AvatarStorage {
+ public:
+ AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile);
+
+ virtual bool hasAvatar(const std::string& hash) const;
+ virtual void addAvatar(const std::string& hash, const ByteArray& avatar);
+ virtual ByteArray getAvatar(const std::string& hash) const;
+
+ virtual boost::filesystem::path getAvatarPath(const std::string& hash) const;
+
+ virtual void setAvatarForJID(const JID& jid, const std::string& hash);
+ virtual std::string getAvatarForJID(const JID& jid) const;
+
+ private:
+ void saveJIDAvatars();
+
+ private:
+ boost::filesystem::path avatarsDir;
+ boost::filesystem::path avatarsFile;
+ typedef std::map<JID, std::string> JIDAvatarMap;
+ JIDAvatarMap jidAvatars;
+ };
+
+}
diff --git a/Swift/Controllers/Storages/CapsFileStorage.cpp b/Swift/Controllers/Storages/CapsFileStorage.cpp
new file mode 100644
index 0000000..b7593fd
--- /dev/null
+++ b/Swift/Controllers/Storages/CapsFileStorage.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Storages/CapsFileStorage.h"
+
+#include <Swiften/Entity/GenericPayloadPersister.h>
+#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h"
+#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h"
+#include "Swiften/StringCodecs/Hexify.h"
+#include "Swiften/StringCodecs/Base64.h"
+
+using namespace Swift;
+
+typedef GenericPayloadPersister<DiscoInfo, DiscoInfoParser, DiscoInfoSerializer> DiscoInfoPersister;
+
+CapsFileStorage::CapsFileStorage(const boost::filesystem::path& path) : path(path) {
+}
+
+DiscoInfo::ref CapsFileStorage::getDiscoInfo(const std::string& hash) const {
+ return DiscoInfoPersister().loadPayloadGeneric(getCapsPath(hash));
+}
+
+void CapsFileStorage::setDiscoInfo(const std::string& hash, DiscoInfo::ref discoInfo) {
+ DiscoInfo::ref bareDiscoInfo(new DiscoInfo(*discoInfo.get()));
+ bareDiscoInfo->setNode("");
+ DiscoInfoPersister().savePayload(bareDiscoInfo, getCapsPath(hash));
+}
+
+boost::filesystem::path CapsFileStorage::getCapsPath(const std::string& hash) const {
+ return path / (Hexify::hexify(Base64::decode(hash)) + ".xml");
+}
diff --git a/Swift/Controllers/Storages/CapsFileStorage.h b/Swift/Controllers/Storages/CapsFileStorage.h
new file mode 100644
index 0000000..b3757e0
--- /dev/null
+++ b/Swift/Controllers/Storages/CapsFileStorage.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include "Swiften/Disco/CapsStorage.h"
+#include <string>
+
+namespace Swift {
+ class CapsFileStorage : public CapsStorage {
+ public:
+ CapsFileStorage(const boost::filesystem::path& path);
+
+ virtual DiscoInfo::ref getDiscoInfo(const std::string& hash) const;
+ virtual void setDiscoInfo(const std::string& hash, DiscoInfo::ref discoInfo);
+
+ private:
+ boost::filesystem::path getCapsPath(const std::string& hash) const;
+
+ private:
+ boost::filesystem::path path;
+ };
+}
diff --git a/Swift/Controllers/CertificateFileStorage.cpp b/Swift/Controllers/Storages/CertificateFileStorage.cpp
index cf924ee..a4a95c7 100644
--- a/Swift/Controllers/CertificateFileStorage.cpp
+++ b/Swift/Controllers/Storages/CertificateFileStorage.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include <Swift/Controllers/CertificateFileStorage.h>
+#include <Swift/Controllers/Storages/CertificateFileStorage.h>
#include <iostream>
#include <boost/filesystem/fstream.hpp>
@@ -23,7 +23,7 @@ bool CertificateFileStorage::hasCertificate(Certificate::ref certificate) const
boost::filesystem::path certificatePath = getCertificatePath(certificate);
if (boost::filesystem::exists(certificatePath)) {
ByteArray data;
- data.readFromFile(certificatePath.string());
+ readByteArrayFromFile(data, certificatePath.string());
Certificate::ref storedCertificate = certificateFactory->createCertificateFromDER(data);
if (storedCertificate && storedCertificate->toDER() == certificate->toDER()) {
return true;
@@ -50,7 +50,7 @@ void CertificateFileStorage::addCertificate(Certificate::ref certificate) {
}
boost::filesystem::ofstream file(certificatePath, boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out);
ByteArray data = certificate->toDER();
- file.write(reinterpret_cast<const char*>(data.getData()), data.getSize());
+ file.write(reinterpret_cast<const char*>(vecptr(data)), data.size());
file.close();
}
diff --git a/Swift/Controllers/CertificateFileStorage.h b/Swift/Controllers/Storages/CertificateFileStorage.h
index 2b853ed..f7a60b9 100644
--- a/Swift/Controllers/CertificateFileStorage.h
+++ b/Swift/Controllers/Storages/CertificateFileStorage.h
@@ -8,7 +8,7 @@
#include <boost/filesystem.hpp>
-#include "Swift/Controllers/CertificateStorage.h"
+#include "Swift/Controllers/Storages/CertificateStorage.h"
namespace Swift {
class CertificateFactory;
diff --git a/Swift/Controllers/CertificateFileStorageFactory.h b/Swift/Controllers/Storages/CertificateFileStorageFactory.h
index 7ed8287..b215165 100644
--- a/Swift/Controllers/CertificateFileStorageFactory.h
+++ b/Swift/Controllers/Storages/CertificateFileStorageFactory.h
@@ -6,8 +6,8 @@
#pragma once
-#include <Swift/Controllers/CertificateStorageFactory.h>
-#include <Swift/Controllers/CertificateFileStorage.h>
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateFileStorage.h>
namespace Swift {
class CertificateFactory;
diff --git a/Swift/Controllers/Storages/CertificateMemoryStorage.cpp b/Swift/Controllers/Storages/CertificateMemoryStorage.cpp
new file mode 100644
index 0000000..71d7c4a
--- /dev/null
+++ b/Swift/Controllers/Storages/CertificateMemoryStorage.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Storages/CertificateMemoryStorage.h>
+
+#include <Swiften/Base/foreach.h>
+
+using namespace Swift;
+
+CertificateMemoryStorage::CertificateMemoryStorage() {
+}
+
+bool CertificateMemoryStorage::hasCertificate(Certificate::ref certificate) const {
+ foreach(Certificate::ref storedCert, certificates) {
+ if (storedCert->toDER() == certificate->toDER()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void CertificateMemoryStorage::addCertificate(Certificate::ref certificate) {
+ certificates.push_back(certificate);
+}
diff --git a/Swift/Controllers/Storages/CertificateMemoryStorage.h b/Swift/Controllers/Storages/CertificateMemoryStorage.h
new file mode 100644
index 0000000..5c0333d
--- /dev/null
+++ b/Swift/Controllers/Storages/CertificateMemoryStorage.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <Swift/Controllers/Storages/CertificateStorage.h>
+
+namespace Swift {
+ class CertificateMemoryStorage : public CertificateStorage {
+ public:
+ CertificateMemoryStorage();
+
+ virtual bool hasCertificate(Certificate::ref certificate) const;
+ virtual void addCertificate(Certificate::ref certificate);
+
+ private:
+ std::vector<Certificate::ref> certificates;
+ };
+
+}
diff --git a/Swift/Controllers/CertificateStorage.cpp b/Swift/Controllers/Storages/CertificateStorage.cpp
index 343fccd..ee942c0 100644
--- a/Swift/Controllers/CertificateStorage.cpp
+++ b/Swift/Controllers/Storages/CertificateStorage.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "Swift/Controllers/CertificateStorage.h"
+#include "Swift/Controllers/Storages/CertificateStorage.h"
namespace Swift {
diff --git a/Swift/Controllers/CertificateStorage.h b/Swift/Controllers/Storages/CertificateStorage.h
index f8c6fb5..f8c6fb5 100644
--- a/Swift/Controllers/CertificateStorage.h
+++ b/Swift/Controllers/Storages/CertificateStorage.h
diff --git a/Swift/Controllers/CertificateStorageFactory.cpp b/Swift/Controllers/Storages/CertificateStorageFactory.cpp
index 613a8c3..ba0179a 100644
--- a/Swift/Controllers/CertificateStorageFactory.cpp
+++ b/Swift/Controllers/Storages/CertificateStorageFactory.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include <Swift/Controllers/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
namespace Swift {
diff --git a/Swift/Controllers/CertificateStorageFactory.h b/Swift/Controllers/Storages/CertificateStorageFactory.h
index 5b85757..5b85757 100644
--- a/Swift/Controllers/CertificateStorageFactory.h
+++ b/Swift/Controllers/Storages/CertificateStorageFactory.h
diff --git a/Swift/Controllers/CertificateStorageTrustChecker.h b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h
index f33287c..40838dd 100644
--- a/Swift/Controllers/CertificateStorageTrustChecker.h
+++ b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h
@@ -7,7 +7,7 @@
#pragma once
#include <Swiften/TLS/CertificateTrustChecker.h>
-#include <Swift/Controllers/CertificateStorage.h>
+#include <Swift/Controllers/Storages/CertificateStorage.h>
namespace Swift {
/**
diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp
new file mode 100644
index 0000000..6447099
--- /dev/null
+++ b/Swift/Controllers/Storages/FileStorages.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Storages/FileStorages.h"
+#include "Swift/Controllers/Storages/VCardFileStorage.h"
+#include "Swift/Controllers/Storages/AvatarFileStorage.h"
+#include "Swift/Controllers/Storages/CapsFileStorage.h"
+#include "Swift/Controllers/Storages/RosterFileStorage.h"
+
+namespace Swift {
+
+FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid) {
+ std::string profile = jid.toBare();
+ vcardStorage = new VCardFileStorage(baseDir / profile / "vcards");
+ capsStorage = new CapsFileStorage(baseDir / "caps");
+ avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars");
+ rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml");
+}
+
+FileStorages::~FileStorages() {
+ delete rosterStorage;
+ delete avatarStorage;
+ delete capsStorage;
+ delete vcardStorage;
+}
+
+VCardStorage* FileStorages::getVCardStorage() const {
+ return vcardStorage;
+}
+
+CapsStorage* FileStorages::getCapsStorage() const {
+ return capsStorage;
+}
+
+AvatarStorage* FileStorages::getAvatarStorage() const {
+ return avatarStorage;
+}
+
+RosterStorage* FileStorages::getRosterStorage() const {
+ return rosterStorage;
+}
+
+}
diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h
new file mode 100644
index 0000000..28df314
--- /dev/null
+++ b/Swift/Controllers/Storages/FileStorages.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include "Swiften/Client/Storages.h"
+
+namespace Swift {
+ class VCardFileStorage;
+ class AvatarFileStorage;
+ class CapsFileStorage;
+ class RosterFileStorage;
+ class JID;
+
+ /**
+ * A storages implementation that stores all controller data on disk.
+ */
+ class FileStorages : public Storages {
+ public:
+ /**
+ * Creates the storages interface.
+ *
+ * All data will be stored relative to a base directory, and
+ * for some controllers, in a subdirectory for the given profile.
+ * The data is stored in the following places:
+ * - Avatars: <basedir>/avatars
+ * - VCards: <basedir>/<profile>/vcards
+ * - Entity capabilities: <basedir>/caps
+ *
+ * \param baseDir the base dir to store data relative to
+ * \param jid the subdir in which profile-specific data will be stored.
+ * The bare JID will be used as the subdir name.
+ */
+ FileStorages(const boost::filesystem::path& baseDir, const JID& jid);
+ ~FileStorages();
+
+ virtual VCardStorage* getVCardStorage() const;
+ virtual AvatarStorage* getAvatarStorage() const;
+ virtual CapsStorage* getCapsStorage() const;
+ virtual RosterStorage* getRosterStorage() const;
+
+ private:
+ VCardFileStorage* vcardStorage;
+ AvatarFileStorage* avatarStorage;
+ CapsFileStorage* capsStorage;
+ RosterFileStorage* rosterStorage;
+ };
+}
diff --git a/Swift/Controllers/FileStoragesFactory.h b/Swift/Controllers/Storages/FileStoragesFactory.h
index bd7cdfb..0676bc3 100644
--- a/Swift/Controllers/FileStoragesFactory.h
+++ b/Swift/Controllers/Storages/FileStoragesFactory.h
@@ -6,8 +6,8 @@
#pragma once
-#include "Swift/Controllers/StoragesFactory.h"
-#include "Swiften/Client/FileStorages.h"
+#include "Swift/Controllers/Storages/StoragesFactory.h"
+#include "Swift/Controllers/Storages/FileStorages.h"
namespace Swift {
class FileStoragesFactory : public StoragesFactory {
diff --git a/Swift/Controllers/MemoryStoragesFactory.h b/Swift/Controllers/Storages/MemoryStoragesFactory.h
index 8408e10..0dea349 100644
--- a/Swift/Controllers/MemoryStoragesFactory.h
+++ b/Swift/Controllers/Storages/MemoryStoragesFactory.h
@@ -6,10 +6,12 @@
#pragma once
-#include "Swift/Controllers/StoragesFactory.h"
+#include "Swift/Controllers/Storages/StoragesFactory.h"
#include "Swiften/Client/MemoryStorages.h"
namespace Swift {
+ class JID;
+
class MemoryStoragesFactory : public StoragesFactory {
public:
MemoryStoragesFactory() {}
diff --git a/Swift/Controllers/Storages/RosterFileStorage.cpp b/Swift/Controllers/Storages/RosterFileStorage.cpp
new file mode 100644
index 0000000..73e582f
--- /dev/null
+++ b/Swift/Controllers/Storages/RosterFileStorage.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/Storages/RosterFileStorage.h>
+
+#include <Swiften/Entity/GenericPayloadPersister.h>
+#include <Swiften/Serializer/PayloadSerializers/RosterSerializer.h>
+#include <Swiften/Parser/PayloadParsers/RosterParser.h>
+
+using namespace Swift;
+
+typedef GenericPayloadPersister<RosterPayload, RosterParser, RosterSerializer> RosterPersister;
+
+RosterFileStorage::RosterFileStorage(const boost::filesystem::path& path) : path(path) {
+}
+
+boost::shared_ptr<RosterPayload> RosterFileStorage::getRoster() const {
+ return RosterPersister().loadPayloadGeneric(path);
+}
+
+void RosterFileStorage::setRoster(boost::shared_ptr<RosterPayload> roster) {
+ RosterPersister().savePayload(roster, path);
+}
diff --git a/Swift/Controllers/Storages/RosterFileStorage.h b/Swift/Controllers/Storages/RosterFileStorage.h
new file mode 100644
index 0000000..cb00969
--- /dev/null
+++ b/Swift/Controllers/Storages/RosterFileStorage.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/Roster/RosterStorage.h>
+
+namespace Swift {
+ class RosterFileStorage : public RosterStorage {
+ public:
+ RosterFileStorage(const boost::filesystem::path& path);
+
+ virtual boost::shared_ptr<RosterPayload> getRoster() const;
+ virtual void setRoster(boost::shared_ptr<RosterPayload>);
+
+ private:
+ boost::filesystem::path path;
+ };
+}
diff --git a/Swift/Controllers/StoragesFactory.h b/Swift/Controllers/Storages/StoragesFactory.h
index 441a4e9..203f9c9 100644
--- a/Swift/Controllers/StoragesFactory.h
+++ b/Swift/Controllers/Storages/StoragesFactory.h
@@ -8,6 +8,7 @@
namespace Swift {
class Storages;
+ class JID;
class StoragesFactory {
public:
diff --git a/Swift/Controllers/Storages/VCardFileStorage.cpp b/Swift/Controllers/Storages/VCardFileStorage.cpp
new file mode 100644
index 0000000..d799a90
--- /dev/null
+++ b/Swift/Controllers/Storages/VCardFileStorage.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Storages/VCardFileStorage.h"
+
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem.hpp>
+#include <iostream>
+
+#include <Swiften/Entity/GenericPayloadPersister.h>
+#include <Swiften/Base/String.h>
+#include <Swiften/StringCodecs/Hexify.h>
+#include <Swiften/StringCodecs/SHA1.h>
+#include <Swiften/Base/foreach.h>
+#include "Swiften/JID/JID.h"
+#include "Swiften/Elements/VCard.h"
+#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h"
+#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h"
+#include "Swiften/Parser/PayloadParsers/VCardParser.h"
+
+using namespace Swift;
+
+typedef GenericPayloadPersister<VCard, VCardParser, VCardSerializer> VCardPersister;
+
+VCardFileStorage::VCardFileStorage(boost::filesystem::path dir) : vcardsPath(dir) {
+ cacheFile = vcardsPath / "phashes";
+ if (boost::filesystem::exists(cacheFile)) {
+ try {
+ boost::filesystem::ifstream file(cacheFile);
+ std::string line;
+ if (file.is_open()) {
+ while (!file.eof()) {
+ getline(file, line);
+ std::pair<std::string, std::string> r = String::getSplittedAtFirst(line, ' ');
+ JID jid(r.second);
+ if (jid.isValid()) {
+ photoHashes.insert(std::make_pair(jid, r.first));
+ }
+ else if (!r.first.empty() || !r.second.empty()) {
+ std::cerr << "Invalid entry in phashes file" << std::endl;
+ }
+ }
+ }
+ }
+ catch (...) {
+ std::cerr << "Error reading phashes file" << std::endl;
+ }
+ }
+}
+
+boost::shared_ptr<VCard> VCardFileStorage::getVCard(const JID& jid) const {
+ boost::shared_ptr<VCard> result = VCardPersister().loadPayloadGeneric(getVCardPath(jid));
+ getAndUpdatePhotoHash(jid, result);
+ return result;
+}
+
+void VCardFileStorage::setVCard(const JID& jid, VCard::ref v) {
+ VCardPersister().savePayload(v, getVCardPath(jid));
+ getAndUpdatePhotoHash(jid, v);
+}
+
+boost::filesystem::path VCardFileStorage::getVCardPath(const JID& jid) const {
+ try {
+ std::string file(jid.toString());
+ String::replaceAll(file, '/', "%2f");
+ return boost::filesystem::path(vcardsPath / (file + ".xml"));
+ }
+ catch (const boost::filesystem::filesystem_error& e) {
+ std::cerr << "ERROR: " << e.what() << std::endl;
+ return boost::filesystem::path();
+ }
+}
+
+std::string VCardFileStorage::getPhotoHash(const JID& jid) const {
+ PhotoHashMap::const_iterator i = photoHashes.find(jid);
+ if (i != photoHashes.end()) {
+ return i->second;
+ }
+ else {
+ VCard::ref vCard = getVCard(jid);
+ return getAndUpdatePhotoHash(jid, vCard);
+ }
+}
+
+std::string VCardFileStorage::getAndUpdatePhotoHash(const JID& jid, VCard::ref vCard) const {
+ std::string hash;
+ if (vCard && !vCard->getPhoto().empty()) {
+ hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto()));
+ }
+ std::pair<PhotoHashMap::iterator, bool> r = photoHashes.insert(std::make_pair(jid, hash));
+ if (r.second) {
+ savePhotoHashes();
+ }
+ else if (r.first->second != hash) {
+ r.first->second = hash;
+ savePhotoHashes();
+ }
+ return hash;
+}
+
+void VCardFileStorage::savePhotoHashes() const {
+ try {
+ boost::filesystem::ofstream file(cacheFile);
+ for (PhotoHashMap::const_iterator i = photoHashes.begin(); i != photoHashes.end(); ++i) {
+ file << i->second << " " << i->first.toString() << std::endl;
+ }
+ file.close();
+ }
+ catch (...) {
+ std::cerr << "Error writing vcards file" << std::endl;
+ }
+}
diff --git a/Swift/Controllers/Storages/VCardFileStorage.h b/Swift/Controllers/Storages/VCardFileStorage.h
new file mode 100644
index 0000000..ba422f4
--- /dev/null
+++ b/Swift/Controllers/Storages/VCardFileStorage.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <boost/filesystem/path.hpp>
+#include <string>
+#include <map>
+
+#include "Swiften/VCards/VCardStorage.h"
+
+namespace Swift {
+ class VCardFileStorage : public VCardStorage {
+ public:
+ VCardFileStorage(boost::filesystem::path dir);
+
+ virtual VCard::ref getVCard(const JID& jid) const;
+ virtual void setVCard(const JID& jid, VCard::ref v);
+
+ virtual std::string getPhotoHash(const JID&) const;
+
+ private:
+ boost::filesystem::path getVCardPath(const JID&) const;
+
+ std::string getAndUpdatePhotoHash(const JID& jid, VCard::ref vcard) const;
+ void savePhotoHashes() const;
+
+ private:
+ boost::filesystem::path vcardsPath;
+ boost::filesystem::path cacheFile;
+ typedef std::map<JID, std::string> PhotoHashMap;
+ mutable PhotoHashMap photoHashes;
+ };
+}
diff --git a/Swift/Controllers/SystemTrayController.cpp b/Swift/Controllers/SystemTrayController.cpp
index 598771c..31fd4ff 100644
--- a/Swift/Controllers/SystemTrayController.cpp
+++ b/Swift/Controllers/SystemTrayController.cpp
@@ -22,7 +22,7 @@ SystemTrayController::SystemTrayController(EventController* eventController, Sys
void SystemTrayController::handleEventQueueLengthChange(int /*length*/) {
EventList events = eventController_->getEvents();
bool found = false;
- for (EventList::iterator it = events.begin(); it != events.end(); it++) {
+ for (EventList::iterator it = events.begin(); it != events.end(); ++it) {
if (boost::dynamic_pointer_cast<MessageEvent>(*it)) {
found = true;
break;
diff --git a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h
index c7f8be6..dea3ead 100644
--- a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h
+++ b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h
@@ -10,17 +10,21 @@
#include <boost/shared_ptr.hpp>
#include <string>
+#include <Swiften/JID/JID.h>
+
#include "Swift/Controllers/UIEvents/UIEvent.h"
namespace Swift {
class JoinMUCUIEvent : public UIEvent {
public:
typedef boost::shared_ptr<JoinMUCUIEvent> ref;
- JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& nick = boost::optional<std::string>()) : jid_(jid), nick_(nick) {};
+ JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture){};
boost::optional<std::string> getNick() {return nick_;};
JID getJID() {return jid_;};
+ bool getShouldJoinAutomatically() {return joinAutomatically_;}
private:
JID jid_;
boost::optional<std::string> nick_;
+ bool joinAutomatically_;
};
}
diff --git a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h
new file mode 100644
index 0000000..c3b4b49
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+ class RequestAdHocUIEvent : public UIEvent {
+ public:
+ RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {};
+ const DiscoItems::Item& getCommand() const {return command_;}
+ private:
+ DiscoItems::Item command_;
+ };
+}
diff --git a/Swift/Controllers/UIEvents/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/RequestJoinMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
index dd2ff6c..2c7b105 100644
--- a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
+++ b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h
@@ -6,18 +6,25 @@
#pragma once
-#include <boost/optional.hpp>
#include <boost/shared_ptr.hpp>
-
#include <string>
+
#include <Swift/Controllers/UIEvents/UIEvent.h>
+#include <Swiften/JID/JID.h>
namespace Swift {
class RequestJoinMUCUIEvent : public UIEvent {
public:
typedef boost::shared_ptr<RequestJoinMUCUIEvent> ref;
- RequestJoinMUCUIEvent() {
+ RequestJoinMUCUIEvent(const JID& room = JID()) : room(room) {
}
+
+ const JID& getRoom() const {
+ return room;
+ }
+
+ private:
+ JID room;
};
}
diff --git a/Swift/Controllers/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/AdHocCommandWindow.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h
new file mode 100644
index 0000000..f7a5d39
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+namespace Swift {
+ class AdHocCommandWindow {
+ public:
+ virtual ~AdHocCommandWindow() {};
+ };
+}
diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h
new file mode 100644
index 0000000..ae77180
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h>
+#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h>
+
+namespace Swift {
+ class AdHocCommandWindowFactory {
+ public:
+ virtual ~AdHocCommandWindowFactory() {}
+ /**
+ * The UI should deal with the lifetime of this window (i.e. DeleteOnClose),
+ * so the result isn't returned.
+ */
+ virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0;
+ };
+}
diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h
index a2a0874..d047f8c 100644
--- a/Swift/Controllers/UIInterfaces/ChatListWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h
@@ -1,23 +1,60 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#pragma once
+#include <list>
#include <boost/shared_ptr.hpp>
+#include <Swiften/MUC/MUCBookmark.h>
+#include <Swiften/Elements/StatusShow.h>
+#include <boost/filesystem/path.hpp>
-#include "Swiften/MUC/MUCBookmark.h"
+#include <Swiften/Base/boost_bsignals.h>
namespace Swift {
class ChatListWindow {
public:
+ class Chat {
+ public:
+ Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, const std::string& nick = "")
+ : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), unreadCount(unreadCount), avatarPath(avatarPath) {}
+ /** Assume that nicks and other transient features aren't important for equality */
+ bool operator==(const Chat& other) const {
+ return jid.toBare() == other.jid.toBare()
+ && isMUC == other.isMUC;
+ };
+ void setUnreadCount(int unread) {
+ unreadCount = unread;
+ }
+ void setStatusType(StatusShow::Type type) {
+ statusType = type;
+ }
+ void setAvatarPath(const boost::filesystem::path& path) {
+ avatarPath = path;
+ }
+ JID jid;
+ std::string chatName;
+ std::string activity;
+ StatusShow::Type statusType;
+ bool isMUC;
+ std::string nick;
+ int unreadCount;
+ boost::filesystem::path avatarPath;
+ };
virtual ~ChatListWindow();
virtual void setBookmarksEnabled(bool enabled) = 0;
virtual void addMUCBookmark(const MUCBookmark& bookmark) = 0;
virtual void removeMUCBookmark(const MUCBookmark& bookmark) = 0;
- virtual void clear() = 0;
+ virtual void setRecents(const std::list<Chat>& recents) = 0;
+ virtual void setUnreadCount(int unread) = 0;
+ virtual void clearBookmarks() = 0;
+
+ boost::signal<void (const MUCBookmark&)> onMUCBookmarkActivated;
+ boost::signal<void (const Chat&)> onRecentActivated;
+ boost::signal<void ()> onClearRecentsRequested;
};
}
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index c7bcf1e..df57d80 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -14,18 +14,26 @@
#include <vector>
#include <string>
-#include "Swiften/Elements/SecurityLabelsCatalog.h"
-#include "Swiften/Elements/ChatState.h"
+#include <Swiften/Elements/SecurityLabelsCatalog.h>
+#include <Swiften/Elements/ChatState.h>
+#include <Swiften/Elements/Form.h>
+
namespace Swift {
class AvatarManager;
class TreeWidget;
class Roster;
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() {};
@@ -40,6 +48,12 @@ namespace Swift {
virtual void addSystemMessage(const std::string& message) = 0;
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;
@@ -47,6 +61,7 @@ namespace Swift {
virtual void activate() = 0;
virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0;
virtual void setSecurityLabelsEnabled(bool enabled) = 0;
+ virtual void setCorrectionEnabled(Tristate enabled) = 0;
virtual void setUnreadMessageCount(int count) = 0;
virtual void convertToMUC() = 0;
// virtual TreeWidget *getTreeWidget() = 0;
@@ -58,12 +73,42 @@ namespace Swift {
virtual void replaceLastMessage(const std::string& message) = 0;
virtual void setAckState(const std::string& id, AckState state) = 0;
virtual void flash() = 0;
+ virtual void setSubject(const std::string& subject) = 0;
+ /**
+ * Set an alert on the window.
+ * @param alertText Description of alert (required).
+ * @param buttonText Button text to use (optional, no button is shown if empty).
+ */
+ virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0;
+ /**
+ * Removes an alert.
+ */
+ virtual void cancelAlert() = 0;
+
+ /**
+ * Actions that can be performed on the selected occupant.
+ */
+ virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) = 0;
+ virtual void showRoomConfigurationForm(Form::ref) = 0;
boost::signal<void ()> onClosed;
boost::signal<void ()> onAllMessagesRead;
- boost::signal<void (const std::string&)> onSendMessageRequest;
+ boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
+ boost::signal<void ()> onSendCorrectionMessageRequest;
boost::signal<void ()> onUserTyping;
boost::signal<void ()> onUserCancelsTyping;
+ boost::signal<void ()> onAlertButtonClicked;
+ boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged;
+ boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected;
+ boost::signal<void (const std::string&)> onChangeSubjectRequest;
+ boost::signal<void (Form::ref)> onConfigureRequest;
+ boost::signal<void ()> onDestroyRequest;
+
+ // 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/EventWindow.h b/Swift/Controllers/UIInterfaces/EventWindow.h
index e756655..3ca2c82 100644
--- a/Swift/Controllers/UIInterfaces/EventWindow.h
+++ b/Swift/Controllers/UIInterfaces/EventWindow.h
@@ -6,7 +6,8 @@
#pragma once
-#include "boost/shared_ptr.hpp"
+#include <boost/shared_ptr.hpp>
+
#include "Swift/Controllers/XMPPEvents/StanzaEvent.h"
namespace Swift {
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/JoinMUCWindow.h b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h
index 2e3d43c..4873c9b 100644
--- a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h
+++ b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h
@@ -21,7 +21,6 @@ namespace Swift {
virtual void setMUC(const std::string& nick) = 0;
virtual void show() = 0;
- boost::signal<void (const JID& /* muc */, const std::string& /* nick */, bool /* autoJoin */)> onJoinMUC;
boost::signal<void ()> onSearchMUC;
};
}
diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h
index 9c8bd77..cd8021b 100644
--- a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h
+++ b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h
@@ -9,10 +9,11 @@
#include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
namespace Swift {
+ class UIEventStream;
class JoinMUCWindowFactory {
public:
virtual ~JoinMUCWindowFactory() {};
- virtual JoinMUCWindow* createJoinMUCWindow() = 0;
+ virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream) = 0;
};
}
diff --git a/Swift/Controllers/UIInterfaces/LoginWindow.h b/Swift/Controllers/UIInterfaces/LoginWindow.h
index 61fcaa1..fcaeb42 100644
--- a/Swift/Controllers/UIInterfaces/LoginWindow.h
+++ b/Swift/Controllers/UIInterfaces/LoginWindow.h
@@ -27,6 +27,8 @@ namespace Swift {
boost::signal<void (const std::string&, const std::string&, const std::string& /* certificateFile */, bool /* remember password*/, bool /* login automatically */)> onLoginRequest;
virtual void setLoginAutomatically(bool loginAutomatically) = 0;
virtual void quit() = 0;
+ /** Disable any GUI elements that suggest saving of passwords. */
+ virtual void setRememberingAllowed(bool allowed) = 0;
/** Blocking request whether a cert should be permanently trusted.*/
virtual bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref) = 0;
diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h
index 2fd463b..93584e7 100644
--- a/Swift/Controllers/UIInterfaces/MainWindow.h
+++ b/Swift/Controllers/UIInterfaces/MainWindow.h
@@ -9,6 +9,7 @@
#include <string>
#include "Swiften/JID/JID.h"
#include "Swiften/Elements/StatusShow.h"
+#include "Swiften/Elements/DiscoItems.h"
#include "Swiften/Base/boost_bsignals.h"
#include <boost/shared_ptr.hpp>
@@ -33,6 +34,7 @@ namespace Swift {
/** Must be able to cope with NULL to clear the roster */
virtual void setRosterModel(Roster* roster) = 0;
virtual void setConnecting() = 0;
+ virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) = 0;
boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest;
boost::signal<void ()> onSignOutRequest;
diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h
index 9b36ac5..cf89dab 100644
--- a/Swift/Controllers/UIInterfaces/UIFactory.h
+++ b/Swift/Controllers/UIInterfaces/UIFactory.h
@@ -17,6 +17,8 @@
#include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h>
#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 :
@@ -26,11 +28,13 @@ namespace Swift {
public LoginWindowFactory,
public MainWindowFactory,
public MUCSearchWindowFactory,
- public XMLConsoleWidgetFactory,
+ public XMLConsoleWidgetFactory,
public UserSearchWindowFactory,
public JoinMUCWindowFactory,
public ProfileWindowFactory,
- public ContactEditWindowFactory {
+ public ContactEditWindowFactory,
+ public AdHocCommandWindowFactory,
+ public FileTransferListWidgetFactory {
public:
virtual ~UIFactory() {}
};
diff --git a/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h b/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h
index 3cd0947..caec38c 100644
--- a/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h
+++ b/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h
@@ -6,15 +6,15 @@
#pragma once
-#include <string>
+#include <Swiften/Base/SafeByteArray.h>
namespace Swift {
class XMLConsoleWidget {
public:
virtual ~XMLConsoleWidget();
- virtual void handleDataRead(const std::string& data) = 0;
- virtual void handleDataWritten(const std::string& data) = 0;
+ virtual void handleDataRead(const SafeByteArray& data) = 0;
+ virtual void handleDataWritten(const SafeByteArray& data) = 0;
virtual void show() = 0;
virtual void activate() = 0;
diff --git a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp
new file mode 100644
index 0000000..ee0ee9f
--- /dev/null
+++ b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include "Swift/Controllers/ChatMessageSummarizer.h"
+
+using namespace Swift;
+using namespace std;
+
+class ChatMessageSummarizerTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(ChatMessageSummarizerTest);
+ CPPUNIT_TEST(testEmpty);
+ CPPUNIT_TEST(testCurrentNone);
+ CPPUNIT_TEST(testCurrentCount);
+ CPPUNIT_TEST(testCurrentCountOthersNone);
+ CPPUNIT_TEST(testCurrentCountOtherCount);
+ CPPUNIT_TEST(testCurrentNoneOtherCount);
+ CPPUNIT_TEST(testCurrentCountOthersCount);
+ CPPUNIT_TEST(testCurrentNoneOthersCount);
+ CPPUNIT_TEST(testCurrentCountSomeOthersCount);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ChatMessageSummarizerTest() {};
+
+ void setUp() {
+
+ }
+
+ void testEmpty() {
+ string current("");
+ vector<UnreadPair> unreads;
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(current, summary.getSummary(current, unreads));
+ }
+
+ void testCurrentNone() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bob", 0));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(current, summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bob", 3));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (3)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountOthersNone() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 0));
+ unreads.push_back(UnreadPair("Bob", 3));
+ unreads.push_back(UnreadPair("Betty", 0));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (3)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountOtherCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 0));
+ unreads.push_back(UnreadPair("Bob", 3));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (3), Betty (7)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentNoneOtherCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 0));
+ unreads.push_back(UnreadPair("Bob", 0));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob, Betty (7)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentNoneOthersCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 2));
+ unreads.push_back(UnreadPair("Bob", 0));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob and 2 others (9)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountOthersCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 2));
+ unreads.push_back(UnreadPair("Bob", 11));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (11) and 2 others (9)"), summary.getSummary(current, unreads));
+ }
+
+ void testCurrentCountSomeOthersCount() {
+ string current("Bob");
+ vector<UnreadPair> unreads;
+ unreads.push_back(UnreadPair("Bert", 2));
+ unreads.push_back(UnreadPair("Beverly", 0));
+ unreads.push_back(UnreadPair("Bob", 11));
+ unreads.push_back(UnreadPair("Beatrice", 0));
+ unreads.push_back(UnreadPair("Betty", 7));
+ ChatMessageSummarizer summary;
+ CPPUNIT_ASSERT_EQUAL(string("Bob (11) and 2 others (9)"), summary.getSummary(current, unreads));
+ }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageSummarizerTest);
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 53a90a7..ad8856b 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -11,7 +11,7 @@
namespace Swift {
class MockChatWindow : public ChatWindow {
public:
- MockChatWindow() {};
+ MockChatWindow() : labelsEnabled_(false) {};
virtual ~MockChatWindow();
virtual std::string addMessage(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&) {lastMessageBody_ = message; return "";};
@@ -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() {};
@@ -34,12 +39,19 @@ namespace Swift {
virtual void setRosterModel(Roster* /*roster*/) {};
virtual void setTabComplete(TabComplete*) {};
virtual void replaceLastMessage(const std::string&) {};
+ virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&) {};
void setAckState(const std::string& /*id*/, AckState /*state*/) {};
virtual void flash() {};
+ 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 setSubject(const std::string& /*subject*/) {}
+ virtual void showRoomConfigurationForm(Form::ref) {}
boost::signal<void ()> onClosed;
boost::signal<void ()> onAllMessagesRead;
- boost::signal<void (const std::string&)> onSendMessageRequest;
+ boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
std::string name_;
std::string lastMessageBody_;
diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h
index afa5c2a..f773062 100644
--- a/Swift/Controllers/UnitTest/MockMainWindow.h
+++ b/Swift/Controllers/UnitTest/MockMainWindow.h
@@ -20,6 +20,7 @@ namespace Swift {
virtual void setMyAvatarPath(const std::string& /*path*/) {};
virtual void setMyStatusText(const std::string& /*status*/) {};
virtual void setMyStatusType(StatusShow::Type /*type*/) {};
+ virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& /*commands*/) {};
virtual void setConnecting() {};
Roster* roster;
diff --git a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
index 42cfc5f..3e9be13 100644
--- a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
+++ b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp
@@ -70,7 +70,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveFirstPresenceCreatesAvailableNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Online);
@@ -79,7 +79,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveSecondPresenceCreatesStatusChangeNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Away);
notifier->notifications.clear();
@@ -90,7 +90,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveUnavailablePresenceAfterAvailablePresenceCreatesUnavailableNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Away);
notifier->notifications.clear();
@@ -101,7 +101,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveUnavailablePresenceWithoutAvailableDoesNotCreateNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendUnavailablePresence(user1);
@@ -109,7 +109,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveAvailablePresenceAfterUnavailableCreatesAvailableNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Away);
sendUnavailablePresence(user1);
notifier->notifications.clear();
@@ -121,7 +121,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveAvailablePresenceAfterReconnectCreatesAvailableNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Away);
stanzaChannel->setAvailable(false);
stanzaChannel->setAvailable(true);
@@ -134,7 +134,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveAvailablePresenceFromMUCDoesNotCreateNotification() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
mucRegistry->addMUC(JID("teaparty@wonderland.lit"));
sendPresence(JID("teaparty@wonderland.lit/Alice"), StatusShow::Away);
@@ -143,8 +143,8 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testNotificationPicture() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
- avatarManager->avatars[user1] = ByteArray("abcdef");
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
+ avatarManager->avatars[user1] = createByteArray("abcdef");
sendPresence(user1, StatusShow::Online);
@@ -153,7 +153,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testNotificationActivationEmitsSignal() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Online);
CPPUNIT_ASSERT(notifier->notifications[0].callback);
@@ -164,7 +164,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testNotificationSubjectContainsNameForJIDInRoster() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
roster->addContact(user1.toBare(), "User 1", std::vector<std::string>(), RosterItemPayload::Both);
sendPresence(user1, StatusShow::Online);
@@ -175,7 +175,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testNotificationSubjectContainsJIDForJIDNotInRoster() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Online);
@@ -185,7 +185,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testNotificationSubjectContainsStatus() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Away);
@@ -195,7 +195,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testNotificationMessageContainsStatusMessage() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
sendPresence(user1, StatusShow::Away);
@@ -204,7 +204,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveFirstPresenceWithQuietPeriodDoesNotNotify() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
testling->setInitialQuietPeriodMS(10);
sendPresence(user1, StatusShow::Online);
@@ -213,7 +213,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceivePresenceDuringQuietPeriodDoesNotNotify() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
testling->setInitialQuietPeriodMS(10);
sendPresence(user1, StatusShow::Online);
@@ -224,7 +224,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceivePresenceDuringQuietPeriodResetsTimer() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
testling->setInitialQuietPeriodMS(10);
sendPresence(user1, StatusShow::Online);
@@ -237,7 +237,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceivePresenceAfterQuietPeriodNotifies() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
testling->setInitialQuietPeriodMS(10);
sendPresence(user1, StatusShow::Online);
@@ -248,7 +248,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveFirstPresenceWithQuietPeriodDoesNotCountAsQuietPeriod() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
testling->setInitialQuietPeriodMS(10);
timerFactory->setTime(11);
@@ -258,7 +258,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
}
void testReceiveFirstPresenceAfterReconnectWithQuietPeriodDoesNotNotify() {
- std::auto_ptr<PresenceNotifier> testling = createNotifier();
+ boost::shared_ptr<PresenceNotifier> testling = createNotifier();
testling->setInitialQuietPeriodMS(10);
sendPresence(user1, StatusShow::Online);
timerFactory->setTime(15);
@@ -275,8 +275,8 @@ class PresenceNotifierTest : public CppUnit::TestFixture {
private:
- std::auto_ptr<PresenceNotifier> createNotifier() {
- std::auto_ptr<PresenceNotifier> result(new PresenceNotifier(stanzaChannel, notifier, mucRegistry, avatarManager, nickResolver, presenceOracle, timerFactory));
+ boost::shared_ptr<PresenceNotifier> createNotifier() {
+ boost::shared_ptr<PresenceNotifier> result(new PresenceNotifier(stanzaChannel, notifier, mucRegistry, avatarManager, nickResolver, presenceOracle, timerFactory));
result->onNotificationActivated.connect(boost::bind(&PresenceNotifierTest::handleNotificationActivated, this, _1));
result->setInitialQuietPeriodMS(0);
return result;
diff --git a/Swift/Controllers/XMLConsoleController.cpp b/Swift/Controllers/XMLConsoleController.cpp
index a3510d1..d21f312 100644
--- a/Swift/Controllers/XMLConsoleController.cpp
+++ b/Swift/Controllers/XMLConsoleController.cpp
@@ -30,13 +30,13 @@ void XMLConsoleController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) {
}
}
-void XMLConsoleController::handleDataRead(const std::string& data) {
+void XMLConsoleController::handleDataRead(const SafeByteArray& data) {
if (xmlConsoleWidget) {
xmlConsoleWidget->handleDataRead(data);
}
}
-void XMLConsoleController::handleDataWritten(const std::string& data) {
+void XMLConsoleController::handleDataWritten(const SafeByteArray& data) {
if (xmlConsoleWidget) {
xmlConsoleWidget->handleDataWritten(data);
}
diff --git a/Swift/Controllers/XMLConsoleController.h b/Swift/Controllers/XMLConsoleController.h
index d12982f..6426a85 100644
--- a/Swift/Controllers/XMLConsoleController.h
+++ b/Swift/Controllers/XMLConsoleController.h
@@ -11,6 +11,7 @@
#include <boost/shared_ptr.hpp>
#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include <Swiften/Base/SafeByteArray.h>
namespace Swift {
@@ -23,8 +24,8 @@ namespace Swift {
~XMLConsoleController();
public:
- void handleDataRead(const std::string& data);
- void handleDataWritten(const std::string& data);
+ void handleDataRead(const SafeByteArray& data);
+ void handleDataWritten(const SafeByteArray& data);
private:
void handleUIEvent(boost::shared_ptr<UIEvent> event);
diff --git a/Swift/Controllers/XMPPEvents/EventController.cpp b/Swift/Controllers/XMPPEvents/EventController.cpp
index 7f8f216..98fd634 100644
--- a/Swift/Controllers/XMPPEvents/EventController.cpp
+++ b/Swift/Controllers/XMPPEvents/EventController.cpp
@@ -9,6 +9,7 @@
#include <boost/bind.hpp>
#include <algorithm>
+#include <Swiften/Base/foreach.h>
#include "Swift/Controllers/XMPPEvents/MessageEvent.h"
#include "Swift/Controllers/XMPPEvents/ErrorEvent.h"
#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h"
diff --git a/Swift/Controllers/XMPPURIController.cpp b/Swift/Controllers/XMPPURIController.cpp
new file mode 100644
index 0000000..00759b8
--- /dev/null
+++ b/Swift/Controllers/XMPPURIController.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/XMPPURIController.h>
+
+#include <boost/bind.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <SwifTools/URIHandler/URIHandler.h>
+#include <SwifTools/URIHandler/XMPPURI.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+
+using namespace Swift;
+
+XMPPURIController::XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream) : uriHandler(uriHandler), uiEventStream(uiEventStream) {
+ uriHandler->onURI.connect(boost::bind(&XMPPURIController::handleURI, this, _1));
+}
+
+XMPPURIController::~XMPPURIController() {
+ uriHandler->onURI.disconnect(boost::bind(&XMPPURIController::handleURI, this, _1));
+}
+
+void XMPPURIController::handleURI(const std::string& s) {
+ XMPPURI uri = XMPPURI::fromString(s);
+ if (!uri.isNull()) {
+ if (uri.getQueryType() == "join") {
+ uiEventStream->send(boost::make_shared<RequestJoinMUCUIEvent>(uri.getPath()));
+ }
+ else {
+ uiEventStream->send(boost::make_shared<RequestChatUIEvent>(uri.getPath()));
+ }
+ }
+}
diff --git a/Swift/Controllers/XMPPURIController.h b/Swift/Controllers/XMPPURIController.h
new file mode 100644
index 0000000..54534d4
--- /dev/null
+++ b/Swift/Controllers/XMPPURIController.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <Swiften/Base/boost_bsignals.h>
+
+namespace Swift {
+ class URIHandler;
+ class JID;
+ class UIEventStream;
+
+ class XMPPURIController {
+ public:
+ XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream);
+ ~XMPPURIController();
+
+ boost::signal<void (const JID&)> onStartChat;
+ boost::signal<void (const JID&)> onJoinMUC;
+
+ private:
+ void handleURI(const std::string&);
+
+ private:
+ URIHandler* uriHandler;
+ UIEventStream* uiEventStream;
+ };
+}
diff --git a/Swift/QtUI/.gitignore b/Swift/QtUI/.gitignore
index f539e86..53acb9f 100644
--- a/Swift/QtUI/.gitignore
+++ b/Swift/QtUI/.gitignore
@@ -1,3 +1,4 @@
Swift
BuildVersion.h
*.dmg
+swift-open-uri
diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp
index 274a10a..29dba62 100644
--- a/Swift/QtUI/ChatList/ChatListDelegate.cpp
+++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,6 +11,7 @@
#include "Swift/QtUI/Roster/GroupItemDelegate.h"
#include "Swift/QtUI/ChatList/ChatListItem.h"
#include "Swift/QtUI/ChatList/ChatListMUCItem.h"
+#include "Swift/QtUI/ChatList/ChatListRecentItem.h"
#include "Swift/QtUI/ChatList/ChatListGroupItem.h"
namespace Swift {
@@ -27,7 +28,11 @@ QSize ChatListDelegate::sizeHint(const QStyleOptionViewItem& option, const QMode
ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer());
if (item && dynamic_cast<ChatListMUCItem*>(item)) {
return mucSizeHint(option, index);
- } else if (item && dynamic_cast<ChatListGroupItem*>(item)) {
+ }
+ else if (item && dynamic_cast<ChatListRecentItem*>(item)) {
+ return common_.contactSizeHint(option, index);
+ }
+ else if (item && dynamic_cast<ChatListGroupItem*>(item)) {
return groupDelegate_->sizeHint(option, index);
}
return QStyledItemDelegate::sizeHint(option, index);
@@ -40,14 +45,23 @@ QSize ChatListDelegate::mucSizeHint(const QStyleOptionViewItem& /*option*/, cons
return QSize(150, sizeByText);
}
+QSize ChatListDelegate::recentSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
+ return mucSizeHint(option, index);
+}
+
void ChatListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer());
if (item && dynamic_cast<ChatListMUCItem*>(item)) {
paintMUC(painter, option, dynamic_cast<ChatListMUCItem*>(item));
- } else if (item && dynamic_cast<ChatListGroupItem*>(item)) {
+ }
+ else if (item && dynamic_cast<ChatListRecentItem*>(item)) {
+ paintRecent(painter, option, dynamic_cast<ChatListRecentItem*>(item));
+ }
+ else if (item && dynamic_cast<ChatListGroupItem*>(item)) {
ChatListGroupItem* group = dynamic_cast<ChatListGroupItem*>(item);
groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open);
- } else {
+ }
+ else {
QStyledItemDelegate::paint(painter, option, index);
}
}
@@ -78,9 +92,24 @@ void ChatListDelegate::paintMUC(QPainter* painter, const QStyleOptionViewItem& o
painter->setPen(QPen(QColor(160,160,160)));
QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0));
- DelegateCommons::drawElidedText(painter, detailRegion, item->data(DetailTextRole).toString());
+ DelegateCommons::drawElidedText(painter, detailRegion, item->data(ChatListMUCItem::DetailTextRole).toString());
painter->restore();
}
+void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const {
+ QColor nameColor = item->data(Qt::TextColorRole).value<QColor>();
+ QString avatarPath;
+ if (item->data(ChatListRecentItem::AvatarRole).isValid() && !item->data(ChatListRecentItem::AvatarRole).value<QString>().isNull()) {
+ avatarPath = item->data(ChatListRecentItem::AvatarRole).value<QString>();
+ }
+ QIcon presenceIcon = item->data(ChatListRecentItem::PresenceIconRole).isValid() && !item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull()
+ ? item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>()
+ : QIcon(":/icons/offline.png");
+ QString name = item->data(Qt::DisplayRole).toString();
+ //qDebug() << "Avatar for " << name << " = " << avatarPath;
+ QString statusText = item->data(ChatListRecentItem::DetailTextRole).toString();
+ common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount);
+}
+
}
diff --git a/Swift/QtUI/ChatList/ChatListDelegate.h b/Swift/QtUI/ChatList/ChatListDelegate.h
index f6c6c40..a898df4 100644
--- a/Swift/QtUI/ChatList/ChatListDelegate.h
+++ b/Swift/QtUI/ChatList/ChatListDelegate.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -12,6 +12,7 @@
namespace Swift {
class ChatListMUCItem;
+ class ChatListRecentItem;
class ChatListDelegate : public QStyledItemDelegate {
public:
ChatListDelegate();
@@ -20,7 +21,9 @@ namespace Swift {
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
private:
void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, ChatListMUCItem* item) const;
+ void paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const;
QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const;
+ QSize recentSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const;
DelegateCommons common_;
GroupItemDelegate* groupDelegate_;
diff --git a/Swift/QtUI/ChatList/ChatListGroupItem.h b/Swift/QtUI/ChatList/ChatListGroupItem.h
index cc4d4af..a1e479f 100644
--- a/Swift/QtUI/ChatList/ChatListGroupItem.h
+++ b/Swift/QtUI/ChatList/ChatListGroupItem.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -13,8 +13,8 @@
namespace Swift {
class ChatListGroupItem : public ChatListItem {
public:
- ChatListGroupItem(const QString& name, ChatListGroupItem* parent) : ChatListItem(parent), name_(name) {};
- void addItem(ChatListItem* item) {items_.push_back(item); qStableSort(items_.begin(), items_.end(), pointerItemLessThan);};
+ ChatListGroupItem(const QString& name, ChatListGroupItem* parent, bool sorted = true) : ChatListItem(parent), name_(name), sorted_(sorted) {};
+ void addItem(ChatListItem* item) {items_.push_back(item); if (sorted_) {qStableSort(items_.begin(), items_.end(), pointerItemLessThan);}};
void remove(int index) {items_.removeAt(index);};
int rowCount() {return items_.size();};
ChatListItem* item(int i) {return items_[i];};
@@ -30,5 +30,6 @@ namespace Swift {
QString name_;
QList<ChatListItem*> items_;
+ bool sorted_;
};
}
diff --git a/Swift/QtUI/ChatList/ChatListMUCItem.cpp b/Swift/QtUI/ChatList/ChatListMUCItem.cpp
index e374ed5..68f9581 100644
--- a/Swift/QtUI/ChatList/ChatListMUCItem.cpp
+++ b/Swift/QtUI/ChatList/ChatListMUCItem.cpp
@@ -13,7 +13,7 @@ ChatListMUCItem::ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem*
}
-const MUCBookmark& ChatListMUCItem::getBookmark() {
+const MUCBookmark& ChatListMUCItem::getBookmark() const {
return bookmark_;
}
diff --git a/Swift/QtUI/ChatList/ChatListMUCItem.h b/Swift/QtUI/ChatList/ChatListMUCItem.h
index 068f5d6..046d1d4 100644
--- a/Swift/QtUI/ChatList/ChatListMUCItem.h
+++ b/Swift/QtUI/ChatList/ChatListMUCItem.h
@@ -15,16 +15,16 @@
#include "Swift/QtUI/ChatList/ChatListItem.h"
namespace Swift {
- enum MUCItemRoles {
- DetailTextRole = Qt::UserRole/*,
- AvatarRole = Qt::UserRole + 1,
- PresenceIconRole = Qt::UserRole + 2,
- StatusShowTypeRole = Qt::UserRole + 3*/
- };
class ChatListMUCItem : public ChatListItem {
public:
+ enum MUCItemRoles {
+ DetailTextRole = Qt::UserRole/*,
+ AvatarRole = Qt::UserRole + 1,
+ PresenceIconRole = Qt::UserRole + 2,
+ StatusShowTypeRole = Qt::UserRole + 3*/
+ };
ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* parent);
- const MUCBookmark& getBookmark();
+ const MUCBookmark& getBookmark() const;
QVariant data(int role) const;
private:
MUCBookmark bookmark_;
diff --git a/Swift/QtUI/ChatList/ChatListModel.cpp b/Swift/QtUI/ChatList/ChatListModel.cpp
index ba7b766..681c1c2 100644
--- a/Swift/QtUI/ChatList/ChatListModel.cpp
+++ b/Swift/QtUI/ChatList/ChatListModel.cpp
@@ -1,22 +1,25 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "Swift/QtUI/ChatList/ChatListModel.h"
+#include <Swift/QtUI/ChatList/ChatListModel.h>
-#include "Swift/QtUI/ChatList/ChatListMUCItem.h"
+#include <Swift/QtUI/ChatList/ChatListMUCItem.h>
+#include <Swift/QtUI/ChatList/ChatListRecentItem.h>
namespace Swift {
ChatListModel::ChatListModel() {
- root_ = new ChatListGroupItem("", NULL);
+ root_ = new ChatListGroupItem("", NULL, false);
mucBookmarks_ = new ChatListGroupItem(tr("Bookmarked Rooms"), root_);
+ recents_ = new ChatListGroupItem(tr("Recent Chats"), root_, false);
+ root_->addItem(recents_);
root_->addItem(mucBookmarks_);
}
-void ChatListModel::clear() {
+void ChatListModel::clearBookmarks() {
emit layoutAboutToBeChanged();
mucBookmarks_->clear();
emit layoutChanged();
@@ -43,6 +46,15 @@ void ChatListModel::removeMUCBookmark(const Swift::MUCBookmark& bookmark) {
}
}
+void ChatListModel::setRecents(const std::list<ChatListWindow::Chat>& recents) {
+ emit layoutAboutToBeChanged();
+ recents_->clear();
+ foreach (const ChatListWindow::Chat chat, recents) {
+ recents_->addItem(new ChatListRecentItem(chat, recents_));
+ }
+ emit layoutChanged();
+}
+
int ChatListModel::columnCount(const QModelIndex& /*parent*/) const {
return 1;
}
diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h
index adde148..8e7828c 100644
--- a/Swift/QtUI/ChatList/ChatListModel.h
+++ b/Swift/QtUI/ChatList/ChatListModel.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -11,9 +11,10 @@
#include <QAbstractItemModel>
#include <QList>
-#include "Swiften/MUC/MUCBookmark.h"
+#include <Swiften/MUC/MUCBookmark.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
-#include "Swift/QtUI/ChatList/ChatListGroupItem.h"
+#include <Swift/QtUI/ChatList/ChatListGroupItem.h>
namespace Swift {
class ChatListModel : public QAbstractItemModel {
@@ -28,9 +29,11 @@ namespace Swift {
QModelIndex parent(const QModelIndex& index) const;
int rowCount(const QModelIndex& parent = QModelIndex()) const;
ChatListItem* getItemForIndex(const QModelIndex& index) const;
- void clear();
+ void clearBookmarks();
+ void setRecents(const std::list<ChatListWindow::Chat>& recents);
private:
ChatListGroupItem* mucBookmarks_;
+ ChatListGroupItem* recents_;
ChatListGroupItem* root_;
};
diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.cpp b/Swift/QtUI/ChatList/ChatListRecentItem.cpp
new file mode 100644
index 0000000..6c9807f
--- /dev/null
+++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/ChatList/ChatListRecentItem.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+ChatListRecentItem::ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) {
+
+}
+
+const ChatListWindow::Chat& ChatListRecentItem::getChat() const {
+ return chat_;
+}
+
+QVariant ChatListRecentItem::data(int role) const {
+ switch (role) {
+ case Qt::DisplayRole: return P2QSTRING(chat_.chatName);
+ case DetailTextRole: return P2QSTRING(chat_.activity);
+ /*case Qt::TextColorRole: return textColor_;
+ case Qt::BackgroundColorRole: return backgroundColor_;
+ case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant();
+ case StatusTextRole: return statusText_;*/
+ case AvatarRole: return QVariant(QString(chat_.avatarPath.string().c_str()));
+ case PresenceIconRole: return getPresenceIcon();
+ default: return QVariant();
+ }
+}
+
+QIcon ChatListRecentItem::getPresenceIcon() const {
+ QString iconString;
+ switch (chat_.statusType) {
+ case StatusShow::Online: iconString = "online";break;
+ case StatusShow::Away: iconString = "away";break;
+ case StatusShow::XA: iconString = "away";break;
+ case StatusShow::FFC: iconString = "online";break;
+ case StatusShow::DND: iconString = "dnd";break;
+ case StatusShow::None: iconString = "offline";break;
+ }
+ return QIcon(":/icons/" + iconString + ".png");
+}
+
+}
diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.h b/Swift/QtUI/ChatList/ChatListRecentItem.h
new file mode 100644
index 0000000..4e7bc3e
--- /dev/null
+++ b/Swift/QtUI/ChatList/ChatListRecentItem.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QIcon>
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/MUC/MUCBookmark.h>
+#include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
+
+#include <Swift/QtUI/ChatList/ChatListItem.h>
+
+namespace Swift {
+ class ChatListRecentItem : public ChatListItem {
+ public:
+ enum RecentItemRoles {
+ DetailTextRole = Qt::UserRole,
+ AvatarRole = Qt::UserRole + 1,
+ PresenceIconRole = Qt::UserRole + 2/*,
+ StatusShowTypeRole = Qt::UserRole + 3*/
+ };
+ ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent);
+ const ChatListWindow::Chat& getChat() const;
+ QVariant data(int role) const;
+ private:
+ QIcon getPresenceIcon() const;
+ ChatListWindow::Chat chat_;
+ };
+}
diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp
index b532cdb..e5c63f6 100644
--- a/Swift/QtUI/ChatList/QtChatListWindow.cpp
+++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -10,9 +10,11 @@
#include <QContextMenuEvent>
#include "Swift/QtUI/ChatList/ChatListMUCItem.h"
+#include "Swift/QtUI/ChatList/ChatListRecentItem.h"
#include "Swift/QtUI/QtAddBookmarkWindow.h"
#include "Swift/QtUI/QtEditBookmarkWindow.h"
#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
+#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
#include "Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h"
#include "Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h"
#include "Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h"
@@ -68,19 +70,21 @@ void QtChatListWindow::setupContextMenus() {
}
void QtChatListWindow::handleItemActivated(const QModelIndex& index) {
- if (!bookmarksEnabled_) {
- return;
- }
ChatListItem* item = model_->getItemForIndex(index);
- ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(item);
- if (mucItem) {
- boost::shared_ptr<UIEvent> event(new JoinMUCUIEvent(mucItem->getBookmark().getRoom(), mucItem->getBookmark().getNick()));
- eventStream_->send(event);
+ if (ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(item)) {
+ if (bookmarksEnabled_) {
+ onMUCBookmarkActivated(mucItem->getBookmark());
+ }
+ }
+ else if (ChatListRecentItem* recentItem = dynamic_cast<ChatListRecentItem*>(item)) {
+ if (!recentItem->getChat().isMUC || bookmarksEnabled_) {
+ onRecentActivated(recentItem->getChat());
+ }
}
}
-void QtChatListWindow::clear() {
- model_->clear();
+void QtChatListWindow::clearBookmarks() {
+ model_->clearBookmarks();
}
void QtChatListWindow::addMUCBookmark(const MUCBookmark& bookmark) {
@@ -91,6 +95,14 @@ void QtChatListWindow::removeMUCBookmark(const MUCBookmark& bookmark) {
model_->removeMUCBookmark(bookmark);
}
+void QtChatListWindow::setRecents(const std::list<ChatListWindow::Chat>& recents) {
+ model_->setRecents(recents);
+}
+
+void QtChatListWindow::setUnreadCount(int unread) {
+ emit onCountUpdated(unread);
+}
+
void QtChatListWindow::handleRemoveBookmark() {
ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(contextMenuItem_);
if (!mucItem) return;
@@ -111,9 +123,6 @@ void QtChatListWindow::handleEditBookmark() {
void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) {
- if (!bookmarksEnabled_) {
- return;
- }
QModelIndex index = indexAt(event->pos());
ChatListItem* baseItem = index.isValid() ? static_cast<ChatListItem*>(index.internalPointer()) : NULL;
contextMenuItem_ = baseItem;
@@ -123,8 +132,20 @@ void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) {
}
ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(baseItem);
if (mucItem) {
+ if (!bookmarksEnabled_) {
+ return;
+ }
mucMenu_->exec(QCursor::pos());
}
+ else {
+ QMenu menu;
+ QAction* clearRecents = menu.addAction(tr("Clear recents"));
+ menu.addAction(clearRecents);
+ QAction* result = menu.exec(event->globalPos());
+ if (result == clearRecents) {
+ onClearRecentsRequested();
+ }
+ }
}
}
diff --git a/Swift/QtUI/ChatList/QtChatListWindow.h b/Swift/QtUI/ChatList/QtChatListWindow.h
index 3a3e95f..8775c3e 100644
--- a/Swift/QtUI/ChatList/QtChatListWindow.h
+++ b/Swift/QtUI/ChatList/QtChatListWindow.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -23,7 +23,12 @@ namespace Swift {
void addMUCBookmark(const MUCBookmark& bookmark);
void removeMUCBookmark(const MUCBookmark& bookmark);
void setBookmarksEnabled(bool enabled);
- void clear();
+ void setRecents(const std::list<ChatListWindow::Chat>& recents);
+ void setUnreadCount(int unread);
+ void clearBookmarks();
+
+ signals:
+ void onCountUpdated(int count);
private slots:
void handleItemActivated(const QModelIndex&);
void handleAddBookmark();
diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h
index 3aa5fcc..0d864c1 100644
--- a/Swift/QtUI/ChatSnippet.h
+++ b/Swift/QtUI/ChatSnippet.h
@@ -17,16 +17,16 @@ namespace Swift {
public:
ChatSnippet(bool appendToPrevious);
virtual ~ChatSnippet();
-
+
virtual const QString& getContent() const = 0;
virtual QString getContinuationElementID() const { return ""; }
- boost::shared_ptr<ChatSnippet> getContinuationFallbackSnippet() {return continuationFallback_;}
+ boost::shared_ptr<ChatSnippet> getContinuationFallbackSnippet() const {return continuationFallback_;}
bool getAppendToPrevious() const {
return appendToPrevious_;
}
-
+
static QString escape(const QString& original) {
QString result(original);
result.replace("%message%", "&#37;message&#37;");
@@ -37,11 +37,12 @@ namespace Swift {
return result;
}
+ static QString timeToEscapedString(const QDateTime& time);
+
protected:
void setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet> continuationFallback) {
continuationFallback_ = continuationFallback;
}
- static QString timeToEscapedString(const QDateTime& time);
private:
bool appendToPrevious_;
boost::shared_ptr<ChatSnippet> continuationFallback_;
diff --git a/Swift/QtUI/EventViewer/QtEventWindow.h b/Swift/QtUI/EventViewer/QtEventWindow.h
index 03f009b..a20e5ab 100644
--- a/Swift/QtUI/EventViewer/QtEventWindow.h
+++ b/Swift/QtUI/EventViewer/QtEventWindow.h
@@ -6,7 +6,7 @@
#pragma once
-#include "boost/shared_ptr.hpp"
+#include <boost/shared_ptr.hpp>
#include <QTreeView>
diff --git a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h
index 920aaae..a9eb74d 100644
--- a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h
+++ b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h
@@ -15,7 +15,7 @@ namespace Swift {
MUCSearchRoomItem(const QString& node, MUCSearchServiceItem* parent);
MUCSearchServiceItem* getParent();
QVariant data(int role);
- QString getNode() {return node_;}
+ QString getNode() const {return node_;}
private:
MUCSearchServiceItem* parent_;
QString node_;
diff --git a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h
index 411727d..2a28922 100644
--- a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h
+++ b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h
@@ -24,7 +24,7 @@ namespace Swift {
default: return QVariant();
}
}
- QString getHost() {return jidString_;}
+ QString getHost() const {return jidString_;}
private:
QList<MUCSearchItem*> rooms_;
QString jidString_;
diff --git a/Swift/QtUI/QtAdHocCommandWindow.cpp b/Swift/QtUI/QtAdHocCommandWindow.cpp
new file mode 100644
index 0000000..a3bb077
--- /dev/null
+++ b/Swift/QtUI/QtAdHocCommandWindow.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/QtAdHocCommandWindow.h>
+
+#include <boost/bind.hpp>
+#include <QBoxLayout>
+#include <Swift/QtUI/QtFormWidget.h>
+#include <Swiften/Elements/Command.h>
+
+namespace Swift {
+QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) : command_(command) {
+
+ formWidget_ = NULL;
+
+ setAttribute(Qt::WA_DeleteOnClose);
+ command->onNextStageReceived.connect(boost::bind(&QtAdHocCommandWindow::handleNextStageReceived, this, _1));
+ command->onError.connect(boost::bind(&QtAdHocCommandWindow::handleError, this, _1));
+ command->start();
+
+ QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
+ layout->setContentsMargins(0,0,0,0);
+ layout->setSpacing(2);
+ label_ = new QLabel(this);
+ layout->addWidget(label_);
+ QWidget* formContainer = new QWidget(this);
+ layout->addWidget(formContainer);
+ formLayout_ = new QBoxLayout(QBoxLayout::TopToBottom, formContainer);
+ QWidget* buttonsWidget = new QWidget(this);
+ layout->addWidget(buttonsWidget);
+
+ QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget);
+ cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget);
+ buttonsLayout->addWidget(cancelButton_);
+ connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked()));
+ backButton_ = new QPushButton(tr("Back"), buttonsWidget);
+ buttonsLayout->addWidget(backButton_);
+ connect(backButton_, SIGNAL(clicked()), this, SLOT(handlePrevClicked()));
+ nextButton_ = new QPushButton(tr("Next"), buttonsWidget);
+ buttonsLayout->addWidget(nextButton_);
+ connect(nextButton_, SIGNAL(clicked()), this, SLOT(handleNextClicked()));
+ completeButton_ = new QPushButton(tr("Complete"), buttonsWidget);
+ buttonsLayout->addWidget(completeButton_);
+ connect(completeButton_, SIGNAL(clicked()), this, SLOT(handleCompleteClicked()));
+ nextButton_->setEnabled(false);
+ backButton_->setEnabled(false);
+ completeButton_->setEnabled(false);
+ actions_[Command::Next] = nextButton_;
+ actions_[Command::Prev] = backButton_;
+ actions_[Command::Complete] = completeButton_;
+ actions_[Command::Cancel] = cancelButton_;
+ show();
+}
+
+QtAdHocCommandWindow::~QtAdHocCommandWindow() {
+
+}
+
+void QtAdHocCommandWindow::handleCancelClicked() {
+ command_->cancel();
+}
+
+void QtAdHocCommandWindow::handlePrevClicked() {
+ command_->goBack();
+}
+
+void QtAdHocCommandWindow::handleNextClicked() {
+ command_->goNext(formWidget_ ? formWidget_->getCompletedForm() : Form::ref());
+}
+
+void QtAdHocCommandWindow::handleCompleteClicked() {
+ command_->complete(formWidget_ ? formWidget_->getCompletedForm() : Form::ref());
+}
+
+void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) {
+ if (command->getForm()) {
+ setForm(command->getForm());
+ } else {
+ setNoForm();
+ }
+ QString notes;
+ foreach (Command::Note note, command->getNotes()) {
+ if (!notes.isEmpty()) {
+ notes += "\n";
+ QString qNote(note.note.c_str());
+ switch (note.type) {
+ case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break;
+ case Command::Note::Warn: notes += tr("Warning: %1").arg(qNote); break;
+ case Command::Note::Info: notes += qNote; break;
+ }
+ }
+ }
+ label_->setText(notes);
+ setAvailableActions(command);
+}
+
+void QtAdHocCommandWindow::handleError(ErrorPayload::ref /*error*/) {
+ nextButton_->setEnabled(false);
+ backButton_->setEnabled(false);
+ completeButton_->setEnabled(false);
+ label_->setText(tr("Error executing command"));
+}
+
+void QtAdHocCommandWindow::setForm(Form::ref form) {
+ delete formWidget_;
+ formWidget_ = new QtFormWidget(form, this);
+ formLayout_->addWidget(formWidget_);
+}
+
+void QtAdHocCommandWindow::setNoForm() {
+ delete formWidget_;
+}
+
+typedef std::pair<Command::Action, QPushButton*> ActionButton;
+
+void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) {
+ foreach (ActionButton pair, actions_) {
+ OutgoingAdHocCommandSession::ActionState state = command_->getActionState(pair.first);
+ if (state & OutgoingAdHocCommandSession::Present) {
+ pair.second->show();
+ }
+ else {
+ pair.second->hide();
+ }
+ if (state & OutgoingAdHocCommandSession::Enabled) {
+ pair.second->setEnabled(true);
+ }
+ else {
+ pair.second->setEnabled(false);
+ }
+ }
+}
+
+}
diff --git a/Swift/QtUI/QtAdHocCommandWindow.h b/Swift/QtUI/QtAdHocCommandWindow.h
new file mode 100644
index 0000000..adeb3e6
--- /dev/null
+++ b/Swift/QtUI/QtAdHocCommandWindow.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+#include <QPushButton>
+#include <QLabel>
+
+#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h>
+#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h>
+
+class QBoxLayout;
+
+namespace Swift {
+ class QtFormWidget;
+ class QtAdHocCommandWindow : public QWidget, public AdHocCommandWindow {
+ Q_OBJECT
+ public:
+ QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
+ virtual ~QtAdHocCommandWindow();
+ private:
+ void handleNextStageReceived(Command::ref command);
+ void handleError(ErrorPayload::ref error);
+ void setForm(Form::ref);
+ void setNoForm();
+ void setAvailableActions(Command::ref commandResult);
+ private slots:
+ void handleCancelClicked();
+ void handlePrevClicked();
+ void handleNextClicked();
+ void handleCompleteClicked();
+ private:
+ boost::shared_ptr<OutgoingAdHocCommandSession> command_;
+ QtFormWidget* formWidget_;
+ QBoxLayout* formLayout_;
+ Form::ref form_;
+ QLabel* label_;
+ QPushButton* backButton_;
+ QPushButton* nextButton_;
+ QPushButton* completeButton_;
+ QPushButton* cancelButton_;
+ std::map<Command::Action, QPushButton*> actions_;
+ };
+}
diff --git a/Swift/QtUI/QtAvatarWidget.cpp b/Swift/QtUI/QtAvatarWidget.cpp
index 3e3aa69..1efb787 100644
--- a/Swift/QtUI/QtAvatarWidget.cpp
+++ b/Swift/QtUI/QtAvatarWidget.cpp
@@ -47,8 +47,8 @@ void QtAvatarWidget::setAvatar(const ByteArray& data, const std::string& type) {
this->type = type;
QImage image;
- if (!data.isEmpty()) {
- image.loadFromData(reinterpret_cast<const uchar*>(data.getData()), data.getSize());
+ if (!data.empty()) {
+ image.loadFromData(reinterpret_cast<const uchar*>(vecptr(data)), data.size());
}
if (image.isNull()) {
@@ -81,10 +81,10 @@ void QtAvatarWidget::mousePressEvent(QMouseEvent* event) {
QString fileName = QFileDialog::getOpenFileName(this, tr("Select picture"), "", tr("Image Files (*.png *.jpg *.gif)"));
if (!fileName.isEmpty()) {
ByteArray data;
- data.readFromFile(Q2PSTRING(fileName));
+ readByteArrayFromFile(data, Q2PSTRING(fileName));
QBuffer buffer;
- buffer.setData(reinterpret_cast<const char*>(data.getData()), data.getSize());
+ buffer.setData(reinterpret_cast<const char*>(vecptr(data)), data.size());
buffer.open(QIODevice::ReadOnly);
QString type = QImageReader::imageFormat(&buffer).toLower();
if (!type.isEmpty()) {
diff --git a/Swift/QtUI/QtBookmarkDetailWindow.ui b/Swift/QtUI/QtBookmarkDetailWindow.ui
index a77ac76..4a37b2f 100644
--- a/Swift/QtUI/QtBookmarkDetailWindow.ui
+++ b/Swift/QtUI/QtBookmarkDetailWindow.ui
@@ -90,7 +90,7 @@
<item row="4" column="1">
<widget class="QCheckBox" name="autojoin_">
<property name="text">
- <string>Join automatically</string>
+ <string>Enter automatically</string>
</property>
<property name="checked">
<bool>true</bool>
diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp
index 25c7ca2..249080b 100644
--- a/Swift/QtUI/QtChatTabs.cpp
+++ b/Swift/QtUI/QtChatTabs.cpp
@@ -7,6 +7,10 @@
#include "QtChatTabs.h"
#include <algorithm>
+#include <vector>
+
+#include <Swift/Controllers/ChatMessageSummarizer.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
#include <QCloseEvent>
#include <QDesktopWidget>
@@ -236,16 +240,18 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
default : tabTextColor = QColor();
}
tabs_->tabBar()->setTabTextColor(index, tabTextColor);
- int unread = 0;
+
+ std::vector<std::pair<std::string, int> > unreads;
for (int i = 0; i < tabs_->count(); i++) {
QtTabbable* tab = qobject_cast<QtTabbable*>(tabs_->widget(i));
if (tab) {
- unread += tab->getCount();
+ unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount()));
}
}
- QtTabbable* current = qobject_cast<QtTabbable*>(tabs_->currentWidget());
- setWindowTitle(unread > 0 ? QString("(%1) %2").arg(unread).arg(current->windowTitle()) : current->windowTitle());
+ std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(tabs_->currentWidget())->windowTitle()));
+ ChatMessageSummarizer summary;
+ setWindowTitle(summary.getSummary(current, unreads).c_str());
}
void QtChatTabs::flash() {
diff --git a/Swift/QtUI/QtChatTheme.cpp b/Swift/QtUI/QtChatTheme.cpp
index afcf665..1d7a970 100644
--- a/Swift/QtUI/QtChatTheme.cpp
+++ b/Swift/QtUI/QtChatTheme.cpp
@@ -60,7 +60,7 @@ QtChatTheme::QtChatTheme(const QString& themePath) : qrc_(themePath.isEmpty()),
}
-QString QtChatTheme::getBase() {
+QString QtChatTheme::getBase() const {
return qrc_ ? "qrc" + themePath_ : "file://" + themePath_;
}
diff --git a/Swift/QtUI/QtChatTheme.h b/Swift/QtUI/QtChatTheme.h
index 199c66d..c6b02a0 100644
--- a/Swift/QtUI/QtChatTheme.h
+++ b/Swift/QtUI/QtChatTheme.h
@@ -13,23 +13,23 @@ namespace Swift {
class QtChatTheme {
public:
QtChatTheme(const QString& themePath);
- QString getHeader() {return fileContents_[Header];};
- QString getFooter() {return fileContents_[Footer];};
- QString getContent() {return fileContents_[Content];};
- QString getStatus() {return fileContents_[Status];};
- QString getTopic() {return fileContents_[Topic];};
- QString getFileTransferRequest() {return fileContents_[FileTransferRequest];};
- QString getIncomingContent() {return fileContents_[IncomingContent];};
- QString getIncomingNextContent() {return fileContents_[IncomingNextContent];};
- QString getIncomingContext() {return fileContents_[IncomingContext];};
- QString getIncomingNextContext() {return fileContents_[IncomingNextContext];};
- QString getOutgoingContent() {return fileContents_[OutgoingContent];};
- QString getOutgoingNextContent() {return fileContents_[OutgoingNextContent];};
- QString getOutgoingContext() {return fileContents_[OutgoingContext];};
- QString getOutgoingNextContext() {return fileContents_[OutgoingNextContext];};
- QString getTemplate() {return fileContents_[Template];}
- QString getMainCSS() {return fileContents_[MainCSS];}
- QString getBase();
+ QString getHeader() const {return fileContents_[Header];};
+ QString getFooter() const {return fileContents_[Footer];};
+ QString getContent() const {return fileContents_[Content];};
+ QString getStatus() const {return fileContents_[Status];};
+ QString getTopic() const {return fileContents_[Topic];};
+ QString getFileTransferRequest() const {return fileContents_[FileTransferRequest];};
+ QString getIncomingContent() const {return fileContents_[IncomingContent];};
+ QString getIncomingNextContent() const {return fileContents_[IncomingNextContent];};
+ QString getIncomingContext() const {return fileContents_[IncomingContext];};
+ QString getIncomingNextContext() const {return fileContents_[IncomingNextContext];};
+ QString getOutgoingContent() const {return fileContents_[OutgoingContent];};
+ QString getOutgoingNextContent() const {return fileContents_[OutgoingNextContent];};
+ QString getOutgoingContext() const {return fileContents_[OutgoingContext];};
+ QString getOutgoingNextContext() const {return fileContents_[OutgoingNextContext];};
+ QString getTemplate() const {return fileContents_[Template];}
+ QString getMainCSS() const {return fileContents_[MainCSS];}
+ QString getBase() const;
private:
enum files {Header = 0, Footer, Content, Status, Topic, FileTransferRequest, IncomingContent, IncomingNextContent, IncomingContext, IncomingNextContext, OutgoingContent, OutgoingNextContent, OutgoingContext, OutgoingNextContext, Template, MainCSS, TemplateDefault, EndMarker};
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 521b072..7bb5818 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -18,13 +18,16 @@
#include <QMessageBox>
#include <QApplication>
+#include <Swiften/Base/Log.h>
+
#include "QtWebView.h"
#include "QtChatTheme.h"
+#include "QtSwiftUtil.h"
namespace Swift {
-QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent) {
+QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), fontSizeSteps_(0) {
theme_ = theme;
QVBoxLayout* mainLayout = new QVBoxLayout(this);
@@ -35,6 +38,8 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent) {
connect(webView_, SIGNAL(loadFinished(bool)), SLOT(handleViewLoadFinished(bool)));
connect(webView_, SIGNAL(gotFocus()), SIGNAL(gotFocus()));
connect(webView_, SIGNAL(clearRequested()), SLOT(handleClearRequested()));
+ connect(webView_, SIGNAL(fontGrowRequested()), SLOT(increaseFontSize()));
+ connect(webView_, SIGNAL(fontShrinkRequested()), SLOT(decreaseFontSize()));
#ifdef Q_WS_X11
/* To give a border on Linux, where it looks bad without */
QStackedWidget* stack = new QStackedWidget(this);
@@ -46,8 +51,15 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent) {
mainLayout->addWidget(webView_);
#endif
+#ifdef SWIFT_EXPERIMENTAL_FT
+ setAcceptDrops(true);
+#endif
+
webPage_ = new QWebPage(this);
webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
+#ifdef SWIFT_EXPERIMENTAL_FT
+ webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
+#endif
webView_->setPage(webPage_);
connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
@@ -77,13 +89,14 @@ void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) {
if (viewReady_) {
addToDOM(snippet);
} else {
- queuedSnippets_.append(snippet);
+ /* If this asserts, the previous queuing code was necessary and should be reinstated */
+ assert(false);
}
}
QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
QWebElement newElement = newInsertPoint_.clone();
- newElement.setInnerXml(snippet->getContent()); /* FIXME: Outer, surely? */
+ newElement.setInnerXml(snippet->getContent());
Q_ASSERT(!newElement.isNull());
return newElement;
}
@@ -103,12 +116,31 @@ void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
newInsertPoint_.prependOutside(newElement);
}
lastElement_ = newElement;
- //qApp->processEvents();
+ if (fontSizeSteps_ != 0) {
+ double size = 1.0 + 0.2 * fontSizeSteps_;
+ QString sizeString(QString().setNum(size, 'g', 3) + "em");
+ const QWebElementCollection spans = lastElement_.findAll("span");
+ foreach (QWebElement span, spans) {
+ span.setStyleProperty("font-size", sizeString);
+ }
+ }
+}
+
+void QtChatView::addLastSeenLine() {
+ if (lineSeparator_.isNull()) {
+ lineSeparator_ = newInsertPoint_.clone();
+ lineSeparator_.setInnerXml(QString("<hr/>"));
+ newInsertPoint_.prependOutside(lineSeparator_);
+ }
+ else {
+ QWebElement lineSeparatorC = lineSeparator_.clone();
+ lineSeparatorC.removeFromDocument();
+ }
+ newInsertPoint_.prependOutside(lineSeparator_);
}
void QtChatView::replaceLastMessage(const QString& newMessage) {
assert(viewReady_);
- /* FIXME: must be queued? */
rememberScrolledToBottom();
assert(!lastElement_.isNull());
QWebElement replace = lastElement_.findFirst("span.swift_message");
@@ -125,6 +157,29 @@ void QtChatView::replaceLastMessage(const QString& newMessage, const QString& no
replace.setInnerXml(ChatSnippet::escape(note));
}
+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);
+ if (!message.isNull()) {
+ QWebElement replaceContent = message.findFirst("span.swift_message");
+ assert(!replaceContent.isNull());
+ QString old = replaceContent.toOuterXml();
+ replaceContent.setInnerXml(ChatSnippet::escape(newMessage));
+ QWebElement replaceTime = message.findFirst("span.swift_time");
+ assert(!replaceTime.isNull());
+ old = replaceTime.toOuterXml();
+ replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime))));
+ }
+}
+
void QtChatView::copySelectionToClipboard() {
if (!webPage_->selectedText().isEmpty()) {
webPage_->triggerAction(QWebPage::Copy);
@@ -160,17 +215,35 @@ void QtChatView::handleLinkClicked(const QUrl& url) {
QDesktopServices::openUrl(url);
}
-void QtChatView::addQueuedSnippets() {
- for (int i = 0; i < queuedSnippets_.count(); i++) {
- addToDOM(queuedSnippets_[i]);
- }
- queuedSnippets_.clear();
-}
-
void QtChatView::handleViewLoadFinished(bool ok) {
Q_ASSERT(ok);
viewReady_ = true;
- addQueuedSnippets();
+}
+
+void QtChatView::increaseFontSize(int numSteps) {
+ //qDebug() << "Increasing";
+ fontSizeSteps_ += numSteps;
+ emit fontResized(fontSizeSteps_);
+}
+
+void QtChatView::decreaseFontSize() {
+ fontSizeSteps_--;
+ if (fontSizeSteps_ < 0) {
+ fontSizeSteps_ = 0;
+ }
+ emit fontResized(fontSizeSteps_);
+}
+
+void QtChatView::resizeFont(int fontSizeSteps) {
+ fontSizeSteps_ = fontSizeSteps;
+ double size = 1.0 + 0.2 * fontSizeSteps_;
+ QString sizeString(QString().setNum(size, 'g', 3) + "em");
+ //qDebug() << "Setting to " << sizeString;
+ const QWebElementCollection spans = document_.findAll("span");
+ foreach (QWebElement span, spans) {
+ span.setStyleProperty("font-size", sizeString);
+ }
+ webView_->setFontSizeIsMinimal(size == 1.0);
}
void QtChatView::resetView() {
@@ -204,4 +277,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 = " << Q2PSTRING(id) << 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 58b33df..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;
@@ -26,15 +28,21 @@ namespace Swift {
Q_OBJECT
public:
QtChatView(QtChatTheme* theme, QWidget* parent);
-
void addMessage(boost::shared_ptr<ChatSnippet> snippet);
+ void addLastSeenLine();
void replaceLastMessage(const QString& newMessage);
void replaceLastMessage(const QString& newMessage, const QString& note);
+ void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time);
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();
+ void fontResized(int);
public slots:
void copySelectionToClipboard();
@@ -42,6 +50,9 @@ namespace Swift {
void handleLinkClicked(const QUrl&);
void handleKeyPressEvent(QKeyEvent* event);
void resetView();
+ void increaseFontSize(int numSteps = 1);
+ void decreaseFontSize();
+ void resizeFont(int fontSizeSteps);
private slots:
void handleViewLoadFinished(bool);
@@ -51,7 +62,6 @@ namespace Swift {
private:
void headerEncode();
void messageEncode();
- void addQueuedSnippets();
void addToDOM(boost::shared_ptr<ChatSnippet> snippet);
QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet);
@@ -59,10 +69,10 @@ namespace Swift {
bool isAtBottom_;
QtWebView* webView_;
QWebPage* webPage_;
- QList<boost::shared_ptr<ChatSnippet> > queuedSnippets_;
-
+ int fontSizeSteps_;
QtChatTheme* theme_;
QWebElement newInsertPoint_;
+ QWebElement lineSeparator_;
QWebElement lastElement_;
QWebElement document_;
};
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 1a909fd..07ff47e 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -7,16 +7,28 @@
#include "QtChatWindow.h"
#include "QtSwiftUtil.h"
#include "Swift/Controllers/Roster/Roster.h"
-#include "Roster/QtTreeWidget.h"
+#include "Swift/Controllers/Roster/RosterItem.h"
+#include "Swift/Controllers/Roster/ContactRosterItem.h"
+#include "Roster/QtOccupantListWidget.h"
#include "SwifTools/Linkify.h"
#include "QtChatView.h"
#include "MessageSnippet.h"
#include "SystemMessageSnippet.h"
#include "QtTextEdit.h"
+#include "QtSettingsProvider.h"
#include "QtScaledAvatarCache.h"
#include "SwifTools/TabComplete.h"
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
+#include "QtFileTransferJSBridge.h"
+#include <boost/cstdint.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <QLabel>
+#include <QInputDialog>
#include <QApplication>
#include <QBoxLayout>
#include <QCloseEvent>
@@ -28,32 +40,74 @@
#include <QTextEdit>
#include <QTime>
#include <QUrl>
+#include <QPushButton>
+#include <QFileDialog>
+#include <QMenu>
+
+#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;
theme_ = theme;
+ isCorrection_ = false;
+ correctionEnabled_ = Maybe;
updateTitleWithUnreadCount();
+ QtSettingsProvider settings;
+
+#ifdef SWIFT_EXPERIMENTAL_FT
+ setAcceptDrops(true);
+#endif
+
+ alertStyleSheet_ = "background: rgb(255, 255, 153); color: black";
QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(2);
-
-
- QSplitter *logRosterSplitter = new QSplitter(this);
- logRosterSplitter->setAutoFillBackground(true);
- layout->addWidget(logRosterSplitter);
+ alertWidget_ = new QWidget(this);
+ QHBoxLayout* alertLayout = new QHBoxLayout(alertWidget_);
+ layout->addWidget(alertWidget_);
+ alertLabel_ = new QLabel(this);
+ alertLayout->addWidget(alertLabel_);
+ alertButton_ = new QPushButton(this);
+ connect (alertButton_, SIGNAL(clicked()), this, SLOT(handleAlertButtonClicked()));
+ alertLayout->addWidget(alertButton_);
+ QPalette palette = alertWidget_->palette();
+ palette.setColor(QPalette::Window, QColor(Qt::yellow));
+ palette.setColor(QPalette::WindowText, QColor(Qt::black));
+ alertWidget_->setStyleSheet(alertStyleSheet_);
+ alertLabel_->setStyleSheet(alertStyleSheet_);
+ alertWidget_->hide();
+
+ QBoxLayout* subjectLayout = new QBoxLayout(QBoxLayout::LeftToRight);
+ subject_ = new QLineEdit(this);
+ subjectLayout->addWidget(subject_);
+ setSubject("");
+ subject_->setReadOnly(true);
+
+ actionButton_ = new QPushButton(tr("Actions"), this);
+ connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked()));
+ subjectLayout->addWidget(actionButton_);
+
+ subject_->hide();
+ actionButton_->hide();
+
+ layout->addLayout(subjectLayout);
+
+ logRosterSplitter_ = new QSplitter(this);
+ logRosterSplitter_->setAutoFillBackground(true);
+ layout->addWidget(logRosterSplitter_);
messageLog_ = new QtChatView(theme, this);
- logRosterSplitter->addWidget(messageLog_);
+ logRosterSplitter_->addWidget(messageLog_);
- treeWidget_ = new QtTreeWidget(eventStream_);
- treeWidget_->setEditable(false);
+ treeWidget_ = new QtOccupantListWidget(eventStream_, this);
treeWidget_->hide();
- logRosterSplitter->addWidget(treeWidget_);
- logRosterSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ logRosterSplitter_->addWidget(treeWidget_);
+ logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ connect(logRosterSplitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int)));
QWidget* midBar = new QWidget(this);
layout->addWidget(midBar);
@@ -69,10 +123,17 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
labelsWidget_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
midBarLayout->addWidget(labelsWidget_,0);
+ QHBoxLayout* inputBarLayout = new QHBoxLayout();
+ inputBarLayout->setContentsMargins(0,0,0,0);
+ inputBarLayout->setSpacing(2);
input_ = new QtTextEdit(this);
input_->setAcceptRichText(false);
- layout->addWidget(input_);
-
+ inputBarLayout->addWidget(input_);
+ correctingLabel_ = new QLabel(tr("Correcting"), this);
+ inputBarLayout->addWidget(correctingLabel_);
+ correctingLabel_->hide();
+ layout->addLayout(inputBarLayout);
+
inputClearing_ = false;
contactIsTyping_ = false;
@@ -80,16 +141,58 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
connect(input_, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
connect(input_, SIGNAL(textChanged()), this, SLOT(handleInputChanged()));
setFocusProxy(input_);
- logRosterSplitter->setFocusProxy(input_);
+ logRosterSplitter_->setFocusProxy(input_);
midBar->setFocusProxy(input_);
messageLog_->setFocusProxy(input_);
connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(qAppFocusChanged(QWidget*, QWidget*)));
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;
+ if (mucConfigurationWindow) {
+ delete mucConfigurationWindow.data();
+ }
+}
+
+void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) {
+ onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item));
+}
+
+void QtChatWindow::handleFontResized(int fontSizeSteps) {
+ messageLog_->resizeFont(fontSizeSteps);
+}
+
+void QtChatWindow::handleAlertButtonClicked() {
+ onAlertButtonClicked();
+}
+
+void QtChatWindow::setAlert(const std::string& alertText, const std::string& buttonText) {
+ alertLabel_->setText(alertText.c_str());
+ if (buttonText.empty()) {
+ alertButton_->hide();
+ } else {
+ alertButton_->setText(buttonText.c_str());
+ alertButton_->show();
+ }
+ alertWidget_->show();
+}
+
+void QtChatWindow::cancelAlert() {
+ alertWidget_->hide();
}
void QtChatWindow::setTabComplete(TabComplete* completer) {
@@ -118,11 +221,55 @@ void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) {
emit requestActiveTab();
} else if (key == Qt::Key_Tab) {
tabComplete();
+ } else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) {
+ beginCorrection();
+ } else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) {
+ cancelCorrection();
+ } else if (key == Qt::Key_Down || key == Qt::Key_Up) {
+ /* Drop */
} else {
messageLog_->handleKeyPressEvent(event);
}
}
+void QtChatWindow::beginCorrection() {
+ if (correctionEnabled_ == ChatWindow::Maybe) {
+ setAlert(Q2PSTRING(tr("This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message")));
+ } else if (correctionEnabled_ == ChatWindow::No) {
+ setAlert(Q2PSTRING(tr("This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message")));
+ }
+ QTextCursor cursor = input_->textCursor();
+ cursor.select(QTextCursor::Document);
+ cursor.beginEditBlock();
+ cursor.insertText(QString(lastSentMessage_));
+ cursor.endEditBlock();
+ isCorrection_ = true;
+ correctingLabel_->show();
+ input_->setStyleSheet(alertStyleSheet_);
+}
+
+void QtChatWindow::cancelCorrection() {
+ cancelAlert();
+ QTextCursor cursor = input_->textCursor();
+ cursor.select(QTextCursor::Document);
+ cursor.removeSelectedText();
+ isCorrection_ = false;
+ correctingLabel_->hide();
+ input_->setStyleSheet(qApp->styleSheet());
+}
+
+QByteArray QtChatWindow::getSplitterState() {
+ return logRosterSplitter_->saveState();
+}
+
+void QtChatWindow::handleChangeSplitterState(QByteArray state) {
+ logRosterSplitter_->restoreState(state);
+}
+
+void QtChatWindow::handleSplitterMoved(int, int) {
+ emit splitterMoved();
+}
+
void QtChatWindow::tabComplete() {
if (!completer_) {
return;
@@ -150,7 +297,7 @@ void QtChatWindow::tabComplete() {
}
void QtChatWindow::setRosterModel(Roster* roster) {
- treeWidget_->setRosterModel(roster);
+ treeWidget_->setRosterModel(roster);
}
void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {
@@ -185,6 +332,10 @@ void QtChatWindow::setSecurityLabelsEnabled(bool enabled) {
}
}
+void QtChatWindow::setCorrectionEnabled(Tristate enabled) {
+ correctionEnabled_ = enabled;
+}
+
SecurityLabelsCatalog::Item QtChatWindow::getSelectedSecurityLabel() {
assert(labelsWidget_->isEnabled());
return availableLabels_[labelsWidget_->currentIndex()];
@@ -197,22 +348,28 @@ void QtChatWindow::closeEvent(QCloseEvent* event) {
}
void QtChatWindow::convertToMUC() {
+ setAcceptDrops(false);
treeWidget_->show();
+ subject_->show();
+ actionButton_->show();
}
-void QtChatWindow::qAppFocusChanged(QWidget *old, QWidget *now) {
- Q_UNUSED(old);
- Q_UNUSED(now);
+void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) {
if (isWidgetSelected()) {
+ lastLineTracker_.setHasFocus(true);
input_->setFocus();
onAllMessagesRead();
}
-
+ else {
+ lastLineTracker_.setHasFocus(false);
+ }
}
void QtChatWindow::setInputEnabled(bool enabled) {
inputEnabled_ = enabled;
-// input_->setEnabled(enabled);
+ if (!enabled && mucConfigurationWindow) {
+ delete mucConfigurationWindow.data();
+ }
}
void QtChatWindow::showEvent(QShowEvent* event) {
@@ -236,7 +393,7 @@ void QtChatWindow::setContactChatState(ChatState::ChatStateType state) {
QtTabbable::AlertType QtChatWindow::getWidgetAlertState() {
if (contactIsTyping_) {
return ImpendingActivity;
- }
+ }
if (unreadCount_ > 0) {
return WaitingActivity;
}
@@ -265,7 +422,6 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri
if (isWidgetSelected()) {
onAllMessagesRead();
}
-
QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
QString htmlString;
@@ -280,7 +436,13 @@ 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();
+ /* if the line is added we should break the snippet */
+ appendToPrevious = false;
+ }
QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
std::string id = id_.generateID();
messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id))));
@@ -289,6 +451,7 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri
previousSenderName_ = P2QSTRING(senderName);
previousMessageWasSystem_ = false;
previousMessageWasPresence_ = false;
+ previousMessageWasFileTransfer_ = false;
return id;
}
@@ -314,6 +477,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 boost::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(Q2PSTRING(id));
+}
+
+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(Q2PSTRING(id), Q2PSTRING(text));
+}
+
+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(Q2PSTRING(id), Q2PSTRING(path));
+ }
+}
+
void QtChatWindow::addErrorMessage(const std::string& errorMessage) {
if (isWidgetSelected()) {
onAllMessagesRead();
@@ -321,11 +572,12 @@ void QtChatWindow::addErrorMessage(const std::string& errorMessage) {
QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage)));
errorMessageHTML.replace("\n","<br/>");
- messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(QString("<span class=\"error\">" + tr("Couldn't send message: %1") + "</span>").arg(errorMessageHTML), QDateTime::currentDateTime(), false, theme_)));
+ messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_)));
previousMessageWasSelf_ = false;
previousMessageWasSystem_ = true;
previousMessageWasPresence_ = false;
+ previousMessageWasFileTransfer_ = false;
}
void QtChatWindow::addSystemMessage(const std::string& message) {
@@ -341,6 +593,16 @@ 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) {
+ if (!id.empty()) {
+ QString messageHTML(Qt::escape(P2QSTRING(message)));
+ messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML)));
+ messageHTML.replace("\n","<br/>");
+ messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
+ }
}
void QtChatWindow::addPresenceMessage(const std::string& message) {
@@ -364,9 +626,11 @@ void QtChatWindow::returnPressed() {
return;
}
messageLog_->scrollToBottom();
- onSendMessageRequest(Q2PSTRING(input_->toPlainText()));
+ lastSentMessage_ = QString(input_->toPlainText());
+ onSendMessageRequest(Q2PSTRING(input_->toPlainText()), isCorrection_);
inputClearing_ = true;
input_->clear();
+ cancelCorrection();
inputClearing_ = false;
}
@@ -382,7 +646,9 @@ void QtChatWindow::handleInputChanged() {
}
void QtChatWindow::show() {
- QWidget::show();
+ if (parentWidget() == NULL) {
+ QWidget::show();
+ }
emit windowOpening();
}
@@ -399,11 +665,64 @@ void QtChatWindow::resizeEvent(QResizeEvent*) {
}
void QtChatWindow::moveEvent(QMoveEvent*) {
- emit geometryChanged();
+ 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(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile()));
+ } 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)));
}
+void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {
+ treeWidget_->setAvailableOccupantActions(actions);
+}
+
+void QtChatWindow::setSubject(const std::string& subject) {
+ //subject_->setVisible(!subject.empty());
+ subject_->setText(P2QSTRING(subject));
+}
+
+void QtChatWindow::handleActionButtonClicked() {
+ QMenu contextMenu;
+ QAction* changeSubject = contextMenu.addAction(tr("Change subject"));
+ QAction* configure = contextMenu.addAction(tr("Configure room"));
+ QAction* destroy = contextMenu.addAction(tr("Destroy room"));
+ QAction* result = contextMenu.exec(QCursor::pos());
+ if (result == changeSubject) {
+ bool ok;
+ QString subject = QInputDialog::getText(this, tr("Change room subject"), tr("New subject:"), QLineEdit::Normal, subject_->text(), &ok);
+ if (ok) {
+ onChangeSubjectRequest(Q2PSTRING(subject));
+ }
+ }
+ else if (result == configure) {
+ onConfigureRequest(Form::ref());
+ }
+ else if (result == destroy) {
+ onDestroyRequest();
+ }
+}
+
+void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
+ if (mucConfigurationWindow) {
+ delete mucConfigurationWindow.data();
+ }
+ mucConfigurationWindow = new QtMUCConfigurationWindow(form);
+ mucConfigurationWindow->onFormComplete.connect(boost::bind(boost::ref(onConfigureRequest), _1));
+}
+
}
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 910019b..b011427 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -1,30 +1,38 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFT_QtChatWindow_H
-#define SWIFT_QtChatWindow_H
+#pragma once
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/QtUI/QtMUCConfigurationWindow.h>
-#include "QtTabbable.h"
+#include <QtTabbable.h>
-#include "Swiften/Base/IDGenerator.h"
+#include <SwifTools/LastLineTracker.h>
+
+#include <Swiften/Base/IDGenerator.h>
+
+#include <map>
+#include <QPointer>
class QTextEdit;
class QLineEdit;
class QComboBox;
+class QLabel;
+class QSplitter;
+class QPushButton;
namespace Swift {
class QtChatView;
- class QtTreeWidget;
- class QtTreeWidgetFactory;
+ class QtOccupantListWidget;
class QtChatTheme;
class TreeWidget;
class QtTextEdit;
class UIEventStream;
+ class QtFileTransferJSBridge;
class QtChatWindow : public QtTabbable, public ChatWindow {
Q_OBJECT
public:
@@ -35,6 +43,12 @@ namespace Swift {
void addSystemMessage(const std::string& message);
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);
@@ -54,9 +68,22 @@ namespace Swift {
void replaceLastMessage(const std::string& message);
void setAckState(const std::string& id, AckState state);
void flash();
+ QByteArray getSplitterState();
+ virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions);
+ void setSubject(const std::string& subject);
+ void showRoomConfigurationForm(Form::ref);
+
+ public slots:
+ void handleChangeSplitterState(QByteArray state);
+ void handleFontResized(int fontSizeSteps);
+ void setAlert(const std::string& alertText, const std::string& buttonText = "");
+ void cancelAlert();
+ void setCorrectionEnabled(Tristate enabled);
signals:
void geometryChanged();
+ void splitterMoved();
+ void fontResized(int);
protected slots:
void qAppFocusChanged(QWidget* old, QWidget* now);
@@ -64,6 +91,9 @@ namespace Swift {
void resizeEvent(QResizeEvent* event);
void moveEvent(QMoveEvent* event);
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dropEvent(QDropEvent *event);
+
protected:
void showEvent(QShowEvent* event);
@@ -71,31 +101,57 @@ namespace Swift {
void returnPressed();
void handleInputChanged();
void handleKeyPressEvent(QKeyEvent* event);
+ void handleSplitterMoved(int pos, int index);
+ void handleAlertButtonClicked();
+ void handleActionButtonClicked();
+
+
+ void handleFileTransferCancel(QString id);
+ void handleFileTransferSetDescription(QString id);
+ void handleFileTransferStart(QString id);
+ void handleFileTransferAccept(QString id, QString filename);
private:
void updateTitleWithUnreadCount();
void tabComplete();
+ void beginCorrection();
+ void cancelCorrection();
std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time);
+ void handleOccupantSelectionChanged(RosterItem* item);
int unreadCount_;
bool contactIsTyping_;
+ LastLineTracker lastLineTracker_;
QString contact_;
+ QString lastSentMessage_;
QtChatView* messageLog_;
QtChatTheme* theme_;
QtTextEdit* input_;
QComboBox* labelsWidget_;
- QtTreeWidget* treeWidget_;
+ QtOccupantListWidget* treeWidget_;
+ QLabel* correctingLabel_;
+ QLabel* alertLabel_;
+ QWidget* alertWidget_;
+ QPushButton* alertButton_;
TabComplete* completer_;
+ QLineEdit* subject_;
+ QPushButton* actionButton_;
std::vector<SecurityLabelsCatalog::Item> availableLabels_;
+ bool isCorrection_;
bool previousMessageWasSelf_;
bool previousMessageWasSystem_;
bool previousMessageWasPresence_;
+ bool previousMessageWasFileTransfer_;
QString previousSenderName_;
bool inputClearing_;
UIEventStream* eventStream_;
bool inputEnabled_;
IDGenerator id_;
+ QSplitter *logRosterSplitter_;
+ Tristate correctionEnabled_;
+ QString alertStyleSheet_;
+ std::map<QString, QString> descriptions;
+ QtFileTransferJSBridge* fileTransferJS;
+ QPointer<QtMUCConfigurationWindow> mucConfigurationWindow;
};
}
-
-#endif
diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp
index d146474..4943c0e 100644
--- a/Swift/QtUI/QtChatWindowFactory.cpp
+++ b/Swift/QtUI/QtChatWindowFactory.cpp
@@ -16,6 +16,10 @@
namespace Swift {
+
+static const QString SPLITTER_STATE = "mucSplitterState";
+static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry";
+
QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) {
settings_ = settings;
tabs_ = tabs;
@@ -23,7 +27,7 @@ QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider
if (splitter) {
splitter->addWidget(tabs_);
} else if (tabs_) {
- QVariant chatTabsGeometryVariant = settings_->getQSettings()->value("chatTabsGeometry");
+ QVariant chatTabsGeometryVariant = settings_->getQSettings()->value(CHAT_TABS_GEOMETRY);
if (chatTabsGeometryVariant.isValid()) {
tabs_->restoreGeometry(chatTabsGeometryVariant.toByteArray());
}
@@ -43,11 +47,20 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre
theme_ = new QtChatTheme(""); /* Use the inbuilt theme */
}
}
+
QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream);
+ connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved()));
+ connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray)));
+
+ QVariant splitterState = settings_->getQSettings()->value(SPLITTER_STATE);
+ if(splitterState.isValid()) {
+ chatWindow->handleChangeSplitterState(splitterState.toByteArray());
+ }
+
if (tabs_) {
tabs_->addTab(chatWindow);
} else {
- QVariant chatGeometryVariant = settings_->getQSettings()->value("chatTabsGeometry");
+ QVariant chatGeometryVariant = settings_->getQSettings()->value(CHAT_TABS_GEOMETRY);
if (chatGeometryVariant.isValid()) {
chatWindow->restoreGeometry(chatGeometryVariant.toByteArray());
}
@@ -57,7 +70,13 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre
}
void QtChatWindowFactory::handleWindowGeometryChanged() {
- settings_->getQSettings()->setValue("chatTabsGeometry", qobject_cast<QWidget*>(sender())->saveGeometry());
+ settings_->getQSettings()->setValue(CHAT_TABS_GEOMETRY, qobject_cast<QWidget*>(sender())->saveGeometry());
+}
+
+void QtChatWindowFactory::handleSplitterMoved() {
+ QByteArray splitterState = qobject_cast<QtChatWindow*>(sender())->getSplitterState();
+ settings_->getQSettings()->setValue(SPLITTER_STATE, QVariant(splitterState));
+ emit changeSplitterState(splitterState);
}
}
diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h
index 0d47854..f3e8956 100644
--- a/Swift/QtUI/QtChatWindowFactory.h
+++ b/Swift/QtUI/QtChatWindowFactory.h
@@ -22,8 +22,11 @@ namespace Swift {
QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs, const QString& themePath);
~QtChatWindowFactory();
ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);
+ signals:
+ void changeSplitterState(QByteArray);
private slots:
void handleWindowGeometryChanged();
+ void handleSplitterMoved();
private:
QString themePath_;
QtSettingsProvider* settings_;
diff --git a/Swift/QtUI/QtContactEditWindow.cpp b/Swift/QtUI/QtContactEditWindow.cpp
index 4be2939..9b24b23 100644
--- a/Swift/QtUI/QtContactEditWindow.cpp
+++ b/Swift/QtUI/QtContactEditWindow.cpp
@@ -42,6 +42,7 @@ QtContactEditWindow::QtContactEditWindow() : contactEditWidget_(NULL) {
connect(removeButton, SIGNAL(clicked()), this, SLOT(handleRemoveContact()));
buttonLayout->addWidget(removeButton);
QPushButton* okButton = new QPushButton(tr("OK"), this);
+ okButton->setDefault( true );
connect(okButton, SIGNAL(clicked()), this, SLOT(handleUpdateContact()));
buttonLayout->addStretch();
buttonLayout->addWidget(okButton);
diff --git a/Swift/QtUI/QtDBUSURIHandler.cpp b/Swift/QtUI/QtDBUSURIHandler.cpp
new file mode 100644
index 0000000..9b69ca6
--- /dev/null
+++ b/Swift/QtUI/QtDBUSURIHandler.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtDBUSURIHandler.h"
+
+#include <QDBusAbstractAdaptor>
+#include <QDBusConnection>
+
+#include "QtSwiftUtil.h"
+
+using namespace Swift;
+
+namespace {
+ class DBUSAdaptor: public QDBusAbstractAdaptor {
+ Q_OBJECT
+ Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler");
+ public:
+ DBUSAdaptor(QtDBUSURIHandler* uriHandler) : QDBusAbstractAdaptor(uriHandler), uriHandler(uriHandler) {
+ }
+
+ public slots:
+ void openURI(const QString& uri) {
+ uriHandler->onURI(Q2PSTRING(uri));
+ }
+
+ private:
+ QtDBUSURIHandler* uriHandler;
+ };
+}
+
+QtDBUSURIHandler::QtDBUSURIHandler() {
+ new DBUSAdaptor(this);
+ QDBusConnection connection = QDBusConnection::sessionBus();
+ connection.registerService("im.swift.Swift.URIHandler");
+ connection.registerObject("/", this);
+}
+
+#include "QtDBUSURIHandler.moc"
diff --git a/Swift/QtUI/QtDBUSURIHandler.h b/Swift/QtUI/QtDBUSURIHandler.h
new file mode 100644
index 0000000..be1872e
--- /dev/null
+++ b/Swift/QtUI/QtDBUSURIHandler.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <SwifTools/URIHandler/URIHandler.h>
+
+namespace Swift {
+ class QtDBUSURIHandler : public QObject, public URIHandler {
+ public:
+ QtDBUSURIHandler();
+ };
+}
diff --git a/Swift/QtUI/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..63dd45a
--- /dev/null
+++ b/Swift/QtUI/QtFileTransferListItemModel.cpp
@@ -0,0 +1,111 @@
+/*
+ * 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 <boost/cstdint.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 boost::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/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp
new file mode 100644
index 0000000..2df8d7f
--- /dev/null
+++ b/Swift/QtUI/QtFormWidget.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/QtFormWidget.h>
+
+#include <QGridLayout>
+#include <QLabel>
+#include <QListWidget>
+#include <QLineEdit>
+#include <QTextEdit>
+#include <QCheckBox>
+#include <QScrollArea>
+#include <qdebug.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swiften/Base/foreach.h>
+
+namespace Swift {
+
+QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), form_(form) {
+ QGridLayout* thisLayout = new QGridLayout(this);
+ int row = 0;
+ if (!form->getTitle().empty()) {
+ QLabel* instructions = new QLabel(("<b>" + form->getTitle() + "</b>").c_str(), this);
+ thisLayout->addWidget(instructions, row++, 0, 1, 2);
+ }
+ if (!form->getInstructions().empty()) {
+ QLabel* instructions = new QLabel(form->getInstructions().c_str(), this);
+ thisLayout->addWidget(instructions, row++, 0, 1, 2);
+ }
+ QScrollArea* scrollArea = new QScrollArea(this);
+ thisLayout->addWidget(scrollArea);
+ QWidget* scroll = new QWidget(this);
+ QGridLayout* layout = new QGridLayout(scroll);
+ foreach (boost::shared_ptr<FormField> field, form->getFields()) {
+ QWidget* widget = createWidget(field);
+ if (widget) {
+ layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0);
+ layout->addWidget(widget, row++, 1);
+ }
+ }
+ scrollArea->setWidget(scroll);
+ scrollArea->setWidgetResizable(true);
+}
+
+QtFormWidget::~QtFormWidget() {
+
+}
+
+QListWidget* QtFormWidget::createList(FormField::ref field) {
+ QListWidget* listWidget = new QListWidget(this);
+ listWidget->setSortingEnabled(false);
+ listWidget->setSelectionMode(boost::dynamic_pointer_cast<ListMultiFormField>(field) ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection);
+ boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field);
+ boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field);
+ std::vector<bool> selected;
+ foreach (FormField::Option option, field->getOptions()) {
+ listWidget->addItem(option.label.c_str());
+ if (listSingleField) {
+ selected.push_back(option.value == listSingleField->getValue());
+ }
+ else if (listMultiField) {
+ std::string text = option.value;
+ selected.push_back(std::find(listMultiField->getValue().begin(), listMultiField->getValue().end(), text) != listMultiField->getValue().end());
+ }
+
+ }
+ for (int i = 0; i < listWidget->count(); i++) {
+ QListWidgetItem* item = listWidget->item(i);
+ item->setSelected(selected[i]);
+ }
+ return listWidget;
+}
+
+QWidget* QtFormWidget::createWidget(FormField::ref field) {
+ QWidget* widget = NULL;
+ boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field);
+ if (booleanField) {
+ QCheckBox* checkWidget = new QCheckBox(this);
+ checkWidget->setCheckState(booleanField->getValue() ? Qt::Checked : Qt::Unchecked);
+ widget = checkWidget;
+ }
+ boost::shared_ptr<FixedFormField> fixedField = boost::dynamic_pointer_cast<FixedFormField>(field);
+ if (fixedField) {
+ QString value = fixedField->getValue().c_str();
+ widget = new QLabel(value, this);
+ }
+ boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field);
+ if (listSingleField) {
+ widget = createList(field);
+ }
+ boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field);
+ if (textMultiField) {
+ QString value = textMultiField->getValue().c_str();
+ widget = new QTextEdit(value, this);
+ }
+ boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field);
+ if (textPrivateField) {
+ QString value = textPrivateField->getValue().c_str();
+ QLineEdit* lineWidget = new QLineEdit(value, this);
+ lineWidget->setEchoMode(QLineEdit::Password);
+ widget = lineWidget;
+ }
+ boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field);
+ if (textSingleField) {
+ QString value = textSingleField->getValue().c_str();
+ widget = new QLineEdit(value, this);
+ }
+ boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field);
+ if (jidSingleField) {
+ QString value = jidSingleField->getValue().toString().c_str();
+ widget = new QLineEdit(value, this);
+ }
+ boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field);
+ if (jidMultiField) {
+ QString text;
+ bool prev = false;
+ foreach (JID line, jidMultiField->getValue()) {
+ if (prev) {
+ text += "\n";
+ }
+ prev = true;
+ text += line.toString().c_str();
+ }
+ widget = new QTextEdit(text, this);
+ }
+ boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field);
+ if (listMultiField) {
+ widget = createList(field);
+ }
+ boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field);
+ if (hiddenField) {
+ }
+ fields_[field->getName()] = widget;
+ return widget;
+}
+
+Form::ref QtFormWidget::getCompletedForm() {
+ Form::ref result(new Form(Form::SubmitType));
+ foreach (boost::shared_ptr<FormField> field, form_->getFields()) {
+ boost::shared_ptr<FormField> resultField;
+ boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field);
+ if (booleanField) {
+ resultField = FormField::ref(BooleanFormField::create(qobject_cast<QCheckBox*>(fields_[field->getName()])->checkState() == Qt::Checked));
+ }
+ boost::shared_ptr<FixedFormField> fixedField = boost::dynamic_pointer_cast<FixedFormField>(field);
+ if (fixedField) {
+ resultField = FormField::ref(FixedFormField::create(fixedField->getValue()));
+ }
+ boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field);
+ if (listSingleField) {
+ QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]);
+ if (listWidget->selectedItems().size() > 0) {
+ int i = listWidget->row(listWidget->selectedItems()[0]);
+ resultField = FormField::ref(ListSingleFormField::create(field->getOptions()[i].value));
+ }
+ else {
+ resultField = FormField::ref(ListSingleFormField::create());
+ }
+ }
+ boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field);
+ if (textMultiField) {
+ QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]);
+ QString string = widget->toPlainText();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(TextMultiFormField::create());
+ }
+ else {
+ resultField = FormField::ref(TextMultiFormField::create(Q2PSTRING(string)));
+ }
+ }
+ boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field);
+ if (textPrivateField) {
+ QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]);
+ QString string = widget->text();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(TextPrivateFormField::create());
+ }
+ else {
+ resultField = FormField::ref(TextPrivateFormField::create(Q2PSTRING(string)));
+ }
+ }
+ boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field);
+ if (textSingleField) {
+ QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]);
+ QString string = widget->text();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(TextSingleFormField::create());
+ }
+ else {
+ resultField = FormField::ref(TextSingleFormField::create(Q2PSTRING(string)));
+ }
+ }
+ boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field);
+ if (jidSingleField) {
+ QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]);
+ QString string = widget->text();
+ JID jid(Q2PSTRING(string));
+ if (string.isEmpty()) {
+ resultField = FormField::ref(JIDSingleFormField::create());
+ }
+ else {
+ resultField = FormField::ref(JIDSingleFormField::create(jid));
+ }
+ }
+ boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field);
+ if (jidMultiField) {
+ QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]);
+ QString string = widget->toPlainText();
+ if (string.isEmpty()) {
+ resultField = FormField::ref(JIDMultiFormField::create());
+ }
+ else {
+ QStringList lines = string.split("\n");
+ std::vector<JID> value;
+ foreach (QString line, lines) {
+ value.push_back(JID(Q2PSTRING(line)));
+ }
+ resultField = FormField::ref(JIDMultiFormField::create(value));
+ }
+ }
+ boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field);
+ if (listMultiField) {
+ QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]);
+ std::vector<std::string> values;
+ foreach (QListWidgetItem* item, listWidget->selectedItems()) {
+ values.push_back(field->getOptions()[listWidget->row(item)].value);
+ }
+ resultField = FormField::ref(ListMultiFormField::create(values));
+ }
+ boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field);
+ if (hiddenField) {
+ resultField = FormField::ref(HiddenFormField::create(hiddenField->getValue()));
+ }
+ resultField->setName(field->getName());
+ result->addField(resultField);
+ }
+ return result;
+}
+
+}
diff --git a/Swift/QtUI/QtFormWidget.h b/Swift/QtUI/QtFormWidget.h
new file mode 100644
index 0000000..2fb7b98
--- /dev/null
+++ b/Swift/QtUI/QtFormWidget.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+
+#include <map>
+#include <Swiften/Elements/Form.h>
+
+class QListWidget;
+
+namespace Swift {
+
+class QtFormWidget : public QWidget {
+ Q_OBJECT
+ public:
+ QtFormWidget(Form::ref form, QWidget* parent = NULL);
+ virtual ~QtFormWidget();
+ Form::ref getCompletedForm();
+ private:
+ QWidget* createWidget(FormField::ref field);
+ QListWidget* createList(FormField::ref field);
+ std::map<std::string, QWidget*> fields_;
+ Form::ref form_;
+};
+
+}
diff --git a/Swift/QtUI/QtJoinMUCWindow.cpp b/Swift/QtUI/QtJoinMUCWindow.cpp
index 7980a17..a44cdaf 100644
--- a/Swift/QtUI/QtJoinMUCWindow.cpp
+++ b/Swift/QtUI/QtJoinMUCWindow.cpp
@@ -6,10 +6,13 @@
#include "QtJoinMUCWindow.h"
#include "QtSwiftUtil.h"
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
namespace Swift {
-QtJoinMUCWindow::QtJoinMUCWindow() {
+QtJoinMUCWindow::QtJoinMUCWindow(UIEventStream* uiEventStream) : uiEventStream(uiEventStream) {
ui.setupUi(this);
#if QT_VERSION >= 0x040700
ui.room->setPlaceholderText(tr("someroom@rooms.example.com"));
@@ -35,7 +38,7 @@ void QtJoinMUCWindow::handleJoin() {
lastSetNick = Q2PSTRING(ui.nickName->text());
JID room(Q2PSTRING(ui.room->text()));
- onJoinMUC(room, lastSetNick, ui.joinAutomatically->isChecked());
+ uiEventStream->send(boost::make_shared<JoinMUCUIEvent>(room, lastSetNick, ui.joinAutomatically->isChecked()));
hide();
}
diff --git a/Swift/QtUI/QtJoinMUCWindow.h b/Swift/QtUI/QtJoinMUCWindow.h
index 6e8e846..90b4f3f 100644
--- a/Swift/QtUI/QtJoinMUCWindow.h
+++ b/Swift/QtUI/QtJoinMUCWindow.h
@@ -11,10 +11,11 @@
#include <Swift/QtUI/ui_QtJoinMUCWindow.h>
namespace Swift {
+ class UIEventStream;
class QtJoinMUCWindow : public QWidget, public JoinMUCWindow {
Q_OBJECT
public:
- QtJoinMUCWindow();
+ QtJoinMUCWindow(UIEventStream* uiEventStream);
virtual void setNick(const std::string& nick);
virtual void setMUC(const std::string& nick);
@@ -28,5 +29,6 @@ namespace Swift {
private:
Ui::QtJoinMUCWindow ui;
std::string lastSetNick;
+ UIEventStream* uiEventStream;
};
}
diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp
index fc92792..0db6071 100644
--- a/Swift/QtUI/QtLoginWindow.cpp
+++ b/Swift/QtUI/QtLoginWindow.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -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"
@@ -39,7 +41,7 @@
namespace Swift{
-QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
+QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow(), forgetful_(false) {
uiEventStream_ = uiEventStream;
setWindowTitle("Swift");
@@ -56,9 +58,9 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
stack_ = new QStackedWidget(centralWidget);
topLayout->addWidget(stack_);
topLayout->setMargin(0);
- QWidget *wrapperWidget = new QWidget(this);
- wrapperWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
- QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, wrapperWidget);
+ loginWidgetWrapper_ = new QWidget(this);
+ loginWidgetWrapper_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
+ QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, loginWidgetWrapper_);
layout->addStretch(2);
QLabel* logo = new QLabel(this);
@@ -139,7 +141,7 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
layout->addWidget(loginAutomatically_);
connect(loginButton_, SIGNAL(clicked()), SLOT(loginClicked()));
- stack_->addWidget(wrapperWidget);
+ stack_->addWidget(loginWidgetWrapper_);
#ifdef SWIFTEN_PLATFORM_MACOSX
menuBar_ = new QMenuBar(NULL);
#else
@@ -162,9 +164,15 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
connect(aboutAction, SIGNAL(triggered()), SLOT(handleAbout()));
swiftMenu_->addAction(aboutAction);
- QAction* xmlConsoleAction = new QAction(tr("&Show Debug Console"), this);
- connect(xmlConsoleAction, SIGNAL(triggered()), SLOT(handleShowXMLConsole()));
- generalMenu_->addAction(xmlConsoleAction);
+ xmlConsoleAction_ = new QAction(tr("&Show Debug Console"), this);
+ connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(handleShowXMLConsole()));
+ generalMenu_->addAction(xmlConsoleAction_);
+
+#ifdef SWIFT_EXPERIMENTAL_FT
+ fileTransferOverviewAction_ = new QAction(tr("Show &File Transfer Overview"), this);
+ connect(fileTransferOverviewAction_, SIGNAL(triggered()), SLOT(handleShowFileTransferOverview()));
+ generalMenu_->addAction(fileTransferOverviewAction_);
+#endif
toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this);
toggleSoundsAction_->setCheckable(true);
@@ -197,6 +205,17 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() {
this->show();
}
+void QtLoginWindow::setRememberingAllowed(bool allowed) {
+ forgetful_ = true;
+ remember_->setEnabled(allowed);
+ loginAutomatically_->setEnabled(allowed);
+ xmlConsoleAction_->setEnabled(allowed);
+ if (!allowed) {
+ remember_->setChecked(false);
+ loginAutomatically_->setChecked(false);
+ }
+}
+
bool QtLoginWindow::eventFilter(QObject *obj, QEvent *event) {
if (obj == username_->view() && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
@@ -284,11 +303,9 @@ void QtLoginWindow::handleUsernameTextChanged() {
}
void QtLoginWindow::loggedOut() {
- if (stack_->count() > 1) {
- QWidget* current = stack_->currentWidget();
- stack_->setCurrentIndex(0);
- stack_->removeWidget(current);
- }
+ stack_->removeWidget(stack_->currentWidget());
+ stack_->addWidget(loginWidgetWrapper_);
+ stack_->setCurrentWidget(loginWidgetWrapper_);
setInitialMenus();
setIsLoggingIn(false);
}
@@ -306,6 +323,10 @@ void QtLoginWindow::setIsLoggingIn(bool loggingIn) {
void QtLoginWindow::loginClicked() {
if (username_->isEnabled()) {
onLoginRequest(Q2PSTRING(username_->currentText()), Q2PSTRING(password_->text()), Q2PSTRING(certificateFile_), remember_->isChecked(), loginAutomatically_->isChecked());
+ if (forgetful_) { /* Mustn't remember logins */
+ username_->clearEditText();
+ password_->setText("");
+ }
} else {
onCancelLoginRequest();
}
@@ -343,6 +364,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)));
}
@@ -370,6 +395,7 @@ void QtLoginWindow::setInitialMenus() {
void QtLoginWindow::morphInto(MainWindow *mainWindow) {
QtMainWindow *qtMainWindow = dynamic_cast<QtMainWindow*>(mainWindow);
assert(qtMainWindow);
+ stack_->removeWidget(loginWidgetWrapper_);
stack_->addWidget(qtMainWindow);
stack_->setCurrentWidget(qtMainWindow);
setEnabled(true);
diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h
index 3f3b5f8..414bb20 100644
--- a/Swift/QtUI/QtLoginWindow.h
+++ b/Swift/QtUI/QtLoginWindow.h
@@ -1,11 +1,10 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#ifndef SWIFT_QtLoginWindow_H
-#define SWIFT_QtLoginWindow_H
+#pragma once
#include <QMainWindow>
#include <QPointer>
@@ -37,6 +36,7 @@ namespace Swift {
virtual void removeAvailableAccount(const std::string& jid);
virtual void setLoginAutomatically(bool loginAutomatically);
virtual void setIsLoggingIn(bool loggingIn);
+ virtual void setRememberingAllowed(bool allowed);
void selectUser(const std::string& user);
bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate);
void hide();
@@ -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();
@@ -65,6 +66,7 @@ namespace Swift {
private:
void setInitialMenus();
+ QWidget* loginWidgetWrapper_;
QStringList usernames_;
QStringList passwords_;
QStringList certificateFiles_;
@@ -85,7 +87,8 @@ namespace Swift {
QAction* toggleNotificationsAction_;
UIEventStream* uiEventStream_;
QPointer<QtAboutWidget> aboutDialog_;
+ bool forgetful_;
+ QAction* xmlConsoleAction_;
+ QAction* fileTransferOverviewAction_;
};
}
-
-#endif
diff --git a/Swift/QtUI/QtMUCConfigurationWindow.cpp b/Swift/QtUI/QtMUCConfigurationWindow.cpp
new file mode 100644
index 0000000..a8dec2a
--- /dev/null
+++ b/Swift/QtUI/QtMUCConfigurationWindow.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/QtUI/QtMUCConfigurationWindow.h>
+
+#include <boost/bind.hpp>
+#include <QBoxLayout>
+#include <Swift/QtUI/QtFormWidget.h>
+
+namespace Swift {
+QtMUCConfigurationWindow::QtMUCConfigurationWindow(Form::ref form) {
+
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
+ layout->setContentsMargins(0,0,0,0);
+ layout->setSpacing(2);
+ //QLabel* label = new QLabel(this);
+ //label->setText(tr("Room configuration"));
+ //layout->addWidget(label);
+
+ formWidget_ = NULL;
+ formWidget_ = new QtFormWidget(form, this);
+ layout->addWidget(formWidget_);
+
+ QWidget* buttonsWidget = new QWidget(this);
+ layout->addWidget(buttonsWidget);
+
+ QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget);
+ cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget);
+ buttonsLayout->addWidget(cancelButton_);
+ connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked()));
+ okButton_ = new QPushButton(tr("OK"), buttonsWidget);
+ buttonsLayout->addWidget(okButton_);
+ connect(okButton_, SIGNAL(clicked()), this, SLOT(handleOKClicked()));
+ show();
+}
+
+QtMUCConfigurationWindow::~QtMUCConfigurationWindow() {
+
+}
+
+void QtMUCConfigurationWindow::handleCancelClicked() {
+ close();
+}
+
+void QtMUCConfigurationWindow::handleOKClicked() {
+ onFormComplete(formWidget_->getCompletedForm());
+ close();
+}
+
+
+}
diff --git a/Swift/QtUI/QtMUCConfigurationWindow.h b/Swift/QtUI/QtMUCConfigurationWindow.h
new file mode 100644
index 0000000..2be126b
--- /dev/null
+++ b/Swift/QtUI/QtMUCConfigurationWindow.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QWidget>
+#include <QPushButton>
+#include <QLabel>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Elements/Form.h>
+
+class QBoxLayout;
+
+namespace Swift {
+ class QtFormWidget;
+ class QtMUCConfigurationWindow : public QWidget {
+ Q_OBJECT
+ public:
+ QtMUCConfigurationWindow(Form::ref form);
+ virtual ~QtMUCConfigurationWindow();
+ boost::signal<void (Form::ref)> onFormComplete;
+ private slots:
+ void handleCancelClicked();
+ void handleOKClicked();
+ private:
+ QtFormWidget* formWidget_;
+ QPushButton* okButton_;
+ QPushButton* cancelButton_;
+ };
+}
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 6391961..9d35435 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -21,20 +21,25 @@
#include <QAction>
#include <QTabWidget>
-#include "QtSwiftUtil.h"
-#include "QtTabWidget.h"
-#include "Roster/QtTreeWidget.h"
-#include "Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h"
-#include "Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h"
-#include "Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h"
-#include "Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h"
-#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
-#include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h"
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtTabWidget.h>
+#include <Swift/QtUI/QtSettingsProvider.h>
+#include <Roster/QtRosterWidget.h>
+#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h>
namespace Swift {
+#define CURRENT_ROSTER_TAB "current_roster_tab"
+
QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventStream) : QWidget(), MainWindow(false) {
uiEventStream_ = uiEventStream;
+ settings_ = settings;
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
mainLayout->setContentsMargins(0,0,0,0);
@@ -57,8 +62,7 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
contactTabLayout->setSpacing(0);
contactTabLayout->setContentsMargins(0, 0, 0, 0);
- treeWidget_ = new QtTreeWidget(uiEventStream_);
- treeWidget_->setEditable(true);
+ treeWidget_ = new QtRosterWidget(uiEventStream_, this);
contactTabLayout->addWidget(treeWidget_);
tabs_->addTab(contactsTabWidget_, tr("&Contacts"));
@@ -67,9 +71,14 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
connect(eventWindow_, SIGNAL(onNewEventCountUpdated(int)), this, SLOT(handleEventCountUpdated(int)));
chatListWindow_ = new QtChatListWindow(uiEventStream_);
+ connect(chatListWindow_, SIGNAL(onCountUpdated(int)), this, SLOT(handleChatCountUpdated(int)));
- tabs_->addTab(eventWindow_, tr("&Notices"));
tabs_->addTab(chatListWindow_, tr("C&hats"));
+ tabs_->addTab(eventWindow_, tr("&Notices"));
+
+ tabs_->setCurrentIndex(settings_->getIntSetting(CURRENT_ROSTER_TAB, 0));
+
+ connect(tabs_, SIGNAL(currentChanged(int)), this, SLOT(handleTabChanged(int)));
this->setLayout(mainLayout);
@@ -99,12 +108,20 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS
chatUserAction_ = new QAction(tr("Start &Chat"), this);
connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool)));
actionsMenu->addAction(chatUserAction_);
+ serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this);
+ actionsMenu->addMenu(serverAdHocMenu_);
actionsMenu->addSeparator();
QAction* signOutAction = new QAction(tr("&Sign Out"), this);
connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction()));
actionsMenu->addAction(signOutAction);
- connect(treeWidget_, SIGNAL(onSomethingSelectedChanged(bool)), editUserAction_, SLOT(setEnabled(bool)));
+ treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1));
+
+ setAvailableAdHocCommands(std::vector<DiscoItems::Item>());
+ QAction* adHocAction = new QAction(tr("Collecting commands..."), this);
+ adHocAction->setEnabled(false);
+ serverAdHocMenu_->addAction(adHocAction);
+ serverAdHocCommandActions_.append(adHocAction);
lastOfflineState_ = false;
uiEventStream_->onUIEvent.connect(boost::bind(&QtMainWindow::handleUIEvent, this, _1));
@@ -114,6 +131,10 @@ QtMainWindow::~QtMainWindow() {
uiEventStream_->onUIEvent.disconnect(boost::bind(&QtMainWindow::handleUIEvent, this, _1));
}
+void QtMainWindow::handleTabChanged(int index) {
+ settings_->storeInt(CURRENT_ROSTER_TAB, index);
+}
+
QtEventWindow* QtMainWindow::getEventWindow() {
return eventWindow_;
}
@@ -132,7 +153,7 @@ void QtMainWindow::handleEditProfileRequest() {
void QtMainWindow::handleEventCountUpdated(int count) {
QColor eventTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default
- int eventIndex = 1;
+ int eventIndex = 2;
tabs_->tabBar()->setTabTextColor(eventIndex, eventTabColor);
QString text = tr("&Notices");
if (count > 0) {
@@ -141,6 +162,17 @@ void QtMainWindow::handleEventCountUpdated(int count) {
tabs_->setTabText(eventIndex, text);
}
+void QtMainWindow::handleChatCountUpdated(int count) {
+ QColor chatTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default
+ int chatIndex = 1;
+ tabs_->tabBar()->setTabTextColor(chatIndex, chatTabColor);
+ QString text = tr("&Chats");
+ if (count > 0) {
+ text += QString(" (%1)").arg(count);
+ }
+ tabs_->setTabText(chatIndex, text);
+}
+
void QtMainWindow::handleAddUserActionTriggered(bool /*checked*/) {
boost::shared_ptr<UIEvent> event(new RequestAddUserDialogUIEvent());
uiEventStream_->send(event);
@@ -206,6 +238,33 @@ void QtMainWindow::setConnecting() {
meView_->setConnecting();
}
+void QtMainWindow::handleAdHocActionTriggered(bool /*checked*/) {
+ QAction* action = qobject_cast<QAction*>(sender());
+ assert(action);
+ DiscoItems::Item command = serverAdHocCommands_[serverAdHocCommandActions_.indexOf(action)];
+ uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestAdHocUIEvent(command)));
+}
+
+void QtMainWindow::setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) {
+ serverAdHocCommands_ = commands;
+ foreach (QAction* action, serverAdHocCommandActions_) {
+ delete action;
+ }
+ serverAdHocMenu_->clear();
+ serverAdHocCommandActions_.clear();
+ foreach (DiscoItems::Item command, commands) {
+ QAction* action = new QAction(P2QSTRING(command.getName()), this);
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(handleAdHocActionTriggered(bool)));
+ serverAdHocMenu_->addAction(action);
+ serverAdHocCommandActions_.append(action);
+ }
+ if (serverAdHocCommandActions_.isEmpty()) {
+ QAction* action = new QAction(tr("No Available Commands"), this);
+ action->setEnabled(false);
+ serverAdHocMenu_->addAction(action);
+ serverAdHocCommandActions_.append(action);
+ }
+}
}
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index 3462bb0..321fa2d 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -8,6 +8,7 @@
#include <QWidget>
#include <QMenu>
+#include <QList>
#include "Swift/Controllers/UIInterfaces/MainWindow.h"
#include "Swift/QtUI/QtRosterHeader.h"
#include "Swift/QtUI/EventViewer/QtEventWindow.h"
@@ -20,12 +21,11 @@ class QLineEdit;
class QPushButton;
class QToolBar;
class QAction;
-
+class QMenu;
class QTabWidget;
namespace Swift {
- class QtTreeWidget;
- class QtTreeWidgetFactory;
+ class QtRosterWidget;
class TreeWidget;
class UIEventStream;
class QtTabWidget;
@@ -35,7 +35,7 @@ namespace Swift {
Q_OBJECT
public:
QtMainWindow(QtSettingsProvider*, UIEventStream* eventStream);
- ~QtMainWindow();
+ virtual ~QtMainWindow();
std::vector<QMenu*> getMenus() {return menus_;}
void setMyNick(const std::string& name);
void setMyJID(const JID& jid);
@@ -46,6 +46,7 @@ namespace Swift {
QtEventWindow* getEventWindow();
QtChatListWindow* getChatListWindow();
void setRosterModel(Roster* roster);
+ void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands);
private slots:
void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage);
void handleUIEvent(boost::shared_ptr<UIEvent> event);
@@ -55,17 +56,22 @@ namespace Swift {
void handleEditProfileAction();
void handleAddUserActionTriggered(bool checked);
void handleChatUserActionTriggered(bool checked);
+ void handleAdHocActionTriggered(bool checked);
void handleEventCountUpdated(int count);
+ void handleChatCountUpdated(int count);
void handleEditProfileRequest();
+ void handleTabChanged(int index);
private:
+ QtSettingsProvider* settings_;
std::vector<QMenu*> menus_;
- QtTreeWidget* treeWidget_;
+ QtRosterWidget* treeWidget_;
QtRosterHeader* meView_;
QAction* addUserAction_;
QAction* editUserAction_;
QAction* chatUserAction_;
QAction* showOfflineAction_;
+ QMenu* serverAdHocMenu_;
QtTabWidget* tabs_;
QWidget* contactsTabWidget_;
QWidget* eventsTabWidget_;
@@ -73,5 +79,7 @@ namespace Swift {
QtChatListWindow* chatListWindow_;
UIEventStream* uiEventStream_;
bool lastOfflineState_;
+ std::vector<DiscoItems::Item> serverAdHocCommands_;
+ QList<QAction*> serverAdHocCommandActions_;
};
}
diff --git a/Swift/QtUI/QtSubscriptionRequestWindow.cpp b/Swift/QtUI/QtSubscriptionRequestWindow.cpp
index 7828103..d22cbd0 100644
--- a/Swift/QtUI/QtSubscriptionRequestWindow.cpp
+++ b/Swift/QtUI/QtSubscriptionRequestWindow.cpp
@@ -28,10 +28,12 @@ QtSubscriptionRequestWindow::QtSubscriptionRequestWindow(boost::shared_ptr<Subsc
layout->addWidget(okButton);
} else {
QPushButton* yesButton = new QPushButton(tr("Yes"), this);
+ yesButton->setDefault(true);
connect(yesButton, SIGNAL(clicked()), this, SLOT(handleYes()));
QPushButton* noButton = new QPushButton(tr("No"), this);
connect(noButton, SIGNAL(clicked()), this, SLOT(handleNo()));
QPushButton* deferButton = new QPushButton(tr("Defer"), this);
+ deferButton->setShortcut(QKeySequence(Qt::Key_Escape));
connect(deferButton, SIGNAL(clicked()), this, SLOT(handleDefer()));
QHBoxLayout* buttonLayout = new QHBoxLayout();
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index 7830150..57f4175 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2011 Kevin Smith
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
@@ -18,13 +18,11 @@
#include "QtUIFactory.h"
#include "QtChatWindowFactory.h"
#include <Swiften/Base/Log.h>
-#include <Swift/Controllers/CertificateFileStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h>
+#include "Swift/Controllers/Storages/FileStoragesFactory.h"
#include "SwifTools/Application/PlatformApplicationPathProvider.h"
-#include "Swiften/Avatars/AvatarFileStorage.h"
-#include "Swiften/Disco/CapsFileStorage.h"
#include <string>
#include "Swiften/Base/Platform.h"
-#include "Swift/Controllers/FileStoragesFactory.h"
#include "Swiften/Elements/Presence.h"
#include "Swiften/Client/Client.h"
#include "Swift/Controllers/MainController.h"
@@ -32,20 +30,30 @@
#include "Swift/Controllers/BuildVersion.h"
#include "SwifTools/AutoUpdater/AutoUpdater.h"
#include "SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h"
+
#if defined(SWIFTEN_PLATFORM_WINDOWS)
#include "WindowsNotifier.h"
-#endif
-#if defined(HAVE_GROWL)
+#elif defined(HAVE_GROWL)
#include "SwifTools/Notifier/GrowlNotifier.h"
#elif defined(SWIFTEN_PLATFORM_LINUX)
#include "FreeDesktopNotifier.h"
#else
#include "SwifTools/Notifier/NullNotifier.h"
#endif
+
#if defined(SWIFTEN_PLATFORM_MACOSX)
#include "SwifTools/Dock/MacOSXDock.h"
-#endif
+#else
#include "SwifTools/Dock/NullDock.h"
+#endif
+
+#if defined(SWIFTEN_PLATFORM_MACOSX)
+#include "QtURIHandler.h"
+#elif defined(SWIFTEN_PLATFORM_WIN32)
+#include <SwifTools/URIHandler/NullURIHandler.h>
+#else
+#include "QtDBUSURIHandler.h"
+#endif
namespace Swift{
@@ -66,12 +74,13 @@ po::options_description QtSwift::getOptionsDescription() {
("latency-debug", "Use latency debugging (unsupported)")
("multi-account", po::value<int>()->default_value(1), "Number of accounts to open windows for (unsupported)")
("start-minimized", "Don't show the login/roster window at startup")
+ ("eagle-mode", "Settings more suitable for military/secure deployments")
;
return result;
}
-QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMainThreadCaller_), autoUpdater_(NULL) {
+QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMainThreadCaller_), autoUpdater_(NULL), idleDetector_(&idleQuerier_, networkFactories_.getTimerFactory(), 1000) {
if (options.count("netbook-mode")) {
splitter_ = new QSplitter();
} else {
@@ -96,6 +105,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
tabs_ = options.count("no-tabs") && !(splitter_ > 0) ? NULL : new QtChatTabs();
bool startMinimized = options.count("start-minimized") > 0;
+ bool eagleMode = options.count("eagle-mode") > 0;
settings_ = new QtSettingsProvider();
applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME);
storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir());
@@ -124,6 +134,14 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
dock_ = new NullDock();
#endif
+#if defined(SWIFTEN_PLATFORM_MACOSX)
+ uriHandler_ = new QtURIHandler();
+#elif defined(SWIFTEN_PLATFORM_WIN32)
+ uriHandler_ = new NullURIHandler();
+#else
+ uriHandler_ = new QtDBUSURIHandler();
+#endif
+
if (splitter_) {
splitter_->show();
}
@@ -146,7 +164,10 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
certificateStorageFactory_,
dock_,
notifier_,
- options.count("latency-debug") > 0);
+ uriHandler_,
+ &idleDetector_,
+ options.count("latency-debug") > 0,
+ eagleMode);
mainControllers_.push_back(mainController);
}
@@ -173,6 +194,7 @@ QtSwift::~QtSwift() {
}
delete tabs_;
delete splitter_;
+ delete uriHandler_;
delete dock_;
delete soundPlayer_;
delete chatWindowFactory_;
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index 978fa14..7f33475 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -21,6 +21,8 @@
#if defined(SWIFTEN_PLATFORM_WINDOWS)
#include "WindowsNotifier.h"
#endif
+#include "SwifTools/Idle/PlatformIdleQuerier.h"
+#include "SwifTools/Idle/ActualIdleDetector.h"
namespace po = boost::program_options;
@@ -44,6 +46,7 @@ namespace Swift {
class QtMUCSearchWindowFactory;
class QtUserSearchWindowFactory;
class EventLoop;
+ class URIHandler;
class QtSwift : public QObject {
Q_OBJECT
@@ -63,12 +66,15 @@ namespace Swift {
QSplitter* splitter_;
QtSoundPlayer* soundPlayer_;
Dock* dock_;
+ URIHandler* uriHandler_;
QtChatTabs* tabs_;
ApplicationPathProvider* applicationPathProvider_;
StoragesFactory* storagesFactory_;
CertificateStorageFactory* certificateStorageFactory_;
AutoUpdater* autoUpdater_;
Notifier* notifier_;
+ PlatformIdleQuerier idleQuerier_;
+ ActualIdleDetector idleDetector_;
#if defined(SWIFTEN_PLATFORM_MACOSX)
CocoaApplication cocoaApplication_;
#endif
diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp
index 3668220..3a62325 100644
--- a/Swift/QtUI/QtTextEdit.cpp
+++ b/Swift/QtUI/QtTextEdit.cpp
@@ -4,7 +4,7 @@
* See Documentation/Licenses/GPLv3.txt for more information.
*/
-#include "QtTextEdit.h"
+#include <Swift/QtUI/QtTextEdit.h>
#include <QFontMetrics>
#include <QKeyEvent>
@@ -22,19 +22,25 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) {
if ((key == Qt::Key_Enter || key == Qt::Key_Return)
&& (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) {
emit returnPressed();
- } else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier)
+ }
+ else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier)
|| (key == Qt::Key_C && modifiers == Qt::ControlModifier && textCursor().selectedText().isEmpty())
|| (key == Qt::Key_W && modifiers == Qt::ControlModifier)
|| (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier)
|| (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier)
-// || (key == Qt::Key_Left && modifiers == (Qt::ControlModifier | Qt::ShiftModifier))
-// || (key == Qt::Key_Right && modifiers == (Qt::ControlModifier | Qt::ShiftModifier))
|| (key == Qt::Key_Tab && modifiers == Qt::ControlModifier)
|| (key == Qt::Key_A && modifiers == Qt::AltModifier)
|| (key == Qt::Key_Tab)
) {
emit unhandledKeyPressEvent(event);
- } else {
+ }
+ else if ((key == Qt::Key_Up)
+ || (key == Qt::Key_Down)
+ ){
+ emit unhandledKeyPressEvent(event);
+ QTextEdit::keyPressEvent(event);
+ }
+ else {
QTextEdit::keyPressEvent(event);
}
}
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 35fbfcd..9de700c 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -23,10 +23,15 @@
#include "UserSearch/QtUserSearchWindow.h"
#include "QtProfileWindow.h"
#include "QtContactEditWindow.h"
+#include "QtAdHocCommandWindow.h"
+#include "QtFileTransferListWidget.h"
+
+#define CHATWINDOW_FONT_SIZE "chatWindowFontSize"
namespace Swift {
QtUIFactory::QtUIFactory(QtSettingsProvider* settings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, bool startMinimized) : settings(settings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized) {
+ chatFontSize = settings->getIntSetting(CHATWINDOW_FONT_SIZE, 0);
}
XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() {
@@ -39,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);
@@ -80,15 +94,36 @@ MUCSearchWindow* QtUIFactory::createMUCSearchWindow() {
}
ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eventStream) {
- return chatWindowFactory->createChatWindow(contact, eventStream);
+ QtChatWindow* window = dynamic_cast<QtChatWindow*>(chatWindowFactory->createChatWindow(contact, eventStream));
+ chatWindows.push_back(window);
+ std::vector<QPointer<QtChatWindow> > deletions;
+ foreach (QPointer<QtChatWindow> existingWindow, chatWindows) {
+ if (existingWindow.isNull()) {
+ deletions.push_back(existingWindow);
+ } else {
+ connect(window, SIGNAL(fontResized(int)), existingWindow, SLOT(handleFontResized(int)));
+ connect(existingWindow, SIGNAL(fontResized(int)), window, SLOT(handleFontResized(int)));
+ }
+ }
+ foreach (QPointer<QtChatWindow> deletedWindow, deletions) {
+ chatWindows.erase(std::remove(chatWindows.begin(), chatWindows.end(), deletedWindow), chatWindows.end());
+ }
+ connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int)));
+ window->handleFontResized(chatFontSize);
+ return window;
+}
+
+void QtUIFactory::handleChatWindowFontResized(int size) {
+ chatFontSize = size;
+ settings->storeInt(CHATWINDOW_FONT_SIZE, size);
}
UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) {
return new QtUserSearchWindow(eventStream, type, groups);
};
-JoinMUCWindow* QtUIFactory::createJoinMUCWindow() {
- return new QtJoinMUCWindow();
+JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) {
+ return new QtJoinMUCWindow(uiEventStream);
}
ProfileWindow* QtUIFactory::createProfileWindow() {
@@ -99,5 +134,8 @@ ContactEditWindow* QtUIFactory::createContactEditWindow() {
return new QtContactEditWindow();
}
+void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) {
+ new QtAdHocCommandWindow(command);
+}
}
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index ddaaf6e..8fc5395 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -7,6 +7,7 @@
#pragma once
#include <QObject>
+#include <QPointer>
#include <Swift/Controllers/UIInterfaces/UIFactory.h>
@@ -20,6 +21,7 @@ namespace Swift {
class QtMainWindow;
class QtChatTheme;
class QtChatWindowFactory;
+ class QtChatWindow;
class QtUIFactory : public QObject, public UIFactory {
Q_OBJECT
@@ -34,12 +36,15 @@ namespace Swift {
virtual MUCSearchWindow* createMUCSearchWindow();
virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);
virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups);
- virtual JoinMUCWindow* createJoinMUCWindow();
+ virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream);
virtual ProfileWindow* createProfileWindow();
virtual ContactEditWindow* createContactEditWindow();
+ virtual FileTransferListWidget* createFileTransferListWidget();
+ virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command);
private slots:
void handleLoginWindowGeometryChanged();
+ void handleChatWindowFontResized(int);
private:
QtSettingsProvider* settings;
@@ -49,6 +54,8 @@ namespace Swift {
QtChatWindowFactory* chatWindowFactory;
QtMainWindow* lastMainWindow;
QtLoginWindow* loginWindow;
+ std::vector<QPointer<QtChatWindow> > chatWindows;
bool startMinimized;
+ int chatFontSize;
};
}
diff --git a/Swift/QtUI/QtURIHandler.cpp b/Swift/QtUI/QtURIHandler.cpp
new file mode 100644
index 0000000..43f3ed1
--- /dev/null
+++ b/Swift/QtUI/QtURIHandler.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtURIHandler.h"
+
+#include <QCoreApplication>
+#include <QFileOpenEvent>
+#include <QUrl>
+
+#include "QtSwiftUtil.h"
+#ifdef Q_WS_MAC
+#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h>
+#endif
+
+using namespace Swift;
+
+QtURIHandler::QtURIHandler() {
+ qApp->installEventFilter(this);
+#ifdef Q_WS_MAC
+ registerAppAsDefaultXMPPURIHandler();
+#endif
+}
+
+bool QtURIHandler::eventFilter(QObject*, QEvent* event) {
+ if (event->type() == QEvent::FileOpen) {
+ QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event);
+ if (fileOpenEvent->url().scheme() == "xmpp") {
+ onURI(Q2PSTRING(fileOpenEvent->url().toString()));
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/Swift/QtUI/QtURIHandler.h b/Swift/QtUI/QtURIHandler.h
new file mode 100644
index 0000000..a02114a
--- /dev/null
+++ b/Swift/QtUI/QtURIHandler.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <SwifTools/URIHandler/URIHandler.h>
+
+class QUrl;
+
+namespace Swift {
+ class QtURIHandler : public QObject, public URIHandler {
+ public:
+ QtURIHandler();
+
+ private:
+ bool eventFilter(QObject* obj, QEvent* event);
+ };
+}
diff --git a/Swift/QtUI/QtWebView.cpp b/Swift/QtUI/QtWebView.cpp
index 9d12010..d849ce7 100644
--- a/Swift/QtUI/QtWebView.cpp
+++ b/Swift/QtUI/QtWebView.cpp
@@ -39,9 +39,15 @@ void QtWebView::dragEnterEvent(QDragEnterEvent*) {
}
+void QtWebView::setFontSizeIsMinimal(bool minimum) {
+ fontSizeIsMinimal = 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);
@@ -55,10 +61,15 @@ void QtWebView::contextMenuEvent(QContextMenuEvent* ev) {
if (removeAction) {
menu->removeAction(action);
}
- }
+ }*/
// Add our own custom actions
menu->addAction(tr("Clear"), this, SIGNAL(clearRequested()));
+ menu->addAction(tr("Increase font size"), this, SIGNAL(fontGrowRequested()));
+ QAction* shrink = new QAction(tr("Decrease font size"), this);
+ shrink->setEnabled(!fontSizeIsMinimal);
+ connect(shrink, SIGNAL(triggered()), this, SIGNAL(fontShrinkRequested()));
+ menu->addAction(shrink);
menu->exec(ev->globalPos());
delete menu;
diff --git a/Swift/QtUI/QtWebView.h b/Swift/QtUI/QtWebView.h
index fbd31e3..202037f 100644
--- a/Swift/QtUI/QtWebView.h
+++ b/Swift/QtUI/QtWebView.h
@@ -18,15 +18,19 @@ namespace Swift {
void keyPressEvent(QKeyEvent* event);
void dragEnterEvent(QDragEnterEvent *event);
void contextMenuEvent(QContextMenuEvent* ev);
+ void setFontSizeIsMinimal(bool minimum);
signals:
void gotFocus();
void clearRequested();
+ void fontGrowRequested();
+ void fontShrinkRequested();
protected:
void focusInEvent(QFocusEvent* event);
private:
std::vector<QWebPage::WebAction> filteredActions;
+ bool fontSizeIsMinimal;
};
}
diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp
index c1b1d0d..e674df8 100644
--- a/Swift/QtUI/QtXMLConsoleWidget.cpp
+++ b/Swift/QtUI/QtXMLConsoleWidget.cpp
@@ -51,6 +51,9 @@ QtXMLConsoleWidget::QtXMLConsoleWidget() {
emit titleUpdated();
}
+QtXMLConsoleWidget::~QtXMLConsoleWidget() {
+}
+
void QtXMLConsoleWidget::showEvent(QShowEvent* event) {
emit windowOpening();
emit titleUpdated(); /* This just needs to be somewhere after construction */
@@ -71,12 +74,12 @@ void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) {
event->accept();
}
-void QtXMLConsoleWidget::handleDataRead(const std::string& data) {
- appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + data + "\n", QColor(33,98,33));
+void QtXMLConsoleWidget::handleDataRead(const SafeByteArray& data) {
+ appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(33,98,33));
}
-void QtXMLConsoleWidget::handleDataWritten(const std::string& data) {
- appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + data + "\n", QColor(155,1,0));
+void QtXMLConsoleWidget::handleDataWritten(const SafeByteArray& data) {
+ appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + 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 a345495..912ad90 100644
--- a/Swift/QtUI/QtXMLConsoleWidget.h
+++ b/Swift/QtUI/QtXMLConsoleWidget.h
@@ -19,12 +19,13 @@ namespace Swift {
public:
QtXMLConsoleWidget();
+ ~QtXMLConsoleWidget();
void show();
void activate();
- virtual void handleDataRead(const std::string& data);
- virtual void handleDataWritten(const std::string& data);
+ virtual void handleDataRead(const SafeByteArray& data);
+ virtual void handleDataWritten(const SafeByteArray& data);
private:
virtual void closeEvent(QCloseEvent* event);
diff --git a/Swift/QtUI/Roster/DelegateCommons.cpp b/Swift/QtUI/Roster/DelegateCommons.cpp
index 164b80f..290794d 100644
--- a/Swift/QtUI/Roster/DelegateCommons.cpp
+++ b/Swift/QtUI/Roster/DelegateCommons.cpp
@@ -6,18 +6,102 @@
#include "DelegateCommons.h"
+#include <QtScaledAvatarCache.h>
+#include <QFileInfo>
+
namespace Swift {
-void DelegateCommons::drawElidedText(QPainter* painter, const QRect& region, const QString& text) {
+void DelegateCommons::drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags) {
QString adjustedText(painter->fontMetrics().elidedText(text, Qt::ElideRight, region.width(), Qt::TextShowMnemonic));
- painter->drawText(region, Qt::AlignTop, adjustedText);
+ painter->drawText(region, flags, adjustedText);
}
+void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount) const {
+ painter->save();
+ QRect fullRegion(option.rect);
+ if ( option.state & QStyle::State_Selected ) {
+ painter->fillRect(fullRegion, option.palette.highlight());
+ painter->setPen(option.palette.highlightedText().color());
+ } else {
+ painter->setPen(QPen(nameColor));
+ }
+
+ QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin));
+
+ int calculatedAvatarSize = presenceIconRegion.height();
+ //This overlaps the presenceIcon, so must be painted first
+ QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize));
+
+ QPixmap avatarPixmap;
+ if (!avatarPath.isEmpty()) {
+ QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath);
+ if (QFileInfo(scaledAvatarPath).exists()) {
+ avatarPixmap.load(scaledAvatarPath);
+ }
+ }
+ if (avatarPixmap.isNull()) {
+ avatarPixmap = QPixmap(":/icons/avatar.png").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ }
+
+ painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap);
+
+ //Paint the presence icon over the top of the avatar
+ presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter);
+
+ QFontMetrics nameMetrics(nameFont);
+ painter->setFont(nameFont);
+ int extraFontWidth = nameMetrics.width("H");
+ int leftOffset = avatarRegion.right() + horizontalMargin * 2 + extraFontWidth / 2;
+ QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0/*-leftOffset*/, 0));
+
+ int nameHeight = nameMetrics.height() + verticalMargin;
+ QRect nameRegion(textRegion.adjusted(0, verticalMargin, 0, 0));
+
+ DelegateCommons::drawElidedText(painter, nameRegion, name);
+
+
+ painter->setFont(detailFont);
+ painter->setPen(QPen(QColor(160,160,160)));
+
+ QRect statusTextRegion(textRegion.adjusted(0, nameHeight, 0, 0));
+ DelegateCommons::drawElidedText(painter, statusTextRegion, statusText);
+
+ if (unreadCount > 0) {
+ QRect unreadRect(fullRegion.right() - unreadCountSize - horizontalMargin, fullRegion.top() + (fullRegion.height() - unreadCountSize) / 2, unreadCountSize, unreadCountSize);
+ QPen pen(QColor("black"));
+ pen.setWidth(1);
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ painter->setPen(pen);
+ painter->setBrush(QBrush(QColor("red"), Qt::SolidPattern));
+ //painter->setBackgroundMode(Qt::OpaqueMode);
+ painter->drawEllipse(unreadRect);
+ painter->setBackgroundMode(Qt::TransparentMode);
+ painter->setPen(QColor("white"));
+ drawElidedText(painter, unreadRect, QString("%1").arg(unreadCount), Qt::AlignCenter);
+ }
+
+ painter->restore();
+}
+
+QSize DelegateCommons::contactSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const {
+ int heightByAvatar = avatarSize + verticalMargin * 2;
+ QFontMetrics nameMetrics(nameFont);
+ QFontMetrics statusMetrics(detailFont);
+ int sizeByText = 2 * verticalMargin + nameMetrics.height() + statusMetrics.height();
+ //Doesn't work, yay! FIXME: why?
+ //QSize size = (option.state & QStyle::State_Selected) ? QSize(150, 80) : QSize(150, avatarSize_ + margin_ * 2);
+ //qDebug() << "Returning size" << size;
+ return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar);
+}
const int DelegateCommons::horizontalMargin = 2;
const int DelegateCommons::verticalMargin = 2;
const int DelegateCommons::farLeftMargin = 2;
+const int DelegateCommons::avatarSize = 20;
+const int DelegateCommons::presenceIconHeight = 16;
+const int DelegateCommons::presenceIconWidth = 16;
+const int DelegateCommons::unreadCountSize = 16;
diff --git a/Swift/QtUI/Roster/DelegateCommons.h b/Swift/QtUI/Roster/DelegateCommons.h
index 9213ef5..e5e4ff9 100644
--- a/Swift/QtUI/Roster/DelegateCommons.h
+++ b/Swift/QtUI/Roster/DelegateCommons.h
@@ -11,6 +11,8 @@
#include <QPainter>
#include <QRect>
#include <QString>
+#include <QIcon>
+#include <QStyleOptionViewItem>
namespace Swift {
class DelegateCommons {
@@ -21,7 +23,10 @@ namespace Swift {
detailFont.setPointSize(nameFont.pointSize() - detailFontSizeDrop);
}
- static void drawElidedText(QPainter* painter, const QRect& region, const QString& text);
+ static void drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags = Qt::AlignTop);
+
+ QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount) const;
int detailFontSizeDrop;
QFont nameFont;
@@ -29,5 +34,9 @@ namespace Swift {
static const int horizontalMargin;
static const int verticalMargin;
static const int farLeftMargin;
+ static const int avatarSize;
+ static const int presenceIconHeight;
+ static const int presenceIconWidth;
+ static const int unreadCountSize;
};
}
diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.cpp b/Swift/QtUI/Roster/QtOccupantListWidget.cpp
new file mode 100644
index 0000000..cbda0f1
--- /dev/null
+++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+
+#include "Roster/QtOccupantListWidget.h"
+
+#include <QContextMenuEvent>
+#include <QMenu>
+#include <QAction>
+#include <QInputDialog>
+
+#include "Swift/Controllers/Roster/ContactRosterItem.h"
+#include "Swift/Controllers/Roster/GroupRosterItem.h"
+#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "QtSwiftUtil.h"
+
+namespace Swift {
+
+QtOccupantListWidget::QtOccupantListWidget(UIEventStream* eventStream, QWidget* parent) : QtTreeWidget(eventStream, parent) {
+
+}
+
+QtOccupantListWidget::~QtOccupantListWidget() {
+
+}
+
+void QtOccupantListWidget::setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions) {
+ availableOccupantActions_ = actions;
+}
+
+void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) {
+ QModelIndex index = indexAt(event->pos());
+ if (!index.isValid()) {
+ return;
+ }
+ RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
+ ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
+ if (contact) {
+ QMenu contextMenu;
+ std::map<QAction*, ChatWindow::OccupantAction> actions;
+ foreach (ChatWindow::OccupantAction availableAction, availableOccupantActions_) {
+ QString text = "Error: missing string";
+ switch (availableAction) {
+ case ChatWindow::Kick: text = tr("Kick user"); break;
+ }
+ QAction* action = contextMenu.addAction(text);
+ actions[action] = availableAction;
+ }
+ QAction* result = contextMenu.exec(event->globalPos());
+ if (result) {
+ onOccupantActionSelected(actions[result], contact);
+ }
+ }
+}
+
+}
+
diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.h b/Swift/QtUI/Roster/QtOccupantListWidget.h
new file mode 100644
index 0000000..ef29c00
--- /dev/null
+++ b/Swift/QtUI/Roster/QtOccupantListWidget.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/QtUI/Roster/QtTreeWidget.h"
+
+#include "Swiften/Base/boost_bsignals.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+
+namespace Swift {
+
+class QtOccupantListWidget : public QtTreeWidget {
+ Q_OBJECT
+ public:
+ QtOccupantListWidget(UIEventStream* eventStream, QWidget* parent = 0);
+ virtual ~QtOccupantListWidget();
+ void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions);
+ boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected;
+ protected:
+ void contextMenuEvent(QContextMenuEvent* event);
+ private:
+ std::vector<ChatWindow::OccupantAction> availableOccupantActions_;
+};
+
+}
+
diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp
new file mode 100644
index 0000000..4c96695
--- /dev/null
+++ b/Swift/QtUI/Roster/QtRosterWidget.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Roster/QtRosterWidget.h"
+
+#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"
+#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "QtSwiftUtil.h"
+
+namespace Swift {
+
+QtRosterWidget::QtRosterWidget(UIEventStream* eventStream, QWidget* parent) : QtTreeWidget(eventStream, parent) {
+
+}
+
+QtRosterWidget::~QtRosterWidget() {
+
+}
+
+void QtRosterWidget::handleEditUserActionTriggered(bool /*checked*/) {
+ QModelIndexList selectedIndexList = getSelectedIndexes();
+ if (selectedIndexList.empty()) {
+ return;
+ }
+ QModelIndex index = selectedIndexList[0];
+ if (!index.isValid()) {
+ return;
+ }
+ RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
+ if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) {
+ eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
+ }
+}
+
+void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) {
+ QModelIndex index = indexAt(event->pos());
+ if (!index.isValid()) {
+ return;
+ }
+ RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
+ QMenu contextMenu;
+ if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) {
+ QAction* editContact = contextMenu.addAction(tr("Edit"));
+ QAction* removeContact = contextMenu.addAction(tr("Remove"));
+#ifdef SWIFT_EXPERIMENTAL_FT
+ QAction* sendFile = NULL;
+ if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) {
+ sendFile = contextMenu.addAction(tr("Send File"));
+ }
+#endif
+ QAction* result = contextMenu.exec(event->globalPos());
+ if (result == editContact) {
+ eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
+ }
+ else if (result == removeContact) {
+ if (QtContactEditWindow::confirmContactDeletion(contact->getJID())) {
+ eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID()));
+ }
+ }
+#ifdef SWIFT_EXPERIMENTAL_FT
+ else if (sendFile && result == sendFile) {
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;"));
+ if (!fileName.isEmpty()) {
+ eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), Q2PSTRING(fileName)));
+ }
+ }
+#endif
+ }
+ else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) {
+ QAction* renameGroupAction = contextMenu.addAction(tr("Rename"));
+ QAction* result = contextMenu.exec(event->globalPos());
+ if (result == renameGroupAction) {
+ renameGroup(group);
+ }
+ }
+}
+
+void QtRosterWidget::renameGroup(GroupRosterItem* group) {
+ bool ok;
+ QString newName = QInputDialog::getText(NULL, tr("Rename group"), tr("Enter a new name for group '%1':").arg(P2QSTRING(group->getDisplayName())), QLineEdit::Normal, P2QSTRING(group->getDisplayName()), &ok);
+ if (ok) {
+ eventStream_->send(boost::make_shared<RenameGroupUIEvent>(group->getDisplayName(), Q2PSTRING(newName)));
+ }
+}
+
+}
diff --git a/Swift/QtUI/Roster/QtRosterWidget.h b/Swift/QtUI/Roster/QtRosterWidget.h
new file mode 100644
index 0000000..f870237
--- /dev/null
+++ b/Swift/QtUI/Roster/QtRosterWidget.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/QtUI/Roster/QtTreeWidget.h"
+
+namespace Swift {
+class QtRosterWidget : public QtTreeWidget {
+ Q_OBJECT
+ public:
+ QtRosterWidget(UIEventStream* eventStream, QWidget* parent = 0);
+ virtual ~QtRosterWidget();
+ public slots:
+ void handleEditUserActionTriggered(bool checked);
+ protected:
+ void contextMenuEvent(QContextMenuEvent* event);
+ private:
+ void renameGroup(GroupRosterItem* group);
+};
+
+}
diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp
index 1296872..690515d 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.cpp
+++ b/Swift/QtUI/Roster/QtTreeWidget.cpp
@@ -6,25 +6,21 @@
#include "Roster/QtTreeWidget.h"
-#include <QMenu>
-#include <QContextMenuEvent>
-#include <QInputDialog>
#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/RequestContactEditorUIEvent.h"
-#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h"
-#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h"
+#include "Swift/Controllers/UIEvents/SendFileUIEvent.h"
#include "QtSwiftUtil.h"
-#include "QtContactEditWindow.h"
namespace Swift {
-QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, QWidget* parent) : QTreeView(parent), editable_(false) {
+QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, QWidget* parent) : QTreeView(parent) {
eventStream_ = eventStream;
model_ = new RosterModel(this);
setModel(model_);
@@ -38,6 +34,9 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, QWidget* parent) : QTreeV
expandAll();
setAnimated(true);
setIndentation(0);
+#ifdef SWIFT_EXPERIMENTAL_FT
+ setAcceptDrops(true);
+#endif
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)));
@@ -89,21 +88,20 @@ QModelIndexList QtTreeWidget::getSelectedIndexes() const {
}
void QtTreeWidget::currentChanged(const QModelIndex& current, const QModelIndex& previous) {
- bool valid = false;
+ RosterItem* item = NULL;
QModelIndexList selectedIndexList = getSelectedIndexes();
- if (!editable_ || selectedIndexList.empty() || !selectedIndexList[0].isValid()) {
+ if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) {
/* I didn't quite understand why using current didn't seem to work here.*/
}
else if (current.isValid()) {
- RosterItem* item = static_cast<RosterItem*>(current.internalPointer());
- if (dynamic_cast<ContactRosterItem*>(item)) {
- valid = true;
- }
+ item = static_cast<RosterItem*>(current.internalPointer());
+ item = dynamic_cast<ContactRosterItem*>(item);
}
- onSomethingSelectedChanged(valid);
+ onSomethingSelectedChanged(item);
QTreeView::currentChanged(current, previous);
}
+
void QtTreeWidget::handleItemActivated(const QModelIndex& index) {
RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
@@ -112,62 +110,39 @@ void QtTreeWidget::handleItemActivated(const QModelIndex& index) {
}
}
-void QtTreeWidget::handleEditUserActionTriggered(bool /*checked*/) {
- if (!editable_) {
- return;
- }
- QModelIndexList selectedIndexList = getSelectedIndexes();
- if (selectedIndexList.empty()) {
- return;
- }
- QModelIndex index = selectedIndexList[0];
- if (!index.isValid()) {
- return;
- }
- RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
- if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) {
- eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
+void QtTreeWidget::dragEnterEvent(QDragEnterEvent *event) {
+ if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) {
+ event->acceptProposedAction();
}
}
-void QtTreeWidget::contextMenuEvent(QContextMenuEvent* event) {
- if (!editable_) {
- return;
- }
+void QtTreeWidget::dropEvent(QDropEvent *event) {
QModelIndex index = indexAt(event->pos());
- if (!index.isValid()) {
- return;
- }
- RosterItem* item = static_cast<RosterItem*>(index.internalPointer());
- QMenu contextMenu;
- if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) {
- QAction* editContact = contextMenu.addAction(tr("Edit"));
- QAction* removeContact = contextMenu.addAction(tr("Remove"));
- QAction* result = contextMenu.exec(event->globalPos());
- if (result == editContact) {
- eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID()));
- }
- else if (result == removeContact) {
- if (QtContactEditWindow::confirmContactDeletion(contact->getJID())) {
- eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID()));
+ 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(), Q2PSTRING(filename)));
+ }
}
}
}
- else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) {
- QAction* renameGroupAction = contextMenu.addAction(tr("Rename"));
- QAction* result = contextMenu.exec(event->globalPos());
- if (result == renameGroupAction) {
- renameGroup(group);
- }
- }
}
-void QtTreeWidget::renameGroup(GroupRosterItem* group) {
- bool ok;
- QString newName = QInputDialog::getText(NULL, tr("Rename group"), tr("Enter a new name for group '%1':").arg(P2QSTRING(group->getDisplayName())), QLineEdit::Normal, P2QSTRING(group->getDisplayName()), &ok);
- if (ok) {
- eventStream_->send(boost::make_shared<RenameGroupUIEvent>(group->getDisplayName(), Q2PSTRING(newName)));
+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) {
diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h
index 4ecba83..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"
@@ -23,13 +26,7 @@ class QtTreeWidget : public QTreeView{
QtTreeWidgetItem* getRoot();
void setRosterModel(Roster* roster);
Roster* getRoster() {return roster_;}
- void setEditable(bool b) { editable_ = b; }
-
- signals:
- void onSomethingSelectedChanged(bool);
-
- public slots:
- void handleEditUserActionTriggered(bool checked);
+ boost::signal<void (RosterItem*)> onSomethingSelectedChanged;
private slots:
void handleItemActivated(const QModelIndex&);
@@ -38,22 +35,24 @@ class QtTreeWidget : public QTreeView{
void handleCollapsed(const QModelIndex&);
void handleClicked(const QModelIndex&);
protected:
- void contextMenuEvent(QContextMenuEvent* event);
- protected slots:
- virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous);
+ void dragEnterEvent(QDragEnterEvent* event);
+ void dropEvent(QDropEvent* event);
+ void dragMoveEvent(QDragMoveEvent* event);
+ protected:
+ QModelIndexList getSelectedIndexes() const;
private:
- void renameGroup(GroupRosterItem* group);
void drawBranches(QPainter*, const QRect&, const QModelIndex&) const;
- QModelIndexList getSelectedIndexes() const;
-
+ protected slots:
+ virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous);
+ protected:
+ UIEventStream* eventStream_;
+
private:
RosterModel* model_;
Roster* roster_;
RosterDelegate* delegate_;
QtTreeWidgetItem* treeRoot_;
- UIEventStream* eventStream_;
- bool editable_;
};
}
diff --git a/Swift/QtUI/Roster/QtTreeWidgetItem.cpp b/Swift/QtUI/Roster/QtTreeWidgetItem.cpp
deleted file mode 100644
index fcd8691..0000000
--- a/Swift/QtUI/Roster/QtTreeWidgetItem.cpp
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (c) 2010 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include "Swift/QtUI/Roster/QtTreeWidgetItem.h"
-#include "Swift/QtUI/Roster/QtTreeWidget.h"
-
-#include <qdebug.h>
-#include <QtAlgorithms>
-#include <algorithm>
-
-namespace Swift {
-
-QtTreeWidgetItem::QtTreeWidgetItem(QtTreeWidgetItem* parentItem) : QObject(), textColor_(0,0,0), backgroundColor_(255,255,255) {
- parent_ = parentItem;
- shown_ = true;
- expanded_ = true;
-}
-
-
-void QtTreeWidgetItem::setText(const std::string& text) {
- displayName_ = P2QSTRING(text);
- displayNameLower_ = displayName_.toLower();
- emit changed(this);
-}
-
-void QtTreeWidgetItem::setStatusText(const std::string& text) {
- statusText_ = P2QSTRING(text);
- emit changed(this);
-}
-
-void QtTreeWidgetItem::setAvatarPath(const std::string& path) {
- avatar_ = QIcon(P2QSTRING(path));
- emit changed(this);
-}
-
-void QtTreeWidgetItem::setStatusShow(StatusShow::Type show) {
- statusShowType_ = show;
- int color = 0;
- switch (show) {
- case StatusShow::Online: color = 0x000000; mergedShowType_ = StatusShow::Online; break;
- case StatusShow::Away: color = 0x336699; mergedShowType_ = StatusShow::Away; break;
- case StatusShow::XA: color = 0x336699; mergedShowType_ = StatusShow::Away; break;
- case StatusShow::FFC: color = 0x000000; mergedShowType_ = StatusShow::Online; break;
- case StatusShow::DND: color = 0x990000; mergedShowType_ = show; break;
- case StatusShow::None: color = 0x7F7F7F; mergedShowType_ = show; break;
- }
- setTextColor(color);
- emit changed(this);
-}
-
-void QtTreeWidgetItem::setTextColor(unsigned long color) {
- textColor_ = QColor(
- ((color & 0xFF0000)>>16),
- ((color & 0xFF00)>>8),
- (color & 0xFF));
-}
-
-void QtTreeWidgetItem::setBackgroundColor(unsigned long color) {
- backgroundColor_ = QColor(
- ((color & 0xFF0000)>>16),
- ((color & 0xFF00)>>8),
- (color & 0xFF));
-}
-
-void QtTreeWidgetItem::setExpanded(bool expanded) {
- expanded_ = expanded;
- emit changed(this);
-}
-
-void QtTreeWidgetItem::hide() {
- shown_ = false;
- emit changed(this);
-}
-
-void QtTreeWidgetItem::show() {
- shown_ = true;
- emit changed(this);
-}
-
-bool QtTreeWidgetItem::isShown() {
- return isContact() ? shown_ : shownChildren_.size() > 0;
-}
-
-QWidget* QtTreeWidgetItem::getCollapsedRosterWidget() {
- QWidget* widget = new QWidget();
- return widget;
-}
-
-QWidget* QtTreeWidgetItem::getExpandedRosterWidget() {
- QWidget* widget = new QWidget();
- return widget;
-}
-
-QtTreeWidgetItem::~QtTreeWidgetItem() {
- //It's possible (due to the way the roster deletes items in unknown order when it is deleted)
- // That the children will be deleted before the groups, or that the groups are deleted
- // before the children. If the children are deleted first, they will let the parent know that
- // They've been deleted. If the parent is deleted first, it must tell the children not to
- // tell it when they're deleted. Everything will be deleted in the end, because all the
- // widgets are owned by the Roster in Swiften.
- if (parent_) {
- parent_->removeChild(this);
- }
-
- for (int i = 0; i < children_.size(); i++) {
- children_[i]->parentItemHasBeenDeleted();
- }
-}
-
-void QtTreeWidgetItem::parentItemHasBeenDeleted() {
- parent_ = NULL;
-}
-
-QtTreeWidgetItem* QtTreeWidgetItem::getParentItem() {
- return parent_;
-}
-
-void QtTreeWidgetItem::addChild(QtTreeWidgetItem* child) {
- children_.append(child);
- connect(child, SIGNAL(changed(QtTreeWidgetItem*)), this, SLOT(handleChanged(QtTreeWidgetItem*)));
- handleChanged(child);
-}
-
-void QtTreeWidgetItem::removeChild(QtTreeWidgetItem* child) {
- children_.removeAll(child);
- handleChanged(this);
-}
-
-void bubbleSort(QList<QtTreeWidgetItem*>& list) {
- bool done = false;
- for (int i = 0; i < list.size() - 1 && !done; i++) {
- done = true;
- for (int j = i + 1; j < list.size(); j++) {
- if (*(list[j]) < *(list[j - 1])) {
- done = false;
- QtTreeWidgetItem* lower = list[j];
- list[j] = list[j - 1];
- list[j - 1] = lower;
- }
- }
- }
-}
-
-void QtTreeWidgetItem::handleChanged(QtTreeWidgetItem* child) {
- /*We don't use the much faster qStableSort because it causes changed(child) and all sorts of nasty recursion*/
- //qStableSort(children_.begin(), children_.end(), itemLessThan);
- //bubbleSort(children_);
- std::stable_sort(children_.begin(), children_.end(), itemLessThan);
- shownChildren_.clear();
- for (int i = 0; i < children_.size(); i++) {
- if (children_[i]->isShown()) {
- shownChildren_.append(children_[i]);
- }
- }
- emit changed(child);
-}
-
-int QtTreeWidgetItem::rowCount() {
- //qDebug() << "Returning size of " << children_.size() << " for item " << displayName_;
- return shownChildren_.size();
-}
-
-int QtTreeWidgetItem::rowOf(QtTreeWidgetItem* item) {
- return shownChildren_.indexOf(item);
-}
-
-int QtTreeWidgetItem::row() {
- return parent_ ? parent_->rowOf(this) : 0;
-}
-
-QtTreeWidgetItem* QtTreeWidgetItem::getItem(int row) {
- //qDebug() << "Returning row " << row << " from item " << displayName_;
- Q_ASSERT(row >= 0);
- Q_ASSERT(row < rowCount());
- return shownChildren_[row];
-}
-
-
-QVariant QtTreeWidgetItem::data(int role) {
- if (!isContact()) {
- setTextColor(0xFFFFFF);
- setBackgroundColor(0x969696);
- }
- switch (role) {
- case Qt::DisplayRole: return displayName_;
- case Qt::TextColorRole: return textColor_;
- case Qt::BackgroundColorRole: return backgroundColor_;
- case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant();
- case StatusTextRole: return statusText_;
- case AvatarRole: return avatar_;
- case PresenceIconRole: return getPresenceIcon();
- default: return QVariant();
- }
-}
-
-QString QtTreeWidgetItem::toolTipString() {
- return displayName_ + "\n " + statusText_;
-}
-
-QIcon QtTreeWidgetItem::getPresenceIcon() {
- QString iconString;
- switch (statusShowType_) {
- case StatusShow::Online: iconString = "online";break;
- case StatusShow::Away: iconString = "away";break;
- case StatusShow::XA: iconString = "away";break;
- case StatusShow::FFC: iconString = "online";break;
- case StatusShow::DND: iconString = "dnd";break;
- case StatusShow::None: iconString = "offline";break;
- }
- return QIcon(":/icons/" + iconString + ".png");
-}
-
-bool QtTreeWidgetItem::isContact() const {
- return children_.size() == 0;
-}
-
-bool QtTreeWidgetItem::isExpanded() {
- return expanded_;
-}
-
-bool QtTreeWidgetItem::operator<(const QtTreeWidgetItem& item) const {
- if (isContact()) {
- if (!item.isContact()) {
- return false;
- }
- return getStatusShowMerged() == item.getStatusShowMerged() ? getLowerName() < item.getLowerName() : getStatusShowMerged() < item.getStatusShowMerged();
- } else {
- if (item.isContact()) {
- return true;
- }
- return getLowerName() < item.getLowerName();
- }
-}
-
-bool itemLessThan(QtTreeWidgetItem* left, QtTreeWidgetItem* right) {
- return *left < *right;
-}
-
-}
diff --git a/Swift/QtUI/Roster/QtTreeWidgetItem.h b/Swift/QtUI/Roster/QtTreeWidgetItem.h
deleted file mode 100644
index 6855989..0000000
--- a/Swift/QtUI/Roster/QtTreeWidgetItem.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (c) 2010 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include <QColor>
-#include <QVariant>
-#include <string>
-
-#include "Swiften/Roster/TreeWidgetFactory.h"
-#include "Swiften/Roster/TreeWidget.h"
-#include "Swiften/Roster/TreeWidgetItem.h"
-#include "Swift/QtUI/QtSwiftUtil.h"
-
-namespace Swift {
- enum RosterRoles {
- StatusTextRole = Qt::UserRole,
- AvatarRole = Qt::UserRole + 1,
- PresenceIconRole = Qt::UserRole + 2,
- StatusShowTypeRole = Qt::UserRole + 3
- };
-
-class QtTreeWidget;
-class QtTreeWidgetItem : public QObject, public TreeWidgetItem {
- Q_OBJECT
- public:
- ~QtTreeWidgetItem();
- void addChild(QtTreeWidgetItem* child);
- void removeChild(QtTreeWidgetItem* child);
- QtTreeWidgetItem* getParentItem();
- int rowCount();
- int rowOf(QtTreeWidgetItem* item);
- int row();
- QtTreeWidgetItem* getItem(int row);
- QVariant data(int role);
- QIcon getPresenceIcon();
- QtTreeWidgetItem(QtTreeWidgetItem* parentItem);
- void setText(const std::string& text);
- void setAvatarPath(const std::string& path);
- void setStatusText(const std::string& text);
- void setStatusShow(StatusShow::Type show);
- void setTextColor(unsigned long color);
- void setBackgroundColor(unsigned long color);
- void setExpanded(bool b);
- void parentItemHasBeenDeleted();
- void hide();
- void show();
- bool isShown();
- bool isContact() const;
- bool isExpanded();
- const QString& getName() const {return displayName_;};
- const QString& getLowerName() const {return displayNameLower_;};
- StatusShow::Type getStatusShow() const {return statusShowType_;};
- StatusShow::Type getStatusShowMerged() const {return mergedShowType_;};
-
- QWidget* getCollapsedRosterWidget();
- QWidget* getExpandedRosterWidget();
- bool operator<(const QtTreeWidgetItem& item) const;
-
- signals:
- void changed(QtTreeWidgetItem*);
- private slots:
- void handleChanged(QtTreeWidgetItem* item);
- private:
- QString toolTipString();
- QList<QtTreeWidgetItem*> children_;
- QList<QtTreeWidgetItem*> shownChildren_;
- QtTreeWidgetItem* parent_;
- QString displayName_;
- QString displayNameLower_;
- QString statusText_;
- QColor textColor_;
- QColor backgroundColor_;
- QVariant avatar_;
- bool shown_;
- bool expanded_;
- StatusShow::Type statusShowType_;
- StatusShow::Type mergedShowType_;
-};
-
-bool itemLessThan(QtTreeWidgetItem* left, QtTreeWidgetItem* right);
-
-}
diff --git a/Swift/QtUI/Roster/RosterDelegate.cpp b/Swift/QtUI/Roster/RosterDelegate.cpp
index aaa6236..e40907a 100644
--- a/Swift/QtUI/Roster/RosterDelegate.cpp
+++ b/Swift/QtUI/Roster/RosterDelegate.cpp
@@ -12,7 +12,6 @@
#include <QBrush>
#include <QFontMetrics>
#include <QPainterPath>
-#include <QFileInfo>
#include <QPolygon>
#include <qdebug.h>
#include <QBitmap>
@@ -22,7 +21,6 @@
#include "QtTreeWidget.h"
#include "RosterModel.h"
-#include "QtScaledAvatarCache.h"
namespace Swift {
@@ -43,15 +41,8 @@ QSize RosterDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelI
return contactSizeHint(option, index);
}
-QSize RosterDelegate::contactSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const {
- int heightByAvatar = avatarSize_ + common_.verticalMargin * 2;
- QFontMetrics nameMetrics(common_.nameFont);
- QFontMetrics statusMetrics(common_.detailFont);
- int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height();
- //Doesn't work, yay! FIXME: why?
- //QSize size = (option.state & QStyle::State_Selected) ? QSize(150, 80) : QSize(150, avatarSize_ + margin_ * 2);
- //qDebug() << "Returning size" << size;
- return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar);
+QSize RosterDelegate::contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const {
+ return common_.contactSizeHint(option, index);
}
void RosterDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
@@ -70,70 +61,18 @@ void RosterDelegate::paintGroup(QPainter* painter, const QStyleOptionViewItem& o
}
void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
- //qDebug() << "painting" << index.data(Qt::DisplayRole).toString();
- painter->save();
- //QStyledItemDelegate::paint(painter, option, index);
- //initStyleOption(option, index);
- QRect fullRegion(option.rect);
- if ( option.state & QStyle::State_Selected ) {
- painter->fillRect(fullRegion, option.palette.highlight());
- painter->setPen(option.palette.highlightedText().color());
- } else {
- QColor nameColor = index.data(Qt::TextColorRole).value<QColor>();
- painter->setPen(QPen(nameColor));
- }
-
- QRect presenceIconRegion(QPoint(common_.farLeftMargin, fullRegion.top()), QSize(presenceIconWidth_, fullRegion.height() - common_.verticalMargin));
-
- int calculatedAvatarSize = presenceIconRegion.height();
- //This overlaps the presenceIcon, so must be painted first
- QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth_ / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize));
-
- QPixmap avatarPixmap;
+ QColor nameColor = index.data(Qt::TextColorRole).value<QColor>();
+ QString avatarPath;
if (index.data(AvatarRole).isValid() && !index.data(AvatarRole).value<QString>().isNull()) {
- QString avatarPath = index.data(AvatarRole).value<QString>();
- QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath);
- if (QFileInfo(scaledAvatarPath).exists()) {
- avatarPixmap.load(scaledAvatarPath);
- }
+ avatarPath = index.data(AvatarRole).value<QString>();
}
- if (avatarPixmap.isNull()) {
- avatarPixmap = QPixmap(":/icons/avatar.png").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
- }
-
- painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap);
-
- //Paint the presence icon over the top of the avatar
QIcon presenceIcon = index.data(PresenceIconRole).isValid() && !index.data(PresenceIconRole).value<QIcon>().isNull()
- ? index.data(PresenceIconRole).value<QIcon>()
- : QIcon(":/icons/offline.png");
- presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter);
-
- QFontMetrics nameMetrics(common_.nameFont);
- painter->setFont(common_.nameFont);
- int extraFontWidth = nameMetrics.width("H");
- int leftOffset = avatarRegion.right() + common_.horizontalMargin * 2 + extraFontWidth / 2;
- QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0/*-leftOffset*/, 0));
-
- int nameHeight = nameMetrics.height() + common_.verticalMargin;
- QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0));
-
- DelegateCommons::drawElidedText(painter, nameRegion, index.data(Qt::DisplayRole).toString());
-
-
- painter->setFont(common_.detailFont);
- painter->setPen(QPen(QColor(160,160,160)));
-
- QRect statusTextRegion(textRegion.adjusted(0, nameHeight, 0, 0));
- DelegateCommons::drawElidedText(painter, statusTextRegion, index.data(StatusTextRole).toString());
-
- painter->restore();
+ ? index.data(PresenceIconRole).value<QIcon>()
+ : QIcon(":/icons/offline.png");
+ QString name = index.data(Qt::DisplayRole).toString();
+ QString statusText = index.data(StatusTextRole).toString();
+ common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, 0);
}
-
-const int RosterDelegate::avatarSize_ = 20;
-const int RosterDelegate::presenceIconHeight_ = 16;
-const int RosterDelegate::presenceIconWidth_ = 16;
-
}
diff --git a/Swift/QtUI/Roster/RosterDelegate.h b/Swift/QtUI/Roster/RosterDelegate.h
index e6a16f2..253c11a 100644
--- a/Swift/QtUI/Roster/RosterDelegate.h
+++ b/Swift/QtUI/Roster/RosterDelegate.h
@@ -28,9 +28,5 @@ namespace Swift {
DelegateCommons common_;
GroupItemDelegate* groupDelegate_;
QtTreeWidget* tree_;
- static const int avatarSize_;
- static const int presenceIconHeight_;
- static const int presenceIconWidth_;
-
};
}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 494731c..82e4490 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -25,16 +25,12 @@ myenv.MergeFlags(env["SWIFT_CONTROLLERS_FLAGS"])
myenv.MergeFlags(env["SWIFTOOLS_FLAGS"])
if myenv["HAVE_XSS"] :
myenv.MergeFlags(env["XSS_FLAGS"])
+if env["PLATFORM"] == "posix" :
+ myenv.Append(LIBS = ["X11"])
if myenv["HAVE_SPARKLE"] :
myenv.MergeFlags(env["SPARKLE_FLAGS"])
myenv.MergeFlags(env["SWIFTEN_FLAGS"])
-myenv.MergeFlags(env["LIBIDN_FLAGS"])
-myenv.MergeFlags(env["BOOST_FLAGS"])
-myenv.MergeFlags(env.get("SQLITE_FLAGS", {}))
-myenv.MergeFlags(env["ZLIB_FLAGS"])
-myenv.MergeFlags(env["OPENSSL_FLAGS"])
-myenv.MergeFlags(env.get("LIBXML_FLAGS", ""))
-myenv.MergeFlags(env.get("EXPAT_FLAGS", ""))
+myenv.MergeFlags(env["SWIFTEN_DEP_FLAGS"])
if myenv.get("HAVE_GROWL", False) :
myenv.MergeFlags(myenv["GROWL_FLAGS"])
myenv.Append(CPPDEFINES = ["HAVE_GROWL"])
@@ -59,7 +55,7 @@ if env["PLATFORM"] == "win32" :
myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"])
myenv.Append(LIBS = "qtmain")
-myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("../resources/themes/Default"))))
+myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("#/Swift/resources/themes/Default"))))
sources = [
"main.cpp",
@@ -77,6 +73,7 @@ sources = [
"QtStatusWidget.cpp",
"QtScaledAvatarCache.cpp",
"QtSwift.cpp",
+ "QtURIHandler.cpp",
"QtChatView.cpp",
"QtChatTheme.cpp",
"QtChatTabs.cpp",
@@ -87,6 +84,9 @@ sources = [
"QtTabWidget.cpp",
"QtTextEdit.cpp",
"QtXMLConsoleWidget.cpp",
+ "QtFileTransferListWidget.cpp",
+ "QtFileTransferListItemModel.cpp",
+ "QtAdHocCommandWindow.cpp",
"QtUtilities.cpp",
"QtBookmarkDetailWindow.cpp",
"QtAddBookmarkWindow.cpp",
@@ -97,6 +97,7 @@ sources = [
"MessageSnippet.cpp",
"SystemMessageSnippet.cpp",
"QtElidingLabel.cpp",
+ "QtFormWidget.cpp",
"QtLineEdit.cpp",
"QtJoinMUCWindow.cpp",
"Roster/RosterModel.cpp",
@@ -105,6 +106,8 @@ sources = [
"Roster/RosterDelegate.cpp",
"Roster/GroupItemDelegate.cpp",
"Roster/DelegateCommons.cpp",
+ "Roster/QtRosterWidget.cpp",
+ "Roster/QtOccupantListWidget.cpp",
"EventViewer/EventModel.cpp",
"EventViewer/EventDelegate.cpp",
"EventViewer/TwoLineDelegate.cpp",
@@ -114,6 +117,7 @@ sources = [
"ChatList/ChatListModel.cpp",
"ChatList/ChatListDelegate.cpp",
"ChatList/ChatListMUCItem.cpp",
+ "ChatList/ChatListRecentItem.cpp",
"MUCSearch/QtMUCSearchWindow.cpp",
"MUCSearch/MUCSearchModel.cpp",
"MUCSearch/MUCSearchRoomItem.cpp",
@@ -131,25 +135,38 @@ sources = [
"QtWebView.cpp",
"qrc_DefaultTheme.cc",
"qrc_Swift.cc",
+ "QtFileTransferJSBridge.cpp",
+ "QtMUCConfigurationWindow.cpp",
]
myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift")
if env["PLATFORM"] == "win32" :
- myenv.RES("../resources/Windows/Swift.rc")
+ res = myenv.RES("#/Swift/resources/Windows/Swift.rc")
+ # For some reason, SCons isn't picking up the dependency correctly
+ # Adding it explicitly until i figure out why
+ myenv.Depends(res, "../Controllers/BuildVersion.h")
sources += [
"WindowsNotifier.cpp",
- "../resources/Windows/Swift.res"
+ "#/Swift/resources/Windows/Swift.res"
]
if env["PLATFORM"] == "posix" :
- sources += ["FreeDesktopNotifier.cpp"]
+ sources += [
+ "FreeDesktopNotifier.cpp",
+ "QtDBUSURIHandler.cpp",
+ ]
if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" :
swiftProgram = myenv.Program("Swift", sources)
else :
swiftProgram = myenv.Program("swift", sources)
+if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" :
+ openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp")
+else :
+ openURIProgram = []
+
myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui")
myenv.Uic4("UserSearch/QtUserSearchWizard.ui")
myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui")
@@ -162,7 +179,7 @@ myenv.Qrc("Swift.qrc")
# Resources
commonResources = {
- "": ["../resources/sounds"]
+ "": ["#/Swift/resources/sounds"]
}
################################################################################
@@ -171,16 +188,16 @@ commonResources = {
# Collect available languages
translation_languages = []
-for file in os.listdir(Dir("../Translations").abspath) :
+for file in os.listdir(Dir("#/Swift/Translations").abspath) :
if file.startswith("swift_") and file.endswith(".ts") :
translation_languages.append(file[6:-3])
# Generate translation modules
-translation_sources = [env.File("../Translations/swift.ts").abspath]
+translation_sources = [env.File("#/Swift/Translations/swift.ts").abspath]
translation_modules = []
for lang in translation_languages :
- translation_resource = "../resources/translations/swift_" + lang + ".qm"
- translation_source = "../Translations/swift_" + lang + ".ts"
+ translation_resource = "#/Swift/resources/translations/swift_" + lang + ".qm"
+ translation_source = "#/Swift/Translations/swift_" + lang + ".ts"
translation_sources.append(env.File(translation_source).abspath)
translation_modules.append(env.File(translation_resource).abspath)
myenv.Qm(translation_resource, translation_source)
@@ -214,20 +231,20 @@ if env["PLATFORM"] == "darwin" :
frameworks.append(env["SPARKLE_FRAMEWORK"])
if env["HAVE_GROWL"] :
frameworks.append(env["GROWL_FRAMEWORK"])
- commonResources[""] = commonResources.get("", []) + ["../resources/MacOSX/Swift.icns"]
- app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks)
+ commonResources[""] = commonResources.get("", []) + ["#/Swift/resources/MacOSX/Swift.icns"]
+ app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True)
if env["DIST"] :
myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"])
if env.get("SWIFT_INSTALLDIR", "") :
- env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram)
- env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "../resources/logo/logo-icon-32.xpm")
+ env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram + openURIProgram)
+ env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm")
icons_path = os.path.join(env["SWIFT_INSTALLDIR"], "share", "icons", "hicolor")
- env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "../resources/logo/logo-icon-32.xpm")
- env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "../resources/logo/logo-icon.svg")
+ env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm")
+ env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "#/Swift/resources/logo/logo-icon.svg")
for i in ["16", "22", "24", "64", "128"] :
- env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "../resources/logo/logo-icon-" + i + ".png")
- env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "../resources/swift.desktop")
+ env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "#/Swift/resources/logo/logo-icon-" + i + ".png")
+ env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "#/Swift/resources/swift.desktop")
for dir, resource in commonResources.items() :
env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "swift", dir), resource)
@@ -236,7 +253,7 @@ if env["PLATFORM"] == "win32" :
commonResources[""] = commonResources.get("", []) + [
os.path.join(env["OPENSSL_DIR"], "bin", "ssleay32.dll"),
os.path.join(env["OPENSSL_DIR"], "bin", "libeay32.dll"),
- "../resources/images",
+ "#/Swift/resources/images",
]
myenv.WindowsBundle("Swift",
resources = commonResources,
diff --git a/Swift/QtUI/main.cpp b/Swift/QtUI/main.cpp
index 0146769..2eef379 100644
--- a/Swift/QtUI/main.cpp
+++ b/Swift/QtUI/main.cpp
@@ -44,7 +44,7 @@ int main(int argc, char* argv[]) {
boost::program_options::variables_map vm;
try {
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
- } catch (boost::program_options::unknown_option option) {
+ } catch (const boost::program_options::unknown_option& option) {
#if BOOST_VERSION >= 104200
std::cout << "Ignoring unknown option " << option.get_option_name() << " but continuing." << std::endl;
#else
diff --git a/Swift/QtUI/swift-open-uri.cpp b/Swift/QtUI/swift-open-uri.cpp
new file mode 100644
index 0000000..2d5ef19
--- /dev/null
+++ b/Swift/QtUI/swift-open-uri.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2011 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <QCoreApplication>
+#include <QDBusConnection>
+#include <QDBusMessage>
+#include <iostream>
+
+int main(int argc, char* argv[]) {
+ QCoreApplication app(argc, argv);
+
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ if (!bus.isConnected()) {
+ return -1;
+ }
+ if (argc != 2) {
+ std::cerr << "Usage: " << argv[0] << " uri" << std::endl;
+ return -1;
+ }
+
+ QDBusMessage msg = QDBusMessage::createMethodCall("im.swift.Swift.URIHandler", "/", "im.swift.Swift.URIHandler", "openURI");
+ msg << argv[1];
+
+ bus.call(msg);
+
+ return 0;
+}
diff --git a/Swift/Translations/swift_nl.ts b/Swift/Translations/swift_nl.ts
index 0683260..7e8bfbd 100644
--- a/Swift/Translations/swift_nl.ts
+++ b/Swift/Translations/swift_nl.ts
@@ -416,6 +416,22 @@
<source>There was an error publishing your profile data</source>
<translation>Er is een fout opgetreden bij het publiceren van uw profiel</translation>
</message>
+ <message>
+ <source>%1% (%2%)</source>
+ <translation>%1% (%2%)</translation>
+ </message>
+ <message>
+ <source>%1% and %2% others (%3%)</source>
+ <translation>%1% en %2% anderen (%3%)</translation>
+ </message>
+ <message>
+ <source>%1%, %2% (%3%)</source>
+ <translation>%1%, %2% (%3%)</translation>
+ </message>
+ <message>
+ <source>User address invalid. User address should be of the form &apos;alice@wonderland.lit&apos;</source>
+ <translation>Gebruikersadres ongeldig. Gebruikersadres moet van de vorm &apos;alice@wonderland.lit&apos; zijn</translation>
+ </message>
</context>
<context>
<name>CloseButton</name>
@@ -835,6 +851,10 @@
<source>Bookmarked Rooms</source>
<translation>Bladwijzers</translation>
</message>
+ <message>
+ <source>Recent Chats</source>
+ <translation>Recente conversaties</translation>
+ </message>
</context>
<context>
<name>Swift::QtAboutWidget</name>
@@ -866,6 +886,37 @@
</message>
</context>
<context>
+ <name>Swift::QtAdHocCommandWindow</name>
+ <message>
+ <source>Cancel</source>
+ <translation>Annuleren</translation>
+ </message>
+ <message>
+ <source>Back</source>
+ <translation>Terug</translation>
+ </message>
+ <message>
+ <source>Next</source>
+ <translation>Volgende</translation>
+ </message>
+ <message>
+ <source>Complete</source>
+ <translation>Voltooien</translation>
+ </message>
+ <message>
+ <source>Error: %1</source>
+ <translation>Fout: %1</translation>
+ </message>
+ <message>
+ <source>Warning: %1</source>
+ <translation>Waarschuwing: %1</translation>
+ </message>
+ <message>
+ <source>Error executing command</source>
+ <translation>Fout bij het uitvoeren van de opdracht</translation>
+ </message>
+</context>
+<context>
<name>Swift::QtAvatarWidget</name>
<message>
<source>No picture</source>
@@ -1161,6 +1212,18 @@ afbeelding</translation>
<source>Enter &amp;Room</source>
<translation>&amp;Kamer betreden</translation>
</message>
+ <message>
+ <source>Run Server Command</source>
+ <translation>Voer opdracht op server uit</translation>
+ </message>
+ <message>
+ <source>Collecting commands...</source>
+ <translation>Opdrachten aan het verzamelen...</translation>
+ </message>
+ <message>
+ <source>No Available Commands</source>
+ <translation>Geen beschikbare opdrachten</translation>
+ </message>
</context>
<context>
<name>Swift::QtNameWidget</name>
diff --git a/Swift/resources/logo/dmg.png b/Swift/resources/logo/dmg.png
new file mode 100644
index 0000000..64df425
--- /dev/null
+++ b/Swift/resources/logo/dmg.png
Binary files differ