diff options
Diffstat (limited to 'Swift')
349 files changed, 13746 insertions, 2648 deletions
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md index 544dcfa..097de4d 100644 --- a/Swift/ChangeLog.md +++ b/Swift/ChangeLog.md @@ -1,3 +1,8 @@ +3.0-beta1 +--------- +- Allow toggling of a more compact roster display. +- Remember status settings and provide quick access to them with searching of recent selections in the status setter. + 2.1 --- - Fixed potential crash when using proxies on Mac OS X. diff --git a/Swift/Controllers/BlockListController.cpp b/Swift/Controllers/BlockListController.cpp new file mode 100644 index 0000000..e7bc45d --- /dev/null +++ b/Swift/Controllers/BlockListController.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/BlockListController.h> + +#include <boost/bind.hpp> + +#include <Swiften/Client/ClientBlockListManager.h> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/format.h> +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + +namespace Swift { + +BlockListController::BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController) : blockListManager_(blockListManager), blockListEditorWidgetFactory_(blockListEditorWidgetFactory), blockListEditorWidget_(0), eventController_(eventController), remainingRequests_(0) { + uiEventStream->onUIEvent.connect(boost::bind(&BlockListController::handleUIEvent, this, _1)); + blockListManager_->getBlockList()->onItemAdded.connect(boost::bind(&BlockListController::handleBlockListChanged, this)); + blockListManager_->getBlockList()->onItemRemoved.connect(boost::bind(&BlockListController::handleBlockListChanged, this)); +} + +BlockListController::~BlockListController() { + blockListManager_->getBlockList()->onItemAdded.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this)); + blockListManager_->getBlockList()->onItemRemoved.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this)); +} + +void BlockListController::blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID> &jidsToUnblock, std::vector<JID> &jidsToBlock) const { + foreach (const JID& jid, blockListBeforeEdit) { + if (std::find(newBlockList.begin(), newBlockList.end(), jid) == newBlockList.end()) { + jidsToUnblock.push_back(jid); + } + } + + foreach (const JID& jid, newBlockList) { + if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) { + jidsToBlock.push_back(jid); + } + } +} + +void BlockListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + // handle UI dialog + boost::shared_ptr<RequestBlockListDialogUIEvent> requestDialogEvent = boost::dynamic_pointer_cast<RequestBlockListDialogUIEvent>(rawEvent); + if (requestDialogEvent != NULL) { + if (blockListEditorWidget_ == NULL) { + blockListEditorWidget_ = blockListEditorWidgetFactory_->createBlockListEditorWidget(); + blockListEditorWidget_->onSetNewBlockList.connect(boost::bind(&BlockListController::handleSetNewBlockList, this, _1)); + } + blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); + blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + blockListEditorWidget_->show(); + return; + } + + // handle block state change + boost::shared_ptr<RequestChangeBlockStateUIEvent> changeStateEvent = boost::dynamic_pointer_cast<RequestChangeBlockStateUIEvent>(rawEvent); + if (changeStateEvent != NULL) { + if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Blocked) { + GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDRequest(changeStateEvent->getContact()); + blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false)); + blockRequest->send(); + } else if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Unblocked) { + GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDRequest(changeStateEvent->getContact()); + unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false)); + unblockRequest->send(); + } + return; + } +} + +void BlockListController::handleBlockResponse(GenericRequest<BlockPayload>::ref request, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) { + if (error) { + std::string errorMessage; + // FIXME: Handle reporting of list of JIDs in a translatable way. + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to block %1%.")) % jids.at(0).toString()); + if (!error->getText().empty()) { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); + } + eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage)); + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0)) { + blockListEditorWidget_->setBusy(false); + } + } +} + +void BlockListController::handleUnblockResponse(GenericRequest<UnblockPayload>::ref request, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) { + if (error) { + std::string errorMessage; + // FIXME: Handle reporting of list of JIDs in a translatable way. + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to unblock %1%.")) % jids.at(0).toString()); + if (!error->getText().empty()) { + errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); + } + eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage)); + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0)) { + blockListEditorWidget_->setBusy(false); + } + } +} + +void BlockListController::handleSetNewBlockList(const std::vector<JID> &newBlockList) { + std::vector<JID> jidsToBlock; + std::vector<JID> jidsToUnblock; + + blockListDifferences(newBlockList, jidsToUnblock, jidsToBlock); + + if (!jidsToBlock.empty()) { + remainingRequests_++; + GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDsRequest(jidsToBlock); + blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, jidsToBlock, true)); + blockRequest->send(); + } + if (!jidsToUnblock.empty()) { + remainingRequests_++; + GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDsRequest(jidsToUnblock); + unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, jidsToUnblock, true)); + unblockRequest->send(); + } + if (!jidsToBlock.empty() || jidsToUnblock.empty()) { + assert(blockListEditorWidget_); + blockListEditorWidget_->setBusy(true); + } +} + +void BlockListController::handleBlockListChanged() { + if (blockListEditorWidget_) { + std::vector<JID> jidsToBlock; + std::vector<JID> jidsToUnblock; + + blockListDifferences(blockListEditorWidget_->getCurrentBlockList(), jidsToUnblock, jidsToBlock); + blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); + + foreach (const JID& jid, jidsToBlock) { + blockListBeforeEdit.push_back(jid); + } + + foreach (const JID& jid, jidsToUnblock) { + blockListBeforeEdit.erase(std::remove(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid), blockListBeforeEdit.end());; + } + + blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + } +} + +} diff --git a/Swift/Controllers/BlockListController.h b/Swift/Controllers/BlockListController.h new file mode 100644 index 0000000..4c9caad --- /dev/null +++ b/Swift/Controllers/BlockListController.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 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 <Swiften/Queries/GenericRequest.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h> + +namespace Swift { + +class BlockPayload; +class UnblockPayload; +class ClientBlockListManager; +class EventController; + +class BlockListController { +public: + BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController); + ~BlockListController(); + +private: + void blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID>& jidsToUnblock, std::vector<JID>& jidsToBlock) const; + + void handleUIEvent(boost::shared_ptr<UIEvent> event); + + void handleBlockResponse(GenericRequest<BlockPayload>::ref, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor); + void handleUnblockResponse(GenericRequest<UnblockPayload>::ref, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor); + + void handleSetNewBlockList(const std::vector<JID>& newBlockList); + + void handleBlockListChanged(); + +private: + ClientBlockListManager* blockListManager_; + BlockListEditorWidgetFactory* blockListEditorWidgetFactory_; + BlockListEditorWidget* blockListEditorWidget_; + EventController* eventController_; + std::vector<JID> blockListBeforeEdit; + int remainingRequests_; +}; + +} diff --git a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h new file mode 100644 index 0000000..0f85a8a --- /dev/null +++ b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> + +namespace Swift { + class AutoAcceptMUCInviteDecider { + public: + AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) { + } + + bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) { + if (!invite->getIsImpromptu() && !invite->getIsContinuation()) { + return false; + } + + std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE); + if (auto_accept_mode == "no") { + return false; + } else if (auto_accept_mode == "presence") { + return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both; + } else if (auto_accept_mode == "domain") { + return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_; + } else { + assert(false); + } + } + + private: + JID domain_; + XMPPRoster* roster_; + SettingsProvider* settings_; + }; +} diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 16b22fe..13fd22b 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,47 +1,55 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/ChatController.h" +#include <Swift/Controllers/Chat/ChatController.h> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <stdio.h> -#include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.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/ChatWindowFactory.h> #include <Swiften/Client/NickResolver.h> +#include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Idle.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/ClientBlockListManager.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.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> #include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/SettingConstants.h> - -#include <Swiften/Base/Log.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> 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, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings) { +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, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -62,6 +70,10 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ startMessage = str(format(QT_TRANSLATE_NOOP("", "Starting chat with %1% - %2%")) % nick % contact.toBare().toString()); theirPresence = contact.isBare() ? presenceOracle->getHighestPriorityPresence(contact.toBare()) : presenceOracle->getLastPresence(contact); } + Idle::ref idle; + if (theirPresence && (idle = theirPresence->getPayload<Idle>())) { + startMessage += str(format(QT_TRANSLATE_NOOP("", ", who has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); + } startMessage += ": " + statusShowTypeToFriendlyName(theirPresence ? theirPresence->getShow() : StatusShow::None); if (theirPresence && !theirPresence->getStatus().empty()) { startMessage += " (" + theirPresence->getStatus() + ")"; @@ -69,7 +81,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None; chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available); startMessage += "."; - chatWindow_->addSystemMessage(startMessage); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(startMessage), ChatWindow::DefaultDirection); 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)); @@ -79,9 +91,13 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatWindow_->onWhiteboardSessionAccept.connect(boost::bind(&ChatController::handleWhiteboardSessionAccept, this)); chatWindow_->onWhiteboardSessionCancel.connect(boost::bind(&ChatController::handleWhiteboardSessionCancel, this)); chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this)); + chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this)); + chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this)); + chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1)); handleBareJIDCapsChanged(toJID_); settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1)); + eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1)); } void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) { @@ -91,6 +107,7 @@ void ChatController::handleContactNickChanged(const JID& jid, const std::string& } ChatController::~ChatController() { + eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1)); settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1)); nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2)); delete chatStateNotifier_; @@ -139,6 +156,19 @@ void ChatController::setToJID(const JID& jid) { handleBareJIDCapsChanged(toJID_); } +void ChatController::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { + ChatControllerBase::setAvailableServerFeatures(info); + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&ChatController::handleBlockingItemAdded, this, _1)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingItemRemoved, this, _1)); + + handleBlockingStateChanged(); + } +} + bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { return false; } @@ -174,8 +204,11 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me } } -void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { +void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { eventController_->handleIncomingEvent(messageEvent); + if (!messageEvent->getConcluded()) { + highlighter_->handleHighlightAction(highlight); + } } @@ -207,13 +240,72 @@ void ChatController::checkForDisplayingDisplayReceiptsAlert() { } } +void ChatController::handleBlockingStateChanged() { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + if (blockList->getState() == BlockList::Available) { + if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) { + chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); + chatWindow_->setInputEnabled(false); + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + } else { + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + } + } +} + +void ChatController::handleBlockingItemAdded(const JID& jid) { + if (toJID_ == (isInMUC_ ? jid: jid.toBare())) { + chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "You've currently blocked this contact. To continue your conversation you have to unblock the contact first.")); + chatWindow_->setInputEnabled(false); + chatWindow_->setBlockingState(ChatWindow::IsBlocked); + } +} + +void ChatController::handleBlockingItemRemoved(const JID& jid) { + if (toJID_ == (isInMUC_ ? jid: jid.toBare())) { + // FIXME: Support for different types of alerts. + chatWindow_->cancelAlert(); + chatWindow_->setInputEnabled(true); + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + } +} + +void ChatController::handleBlockUserRequest() { + if (isInMUC_) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); + } else { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_.toBare())); + } +} + +void ChatController::handleUnblockUserRequest() { + if (isInMUC_) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_)); + } else { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, toJID_.toBare())); + } +} + +void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) { + boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs)); + eventStream_->send(event); +} + +void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); + if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) { + onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason()); + } +} + + void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) { boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); if (replace) { eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); - replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time()); + replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } 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()); + myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), avatarManager_->getAvatarPath(selfJID_), boost::posix_time::microsec_clock::universal_time(), HighlightAction()); } if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) { @@ -288,7 +380,7 @@ void ChatController::handleFileTransferStart(std::string id, std::string descrip } void ChatController::handleFileTransferAccept(std::string id, std::string filename) { - SWIFT_LOG(debug) "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + SWIFT_LOG(debug) << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; if (ftControllers.find(id) != ftControllers.end()) { ftControllers[id]->accept(filename); } else { @@ -332,6 +424,11 @@ std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> pr response = QT_TRANSLATE_NOOP("", "%1% is now busy"); } } + Idle::ref idle; + if ((idle = presence->getPayload<Idle>())) { + response += str(format(QT_TRANSLATE_NOOP("", " and has been idle since %1%")) % dateTimeToLocalString(idle->getSince())); + } + if (!response.empty()) { response = str(format(response) % nick); } @@ -366,9 +463,9 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc std::string newStatusChangeString = getStatusChangeString(newPresence); if (newStatusChangeString != lastStatusChangeString_) { if (lastWasPresence_) { - chatWindow_->replaceLastMessage(newStatusChangeString); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString)); } else { - chatWindow_->addPresenceMessage(newStatusChangeString); + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); } lastStatusChangeString_ = newStatusChangeString; lastWasPresence_ = true; @@ -393,4 +490,10 @@ void ChatController::logMessage(const std::string& message, const JID& fromJID, } } +ChatWindow* ChatController::detachChatWindow() { + chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + return ChatControllerBase::detachChatWindow(); +} + } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 66ec37d..f8b6d8b 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -22,17 +22,22 @@ namespace Swift { class FileTransferController; class SettingsProvider; class HistoryController; + class HighlightManager; + class ClientBlockListManager; + class UIEvent; class ChatController : public ChatControllerBase { public: - 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, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry); + 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, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); virtual ~ChatController(); virtual void setToJID(const JID& jid); + virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); virtual void setOnline(bool online); virtual void handleNewFileTransferController(FileTransferController* ftc); virtual void handleWhiteboardSessionRequest(bool senderIsSelf); virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/); + virtual ChatWindow* detachChatWindow(); protected: void cancelReplaces(); @@ -45,7 +50,7 @@ namespace Swift { bool isIncomingMessageFromMe(boost::shared_ptr<Message> message); void postSendMessage(const std::string &body, boost::shared_ptr<Stanza> sentStanza); void preHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); - void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent); + void postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction&); void preSendMessageRequest(boost::shared_ptr<Message>); std::string senderDisplayNameFromMessage(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const; @@ -66,6 +71,19 @@ namespace Swift { void handleSettingChanged(const std::string& settingPath); void checkForDisplayingDisplayReceiptsAlert(); + void handleBlockingStateChanged(); + void handleBlockingItemAdded(const JID&); + void handleBlockingItemRemoved(const JID&); + + void handleBlockUserRequest(); + void handleUnblockUserRequest(); + + void handleInviteToChat(const std::vector<JID>& droppedJIDs); + void handleInviteToMUCWindowDismissed(); + void handleInviteToMUCWindowCompleted(); + + void handleUIEvent(boost::shared_ptr<UIEvent> event); + private: NickResolver* nickResolver_; ChatStateNotifier* chatStateNotifier_; @@ -85,6 +103,11 @@ namespace Swift { std::map<std::string, FileTransferController*> ftControllers; SettingsProvider* settings_; std::string lastWbID_; + + ClientBlockListManager* clientBlockListManager_; + boost::bsignals::scoped_connection blockingOnStateChangedConnection_; + boost::bsignals::scoped_connection blockingOnItemAddedConnection_; + boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 50709f7..23137dc 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/ChatControllerBase.h" +#include <Swift/Controllers/Chat/ChatControllerBase.h> #include <sstream> #include <map> @@ -13,32 +13,42 @@ #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <boost/algorithm/string.hpp> -#include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Path.h> #include <Swiften/Base/String.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Elements/Delay.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.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> #include <Swiften/Avatars/AvatarManager.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> 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, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry) { +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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) { chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); chatWindow_->onLogCleared.connect(boost::bind(&ChatControllerBase::handleLogCleared, this)); entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); + highlighter_ = highlightManager->createHighlighter(); setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); } @@ -51,17 +61,29 @@ void ChatControllerBase::handleLogCleared() { cancelReplaces(); } +ChatWindow* ChatControllerBase::detachChatWindow() { + ChatWindow* chatWindow = chatWindow_; + chatWindow_ = NULL; + return chatWindow; +} + void ChatControllerBase::handleCapsChanged(const JID& jid) { if (jid.compare(toJID_, JID::WithoutResource) == 0) { handleBareJIDCapsChanged(jid); } } +void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) { + if (chatWindow_) { + chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu); + } +} + void ChatControllerBase::createDayChangeTimer() { if (timerFactory_) { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); boost::posix_time::ptime midnight(now.date() + boost::gregorian::days(1)); - long millisecondsUntilMidnight = (midnight - now).total_milliseconds(); + int millisecondsUntilMidnight = boost::numeric_cast<int>((midnight - now).total_milliseconds()); dateChangeTimer_ = boost::shared_ptr<Timer>(timerFactory_->createTimer(millisecondsUntilMidnight)); dateChangeTimer_->onTick.connect(boost::bind(&ChatControllerBase::handleDayChangeTick, this)); dateChangeTimer_->start(); @@ -71,7 +93,7 @@ void ChatControllerBase::createDayChangeTimer() { void ChatControllerBase::handleDayChangeTick() { dateChangeTimer_->stop(); boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection); dayTicked(); createDayChangeTimer(); } @@ -112,7 +134,7 @@ void ChatControllerBase::handleAllMessagesRead() { } int ChatControllerBase::getUnreadCount() { - return targetedUnreadMessages_.size(); + return boost::numeric_cast<int>(targetedUnreadMessages_.size()); } void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) { @@ -175,19 +197,19 @@ void ChatControllerBase::activateChatWindow() { chatWindow_->activate(); } -std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { +std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { if (boost::starts_with(message, "/me ")) { - return chatWindow_->addAction(String::getSplittedAtFirst(message, ' ').second, senderName, senderIsSelf, label, avatarPath, time); + return chatWindow_->addAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); } else { - return chatWindow_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time); + return chatWindow_->addMessage(chatMessageParser_->parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight); } } -void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { +void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { if (boost::starts_with(message, "/me ")) { - chatWindow_->replaceWithAction(String::getSplittedAtFirst(message, ' ').second, id, time); + chatWindow_->replaceWithAction(chatMessageParser_->parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight); } else { - chatWindow_->replaceMessage(message, id, time); + chatWindow_->replaceMessage(chatMessageParser_->parseMessageBody(message), id, time, highlight); } } @@ -205,9 +227,12 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m } boost::shared_ptr<Message> message = messageEvent->getStanza(); std::string body = message->getBody(); + HighlightAction highlight; if (message->isError()) { - std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); - chatWindow_->addErrorMessage(errorMessage); + if (!message->getTo().getResource().empty()) { + std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + } } else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { handleMUCInvitation(messageEvent->getStanza()); @@ -231,7 +256,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); std::ostringstream s; s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 << " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << "."; - chatWindow_->addSystemMessage(std::string(s.str())); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection); } boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>(); @@ -243,6 +268,11 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m } onActivity(body); + // Highlight + if (!isIncomingMessageFromMe(message)) { + highlight = highlighter_->findAction(body, senderDisplayNameFromMessage(from)); + } + boost::shared_ptr<Replace> replace = message->getPayload<Replace>(); if (replace) { std::string body = message->getBody(); @@ -250,19 +280,19 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m std::map<JID, std::string>::iterator lastMessage; lastMessage = lastMessagesUIID_.find(from); if (lastMessage != lastMessagesUIID_.end()) { - replaceMessage(body, lastMessagesUIID_[from], timeStamp); + replaceMessage(body, lastMessagesUIID_[from], timeStamp, highlight); } } else { - lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp); + lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, avatarManager_->getAvatarPath(from), timeStamp, highlight); } logMessage(body, from, selfJID_, timeStamp, true); } chatWindow_->show(); - chatWindow_->setUnreadMessageCount(unreadMessages_.size()); + chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); - postHandleIncomingMessage(messageEvent); + postHandleIncomingMessage(messageEvent, highlight); } std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) { @@ -296,23 +326,28 @@ std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); } } + assert(false); return defaultMessage; } void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) { unreadMessages_.push_back(event); chatWindow_->show(); - chatWindow_->setUnreadMessageCount(unreadMessages_.size()); + chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size())); onUnreadCountChanged(); - chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect()); + chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu()); eventController_->handleIncomingEvent(event); } void ChatControllerBase::handleMUCInvitation(Message::ref message) { MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); - MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true); - handleGeneralMUCInvitation(inviteEvent); + if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { + eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true)); + } else { + MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu()); + handleGeneralMUCInvitation(inviteEvent); + } } void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { @@ -327,10 +362,8 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { password = *message->getPayload<MUCUserPayload>()->getPassword(); } - MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false); + MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false); handleGeneralMUCInvitation(inviteEvent); } - - } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index b26af02..7db94a4 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -8,72 +8,82 @@ #include <map> #include <vector> +#include <string> + #include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include <boost/filesystem.hpp> +#include <boost/filesystem/path.hpp> #include <boost/optional.hpp> #include <boost/date_time/posix_time/posix_time.hpp> -#include "Swiften/Network/Timer.h" -#include "Swiften/Network/TimerFactory.h" -#include "Swiften/Elements/Stanza.h" -#include <string> -#include "Swiften/Elements/DiscoInfo.h" -#include "Swift/Controllers/XMPPEvents/MessageEvent.h" +#include <Swiften/Network/Timer.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Elements/Stanza.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/SecurityLabelsCatalog.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Base/IDGenerator.h> +#include <Swiften/MUC/MUCRegistry.h> + +#include <Swift/Controllers/XMPPEvents/MessageEvent.h> #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/SecurityLabelsCatalog.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/Base/IDGenerator.h" #include <Swift/Controllers/HistoryController.h> -#include <Swiften/MUC/MUCRegistry.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class IQRouter; class StanzaChannel; - class ChatWindow; class ChatWindowFactory; class AvatarManager; class UIEventStream; class EventController; class EntityCapsProvider; + class HighlightManager; + class Highlighter; + class ChatMessageParser; + class AutoAcceptMUCInviteDecider; class ChatControllerBase : public boost::bsignals::trackable { public: virtual ~ChatControllerBase(); void showChatWindow(); void activateChatWindow(); - void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); + virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<MessageEvent> message); - 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& time); - void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); + void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); virtual void setOnline(bool online); virtual void setEnabled(bool enabled); - virtual void setToJID(const JID& jid) {toJID_ = jid;}; + 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); + void setCanStartImpromptuChats(bool supportsImpromptu); + virtual ChatWindow* detachChatWindow(); + boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC; 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, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry); + 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, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); /** * Pass the Message appended, and the stanza used to send it. */ - virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {}; + virtual void postSendMessage(const std::string&, boost::shared_ptr<Stanza>) {} virtual std::string senderDisplayNameFromMessage(const JID& from) = 0; virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0; - virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}; - virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {}; - virtual void preSendMessageRequest(boost::shared_ptr<Message>) {}; + virtual void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>) {} + virtual void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&) {} + virtual void preSendMessageRequest(boost::shared_ptr<Message>) {} 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 dayTicked() {} virtual void handleBareJIDCapsChanged(const JID& jid) = 0; std::string getErrorMessage(boost::shared_ptr<ErrorPayload>); virtual void setContactIsReceivingPresence(bool /* isReceivingPresence */) {} @@ -116,5 +126,9 @@ namespace Swift { SecurityLabelsCatalog::Item lastLabel_; HistoryController* historyController_; MUCRegistry* mucRegistry_; + Highlighter* highlighter_; + ChatMessageParser* chatMessageParser_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + UIEventStream* eventStream_; }; } diff --git a/Swift/Controllers/Chat/ChatMessageParser.cpp b/Swift/Controllers/Chat/ChatMessageParser.cpp new file mode 100644 index 0000000..ce184ea --- /dev/null +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Chat/ChatMessageParser.h> + +#include <vector> +#include <utility> + +#include <boost/smart_ptr/make_shared.hpp> +#include <boost/algorithm/string.hpp> + +#include <Swiften/Base/Regex.h> +#include <Swiften/Base/foreach.h> + +#include <SwifTools/Linkify.h> + + +namespace Swift { + + ChatMessageParser::ChatMessageParser(const std::map<std::string, std::string>& emoticons) : emoticons_(emoticons) { + + } + + typedef std::pair<std::string, std::string> StringPair; + + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body) { + ChatWindow::ChatMessage parsedMessage; + std::string remaining = body; + /* Parse one, URLs */ + while (!remaining.empty()) { + bool found = false; + std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining); + remaining = ""; + for (size_t i = 0; i < links.first.size(); i++) { + const std::string& part = links.first[i]; + if (found) { + // Must be on the last part, then + remaining = part; + } + else { + if (i == links.second) { + found = true; + parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part)); + } + else { + parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part)); + } + } + } + } + + + + std::string regexString; + /* Parse two, emoticons */ + foreach (StringPair emoticon, emoticons_) { + /* Construct a regexp that finds an instance of any of the emoticons inside a group */ + regexString += regexString.empty() ? "(" : "|"; + regexString += Regex::escape(emoticon.first); + } + if (!regexString.empty()) { + regexString += ")"; + boost::regex emoticonRegex(regexString); + + ChatWindow::ChatMessage newMessage; + foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + try { + boost::match_results<std::string::const_iterator> match; + const std::string& text = textPart->text; + std::string::const_iterator start = text.begin(); + while (regex_search(start, text.end(), match, emoticonRegex)) { + std::string::const_iterator matchStart = match[0].first; + std::string::const_iterator matchEnd = match[0].second; + if (start != matchStart) { + /* If we're skipping over plain text since the previous emoticon, record it as plain text */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart))); + } + boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>(); + std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(match.str()); + assert (emoticonIterator != emoticons_.end()); + const StringPair& emoticon = *emoticonIterator; + emoticonPart->imagePath = emoticon.second; + emoticonPart->alternativeText = emoticon.first; + newMessage.append(emoticonPart); + start = matchEnd; + } + if (start != text.end()) { + /* If there's plain text after the last emoticon, record it */ + newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end()))); + } + + } + catch (std::runtime_error) { + /* Basically too expensive to compute the regex results and it gave up, so pass through as text */ + newMessage.append(part); + } + } + else { + newMessage.append(part); + } + } + parsedMessage = newMessage; + + } + return parsedMessage; + } +} diff --git a/Swift/Controllers/Chat/ChatMessageParser.h b/Swift/Controllers/Chat/ChatMessageParser.h new file mode 100644 index 0000000..c9b9456 --- /dev/null +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + +namespace Swift { + + class ChatMessageParser { + public: + ChatMessageParser(const std::map<std::string, std::string>& emoticons); + ChatWindow::ChatMessage parseMessageBody(const std::string& body); + private: + std::map<std::string, std::string> emoticons_; + + }; +} diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 1e0e9c2..5d69019 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,48 +1,93 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/ChatsManager.h" +#include <Swift/Controllers/Chat/ChatsManager.h> #include <boost/bind.hpp> #include <boost/algorithm/string.hpp> #include <boost/smart_ptr/make_shared.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> +#include <boost/serialization/vector.hpp> +#include <boost/serialization/map.hpp> +#include <boost/serialization/string.hpp> +#include <boost/serialization/split_free.hpp> #include <Swiften/Base/foreach.h> +#include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swiften/Elements/ChatState.h> +#include <Swiften/Elements/MUCUserPayload.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/MUC/MUCBookmarkManager.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Elements/MUCInvitationPayload.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/StanzaChannel.h> + #include <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <Swift/Controllers/Chat/MUCSearchController.h> +#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.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/UIEvents/InviteToMUCUIEvent.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/Elements/MUCUserPayload.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Elements/DeliveryReceiptRequest.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> -#include <Swiften/Elements/MUCInvitationPayload.h> -#include <Swiften/Roster/XMPPRoster.h> #include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> -#include <Swiften/Client/StanzaChannel.h> #include <Swift/Controllers/WhiteboardManager.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/StringCodecs/Base64.h> +#include <Swiften/Base/Log.h> + +namespace boost { +namespace serialization { + template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { + std::string jidStr = jid.toString(); + ar << jidStr; + } + + template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { + std::string stringJID; + ar >> stringJID; + jid = Swift::JID(stringJID); + } + + template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){ + split_free(ar, t, file_version); + } + + template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int /*version*/) { + ar & chat.jid; + ar & chat.chatName; + ar & chat.activity; + ar & chat.isMUC; + ar & chat.nick; + ar & chat.impromptuJIDs; + } +} +} namespace Swift { @@ -74,7 +119,11 @@ ChatsManager::ChatsManager( bool eagleMode, SettingsProvider* settings, HistoryController* historyController, - WhiteboardManager* whiteboardManager) : + WhiteboardManager* whiteboardManager, + HighlightManager* highlightManager, + ClientBlockListManager* clientBlockListManager, + const std::map<std::string, std::string>& emoticons, + UserSearchController* inviteUserSearchController) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), @@ -86,7 +135,10 @@ ChatsManager::ChatsManager( eagleMode_(eagleMode), settings_(settings), historyController_(historyController), - whiteboardManager_(whiteboardManager) { + whiteboardManager_(whiteboardManager), + highlightManager_(highlightManager), + clientBlockListManager_(clientBlockListManager), + inviteUserSearchController_(inviteUserSearchController) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -100,6 +152,7 @@ ChatsManager::ChatsManager( uiEventStream_ = uiEventStream; mucBookmarkManager_ = NULL; profileSettings_ = profileSettings; + chatMessageParser_ = new ChatMessageParser(emoticons); presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1)); uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1)); @@ -127,6 +180,8 @@ ChatsManager::ChatsManager( setupBookmarks(); loadRecents(); + + autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); } ChatsManager::~ChatsManager() { @@ -144,25 +199,26 @@ ChatsManager::~ChatsManager() { } delete mucBookmarkManager_; delete mucSearchController_; + delete chatMessageParser_; + delete autoAcceptMUCInviteDecider_; } 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.empty()) { - /* Work around Boost bug https://svn.boost.org/trac/boost/ticket/4751 */ - activity.push_back(""); - } - std::string recent = chat.jid.toString() + "\t" + (eagleMode_ ? "" : activity[0]) + "\t" + (chat.isMUC ? "true" : "false") + "\t" + chat.nick; - recents += recent + "\n"; - if (i++ > 25) { - break; + std::stringstream serializeStream; + boost::archive::text_oarchive oa(serializeStream); + std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); + if (recentsLimited.size() > 25) { + recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end()); + } + if (eagleMode_) { + foreach(ChatListWindow::Chat& chat, recentsLimited) { + chat.activity = ""; } } - profileSettings_->storeString(RECENT_CHATS, recents); + + oa << recentsLimited; + std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); + profileSettings_->storeString(RECENT_CHATS, serializedStr); } void ChatsManager::handleClearRecentsRequested() { @@ -204,43 +260,70 @@ void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) } } -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; +ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const { + ChatListWindow::Chat fixedChat = chat; + if (fixedChat.isMUC) { + if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) { + fixedChat.statusType = StatusShow::Online; } - JID jid(recent[0]); - if (!jid.isValid()) { - continue; + } else { + if (avatarManager_) { + fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid); } - 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; + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(fixedChat.jid.toBare()); + fixedChat.statusType = presence ? presence->getShow() : StatusShow::None; + } + return fixedChat; +} + +void ChatsManager::loadRecents() { + std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS)); + if (recentsString.find("\t") != std::string::npos) { + // old format + 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; } - } else { - if (avatarManager_) { - path = avatarManager_->getAvatarPath(jid); + std::vector<std::string> recent; + boost::split(recent, recentString, boost::is_any_of("\t")); + if (recent.size() < 4) { + continue; } - Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare()); - type = presence ? presence->getShow() : StatusShow::None; + 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; + + ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick); + chat = updateChatStatusAndAvatarHelper(chat); + prependRecent(chat); + } + } else if (!recentsString.empty()){ + // boost searilaize based format + ByteArray debase64 = Base64::decode(recentsString); + std::vector<ChatListWindow::Chat> recentChats; + std::stringstream deserializeStream(std::string((const char*)debase64.data(), debase64.size())); + try { + boost::archive::text_iarchive ia(deserializeStream); + ia >> recentChats; + } catch (const boost::archive::archive_exception& e) { + SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl; + return; } - ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick); - prependRecent(chat); + foreach(ChatListWindow::Chat chat, recentChats) { + chat.statusType = StatusShow::None; + chat = updateChatStatusAndAvatarHelper(chat); + prependRecent(chat); + } } handleUnreadCountChanged(NULL); } @@ -268,7 +351,7 @@ void ChatsManager::handleBookmarksReady() { void ChatsManager::handleMUCBookmarkAdded(const MUCBookmark& bookmark) { std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom()); if (it == mucControllers_.end() && bookmark.getAutojoin()) { - handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false); + handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false ); } chatListWindow_->addMUCBookmark(bookmark); } @@ -289,9 +372,16 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const type = StatusShow::Online; } nick = controller->getNick(); + + if (controller->isImpromptu()) { + ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick); + typedef std::pair<std::string, JID> StringJIDPair; + std::map<std::string, JID> participants = controller->getParticipantJIDs(); + chat.impromptuJIDs = participants; + return chat; + } } return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick); - } else { ChatController* controller = getChatControllerIfExists(jid, false); if (controller) { @@ -340,14 +430,33 @@ void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { chatListWindow_->setUnreadCount(unreadTotal); } +boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const ChatListWindow::Chat& chat) { + std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat); + if (result != recentChats_.end()) { + ChatListWindow::Chat existingChat = *result; + recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); + return boost::optional<ChatListWindow::Chat>(existingChat); + } else { + return boost::optional<ChatListWindow::Chat>(); + } +} + void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) { - recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); - recentChats_.push_front(chat); + boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); + ChatListWindow::Chat mergedChat = chat; + if (oldChat && !oldChat->impromptuJIDs.empty()) { + mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); + } + recentChats_.push_front(mergedChat); } void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) { - recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); - recentChats_.push_back(chat); + boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat); + ChatListWindow::Chat mergedChat = chat; + if (oldChat && !oldChat->impromptuJIDs.empty()) { + mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end()); + } + recentChats_.push_back(mergedChat); } void ChatsManager::handleUserLeftMUC(MUCController* mucController) { @@ -375,6 +484,27 @@ void ChatsManager::handleSettingChanged(const std::string& settingPath) { } } +void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID) { + // send impromptu invites for the new MUC + std::vector<JID> missingJIDsToInvite = jidsToInvite; + + typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc->getOccupants(); + foreach(StringMUCOccupantPair occupant, occupants) { + boost::optional<JID> realJID = occupant.second.getRealJID(); + if (realJID) { + missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end()); + } + } + + if (reuseChatJID) { + muc->invitePerson(reuseChatJID.get(), reason, true, true); + } + foreach(const JID& jid, missingJIDsToInvite) { + muc->invitePerson(jid, reason, true); + } +} + void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<RequestChatUIEvent> chatEvent = boost::dynamic_pointer_cast<RequestChatUIEvent>(event); if (chatEvent) { @@ -392,13 +522,24 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { return; } + boost::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = boost::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event); + if (createImpromptuMUCEvent) { + assert(!localMUCServiceJID_.toString().empty()); + // create new muc + JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID(); + + // join muc + MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true); + mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>())); + mucControllers_[roomJID]->activateChatWindow(); + } boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event); if (editMUCBookmarkEvent) { mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark()); } else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { - handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew()); + handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu()); mucControllers_[joinEvent->getJID()]->activateChatWindow(); } else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { @@ -420,6 +561,22 @@ void ChatsManager::markAllRecentsOffline() { chatListWindow_->setRecents(recentChats_); } +void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) { + JID reuseChatInvite = chatController->getToJID(); + chatControllers_.erase(chatController->getToJID()); + delete chatController; + + // join new impromptu muc + assert(!localMUCServiceJID_.toString().empty()); + + // create new muc + JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_); + + // join muc + MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow); + mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite))); +} + /** * If a resource goes offline, release bound chatdialog to that resource. */ @@ -497,6 +654,11 @@ void ChatsManager::setOnline(bool enabled) { markAllRecentsOffline(); } else { setupBookmarks(); + localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_); + localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2)); + localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); + localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this)); + localMUCServiceFinderWalker_->beginWalk(); } } @@ -521,12 +683,14 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact) ChatController* ChatsManager::createNewChatController(const JID& contact) { assert(chatControllers_.find(contact) == chatControllers_.end()); - ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_); + ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, autoAcceptMUCInviteDecider_); 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)); + controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3)); updatePresenceReceivingStateOnChatController(contact); + controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty()); return controller; } @@ -553,7 +717,6 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool } else { return pair.second; } - } } return NULL; @@ -568,10 +731,11 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) { chatControllers_[to]->setToJID(to); } -void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew) { +MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) { + MUC::ref muc; if (!stanzaChannel_->isAvailable()) { /* This is potentially not the optimal solution, but it will avoid consistency issues.*/ - return; + return muc; } if (addAutoJoin) { MUCBookmark bookmark(mucJID, mucJID.getNode()); @@ -590,11 +754,27 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional it->second->rejoin(); } else { std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_); - MUC::ref muc = mucManager->createMUC(mucJID); + muc = mucManager->createMUC(mucJID); if (createAsReservedIfNew) { muc->setCreateAsReservedIfNew(); } - MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_); + if (isImpromptu) { + muc->setCreateAsReservedIfNew(); + } + + MUCController* controller = NULL; + SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL; + if (reuseChatwindow) { + chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); + } + controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_, isImpromptu, autoAcceptMUCInviteDecider_); + if (chatWindowFactoryAdapter) { + /* The adapters are only passed to chat windows, which are deleted in their + * controllers' dtor, which are deleted in ChatManager's dtor. The adapters + * are also deleted there.*/ + chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter; + } + mucControllers_[mucJID] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); @@ -605,6 +785,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional } mucControllers_[mucJID]->showChatWindow(); + return muc; } void ChatsManager::handleSearchMUCRequest() { @@ -635,7 +816,25 @@ void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { return; } } - + + // check for impromptu invite to potentially auto-accept + MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>(); + if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { + if (invite->getIsContinuation()) { + // check for existing chat controller for the from JID + ChatController* controller = getChatControllerIfExists(jid); + if (controller) { + ChatWindow* window = controller->detachChatWindow(); + chatControllers_.erase(jid); + delete controller; + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window); + } + } else { + handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true); + return; + } + } + //if not a mucroom if (!event->isReadable() && !isInvite && !isMediatedInvite) { /* Only route such messages if a window exists, don't open new windows for them.*/ @@ -688,7 +887,15 @@ void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWin } void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { - if (chat.isMUC) { + if (chat.isMUC && !chat.impromptuJIDs.empty()) { + typedef std::pair<std::string, JID> StringJIDPair; + std::vector<JID> inviteJIDs; + foreach(StringJIDPair pair, chat.impromptuJIDs) { + inviteJIDs.push_back(pair.second); + } + uiEventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, "")); + } + else if (chat.isMUC) { /* FIXME: This means that recents requiring passwords will just flat-out not work */ uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick)); } @@ -697,4 +904,42 @@ void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { } } +void ChatsManager::handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info) { + foreach (DiscoInfo::Identity identity, info->getIdentities()) { + if ((identity.getCategory() == "directory" + && identity.getType() == "chatroom") + || (identity.getCategory() == "conference" + && identity.getType() == "text")) { + localMUCServiceJID_ = service; + localMUCServiceFinderWalker_->endWalk(); + SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl; + break; + } + } +} + +void ChatsManager::handleLocalServiceWalkFinished() { + onImpromptuMUCServiceDiscovered(!localMUCServiceJID_.toString().empty()); +} + +std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const { + return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); +} + +std::vector<Contact> Swift::ChatsManager::getContacts() { + std::vector<Contact> result; + foreach (ChatListWindow::Chat chat, recentChats_) { + if (!chat.isMUC) { + result.push_back(Contact(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath)); + } + } + return result; +} + +ChatsManager::SingleChatWindowFactoryAdapter::SingleChatWindowFactoryAdapter(ChatWindow* chatWindow) : chatWindow_(chatWindow) {} +ChatsManager::SingleChatWindowFactoryAdapter::~SingleChatWindowFactoryAdapter() {} +ChatWindow* ChatsManager::SingleChatWindowFactoryAdapter::createChatWindow(const JID &, UIEventStream*) { + return chatWindow_; +} + } diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 5b8b785..c9dd856 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -7,19 +7,26 @@ #pragma once #include <map> +#include <string> #include <boost/shared_ptr.hpp> -#include <string> +#include <Swiften/Base/IDGenerator.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 <Swiften/MUC/MUCBookmark.h> +#include <Swiften/MUC/MUC.h> + + +#include <Swift/Controllers/ContactProvider.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/ChatListWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> + namespace Swift { class EventController; @@ -27,7 +34,6 @@ namespace Swift { class ChatControllerBase; class MUCController; class MUCManager; - class ChatWindowFactory; class JoinMUCWindow; class JoinMUCWindowFactory; class NickResolver; @@ -50,20 +56,42 @@ namespace Swift { class SettingsProvider; class WhiteboardManager; class HistoryController; - - class ChatsManager { + class HighlightManager; + class ClientBlockListManager; + class ChatMessageParser; + class DiscoServiceWalker; + class AutoAcceptMUCInviteDecider; + class UserSearchController; + + class ChatsManager : public ContactProvider { public: - ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager); + 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* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<Message> message); + std::vector<ChatListWindow::Chat> getRecentChats() const; + virtual std::vector<Contact> getContacts(); + + boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered; + + private: + class SingleChatWindowFactoryAdapter : public ChatWindowFactory { + public: + SingleChatWindowFactoryAdapter(ChatWindow* chatWindow); + virtual ~SingleChatWindowFactoryAdapter(); + virtual ChatWindow* createChatWindow(const JID &, UIEventStream*); + + private: + ChatWindow* chatWindow_; + }; 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>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew); + void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>()); + MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = 0); void handleSearchMUCRequest(); void handleMUCSelectedAfterSearch(const JID&); void rebindControllerJID(const JID& from, const JID& to); @@ -77,6 +105,7 @@ namespace Swift { void handleNewFileTransferController(FileTransferController*); void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf); void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state); + boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat); void appendRecent(const ChatListWindow::Chat& chat); void prependRecent(const ChatListWindow::Chat& chat); void setupBookmarks(); @@ -94,8 +123,14 @@ namespace Swift { void handleRosterCleared(); void handleSettingChanged(const std::string& settingPath); void markAllRecentsOffline(); + void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason); + + void handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info); + void handleLocalServiceWalkFinished(); void updatePresenceReceivingStateOnChatController(const JID&); + ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const; + ChatController* getChatControllerOrFindAnother(const JID &contact); ChatController* createNewChatController(const JID &contact); @@ -105,6 +140,7 @@ namespace Swift { private: std::map<JID, MUCController*> mucControllers_; std::map<JID, ChatController*> chatControllers_; + std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_; EventController* eventController_; JID jid_; StanzaChannel* stanzaChannel_; @@ -136,5 +172,13 @@ namespace Swift { SettingsProvider* settings_; HistoryController* historyController_; WhiteboardManager* whiteboardManager_; + HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; + ChatMessageParser* chatMessageParser_; + JID localMUCServiceJID_; + boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + UserSearchController* inviteUserSearchController_; + IDGenerator idGenerator_; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 50eee68..37631a5 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -19,11 +19,13 @@ #include <Swiften/Base/foreach.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/Controllers/UIInterfaces/InviteToChatWindow.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swiften/Avatars/AvatarManager.h> @@ -35,7 +37,10 @@ #include <Swift/Controllers/Roster/SetPresence.h> #include <Swiften/Disco/EntityCapsProvider.h> #include <Swiften/Roster/XMPPRoster.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swiften/Base/Log.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 @@ -61,22 +66,24 @@ MUCController::MUCController ( EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, - MUCRegistry* mucRegistry) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) { + MUCRegistry* mucRegistry, + HighlightManager* highlightManager, + ChatMessageParser* chatMessageParser, + bool isImpromptu, + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : + ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) { parting_ = true; joined_ = false; lastWasPresence_ = false; shouldJoinOnReconnect_ = true; doneGettingHistory_ = false; events_ = uiEventStream; - inviteWindow_ = NULL; xmppRoster_ = roster; roster_ = new Roster(false, true); completer_ = new TabComplete(); chatWindow_->setRosterModel(roster_); 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)); @@ -84,7 +91,7 @@ MUCController::MUCController ( chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this)); chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); - chatWindow_->onInvitePersonToThisMUCRequest.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this)); + chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1)); chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this)); chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1)); muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); @@ -94,24 +101,34 @@ MUCController::MUCController ( muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); - muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); - muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3)); muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2)); + muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); + muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); + highlighter_->setMode(Highlighter::MUCMode); + highlighter_->setNick(nick_); if (timerFactory) { loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); loginCheckTimer_->start(); } - chatWindow_->convertToMUC(); + if (isImpromptu) { + muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this)); + chatWindow_->convertToMUC(true); + } else { + chatWindow_->convertToMUC(); + chatWindow_->setName(muc->getJID().getNode()); + } setOnline(true); if (avatarManager_ != NULL) { avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1))); } handleBareJIDCapsChanged(muc->getJID()); + eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1)); } MUCController::~MUCController() { + eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1)); chatWindow_->setRosterModel(NULL); delete roster_; if (loginCheckTimer_) { @@ -146,6 +163,7 @@ void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item if (muc_->getOccupant(item->getJID().getResource()).getRealJID()) { actions.push_back(ChatWindow::AddContact); } + actions.push_back(ChatWindow::ShowProfile); } chatWindow_->setAvailableOccupantActions(actions); } @@ -164,6 +182,7 @@ void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction a case ChatWindow::MakeParticipant: muc_->changeOccupantRole(mucJID, MUCOccupant::Participant);break; case ChatWindow::MakeVisitor: muc_->changeOccupantRole(mucJID, MUCOccupant::Visitor);break; case ChatWindow::AddContact: if (occupant.getRealJID()) events_->send(boost::make_shared<RequestAddUserDialogUIEvent>(realJID, occupant.getNick()));break; + case ChatWindow::ShowProfile: events_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(mucJID));break; } } @@ -217,9 +236,31 @@ const std::string& MUCController::getNick() { return nick_; } +bool MUCController::isImpromptu() const { + return isImpromptu_; +} + +std::map<std::string, JID> MUCController::getParticipantJIDs() const { + std::map<std::string, JID> participants; + typedef std::pair<std::string, MUCOccupant> MUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); + foreach(const MUCOccupantPair& occupant, occupants) { + if (occupant.first != nick_) { + participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID(); + } + } + return participants; +} + +void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const { + foreach (const JID& jid, jids) { + muc_->invitePerson(jid, reason, isImpromptu_); + } +} + void MUCController::handleJoinTimeoutTick() { receivedActivity(); - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection); } void MUCController::receivedActivity() { @@ -228,6 +269,9 @@ void MUCController::receivedActivity() { } } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-enum" + void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { receivedActivity(); std::string errorMessage = QT_TRANSLATE_NOOP("", "Unable to enter this room"); @@ -267,20 +311,29 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { } } errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); parting_ = true; - if (!rejoinNick.empty()) { - nick_ = rejoinNick; + if (!rejoinNick.empty() && renameCounter_ < 10) { + renameCounter_++; + setNick(rejoinNick); rejoin(); } } +#pragma clang diagnostic pop + void MUCController::handleJoinComplete(const std::string& nick) { receivedActivity(); + renameCounter_ = 0; joined_ = true; - std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); - nick_ = nick; - chatWindow_->addSystemMessage(joinMessage); + std::string joinMessage; + if (isImpromptu_) { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered chat as %1%.")) % nick); + } else { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); + } + setNick(nick); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::DefaultDirection); #ifdef SWIFT_EXPERIMENTAL_HISTORY addRecentLogs(); @@ -292,14 +345,17 @@ void MUCController::handleJoinComplete(const std::string& nick) { MUCOccupant occupant = muc_->getOccupant(nick); setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); onUserJoined(); + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } } void MUCController::handleAvatarChanged(const JID& jid) { if (parting_ || !jid.equals(toJID_, JID::WithoutResource)) { return; } - std::string path = avatarManager_->getAvatarPath(jid).string(); - roster_->applyOnItems(SetAvatar(jid, path, JID::WithResource)); + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource)); } void MUCController::handleWindowClosed() { @@ -323,22 +379,26 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { NickJoinPart event(occupant.getNick(), Join); appendToJoinParts(joinParts_, event); std::string groupName(roleToGroupName(occupant.getRole())); - roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole())); if (joined_) { std::string joinString; MUCOccupant::Role role = occupant.getRole(); if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) { - joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the room as a %2%.")) % occupant.getNick() % roleToFriendlyName(role)); + joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); } else { - joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the room.")) % occupant.getNick()); + joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room"))); } if (shouldUpdateJoinParts()) { updateJoinParts(); } else { addPresenceMessage(joinString); + } + if (isImpromptu_) { + setImpromptuWindowTitle(); + onActivity(""); } } if (avatarManager_ != NULL) { @@ -348,7 +408,7 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { void MUCController::addPresenceMessage(const std::string& message) { lastWasPresence_ = true; - chatWindow_->addPresenceMessage(message); + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(message), ChatWindow::DefaultDirection); } @@ -386,6 +446,7 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) { case MUCOccupant::Visitor: return QT_TRANSLATE_NOOP("", "visitor"); case MUCOccupant::NoRole: return ""; } + assert(false); return ""; } @@ -396,6 +457,7 @@ std::string MUCController::roleToSortName(MUCOccupant::Role role) { case MUCOccupant::Visitor: return "3"; case MUCOccupant::NoRole: return "4"; } + assert(false); return "5"; } @@ -433,7 +495,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes joined_ = true; if (message->hasSubject() && message->getBody().empty()) { - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject()));; + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);; chatWindow_->setSubject(message->getSubject()); doneGettingHistory_ = true; } @@ -448,10 +510,13 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes } } -void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { +void MUCController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent, const HighlightAction& highlight) { boost::shared_ptr<Message> message = messageEvent->getStanza(); if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>()) { eventController_->handleIncomingEvent(messageEvent); + if (!messageEvent->getConcluded()) { + highlighter_->handleHighlightAction(highlight); + } } } @@ -465,9 +530,9 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC realJID = occupant.getRealJID().get(); } std::string group(roleToGroupName(occupant.getRole())); - roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid)); roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))); + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection); if (nick == nick_) { setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); } @@ -487,7 +552,6 @@ std::string MUCController::roleToGroupName(MUCOccupant::Role role) { case MUCOccupant::Participant: result = QT_TRANSLATE_NOOP("", "Participants"); break; case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break; case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break; - default: assert(false); } return result; } @@ -500,11 +564,16 @@ void MUCController::setOnline(bool online) { processUserPart(); } else { if (shouldJoinOnReconnect_) { - chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())); + renameCounter_ = 0; + if (isImpromptu_) { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to enter chat")), ChatWindow::DefaultDirection); + } else { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection); + } if (loginCheckTimer_) { loginCheckTimer_->start(); } - nick_ = desiredNick_; + setNick(desiredNick_); rejoin(); } } @@ -573,6 +642,10 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving if (clearAfter) { clearPresenceQueue(); } + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } } void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) { @@ -598,7 +671,7 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo } void MUCController::updateJoinParts() { - chatWindow_->replaceLastMessage(generateJoinPartString(joinParts_)); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu()))); } void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) { @@ -611,7 +684,8 @@ void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, cons switch (newEvent.type) { case Join: type = (type == Part) ? PartThenJoin : Join; break; case Part: type = (type == Join) ? JoinThenPart : Part; break; - default: /*Nothing to see here */;break; + case PartThenJoin: break; + case JoinThenPart: break; } (*it).type = type; break; @@ -638,7 +712,7 @@ std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart return result; } -std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts) { +std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu) { std::vector<NickJoinPart> sorted[4]; std::string eventStrings[4]; foreach (NickJoinPart event, joinParts) { @@ -653,34 +727,34 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart switch (i) { case Join: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have entered the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has entered the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room")); } break; case Part: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room")); } break; case JoinThenPart: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have entered then left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has entered then left the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room")); } break; case PartThenJoin: if (sorted[i].size() > 1) { - eventString = QT_TRANSLATE_NOOP("", "%1% have left then returned to the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room")); } else { - eventString = QT_TRANSLATE_NOOP("", "%1% has left then returned to the room"); + eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room")); } break; } @@ -717,17 +791,29 @@ void MUCController::handleConfigureRequest(Form::ref form) { 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); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); } void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) { std::string errorMessage = getErrorMessage(error); errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); +} + +void MUCController::configureAsImpromptuRoom(Form::ref form) { + muc_->configureRoom(buildImpromptuRoomConfiguration(form)); + isImpromptuAlreadyConfigured_ = true; + onImpromptuConfigCompleted(); } void MUCController::handleConfigurationFormReceived(Form::ref form) { - chatWindow_->showRoomConfigurationForm(form); + if (isImpromptu_) { + if (!isImpromptuAlreadyConfigured_) { + configureAsImpromptuRoom(form); + } + } else { + chatWindow_->showRoomConfigurationForm(form); + } } void MUCController::handleConfigurationCancelled() { @@ -738,32 +824,18 @@ void MUCController::handleDestroyRoomRequest() { muc_->destroyRoom(); } -void MUCController::handleInvitePersonToThisMUCRequest() { - if (!inviteWindow_) { - inviteWindow_ = chatWindow_->createInviteToChatWindow(); - inviteWindow_->onCompleted.connect(boost::bind(&MUCController::handleInviteToMUCWindowCompleted, this)); - inviteWindow_->onDismissed.connect(boost::bind(&MUCController::handleInviteToMUCWindowDismissed, this)); - } - std::vector<std::pair<JID, std::string> > autoCompletes; - foreach (XMPPRosterItem item, xmppRoster_->getItems()) { - std::pair<JID, std::string> jidName; - jidName.first = item.getJID(); - jidName.second = item.getName(); - autoCompletes.push_back(jidName); - //std::cerr << "MUCController adding " << item.getJID().toString() << std::endl; - } - inviteWindow_->setAutoCompletions(autoCompletes); -} - -void MUCController::handleInviteToMUCWindowDismissed() { - inviteWindow_= NULL; +void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) { + boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite)); + eventStream_->send(event); } -void MUCController::handleInviteToMUCWindowCompleted() { - foreach (const JID& jid, inviteWindow_->getJIDs()) { - muc_->invitePerson(jid, inviteWindow_->getReason()); +void MUCController::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event); + if (inviteEvent && inviteEvent->getRoom() == muc_->getJID()) { + foreach (const JID& jid, inviteEvent->getInvites()) { + muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_); + } } - inviteWindow_ = NULL; } void MUCController::handleGetAffiliationsRequest() { @@ -811,7 +883,7 @@ void MUCController::addRecentLogs() { bool senderIsSelf = nick_ == message.getFromJID().getResource(); // the chatWindow uses utc timestamps - addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), std::string(avatarManager_->getAvatarPath(message.getFromJID()).string()), message.getTime() - boost::posix_time::hours(message.getOffset())); + addMessage(message.getMessage(), senderDisplayNameFromMessage(message.getFromJID()), senderIsSelf, boost::shared_ptr<SecurityLabel>(new SecurityLabel()), avatarManager_->getAvatarPath(message.getFromJID()), message.getTime() - boost::posix_time::hours(message.getOffset()), HighlightAction()); } } @@ -840,4 +912,78 @@ void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) { } } +void MUCController::setNick(const std::string& nick) { + nick_ = nick; + highlighter_->setNick(nick_); +} + +Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) { + Form::ref result = boost::make_shared<Form>(Form::SubmitType); + std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"}; + std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4); + foreach (boost::shared_ptr<FormField> field, roomConfigurationForm->getFields()) { + boost::shared_ptr<FormField> resultField; + if (field->getName() == "muc#roomconfig_enablelogging") { + resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_persistentroom") { + resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_publicroom") { + resultField = boost::make_shared<FormField>(FormField::BooleanType, "0"); + } + if (field->getName() == "muc#roomconfig_whois") { + resultField = boost::make_shared<FormField>(FormField::ListSingleType, "anyone"); + } + + if (field->getName() == "FORM_TYPE") { + resultField = boost::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig"); + } + + if (resultField) { + impromptuConfigsMissing.erase(field->getName()); + resultField->setName(field->getName()); + result->addField(resultField); + } + } + + foreach (const std::string& config, impromptuConfigsMissing) { + if (config == "muc#roomconfig_publicroom") { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection); + } else if (config == "muc#roomconfig_whois") { + chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection); + } + } + + return result; +} + +void MUCController::setImpromptuWindowTitle() { + std::string title; + typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair; + std::map<std::string, MUCOccupant> occupants = muc_->getOccupants(); + if (occupants.size() <= 1) { + title = QT_TRANSLATE_NOOP("", "Empty Chat"); + } else { + foreach (StringMUCOccupantPair pair, occupants) { + if (pair.first != nick_) { + title += (title.empty() ? "" : ", ") + pair.first; + } + } + } + chatWindow_->setName(title); +} + +void MUCController::handleRoomUnlocked() { + // Handle buggy MUC implementations where the joined room already exists and is unlocked. + // Configure the room again in this case. + if (!isImpromptuAlreadyConfigured_) { + if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) { + muc_->requestConfigurationForm(); + } else if (isImpromptu_) { + onImpromptuConfigCompleted(); + } + } +} + } diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 63893d0..9283438 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,24 +1,27 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once +#include <set> +#include <string> +#include <map> + #include <boost/shared_ptr.hpp> -#include <Swiften/Base/boost_bsignals.h> #include <boost/signals/connection.hpp> -#include <set> -#include <string> +#include <Swiften/Base/boost_bsignals.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/Chat/ChatControllerBase.h> #include <Swift/Controllers/Roster/RosterItem.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> @@ -31,30 +34,35 @@ namespace Swift { class UIEventStream; class TimerFactory; class TabComplete; - class InviteToChatWindow; class XMPPRoster; + class HighlightManager; + class UIEvent; enum JoinPart {Join, Part, JoinThenPart, PartThenJoin}; struct NickJoinPart { - NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {}; + NickJoinPart(const std::string& nick, JoinPart type) : nick(nick), type(type) {} std::string nick; JoinPart type; }; class MUCController : public ChatControllerBase { public: - MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry); + MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider); ~MUCController(); boost::signal<void ()> onUserLeft; boost::signal<void ()> onUserJoined; + boost::signal<void ()> onImpromptuConfigCompleted; 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 generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu); static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); bool isJoined(); const std::string& getNick(); + bool isImpromptu() const; + std::map<std::string, JID> getParticipantJIDs() const; + void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; protected: void preSendMessageRequest(boost::shared_ptr<Message> message); @@ -62,7 +70,7 @@ namespace Swift { std::string senderDisplayNameFromMessage(const JID& from); boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message> message) const; void preHandleIncomingMessage(boost::shared_ptr<MessageEvent>); - void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>); + void postHandleIncomingMessage(boost::shared_ptr<MessageEvent>, const HighlightAction&); void cancelReplaces(); void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming); @@ -98,7 +106,7 @@ namespace Swift { void handleConfigurationFailed(ErrorPayload::ref); void handleConfigurationFormReceived(Form::ref); void handleDestroyRoomRequest(); - void handleInvitePersonToThisMUCRequest(); + void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite); void handleConfigurationCancelled(); void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role); void handleGetAffiliationsRequest(); @@ -106,8 +114,14 @@ namespace Swift { void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes); void handleInviteToMUCWindowDismissed(); void handleInviteToMUCWindowCompleted(); + void handleUIEvent(boost::shared_ptr<UIEvent> event); void addRecentLogs(); void checkDuplicates(boost::shared_ptr<Message> newMessage); + void setNick(const std::string& nick); + void setImpromptuWindowTitle(); + void handleRoomUnlocked(); + void configureAsImpromptuRoom(Form::ref form); + Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm); private: MUC::ref muc_; @@ -127,9 +141,11 @@ namespace Swift { std::vector<NickJoinPart> joinParts_; boost::posix_time::ptime lastActivity_; boost::optional<std::string> password_; - InviteToChatWindow* inviteWindow_; XMPPRoster* xmppRoster_; std::vector<HistoryMessage> joinContext_; + size_t renameCounter_; + bool isImpromptu_; + bool isImpromptuAlreadyConfigured_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp new file mode 100644 index 0000000..44d7834 --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013 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 <hippomocks.h> + +#include <Swift/Controllers/Chat/ChatMessageParser.h> + +using namespace Swift; + +class ChatMessageParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ChatMessageParserTest); + CPPUNIT_TEST(testFullBody); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() { + smile1_ = ":)"; + smile1Path_ = "/blah/smile1.png"; + smile2_ = ":("; + smile2Path_ = "/blah/smile2.jpg"; + emoticons_[smile1_] = smile1Path_; + emoticons_[smile2_] = smile2Path_; + } + + void tearDown() { + emoticons_.clear(); + } + + void assertText(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->text); + } + + void assertEmoticon(const ChatWindow::ChatMessage& result, size_t index, const std::string& text, const std::string& path) { + boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); + CPPUNIT_ASSERT_EQUAL(path, part->imagePath); + } + + void assertURL(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatURIMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->target); + } + + void testFullBody() { + ChatMessageParser testling(emoticons_); + ChatWindow::ChatMessage result = testling.parseMessageBody(":) shiny :( :) http://wonderland.lit/blah http://denmark.lit boom boom"); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, " shiny "); + assertEmoticon(result, 2, smile2_, smile2Path_); + assertText(result, 3, " "); + assertEmoticon(result, 4, smile1_, smile1Path_); + assertText(result, 5, " "); + assertURL(result, 6, "http://wonderland.lit/blah"); + assertText(result, 7, " "); + assertURL(result, 8, "http://denmark.lit"); + assertText(result, 9, " boom boom"); + } + +private: + std::map<std::string, std::string> emoticons_; + std::string smile1_; + std::string smile1Path_; + std::string smile2_; + std::string smile2Path_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageParserTest); + diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 482b19c..f5a3003 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -10,48 +10,49 @@ #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" -#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h" -#include "Swiften/Client/Client.h" -#include "Swiften/Disco/EntityCapsManager.h" -#include "Swiften/Disco/CapsProvider.h" -#include "Swiften/MUC/MUCManager.h" -#include "Swift/Controllers/Chat/ChatController.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swiften/Presence/StanzaChannelPresenceSender.h" -#include "Swiften/Avatars/NullAvatarManager.h" -#include "Swiften/Avatars/AvatarMemoryStorage.h" -#include "Swiften/VCards/VCardManager.h" -#include "Swiften/VCards/VCardMemoryStorage.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/Presence/DirectedPresenceSender.h" -#include "Swiften/Roster/XMPPRosterImpl.h" -#include "Swift/Controllers/UnitTest/MockChatWindow.h" -#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/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> +#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> +#include <Swiften/Client/Client.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swift/Controllers/Chat/ChatController.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/Chat/MUCController.h> +#include <Swiften/Presence/StanzaChannelPresenceSender.h> +#include <Swiften/Avatars/NullAvatarManager.h> +#include <Swiften/Avatars/AvatarMemoryStorage.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/VCards/VCardMemoryStorage.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Presence/DirectedPresenceSender.h> +#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swift/Controllers/UnitTest/MockChatWindow.h> +#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/Elements/DeliveryReceiptRequest.h" -#include "Swiften/Elements/DeliveryReceipt.h" +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Base/Algorithm.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/WhiteboardManager.h> #include <Swiften/Whiteboard/WhiteboardSessionManager.h> +#include <Swiften/Client/ClientBlockListManager.h> using namespace Swift; @@ -106,18 +107,22 @@ public: avatarManager_ = new NullAvatarManager(); wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_); wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); + highlightManager_ = new HighlightManager(settings_); 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_, xmppRoster_, false, settings_, NULL, wbManager_); + clientBlockListManager_ = new ClientBlockListManager(iqRouter_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL); manager_->setAvatarManager(avatarManager_); - }; + } void tearDown() { + delete highlightManager_; //delete chatListWindowFactory delete profileSettings_; delete avatarManager_; delete manager_; + delete clientBlockListManager_; delete ftOverview_; delete ftManager_; delete wbSessionManager_; @@ -481,6 +486,9 @@ private: FileTransferManager* ftManager_; WhiteboardSessionManager* wbSessionManager_; WhiteboardManager* wbManager_; + HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; + std::map<std::string, std::string> emoticons_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index 4f37229..5ca0687 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -26,6 +26,14 @@ #include "Swiften/Network/TimerFactory.h" #include "Swiften/Elements/MUCUserPayload.h" #include "Swiften/Disco/DummyEntityCapsProvider.h" +#include <Swiften/VCards/VCardMemoryStorage.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> +#include <Swiften/Crypto/CryptoProvider.h> using namespace Swift; @@ -44,6 +52,7 @@ class MUCControllerTest : public CppUnit::TestFixture { public: void setUp() { + crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); self_ = JID("girl@wonderland.lit/rabbithole"); nick_ = "aLiCe"; mucJID_ = JID("teaparty@rooms.wonderland.lit"); @@ -53,6 +62,7 @@ public: iqRouter_ = new IQRouter(iqChannel_); eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); presenceOracle_ = new PresenceOracle(stanzaChannel_); presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); directedPresenceSender_ = new DirectedPresenceSender(presenceSender_); @@ -62,12 +72,21 @@ public: window_ = new MockChatWindow(); mucRegistry_ = new MUCRegistry(); entityCapsProvider_ = new DummyEntityCapsProvider(); + settings_ = new DummySettingsProvider(); + highlightManager_ = new HighlightManager(settings_); muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_); - }; + chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>()); + vcardStorage_ = new VCardMemoryStorage(crypto_.get()); + vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_); + controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL); + } void tearDown() { + delete vcardManager_; + delete vcardStorage_; + delete highlightManager_; + delete settings_; delete entityCapsProvider_; delete controller_; delete eventController_; @@ -81,6 +100,7 @@ public: delete iqChannel_; delete mucRegistry_; delete avatarManager_; + delete chatMessageParser_; } void finishJoin() { @@ -296,25 +316,25 @@ public: void testJoinPartStringContructionSimple() { std::vector<NickJoinPart> list; list.push_back(NickJoinPart("Kev", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Bert", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Ernie", Join)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false)); } void testJoinPartStringContructionMixed() { std::vector<NickJoinPart> list; list.push_back(NickJoinPart("Kev", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Remko", Part)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Bert", PartThenJoin)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); list.push_back(NickJoinPart("Ernie", JoinThenPart)); - CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list)); + CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false)); } private: @@ -327,6 +347,7 @@ private: IQRouter* iqRouter_; EventController* eventController_; ChatWindowFactory* chatWindowFactory_; + UserSearchWindowFactory* userSearchWindowFactory_; MUCController* controller_; // NickResolver* nickResolver_; PresenceOracle* presenceOracle_; @@ -338,6 +359,12 @@ private: MockChatWindow* window_; MUCRegistry* mucRegistry_; DummyEntityCapsProvider* entityCapsProvider_; + DummySettingsProvider* settings_; + HighlightManager* highlightManager_; + ChatMessageParser* chatMessageParser_; + boost::shared_ptr<CryptoProvider> crypto_; + VCardManager* vcardManager_; + VCardMemoryStorage* vcardStorage_; }; CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h index 5bbd490..5fa264d 100644 --- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -12,12 +12,12 @@ namespace Swift { class MockChatListWindow : public ChatListWindow { public: - MockChatListWindow() {}; - virtual ~MockChatListWindow() {}; + MockChatListWindow() {} + virtual ~MockChatListWindow() {} void addMUCBookmark(const MUCBookmark& /*bookmark*/) {} void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {} - void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {}; - void removeWhiteboardSession(const JID& /*jid*/) {}; + void addWhiteboardSession(const ChatListWindow::Chat& /*chat*/) {} + void removeWhiteboardSession(const JID& /*jid*/) {} void setBookmarksEnabled(bool /*enabled*/) {} void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} void setUnreadCount(int /*unread*/) {} diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp index 839f4fa..3c7eb67 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -15,18 +15,24 @@ #include <Swiften/Disco/GetDiscoItemsRequest.h> #include <Swiften/Disco/DiscoServiceWalker.h> #include <Swiften/VCards/VCardManager.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Avatars/AvatarManager.h> #include <Swift/Controllers/ContactEditController.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> #include <Swift/Controllers/Roster/RosterController.h> +#include <Swift/Controllers/ContactSuggester.h> namespace Swift { -UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController) { +UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController), contactSuggester_(contactSuggester), avatarManager_(avatarManager), presenceOracle_(presenceOracle) { uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); + avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); + presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); window_ = NULL; discoWalker_ = NULL; } @@ -38,40 +44,61 @@ UserSearchController::~UserSearchController() { window_->onNameSuggestionRequested.disconnect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); delete window_; } + presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1)); vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2)); uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); } +UserSearchWindow* UserSearchController::getUserSearchWindow() { + initializeUserWindow(); + assert(window_); + return window_; +} + +void UserSearchController::setCanInitiateImpromptuMUC(bool supportsImpromptu) { + if (!window_) { + initializeUserWindow(); + } + window_->setCanStartImpromptuChats(supportsImpromptu); +} + void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) { bool handle = false; - boost::shared_ptr<RequestAddUserDialogUIEvent> request = boost::shared_ptr<RequestAddUserDialogUIEvent>(); - if (type_ == AddContact) { - if ((request = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) { - handle = true; - } - } else { - if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) { - handle = true; - } + boost::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = boost::shared_ptr<RequestAddUserDialogUIEvent>(); + RequestInviteToMUCUIEvent::ref inviteToMUCRequest = RequestInviteToMUCUIEvent::ref(); + switch (type_) { + case AddContact: + if ((addUserRequest = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) { + handle = true; + } + break; + case StartChat: + if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) { + handle = true; + } + break; + case InviteToChat: + if ((inviteToMUCRequest = boost::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) { + handle = true; + } + break; } if (handle) { - if (!window_) { - window_ = factory_->createUserSearchWindow(type_ == AddContact ? UserSearchWindow::AddContact : UserSearchWindow::ChatToContact, uiEventStream_, rosterController_->getGroups()); - window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); - window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); - window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); - window_->setSelectedService(JID(jid_.getDomain())); - window_->clear(); - } + initializeUserWindow(); window_->show(); - if (request) { - const std::string& name = request->getPredefinedName(); - const JID& jid = request->getPredefinedJID(); + if (addUserRequest) { + const std::string& name = addUserRequest->getPredefinedName(); + const JID& jid = addUserRequest->getPredefinedJID(); if (!name.empty() && jid.isValid()) { window_->prepopulateJIDAndName(jid, name); } + } else if (inviteToMUCRequest) { + window_->setJIDs(inviteToMUCRequest->getInvites()); + window_->setRoomJID(inviteToMUCRequest->getRoom()); } return; } @@ -98,7 +125,6 @@ void UserSearchController::endDiscoWalker() { } } - void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { //bool isUserDirectory = false; bool supports55 = false; @@ -166,11 +192,64 @@ void UserSearchController::handleNameSuggestionRequest(const JID &jid) { } } +void UserSearchController::handleContactSuggestionsRequested(std::string text) { + window_->setContactSuggestions(contactSuggester_->getSuggestions(text)); +} + void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) { if (jid == suggestionsJID_) { window_->setNameSuggestions(ContactEditController::nameSuggestionsFromVCard(vcard)); suggestionsJID_ = JID(); } + handleJIDUpdateRequested(std::vector<JID>(1, jid)); +} + +void UserSearchController::handleAvatarChanged(const JID& jid) { + handleJIDUpdateRequested(std::vector<JID>(1, jid)); +} + +void UserSearchController::handlePresenceChanged(Presence::ref presence) { + handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare())); +} + +void UserSearchController::handleJIDUpdateRequested(const std::vector<JID>& jids) { + if (window_) { + std::vector<Contact> updates; + foreach(const JID& jid, jids) { + updates.push_back(convertJIDtoContact(jid)); + } + window_->updateContacts(updates); + } +} + +Contact UserSearchController::convertJIDtoContact(const JID& jid) { + Contact contact; + contact.jid = jid; + + // name lookup + boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid); + if (rosterItem && !rosterItem->getName().empty()) { + contact.name = rosterItem->getName(); + } else { + VCard::ref vcard = vcardManager_->getVCard(jid); + if (vcard && !vcard->getFullName().empty()) { + contact.name = vcard->getFullName(); + } else { + contact.name = jid.toString(); + } + } + + // presence lookup + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid); + if (presence) { + contact.statusType = presence->getShow(); + } else { + contact.statusType = StatusShow::None; + } + + // avatar lookup + contact.avatarPath = avatarManager_->getAvatarPath(jid); + return contact; } void UserSearchController::handleDiscoWalkFinished() { @@ -178,4 +257,30 @@ void UserSearchController::handleDiscoWalkFinished() { endDiscoWalker(); } +void UserSearchController::initializeUserWindow() { + if (!window_) { + UserSearchWindow::Type windowType = UserSearchWindow::AddContact; + switch(type_) { + case AddContact: + windowType = UserSearchWindow::AddContact; + break; + case StartChat: + windowType = UserSearchWindow::ChatToContact; + break; + case InviteToChat: + windowType = UserSearchWindow::InviteToChat; + break; + } + + window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups()); + window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1)); + window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); + window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1)); + window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1)); + window_->setSelectedService(JID(jid_.getDomain())); + window_->clear(); + } +} + } diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h index ce0754c..21cad5e 100644 --- a/Swift/Controllers/Chat/UserSearchController.h +++ b/Swift/Controllers/Chat/UserSearchController.h @@ -18,6 +18,7 @@ #include <Swiften/Elements/DiscoItems.h> #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Elements/VCard.h> +#include <Swiften/Elements/Presence.h> namespace Swift { class UIEventStream; @@ -28,6 +29,10 @@ namespace Swift { class DiscoServiceWalker; class RosterController; class VCardManager; + class ContactSuggester; + class AvatarManager; + class PresenceOracle; + class Contact; class UserSearchResult { public: @@ -41,10 +46,13 @@ namespace Swift { class UserSearchController { public: - enum Type {AddContact, StartChat}; - UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController); + enum Type {AddContact, StartChat, InviteToChat}; + UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle); ~UserSearchController(); + UserSearchWindow* getUserSearchWindow(); + void setCanInitiateImpromptuMUC(bool supportsImpromptu); + private: void handleUIEvent(boost::shared_ptr<UIEvent> event); void handleFormRequested(const JID& service); @@ -54,8 +62,14 @@ namespace Swift { void handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid); void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error); void handleNameSuggestionRequest(const JID& jid); + void handleContactSuggestionsRequested(std::string text); void handleVCardChanged(const JID& jid, VCard::ref vcard); + void handleAvatarChanged(const JID& jid); + void handlePresenceChanged(Presence::ref presence); + void handleJIDUpdateRequested(const std::vector<JID>& jids); + Contact convertJIDtoContact(const JID& jid); void endDiscoWalker(); + void initializeUserWindow(); private: Type type_; @@ -68,5 +82,8 @@ namespace Swift { RosterController* rosterController_; UserSearchWindow* window_; DiscoServiceWalker* discoWalker_; + ContactSuggester* contactSuggester_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; }; } diff --git a/Swift/Controllers/ChatMessageSummarizer.cpp b/Swift/Controllers/ChatMessageSummarizer.cpp index d95a177..014e032 100644 --- a/Swift/Controllers/ChatMessageSummarizer.cpp +++ b/Swift/Controllers/ChatMessageSummarizer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -38,7 +38,7 @@ string ChatMessageSummarizer::getSummary(const string& current, const vector<Unr 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%)")); + string result(QT_TRANSLATE_NOOP("", "%1%; %2% (%3%)")); myString = str(format(result) % myString % others[0].first % otherCount); } return myString; diff --git a/Swift/Controllers/Contact.cpp b/Swift/Controllers/Contact.cpp new file mode 100644 index 0000000..7eb446c --- /dev/null +++ b/Swift/Controllers/Contact.cpp @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +Contact::Contact() { +} + +Contact::Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path) : name(name), jid(jid), statusType(statusType), avatarPath(path) { +} + +} diff --git a/Swift/Controllers/Contact.h b/Swift/Controllers/Contact.h new file mode 100644 index 0000000..039cd23 --- /dev/null +++ b/Swift/Controllers/Contact.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class Contact { + public: + Contact(); + Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path); + + public: + std::string name; + JID jid; + StatusShow::Type statusType; + boost::filesystem::path avatarPath; +}; + +} diff --git a/Swift/Controllers/ContactProvider.cpp b/Swift/Controllers/ContactProvider.cpp new file mode 100644 index 0000000..7dd1abf --- /dev/null +++ b/Swift/Controllers/ContactProvider.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/ContactProvider.h> + +namespace Swift { + +ContactProvider::~ContactProvider() { + +} + +} diff --git a/Swift/Controllers/ContactProvider.h b/Swift/Controllers/ContactProvider.h new file mode 100644 index 0000000..9ce371f --- /dev/null +++ b/Swift/Controllers/ContactProvider.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class ContactProvider { + public: + virtual ~ContactProvider(); + virtual std::vector<Contact> getContacts() = 0; +}; + +} diff --git a/Swift/Controllers/ContactSuggester.cpp b/Swift/Controllers/ContactSuggester.cpp new file mode 100644 index 0000000..f1104b0 --- /dev/null +++ b/Swift/Controllers/ContactSuggester.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/ContactSuggester.h> + +#include <boost/algorithm/string/find.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> + +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/ContactProvider.h> + +#include <algorithm> +#include <vector> +#include <set> + +namespace lambda = boost::lambda; + +namespace Swift { + +ContactSuggester::ContactSuggester() { +} + +ContactSuggester::~ContactSuggester() { +} + +void ContactSuggester::addContactProvider(ContactProvider* provider) { + contactProviders_.push_back(provider); +} + +bool ContactSuggester::matchContact(const std::string& search, const Contact& c) { + return fuzzyMatch(c.name, search) || fuzzyMatch(c.jid.toString(), search); +} + +std::vector<Contact> ContactSuggester::getSuggestions(const std::string& search) const { + std::vector<Contact> results; + + foreach(ContactProvider* provider, contactProviders_) { + append(results, provider->getContacts()); + } + + std::sort(results.begin(), results.end(), + lambda::bind(&Contact::jid, lambda::_1) < lambda::bind(&Contact::jid, lambda::_2)); + results.erase(std::unique(results.begin(), results.end(), + lambda::bind(&Contact::jid, lambda::_1) == lambda::bind(&Contact::jid, lambda::_2)), + results.end()); + results.erase(std::remove_if(results.begin(), results.end(), !lambda::bind(&ContactSuggester::matchContact, search, lambda::_1)), + results.end()); + std::sort(results.begin(), results.end(), ContactSuggester::chatSortPredicate); + + return results; +} + +bool ContactSuggester::fuzzyMatch(std::string text, std::string match) { + for (std::string::iterator currentQueryChar = match.begin(); currentQueryChar != match.end(); currentQueryChar++) { + //size_t result = text.find(*currentQueryChar); + std::string::iterator result = boost::algorithm::ifind_first(text, std::string(currentQueryChar, currentQueryChar+1)).begin(); + if (result == text.end()) { + return false; + } + text.erase(result); + } + return true; +} + +bool ContactSuggester::chatSortPredicate(const Contact& a, const Contact& b) { + if (a.statusType == b.statusType) { + return a.name.compare(b.name) < 0; + } else { + return a.statusType < b.statusType; + } +} + +} diff --git a/Swift/Controllers/ContactSuggester.h b/Swift/Controllers/ContactSuggester.h new file mode 100644 index 0000000..137e5d3 --- /dev/null +++ b/Swift/Controllers/ContactSuggester.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +#include <Swift/Controllers/Contact.h> + +namespace Swift { + class ContactProvider; + + class ContactSuggester { + public: + ContactSuggester(); + ~ContactSuggester(); + + void addContactProvider(ContactProvider* provider); + + std::vector<Contact> getSuggestions(const std::string& search) const; + private: + static bool matchContact(const std::string& search, const Contact& c); + /** + * Performs fuzzy matching on the string text. Matches when each character of match string is present in sequence in text string. + */ + static bool fuzzyMatch(std::string text, std::string match); + static bool chatSortPredicate(const Contact& a, const Contact& b); + + private: + std::vector<ContactProvider*> contactProviders_; + }; +} diff --git a/Swift/Controllers/ContactsFromXMPPRoster.cpp b/Swift/Controllers/ContactsFromXMPPRoster.cpp new file mode 100644 index 0000000..15a7767 --- /dev/null +++ b/Swift/Controllers/ContactsFromXMPPRoster.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/ContactsFromXMPPRoster.h> + +#include <Swiften/Base/foreach.h> + +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/XMPPRosterItem.h> + +namespace Swift { + +ContactsFromXMPPRoster::ContactsFromXMPPRoster(XMPPRoster* roster, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : roster_(roster), avatarManager_(avatarManager), presenceOracle_(presenceOracle) { +} + +ContactsFromXMPPRoster::~ContactsFromXMPPRoster() { +} + +std::vector<Contact> ContactsFromXMPPRoster::getContacts() { + std::vector<Contact> results; + std::vector<XMPPRosterItem> rosterItems = roster_->getItems(); + foreach(const XMPPRosterItem& rosterItem, rosterItems) { + Contact contact(rosterItem.getName().empty() ? rosterItem.getJID().toString() : rosterItem.getName(), rosterItem.getJID(), StatusShow::None,""); + contact.statusType = presenceOracle_->getHighestPriorityPresence(contact.jid) ? presenceOracle_->getHighestPriorityPresence(contact.jid)->getShow() : StatusShow::None; + contact.avatarPath = avatarManager_->getAvatarPath(contact.jid); + results.push_back(contact); + } + return results; +} + +} diff --git a/Swift/Controllers/ContactsFromXMPPRoster.h b/Swift/Controllers/ContactsFromXMPPRoster.h new file mode 100644 index 0000000..3815a99 --- /dev/null +++ b/Swift/Controllers/ContactsFromXMPPRoster.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/ContactProvider.h> + +namespace Swift { + +class PresenceOracle; +class AvatarManager; +class XMPPRoster; + +class ContactsFromXMPPRoster : public ContactProvider { + public: + ContactsFromXMPPRoster(XMPPRoster* roster, AvatarManager* avatarManager, PresenceOracle* presenceOracle); + virtual ~ContactsFromXMPPRoster(); + + virtual std::vector<Contact> getContacts(); + private: + XMPPRoster* roster_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; +}; + +} diff --git a/Swift/Controllers/DummySoundPlayer.h b/Swift/Controllers/DummySoundPlayer.h index 36dcb28..b91192c 100644 --- a/Swift/Controllers/DummySoundPlayer.h +++ b/Swift/Controllers/DummySoundPlayer.h @@ -11,6 +11,6 @@ namespace Swift { class DummySoundPlayer : public SoundPlayer { public: - void playSound(SoundEffect sound) {}; + void playSound(SoundEffect sound) {} }; } diff --git a/Swift/Controllers/DummySystemTray.h b/Swift/Controllers/DummySystemTray.h index 41da4cd..451588e 100644 --- a/Swift/Controllers/DummySystemTray.h +++ b/Swift/Controllers/DummySystemTray.h @@ -11,8 +11,8 @@ namespace Swift { class DummySystemTray : public SystemTray { public: - void setUnreadMessages(bool some) {}; - void setStatusType(StatusShow::Type type) {}; + void setUnreadMessages(bool some) {} + void setStatusType(StatusShow::Type type) {} void setConnecting() {} }; } diff --git a/Swift/Controllers/FileTransfer/FileTransferController.cpp b/Swift/Controllers/FileTransfer/FileTransferController.cpp index 3feaf49..0160a7a 100644 --- a/Swift/Controllers/FileTransfer/FileTransferController.cpp +++ b/Swift/Controllers/FileTransfer/FileTransferController.cpp @@ -10,6 +10,7 @@ #include <Swiften/FileTransfer/FileReadBytestream.h> #include <Swiften/Base/boost_bsignals.h> #include <boost/bind.hpp> +#include <boost/filesystem.hpp> #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include <Swiften/Base/Log.h> #include <Swift/Controllers/Intl.h> @@ -24,7 +25,7 @@ FileTransferController::FileTransferController(const JID& receipient, const std: } 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) { + sending(false), otherParty(transfer->getSender()), filename(transfer->getFileName()), transfer(transfer), ftManager(0), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { } @@ -41,7 +42,7 @@ std::string FileTransferController::setChatWindow(ChatWindow* wnd, std::string n if (sending) { uiID = wnd->addFileTransfer(QT_TRANSLATE_NOOP("", "me"), true, filename, boost::filesystem::file_size(boost::filesystem::path(filename))); } else { - uiID = wnd->addFileTransfer(nickname, false, filename, transfer->fileSizeInBytes); + uiID = wnd->addFileTransfer(nickname, false, filename, transfer->getFileSizeInBytes()); } return uiID; } @@ -64,7 +65,7 @@ int FileTransferController::getProgress() const { boost::uintmax_t FileTransferController::getSize() const { if (transfer) { - return transfer->fileSizeInBytes; + return transfer->getFileSizeInBytes(); } else { return 0; } @@ -75,9 +76,9 @@ void FileTransferController::start(std::string& description) { 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 = new FileTransferProgressInfo(outgoingTransfer->getFileSizeInBytes()); ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); - outgoingTransfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + outgoingTransfer->onStateChanged.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); outgoingTransfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); outgoingTransfer->start(); transfer = outgoingTransfer; @@ -92,9 +93,9 @@ void FileTransferController::accept(std::string& file) { if (incomingTransfer) { fileWriteStream = boost::make_shared<FileWriteBytestream>(boost::filesystem::path(file)); - ftProgressInfo = new FileTransferProgressInfo(transfer->fileSizeInBytes); + ftProgressInfo = new FileTransferProgressInfo(transfer->getFileSizeInBytes()); ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); - transfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + transfer->onStateChanged.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); transfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); incomingTransfer->accept(fileWriteStream); } else { @@ -113,7 +114,10 @@ void FileTransferController::cancel() { void FileTransferController::handleFileTransferStateChange(FileTransfer::State state) { currentState = state; onStateChage(); - switch(state.state) { + switch(state.type) { + case FileTransfer::State::Initial: + assert(false); + return; case FileTransfer::State::Negotiating: chatWindow->setFileTransferStatus(uiID, ChatWindow::Negotiating); return; @@ -138,7 +142,7 @@ void FileTransferController::handleFileTransferStateChange(FileTransfer::State s case FileTransfer::State::WaitingForStart: return; } - std::cerr << "Unhandled FileTransfer::State!" << std::endl; + assert(false); } void FileTransferController::handleProgressPercentageChange(int percentage) { diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp index 6d19fa1..3081f71 100644 --- a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp @@ -6,6 +6,8 @@ #include "FileTransferProgressInfo.h" +#include <boost/numeric/conversion/cast.hpp> + #include <Swiften/Base/Log.h> namespace Swift { @@ -16,7 +18,7 @@ FileTransferProgressInfo::FileTransferProgressInfo(boost::uintmax_t completeByte void FileTransferProgressInfo::setBytesProcessed(int processedBytes) { int oldPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); - completedBytes += processedBytes; + completedBytes += boost::numeric_cast<boost::uintmax_t>(processedBytes); int newPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); if (oldPercentage != newPercentage) { onProgressPercentage(newPercentage); diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp new file mode 100644 index 0000000..d4d199d --- /dev/null +++ b/Swift/Controllers/HighlightAction.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/HighlightAction.h> + +namespace Swift { + +void HighlightAction::setHighlightText(bool highlightText) +{ + highlightText_ = highlightText; + if (!highlightText_) { + textColor_.clear(); + textBackground_.clear(); + } +} + +void HighlightAction::setPlaySound(bool playSound) +{ + playSound_ = playSound; + if (!playSound_) { + soundFile_.clear(); + } +} + +} diff --git a/Swift/Controllers/HighlightAction.h b/Swift/Controllers/HighlightAction.h new file mode 100644 index 0000000..bfbed74 --- /dev/null +++ b/Swift/Controllers/HighlightAction.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +namespace Swift { + + class HighlightRule; + + class HighlightAction { + public: + HighlightAction() : highlightText_(false), playSound_(false) {} + + bool highlightText() const { return highlightText_; } + void setHighlightText(bool highlightText); + + const std::string& getTextColor() const { return textColor_; } + void setTextColor(const std::string& textColor) { textColor_ = textColor; } + + const std::string& getTextBackground() const { return textBackground_; } + void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; } + + bool playSound() const { return playSound_; } + void setPlaySound(bool playSound); + + const std::string& getSoundFile() const { return soundFile_; } + void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; } + + bool isEmpty() const { return !highlightText_ && !playSound_; } + + private: + bool highlightText_; + std::string textColor_; + std::string textBackground_; + + bool playSound_; + std::string soundFile_; + }; + +} diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp new file mode 100644 index 0000000..899e4bb --- /dev/null +++ b/Swift/Controllers/HighlightEditorController.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/bind.hpp> + +#include <Swift/Controllers/HighlightEditorController.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWidget.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h> +#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +namespace Swift { + +HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager) : highlightEditorWidgetFactory_(highlightEditorWidgetFactory), highlightEditorWidget_(NULL), highlightManager_(highlightManager) +{ + uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1)); +} + +HighlightEditorController::~HighlightEditorController() +{ + delete highlightEditorWidget_; + highlightEditorWidget_ = NULL; +} + +void HighlightEditorController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) +{ + boost::shared_ptr<RequestHighlightEditorUIEvent> event = boost::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent); + if (event) { + if (!highlightEditorWidget_) { + highlightEditorWidget_ = highlightEditorWidgetFactory_->createHighlightEditorWidget(); + highlightEditorWidget_->setHighlightManager(highlightManager_); + } + highlightEditorWidget_->show(); + } +} + +} diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h new file mode 100644 index 0000000..3868251 --- /dev/null +++ b/Swift/Controllers/HighlightEditorController.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * 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/UIEvent.h> + +namespace Swift { + + class UIEventStream; + + class HighlightEditorWidgetFactory; + class HighlightEditorWidget; + + class HighlightManager; + + class HighlightEditorController { + public: + HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWidgetFactory* highlightEditorWidgetFactory, HighlightManager* highlightManager); + ~HighlightEditorController(); + + HighlightManager* getHighlightManager() const { return highlightManager_; } + + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + + private: + HighlightEditorWidgetFactory* highlightEditorWidgetFactory_; + HighlightEditorWidget* highlightEditorWidget_; + HighlightManager* highlightManager_; + }; + +} diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp new file mode 100644 index 0000000..7ab578e --- /dev/null +++ b/Swift/Controllers/HighlightManager.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cassert> + +#include <boost/algorithm/string.hpp> +#include <boost/regex.hpp> +#include <boost/bind.hpp> +#include <boost/numeric/conversion/cast.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> + +/* How does highlighting work? + * + * HighlightManager manages a list of if-then rules used to highlight messages. + * Rule is represented by HighlightRule. Action ("then" part) is HighlightAction. + * + * + * HighlightManager is also used as a factory for Highlighter objects. + * Each ChatControllerBase has its own Highlighter. + * Highligher may be customized by using setNick(), etc. + * + * ChatControllerBase passes incoming messages to Highlighter and gets HighlightAction in return + * (first matching rule is returned). + * If needed, HighlightAction is then passed back to Highlighter for further handling. + * This results in HighlightManager emiting onHighlight event, + * which is handled by SoundController to play sound notification + */ + +namespace Swift { + +HighlightManager::HighlightManager(SettingsProvider* settings) + : settings_(settings) + , storingSettings_(false) +{ + loadSettings(); + settings_->onSettingChanged.connect(boost::bind(&HighlightManager::handleSettingChanged, this, _1)); +} + +void HighlightManager::handleSettingChanged(const std::string& settingPath) +{ + if (!storingSettings_ && SettingConstants::HIGHLIGHT_RULES.getKey() == settingPath) { + loadSettings(); + } +} + +void HighlightManager::loadSettings() +{ + std::string highlightRules = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); + if (highlightRules == "@") { + rules_ = getDefaultRules(); + } else { + rules_ = rulesFromString(highlightRules); + } +} + +std::string HighlightManager::rulesToString() const +{ + std::string s; + foreach (HighlightRule r, rules_) { + s += r.toString() + '\f'; + } + if (s.size()) { + s.erase(s.end() - 1); + } + return s; +} + +std::vector<HighlightRule> HighlightManager::rulesFromString(const std::string& rulesString) +{ + std::vector<HighlightRule> rules; + std::string s(rulesString); + typedef boost::split_iterator<std::string::iterator> split_iterator; + for (split_iterator it = boost::make_split_iterator(s, boost::first_finder("\f")); it != split_iterator(); ++it) { + HighlightRule r = HighlightRule::fromString(boost::copy_range<std::string>(*it)); + if (!r.isEmpty()) { + rules.push_back(r); + } + } + return rules; +} + +std::vector<HighlightRule> HighlightManager::getDefaultRules() +{ + std::vector<HighlightRule> rules; + HighlightRule r; + r.setMatchChat(true); + r.getAction().setPlaySound(true); + rules.push_back(r); + return rules; +} + +void HighlightManager::storeSettings() +{ + storingSettings_ = true; // don't reload settings while saving + settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); + storingSettings_ = false; +} + +HighlightRule HighlightManager::getRule(int index) const +{ + assert(index >= 0 && static_cast<size_t>(index) < rules_.size()); + return rules_[static_cast<size_t>(index)]; +} + +void HighlightManager::setRule(int index, const HighlightRule& rule) +{ + assert(index >= 0 && static_cast<size_t>(index) < rules_.size()); + rules_[static_cast<size_t>(index)] = rule; + storeSettings(); +} + +void HighlightManager::insertRule(int index, const HighlightRule& rule) +{ + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_.size()); + rules_.insert(rules_.begin() + index, rule); + storeSettings(); +} + +void HighlightManager::removeRule(int index) +{ + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_.size()); + rules_.erase(rules_.begin() + index); + storeSettings(); +} + +Highlighter* HighlightManager::createHighlighter() +{ + return new Highlighter(this); +} + +} diff --git a/Swift/Controllers/HighlightManager.h b/Swift/Controllers/HighlightManager.h new file mode 100644 index 0000000..d195d05 --- /dev/null +++ b/Swift/Controllers/HighlightManager.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/HighlightRule.h> + +namespace Swift { + + class SettingsProvider; + class Highlighter; + + class HighlightManager { + public: + HighlightManager(SettingsProvider* settings); + + Highlighter* createHighlighter(); + + const std::vector<HighlightRule>& getRules() const { return rules_; } + HighlightRule getRule(int index) const; + void setRule(int index, const HighlightRule& rule); + void insertRule(int index, const HighlightRule& rule); + void removeRule(int index); + + boost::signal<void (const HighlightAction&)> onHighlight; + + private: + void handleSettingChanged(const std::string& settingPath); + + std::string rulesToString() const; + static std::vector<HighlightRule> rulesFromString(const std::string&); + static std::vector<HighlightRule> getDefaultRules(); + + SettingsProvider* settings_; + bool storingSettings_; + void storeSettings(); + void loadSettings(); + + std::vector<HighlightRule> rules_; + }; + +} diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp new file mode 100644 index 0000000..9ca7d86 --- /dev/null +++ b/Swift/Controllers/HighlightRule.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <algorithm> +#include <boost/algorithm/string.hpp> +#include <boost/lambda/lambda.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Regex.h> +#include <Swift/Controllers/HighlightRule.h> + +namespace Swift { + +HighlightRule::HighlightRule() + : nickIsKeyword_(false) + , matchCase_(false) + , matchWholeWords_(false) + , matchChat_(false) + , matchMUC_(false) +{ +} + +boost::regex HighlightRule::regexFromString(const std::string & s) const +{ + std::string escaped = Regex::escape(s); + std::string word = matchWholeWords_ ? "\\b" : ""; + boost::regex::flag_type flags = boost::regex::normal; + if (!matchCase_) { + flags |= boost::regex::icase; + } + return boost::regex(word + escaped + word, flags); +} + +void HighlightRule::updateRegex() const +{ + keywordRegex_.clear(); + foreach (const std::string & k, keywords_) { + keywordRegex_.push_back(regexFromString(k)); + } + senderRegex_.clear(); + foreach (const std::string & s, senders_) { + senderRegex_.push_back(regexFromString(s)); + } +} + +std::string HighlightRule::boolToString(bool b) +{ + return b ? "1" : "0"; +} + +bool HighlightRule::boolFromString(const std::string& s) +{ + return s == "1"; +} + +std::string HighlightRule::toString() const +{ + std::vector<std::string> v; + v.push_back(boost::join(senders_, "\t")); + v.push_back(boost::join(keywords_, "\t")); + v.push_back(boolToString(nickIsKeyword_)); + v.push_back(boolToString(matchChat_)); + v.push_back(boolToString(matchMUC_)); + v.push_back(boolToString(matchCase_)); + v.push_back(boolToString(matchWholeWords_)); + v.push_back(boolToString(action_.highlightText())); + v.push_back(action_.getTextColor()); + v.push_back(action_.getTextBackground()); + v.push_back(boolToString(action_.playSound())); + v.push_back(action_.getSoundFile()); + return boost::join(v, "\n"); +} + +HighlightRule HighlightRule::fromString(const std::string& s) +{ + std::vector<std::string> v; + boost::split(v, s, boost::is_any_of("\n")); + + HighlightRule r; + size_t i = 0; + try { + boost::split(r.senders_, v.at(i++), boost::is_any_of("\t")); + r.senders_.erase(std::remove_if(r.senders_.begin(), r.senders_.end(), boost::lambda::_1 == ""), r.senders_.end()); + boost::split(r.keywords_, v.at(i++), boost::is_any_of("\t")); + r.keywords_.erase(std::remove_if(r.keywords_.begin(), r.keywords_.end(), boost::lambda::_1 == ""), r.keywords_.end()); + r.nickIsKeyword_ = boolFromString(v.at(i++)); + r.matchChat_ = boolFromString(v.at(i++)); + r.matchMUC_ = boolFromString(v.at(i++)); + r.matchCase_ = boolFromString(v.at(i++)); + r.matchWholeWords_ = boolFromString(v.at(i++)); + r.action_.setHighlightText(boolFromString(v.at(i++))); + r.action_.setTextColor(v.at(i++)); + r.action_.setTextBackground(v.at(i++)); + r.action_.setPlaySound(boolFromString(v.at(i++))); + r.action_.setSoundFile(v.at(i++)); + } + catch (std::out_of_range) { + // this may happen if more properties are added to HighlightRule object, etc... + // in such case, default values (set by default constructor) will be used + } + + r.updateRegex(); + + return r; +} + +bool HighlightRule::isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType messageType) const +{ + if ((messageType == HighlightRule::ChatMessage && matchChat_) || (messageType == HighlightRule::MUCMessage && matchMUC_)) { + + bool matchesKeyword = keywords_.empty() && (nick.empty() || !nickIsKeyword_); + bool matchesSender = senders_.empty(); + + foreach (const boost::regex & rx, keywordRegex_) { + if (boost::regex_search(body, rx)) { + matchesKeyword = true; + break; + } + } + + if (!matchesKeyword && nickIsKeyword_ && !nick.empty()) { + if (boost::regex_search(body, regexFromString(nick))) { + matchesKeyword = true; + } + } + + foreach (const boost::regex & rx, senderRegex_) { + if (boost::regex_search(sender, rx)) { + matchesSender = true; + break; + } + } + + if (matchesKeyword && matchesSender) { + return true; + } + } + + return false; +} + +void HighlightRule::setSenders(const std::vector<std::string>& senders) +{ + senders_ = senders; + updateRegex(); +} + +void HighlightRule::setKeywords(const std::vector<std::string>& keywords) +{ + keywords_ = keywords; + updateRegex(); +} + +void HighlightRule::setNickIsKeyword(bool nickIsKeyword) +{ + nickIsKeyword_ = nickIsKeyword; + updateRegex(); +} + +void HighlightRule::setMatchCase(bool matchCase) +{ + matchCase_ = matchCase; + updateRegex(); +} + +void HighlightRule::setMatchWholeWords(bool matchWholeWords) +{ + matchWholeWords_ = matchWholeWords; + updateRegex(); +} + +void HighlightRule::setMatchChat(bool matchChat) +{ + matchChat_ = matchChat; + updateRegex(); +} + +void HighlightRule::setMatchMUC(bool matchMUC) +{ + matchMUC_ = matchMUC; + updateRegex(); +} + +bool HighlightRule::isEmpty() const +{ + return senders_.empty() && keywords_.empty() && !nickIsKeyword_ && !matchChat_ && !matchMUC_ && action_.isEmpty(); +} + +} diff --git a/Swift/Controllers/HighlightRule.h b/Swift/Controllers/HighlightRule.h new file mode 100644 index 0000000..1abfa5a --- /dev/null +++ b/Swift/Controllers/HighlightRule.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <boost/regex.hpp> + +#include <Swift/Controllers/HighlightAction.h> + +namespace Swift { + + class HighlightRule { + public: + HighlightRule(); + + enum MessageType { ChatMessage, MUCMessage }; + + bool isMatch(const std::string& body, const std::string& sender, const std::string& nick, MessageType) const; + + const HighlightAction& getAction() const { return action_; } + HighlightAction& getAction() { return action_; } + + static HighlightRule fromString(const std::string&); + std::string toString() const; + + const std::vector<std::string>& getSenders() const { return senders_; } + void setSenders(const std::vector<std::string>&); + + const std::vector<std::string>& getKeywords() const { return keywords_; } + void setKeywords(const std::vector<std::string>&); + + bool getNickIsKeyword() const { return nickIsKeyword_; } + void setNickIsKeyword(bool); + + bool getMatchCase() const { return matchCase_; } + void setMatchCase(bool); + + bool getMatchWholeWords() const { return matchWholeWords_; } + void setMatchWholeWords(bool); + + bool getMatchChat() const { return matchChat_; } + void setMatchChat(bool); + + bool getMatchMUC() const { return matchMUC_; } + void setMatchMUC(bool); + + bool isEmpty() const; + + private: + static std::string boolToString(bool); + static bool boolFromString(const std::string&); + + std::vector<std::string> senders_; + std::vector<std::string> keywords_; + bool nickIsKeyword_; + + mutable std::vector<boost::regex> senderRegex_; + mutable std::vector<boost::regex> keywordRegex_; + void updateRegex() const; + boost::regex regexFromString(const std::string&) const; + + bool matchCase_; + bool matchWholeWords_; + + bool matchChat_; + bool matchMUC_; + + HighlightAction action_; + }; + +} diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp new file mode 100644 index 0000000..754641a --- /dev/null +++ b/Swift/Controllers/Highlighter.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/HighlightManager.h> + +namespace Swift { + +Highlighter::Highlighter(HighlightManager* manager) + : manager_(manager) +{ + setMode(ChatMode); +} + +void Highlighter::setMode(Mode mode) +{ + mode_ = mode; + messageType_ = mode_ == ChatMode ? HighlightRule::ChatMessage : HighlightRule::MUCMessage; +} + +HighlightAction Highlighter::findAction(const std::string& body, const std::string& sender) const +{ + foreach (const HighlightRule & r, manager_->getRules()) { + if (r.isMatch(body, sender, nick_, messageType_)) { + return r.getAction(); + } + } + + return HighlightAction(); +} + +void Highlighter::handleHighlightAction(const HighlightAction& action) +{ + manager_->onHighlight(action); +} + +} diff --git a/Swift/Controllers/Highlighter.h b/Swift/Controllers/Highlighter.h new file mode 100644 index 0000000..d026f29 --- /dev/null +++ b/Swift/Controllers/Highlighter.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swift/Controllers/HighlightRule.h> + +namespace Swift { + + class HighlightManager; + + class Highlighter { + public: + Highlighter(HighlightManager* manager); + + enum Mode { ChatMode, MUCMode }; + void setMode(Mode mode); + + void setNick(const std::string& nick) { nick_ = nick; } + + HighlightAction findAction(const std::string& body, const std::string& sender) const; + + void handleHighlightAction(const HighlightAction& action); + + private: + HighlightManager* manager_; + Mode mode_; + HighlightRule::MessageType messageType_; + std::string nick_; + }; + +} diff --git a/Swift/Controllers/HistoryViewController.cpp b/Swift/Controllers/HistoryViewController.cpp index 9343017..cfa2482 100644 --- a/Swift/Controllers/HistoryViewController.cpp +++ b/Swift/Controllers/HistoryViewController.cpp @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + #include <Swift/Controllers/HistoryViewController.h> #include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h> @@ -15,6 +21,7 @@ #include <Swiften/Avatars/AvatarManager.h> #include <Swift/Controllers/Roster/SetPresence.h> #include <Swift/Controllers/Roster/SetAvatar.h> +#include <Swiften/Base/Path.h> namespace Swift { static const std::string category[] = { "Contacts", "MUC", "Contacts" }; @@ -146,7 +153,7 @@ void HistoryViewController::handleNewMessage(const HistoryMessage& message) { // update contacts if (!contacts_[message.getType()].count(displayJID)) { - roster_->addContact(displayJID, displayJID, nickResolver_->jidToNick(displayJID), category[message.getType()], avatarManager_->getAvatarPath(displayJID).string()); + roster_->addContact(displayJID, displayJID, nickResolver_->jidToNick(displayJID), category[message.getType()], avatarManager_->getAvatarPath(displayJID)); } contacts_[message.getType()][displayJID].insert(message.getTime().date()); @@ -154,7 +161,7 @@ void HistoryViewController::handleNewMessage(const HistoryMessage& message) { void HistoryViewController::addNewMessage(const HistoryMessage& message, bool addAtTheTop) { bool senderIsSelf = message.getFromJID().toBare() == selfJID_; - std::string avatarPath = avatarManager_->getAvatarPath(message.getFromJID()).string(); + std::string avatarPath = pathToString(avatarManager_->getAvatarPath(message.getFromJID())); std::string nick = message.getType() != HistoryMessage::Groupchat ? nickResolver_->jidToNick(message.getFromJID()) : message.getFromJID().getResource(); historyWindow_->addMessage(message.getMessage(), nick, senderIsSelf, avatarPath, message.getTime(), addAtTheTop); @@ -177,7 +184,7 @@ void HistoryViewController::handleReturnPressed(const std::string& keyword) { else { nick = nickResolver_->jidToNick(jid); } - roster_->addContact(jid, jid, nick, category[type], avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, nick, category[type], avatarManager_->getAvatarPath(jid)); Presence::ref presence = getPresence(jid, type == HistoryMessage::Groupchat); @@ -323,8 +330,7 @@ void HistoryViewController::handlePresenceChanged(Presence::ref presence) { } void HistoryViewController::handleAvatarChanged(const JID& jid) { - std::string path = avatarManager_->getAvatarPath(jid).string(); - roster_->applyOnItems(SetAvatar(jid, path)); + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid))); } Presence::ref HistoryViewController::getPresence(const JID& jid, bool isMUC) { diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 28d890d..14f0727 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,83 +1,99 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* + * Copyright (c) 2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <Swift/Controllers/MainController.h> +#include <stdlib.h> + #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> #include <boost/shared_ptr.hpp> #include <boost/smart_ptr/make_shared.hpp> -#include <string> -#include <stdlib.h> #include <Swiften/Base/format.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/Base/String.h> #include <Swiften/StringCodecs/Base64.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Client/Storages.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Client/Client.h> +#include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Elements/ChatState.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/VCardUpdate.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Disco/CapsInfoGenerator.h> +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/ClientDiscoManager.h> +#include <Swiften/VCards/GetVCardRequest.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/Network/NetworkFactories.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Client/ClientXMLTracer.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Crypto/CryptoProvider.h> + +#include <SwifTools/Dock/Dock.h> +#include <SwifTools/Notifier/TogglableNotifier.h> +#include <SwifTools/Idle/IdleDetector.h> + #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/UIFactory.h> -#include "Swiften/Network/TimerFactory.h" -#include "Swift/Controllers/BuildVersion.h" -#include "Swiften/Client/Storages.h" -#include "Swiften/VCards/VCardManager.h" -#include "Swift/Controllers/Chat/UserSearchController.h" -#include "Swift/Controllers/Chat/ChatsManager.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/EventWindowController.h" -#include "Swift/Controllers/UIInterfaces/LoginWindow.h" -#include "Swift/Controllers/UIInterfaces/LoginWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/MainWindow.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swiften/Client/NickResolver.h" -#include "Swift/Controllers/Roster/RosterController.h" -#include "Swift/Controllers/SoundEventController.h" -#include "Swift/Controllers/SoundPlayer.h" -#include "Swift/Controllers/StatusTracker.h" -#include "Swift/Controllers/SystemTray.h" -#include "Swift/Controllers/SystemTrayController.h" -#include "Swift/Controllers/XMLConsoleController.h" +#include <Swift/Controllers/BuildVersion.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/Chat/ChatsManager.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/EventWindowController.h> +#include <Swift/Controllers/UIInterfaces/LoginWindow.h> +#include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> +#include <Swift/Controllers/Chat/MUCController.h> +#include <Swift/Controllers/Roster/RosterController.h> +#include <Swift/Controllers/SoundEventController.h> +#include <Swift/Controllers/SoundPlayer.h> +#include <Swift/Controllers/StatusTracker.h> +#include <Swift/Controllers/SystemTray.h> +#include <Swift/Controllers/SystemTrayController.h> +#include <Swift/Controllers/XMLConsoleController.h> #include <Swift/Controllers/HistoryController.h> #include <Swift/Controllers/HistoryViewController.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 "Swift/Controllers/WhiteboardManager.h" -#include "SwifTools/Dock/Dock.h" -#include "SwifTools/Notifier/TogglableNotifier.h" -#include "Swiften/Base/foreach.h" -#include "Swiften/Client/Client.h" -#include "Swiften/Presence/PresenceSender.h" -#include "Swiften/Elements/ChatState.h" -#include "Swiften/Elements/Presence.h" -#include "Swiften/Elements/VCardUpdate.h" -#include "Swift/Controllers/Settings/SettingsProvider.h" -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/Disco/CapsInfoGenerator.h" -#include "Swiften/Disco/GetDiscoInfoRequest.h" -#include "Swiften/Disco/ClientDiscoManager.h" -#include "Swiften/VCards/GetVCardRequest.h" -#include "Swiften/StringCodecs/SHA1.h" -#include "Swiften/StringCodecs/Hexify.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/Storages/CertificateStorageFactory.h" -#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h" -#include "Swiften/Network/NetworkFactories.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 <Swift/Controllers/WhiteboardManager.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/Storages/CertificateStorageFactory.h> +#include <Swift/Controllers/Storages/CertificateStorageTrustChecker.h> #include <Swift/Controllers/ProfileController.h> +#include <Swift/Controllers/ShowProfileController.h> #include <Swift/Controllers/ContactEditController.h> #include <Swift/Controllers/XMPPURIController.h> -#include "Swift/Controllers/AdHocManager.h" -#include <SwifTools/Idle/IdleDetector.h> +#include <Swift/Controllers/AdHocManager.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> -#include <Swiften/FileTransfer/FileTransferManager.h> -#include <Swiften/Client/ClientXMLTracer.h> #include <Swift/Controllers/SettingConstants.h> -#include <Swiften/Client/StanzaChannel.h> +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/HighlightEditorController.h> +#include <Swift/Controllers/BlockListController.h> +#include <Swiften/Crypto/CryptoProvider.h> +#include <Swift/Controllers/ContactSuggester.h> +#include <Swift/Controllers/ContactsFromXMPPRoster.h> namespace Swift { @@ -98,6 +114,7 @@ MainController::MainController( Notifier* notifier, URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map<std::string, std::string>& emoticons, bool useDelayForLatency) : eventLoop_(eventLoop), networkFactories_(networkFactories), @@ -109,7 +126,8 @@ MainController::MainController( idleDetector_(idleDetector), loginWindow_(NULL) , useDelayForLatency_(useDelayForLatency), - ftOverview_(NULL) { + ftOverview_(NULL), + emoticons_(emoticons) { storages_ = NULL; certificateStorage_ = NULL; statusTracker_ = NULL; @@ -121,9 +139,15 @@ MainController::MainController( historyViewController_ = NULL; eventWindowController_ = NULL; profileController_ = NULL; + blockListController_ = NULL; + showProfileController_ = NULL; contactEditController_ = NULL; userSearchControllerChat_ = NULL; userSearchControllerAdd_ = NULL; + userSearchControllerInvite_ = NULL; + contactsFromRosterProvider_ = NULL; + contactSuggesterWithoutRoster_ = NULL; + contactSuggesterWithRoster_ = NULL; whiteboardManager_ = NULL; adHocManager_ = NULL; quitRequested_ = false; @@ -142,7 +166,11 @@ MainController::MainController( systemTrayController_ = new SystemTrayController(eventController_, systemTray); loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_); loginWindow_->setShowNotificationToggle(!notifier->isExternallyConfigured()); - soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings); + + highlightManager_ = new HighlightManager(settings_); + highlightEditorController_ = new HighlightEditorController(uiEventStream_, uiFactory_, highlightManager_); + + soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, highlightManager_); xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_); @@ -202,6 +230,8 @@ MainController::~MainController() { eventController_->disconnectAll(); resetClient(); + delete highlightEditorController_; + delete highlightManager_; delete fileTransferListController_; delete xmlConsoleController_; delete xmppURIController_; @@ -225,6 +255,8 @@ void MainController::resetClient() { contactEditController_ = NULL; delete profileController_; profileController_ = NULL; + delete showProfileController_; + showProfileController_ = NULL; delete eventWindowController_; eventWindowController_ = NULL; delete chatsManager_; @@ -255,6 +287,14 @@ void MainController::resetClient() { userSearchControllerChat_ = NULL; delete userSearchControllerAdd_; userSearchControllerAdd_ = NULL; + delete userSearchControllerInvite_; + userSearchControllerInvite_ = NULL; + delete contactSuggesterWithoutRoster_; + contactSuggesterWithoutRoster_ = NULL; + delete contactSuggesterWithRoster_; + contactSuggesterWithRoster_ = NULL; + delete contactsFromRosterProvider_; + contactsFromRosterProvider_ = NULL; delete adHocManager_; adHocManager_ = NULL; delete whiteboardManager_; @@ -297,16 +337,16 @@ void MainController::handleConnected() { myStatusLooksOnline_ = true; if (freshLogin) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); - srand(time(NULL)); - int randomPort = 10000 + rand() % 10000; - client_->getFileTransferManager()->startListeningOnPort(randomPort); + showProfileController_ = new ShowProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); 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_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager()); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this)); + blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_); + contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_); whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager()); @@ -315,14 +355,22 @@ void MainController::handleConnected() { * be before they receive stanzas that need it (e.g. bookmarks).*/ client_->getVCardManager()->requestOwnVCard(); + contactSuggesterWithoutRoster_ = new ContactSuggester(); + contactSuggesterWithRoster_ = new ContactSuggester(); + + userSearchControllerInvite_ = new UserSearchController(UserSearchController::InviteToChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); #ifdef SWIFT_EXPERIMENTAL_HISTORY historyController_ = new HistoryController(storages_->getHistoryStorage()); historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); - 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_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_); + 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_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_); #else - 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_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_); + 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_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), &emoticons_, userSearchControllerInvite_); #endif - + contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle()); + contactSuggesterWithoutRoster_->addContactProvider(chatsManager_); + contactSuggesterWithRoster_->addContactProvider(chatsManager_); + contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_); + client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); @@ -348,10 +396,11 @@ void MainController::handleConnected() { client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); - userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_); - userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_); + userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); + userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithoutRoster_, client_->getAvatarManager(), client_->getPresenceOracle()); adHocManager_ = new AdHocManager(JID(boundJID_.getDomain()), uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow()); + chatsManager_->onImpromptuMUCServiceDiscovered.connect(boost::bind(&UserSearchController::setCanInitiateImpromptuMUC, userSearchControllerChat_, _1)); } loginWindow_->setIsLoggingIn(false); @@ -369,7 +418,9 @@ void MainController::handleConnected() { contactEditController_->setAvailable(true); /* Send presence later to catch all the incoming presences. */ sendPresence(statusTracker_->getNextPresence()); + /* Enable chats last of all, so rejoining MUCs has the right sent presence */ + assert(chatsManager_); chatsManager_->setOnline(true); } @@ -440,7 +491,7 @@ void MainController::handleInputIdleChanged(bool idle) { } else { if (idle) { - if (statusTracker_->goAutoAway()) { + if (statusTracker_->goAutoAway(idleDetector_->getIdleTimeSeconds())) { if (client_ && client_->isAvailable()) { sendPresence(statusTracker_->getNextPresence()); } @@ -545,7 +596,7 @@ void MainController::performLoginFromCachedCredentials() { ClientOptions clientOptions = clientOptions_; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); clientOptions.forgetPassword = eagle; - clientOptions.useTLS = eagle ? ClientOptions::RequireTLS : ClientOptions::UseTLSWhenAvailable; + clientOptions.useTLS = eagle ? ClientOptions::RequireTLS : clientOptions_.useTLS; client_->connect(clientOptions); } @@ -710,6 +761,10 @@ void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> if (!error) { chatsManager_->setServerDiscoInfo(info); adHocManager_->setServerDiscoInfo(info); + if (info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + rosterController_->getWindow()->setBlockingCommandAvailable(true); + rosterController_->initBlockingCommand(); + } } } @@ -717,7 +772,7 @@ void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) { if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().empty()) { return; } - std::string hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + std::string hash = Hexify::hexify(networkFactories_->getCryptoProvider()->getSHA1Hash(vCard->getPhoto())); if (hash != vCardPhotoHash_) { vCardPhotoHash_ = hash; if (client_ && client_->isAvailable()) { diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 2e5bd05..6fbde6d 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -1,29 +1,33 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once -#include <Swiften/Base/boost_bsignals.h> -#include <boost/shared_ptr.hpp> #include <vector> - -#include "Swiften/Network/Timer.h" +#include <map> #include <string> -#include "Swiften/Client/ClientError.h" -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/Elements/VCard.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/Elements/Presence.h" -#include "Swift/Controllers/Settings/SettingsProvider.h" -#include "Swift/Controllers/ProfileSettingsProvider.h" -#include "Swiften/Elements/CapsInfo.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swift/Controllers/UIEvents/UIEvent.h" -#include "Swiften/Client/ClientXMLTracer.h" + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Network/Timer.h> +#include <Swiften/Client/ClientError.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/CapsInfo.h> +#include <Swiften/Client/ClientXMLTracer.h> + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/ProfileSettingsProvider.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + namespace Swift { class IdleDetector; @@ -43,6 +47,7 @@ namespace Swift { class MUCController; class Notifier; class ProfileController; + class ShowProfileController; class ContactEditController; class TogglableNotifier; class PresenceNotifier; @@ -71,6 +76,11 @@ namespace Swift { class AdHocCommandWindowFactory; class FileTransferOverview; class WhiteboardManager; + class HighlightManager; + class HighlightEditorController; + class BlockListController; + class ContactSuggester; + class ContactsFromXMPPRoster; class MainController { public: @@ -87,6 +97,7 @@ namespace Swift { Notifier* notifier, URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map<std::string, std::string>& emoticons, bool useDelayForLatency); ~MainController(); @@ -151,9 +162,14 @@ namespace Swift { HistoryViewController* historyViewController_; HistoryController* historyController_; FileTransferListController* fileTransferListController_; + BlockListController* blockListController_; ChatsManager* chatsManager_; ProfileController* profileController_; + ShowProfileController* showProfileController_; ContactEditController* contactEditController_; + ContactsFromXMPPRoster* contactsFromRosterProvider_; + ContactSuggester* contactSuggesterWithoutRoster_; + ContactSuggester* contactSuggesterWithRoster_; JID jid_; JID boundJID_; SystemTrayController* systemTrayController_; @@ -167,6 +183,7 @@ namespace Swift { bool useDelayForLatency_; UserSearchController* userSearchControllerChat_; UserSearchController* userSearchControllerAdd_; + UserSearchController* userSearchControllerInvite_; int timeBeforeNextReconnect_; Timer::ref reconnectTimer_; StatusTracker* statusTracker_; @@ -176,5 +193,8 @@ namespace Swift { static const int SecondsToWaitBeforeForceQuitting; FileTransferOverview* ftOverview_; WhiteboardManager* whiteboardManager_; + HighlightManager* highlightManager_; + HighlightEditorController* highlightEditorController_; + std::map<std::string, std::string> emoticons_; }; } diff --git a/Swift/Controllers/ProfileController.cpp b/Swift/Controllers/ProfileController.cpp index 101e283..241cc2e 100644 --- a/Swift/Controllers/ProfileController.cpp +++ b/Swift/Controllers/ProfileController.cpp @@ -25,7 +25,7 @@ ProfileController::~ProfileController() { if (profileWindow) { vcardManager->onOwnVCardChanged.disconnect(boost::bind(&ProfileController::handleOwnVCardChanged, this, _1)); profileWindow->onVCardChangeRequest.disconnect(boost::bind(&ProfileController::handleVCardChangeRequest, this, _1)); - delete profileWindow; + profileWindow->onWindowAboutToBeClosed.disconnect(boost::bind(&ProfileController::handleProfileWindowAboutToBeClosed, this, _1)); } uiEventStream->onUIEvent.disconnect(boost::bind(&ProfileController::handleUIEvent, this, _1)); } @@ -37,7 +37,9 @@ void ProfileController::handleUIEvent(UIEvent::ref event) { if (!profileWindow) { profileWindow = profileWindowFactory->createProfileWindow(); + profileWindow->setEditable(true); profileWindow->onVCardChangeRequest.connect(boost::bind(&ProfileController::handleVCardChangeRequest, this, _1)); + profileWindow->onWindowAboutToBeClosed.connect(boost::bind(&ProfileController::handleProfileWindowAboutToBeClosed, this, _1)); vcardManager->onOwnVCardChanged.connect(boost::bind(&ProfileController::handleOwnVCardChanged, this, _1)); } gettingVCard = true; @@ -75,6 +77,10 @@ void ProfileController::handleOwnVCardChanged(VCard::ref vcard) { } } +void ProfileController::handleProfileWindowAboutToBeClosed(const JID&) { + profileWindow = NULL; +} + void ProfileController::setAvailable(bool b) { available = b; if (!available) { diff --git a/Swift/Controllers/ProfileController.h b/Swift/Controllers/ProfileController.h index c1afcf9..538df36 100644 --- a/Swift/Controllers/ProfileController.h +++ b/Swift/Controllers/ProfileController.h @@ -29,6 +29,7 @@ namespace Swift { void handleVCardChangeRequest(VCard::ref vcard); void handleSetVCardResponse(ErrorPayload::ref); void handleOwnVCardChanged(VCard::ref vcard); + void handleProfileWindowAboutToBeClosed(const JID&); void updateDialogStatus(); private: diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 8c388bf..70b4a1b 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -8,11 +8,15 @@ #include "Swift/Controllers/Roster/GroupRosterItem.h" #include <Swiften/Base/foreach.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Elements/Idle.h> + +#include <boost/date_time/posix_time/posix_time.hpp> namespace Swift { -ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID) { +ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID), blockState_(BlockingNotSupported) { } ContactRosterItem::~ContactRosterItem() { @@ -24,12 +28,12 @@ StatusShow::Type ContactRosterItem::getStatusShow() const { StatusShow::Type ContactRosterItem::getSimplifiedStatusShow() const { switch (shownPresence_ ? shownPresence_->getShow() : StatusShow::None) { - case StatusShow::Online: return StatusShow::Online; break; - case StatusShow::Away: return StatusShow::Away; break; - case StatusShow::XA: return StatusShow::Away; break; - case StatusShow::FFC: return StatusShow::Online; break; - case StatusShow::DND: return StatusShow::DND; break; - case StatusShow::None: return StatusShow::None; break; + case StatusShow::Online: return StatusShow::Online; + case StatusShow::Away: return StatusShow::Away; + case StatusShow::XA: return StatusShow::Away; + case StatusShow::FFC: return StatusShow::Online; + case StatusShow::DND: return StatusShow::DND; + case StatusShow::None: return StatusShow::None; } assert(false); return StatusShow::None; @@ -39,11 +43,20 @@ std::string ContactRosterItem::getStatusText() const { return shownPresence_ ? shownPresence_->getStatus() : ""; } -void ContactRosterItem::setAvatarPath(const std::string& path) { +std::string ContactRosterItem::getIdleText() const { + Idle::ref idle = shownPresence_ ? shownPresence_->getPayload<Idle>() : Idle::ref(); + if (!idle || idle->getSince().is_not_a_date_time()) { + return ""; + } else { + return dateTimeToLocalString(idle->getSince()); + } +} + +void ContactRosterItem::setAvatarPath(const boost::filesystem::path& path) { avatarPath_ = path; onDataChanged(); } -const std::string& ContactRosterItem::getAvatarPath() const { +const boost::filesystem::path& ContactRosterItem::getAvatarPath() const { return avatarPath_; } @@ -121,6 +134,14 @@ bool ContactRosterItem::supportsFeature(const Feature feature) const { return features_.find(feature) != features_.end(); } +void ContactRosterItem::setBlockState(BlockState state) { + blockState_ = state; +} + +ContactRosterItem::BlockState ContactRosterItem::blockState() const { + return blockState_; +} + } diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 8389a44..67a9722 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -17,6 +17,8 @@ #include <boost/bind.hpp> #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/ptime.hpp> +#include <boost/filesystem/path.hpp> namespace Swift { @@ -25,7 +27,13 @@ class ContactRosterItem : public RosterItem { public: enum Feature { FileTransferFeature, - WhiteboardFeature, + WhiteboardFeature + }; + + enum BlockState { + BlockingNotSupported, + IsBlocked, + IsUnblocked }; public: @@ -35,8 +43,9 @@ class ContactRosterItem : public RosterItem { StatusShow::Type getStatusShow() const; StatusShow::Type getSimplifiedStatusShow() const; std::string getStatusText() const; - void setAvatarPath(const std::string& path); - const std::string& getAvatarPath() const; + std::string getIdleText() const; + void setAvatarPath(const boost::filesystem::path& path); + const boost::filesystem::path& getAvatarPath() const; const JID& getJID() const; void setDisplayJID(const JID& jid); const JID& getDisplayJID() const; @@ -50,15 +59,21 @@ class ContactRosterItem : public RosterItem { void setSupportedFeatures(const std::set<Feature>& features); bool supportsFeature(Feature feature) const; + + void setBlockState(BlockState state); + BlockState blockState() const; + private: JID jid_; JID displayJID_; - std::string avatarPath_; + boost::posix_time::ptime lastAvailableTime_; + boost::filesystem::path avatarPath_; std::map<std::string, boost::shared_ptr<Presence> > presences_; boost::shared_ptr<Presence> offlinePresence_; boost::shared_ptr<Presence> shownPresence_; std::vector<std::string> groups_; std::set<Feature> features_; + BlockState blockState_; }; } diff --git a/Swift/Controllers/Roster/LeastCommonSubsequence.h b/Swift/Controllers/Roster/LeastCommonSubsequence.h index dd3c95a..9d45679 100644 --- a/Swift/Controllers/Roster/LeastCommonSubsequence.h +++ b/Swift/Controllers/Roster/LeastCommonSubsequence.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Remko Tronçon + * Copyright (c) 2011-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -7,6 +7,7 @@ #pragma once #include <vector> +#include <boost/numeric/conversion/cast.hpp> namespace Swift { using std::equal_to; @@ -14,8 +15,8 @@ namespace Swift { 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; + size_t width = static_cast<size_t>(std::distance(xBegin, xEnd) + 1); + size_t height = static_cast<size_t>(std::distance(yBegin, yEnd) + 1); result.resize(width * height); // Initialize first row & column @@ -30,7 +31,7 @@ namespace Swift { 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)])); + result[i + j*width] = predicate(*(xBegin + boost::numeric_cast<long long>(i)-1), *(yBegin + boost::numeric_cast<long long >(j)-1)) ? result[(i-1) + (j-1)*width] + 1 : std::max(result[i + (j-1)*width], result[i-1 + (j*width)]); } } } @@ -46,29 +47,29 @@ namespace Swift { 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)); + updates.push_back(static_cast<size_t>(std::distance(x.begin(), xBegin))); + postUpdates.push_back(static_cast<size_t>(std::distance(y.begin(), yBegin))); } ++xBegin; ++yBegin; } - size_t prefixLength = std::distance(x.begin(), xBegin); + size_t prefixLength = static_cast<size_t>(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); + updates.push_back(static_cast<size_t>(std::distance(x.begin(), xEnd.base()) - 1)); + postUpdates.push_back(static_cast<size_t>(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()); + size_t xLength = static_cast<size_t>(std::distance(xBegin, xEnd.base())); + size_t yLength = static_cast<size_t>(std::distance(yBegin, yEnd.base())); // Compute LCS matrix std::vector<unsigned int> lcs; @@ -77,7 +78,7 @@ namespace Swift { // Process LCS matrix size_t i = xLength; size_t j = yLength; - const size_t width = xLength + 1; + 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 diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index 65cf4d2..9b45b63 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -22,7 +22,7 @@ namespace Swift { -Roster::Roster(bool sortByStatus, bool fullJIDMapping) { +Roster::Roster(bool sortByStatus, bool fullJIDMapping) : blockingSupported_(false) { sortByStatus_ = sortByStatus; fullJIDMapping_ = fullJIDMapping; root_ = new GroupRosterItem("Dummy-Root", NULL, sortByStatus_); @@ -71,6 +71,28 @@ void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterIt } } +void Roster::setBlockedState(const std::vector<JID> &jids, ContactRosterItem::BlockState state) { + if (!blockingSupported_ ) { + foreach(ItemMap::value_type i, itemMap_) { + foreach(ContactRosterItem* item, i.second) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } + } + } + + foreach(const JID& jid, jids) { + ItemMap::const_iterator i = itemMap_.find(fullJIDMapping_ ? jid : jid.toBare()); + if (i == itemMap_.end()) { + continue; + } + foreach(ContactRosterItem* item, i->second) { + item->setBlockState(state); + } + } + + blockingSupported_ = true; +} + void Roster::removeGroup(const std::string& group) { root_->removeGroupChild(group); } @@ -83,10 +105,13 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) { onChildrenChanged(item); } -void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const std::string& avatarPath) { +void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const boost::filesystem::path& avatarPath) { GroupRosterItem* group(getGroup(groupName)); ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); item->setAvatarPath(avatarPath); + if (blockingSupported_) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } group->addChild(item); ItemMap::iterator i = itemMap_.insert(std::make_pair(fullJIDMapping_ ? jid : jid.toBare(), std::vector<ContactRosterItem*>())).first; if (!i->second.empty()) { @@ -198,13 +223,13 @@ void Roster::removeFilter(RosterFilter *filter) { } void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) { - int oldDisplayedSize = group->getDisplayedChildren().size(); + size_t oldDisplayedSize = group->getDisplayedChildren().size(); bool hide = true; foreach (RosterFilter *filter, filters_) { hide &= (*filter)(contact); } group->setDisplayed(contact, filters_.empty() || !hide); - int newDisplayedSize = group->getDisplayedChildren().size(); + size_t 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 2fcfba5..a4c8b99 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -28,7 +28,7 @@ class Roster { Roster(bool sortByStatus = true, bool fullJIDMapping = false); ~Roster(); - void addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& group, const std::string& avatarPath); + void addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& group, const boost::filesystem::path& avatarPath); void removeContact(const JID& jid); void removeContactFromGroup(const JID& jid, const std::string& group); void removeGroup(const std::string& group); @@ -36,15 +36,16 @@ class Roster { void applyOnItems(const RosterItemOperation& operation); void applyOnAllItems(const RosterItemOperation& operation); void applyOnItem(const RosterItemOperation& operation, const JID& jid); - void addFilter(RosterFilter *filter) {filters_.push_back(filter);filterAll();}; + void addFilter(RosterFilter *filter) {filters_.push_back(filter);filterAll();} void removeFilter(RosterFilter *filter); GroupRosterItem* getRoot(); - std::vector<RosterFilter*> getFilters() {return filters_;}; + std::vector<RosterFilter*> getFilters() {return filters_;} boost::signal<void (GroupRosterItem*)> onChildrenChanged; 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); + void setBlockedState(const std::vector<JID>& jids, ContactRosterItem::BlockState state); private: void handleDataChanged(RosterItem* item); @@ -58,6 +59,7 @@ class Roster { ItemMap itemMap_; bool fullJIDMapping_; bool sortByStatus_; + bool blockingSupported_; }; } diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index ec52993..d277799 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -40,18 +40,20 @@ #include <Swiften/Client/NickManager.h> #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Path.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Disco/EntityCapsManager.h> #include <Swiften/Jingle/JingleSessionManager.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swiften/Client/ClientBlockListManager.h> namespace Swift { /** * 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, 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) { +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, ClientBlockListManager* clientBlockListManager) + : 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), clientBlockListManager_(clientBlockListManager) { assert(fileTransferOverview); iqRouter_ = iqRouter; presenceOracle_ = presenceOracle; @@ -72,7 +74,7 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata uiEventConnection_ = uiEventStream->onUIEvent.connect(boost::bind(&RosterController::handleUIEvent, this, _1)); avatarManager_ = avatarManager; avatarManager_->onAvatarChanged.connect(boost::bind(&RosterController::handleAvatarChanged, this, _1)); - mainWindow_->setMyAvatarPath(avatarManager_->getAvatarPath(myJID_).string()); + mainWindow_->setMyAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_))); nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); mainWindow_->setMyJID(jid); @@ -124,11 +126,11 @@ void RosterController::handleOnJIDAdded(const JID& jid) { std::string name = nickResolver_->jidToNick(jid); if (!groups.empty()) { foreach(const std::string& group, groups) { - roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid)); } } else { - roster_->addContact(jid, jid, name, QT_TRANSLATE_NOOP("", "Contacts"), avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, name, QT_TRANSLATE_NOOP("", "Contacts"), avatarManager_->getAvatarPath(jid)); } applyAllPresenceTo(jid); } @@ -163,7 +165,7 @@ void RosterController::handleOnJIDUpdated(const JID& jid, const std::string& old } foreach(const std::string& group, groups) { if (std::find(oldGroups.begin(), oldGroups.end(), group) == oldGroups.end()) { - roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid).string()); + roster_->addContact(jid, jid, name, group, avatarManager_->getAvatarPath(jid)); } } foreach(const std::string& group, oldGroups) { @@ -183,6 +185,20 @@ void RosterController::handleSettingChanged(const std::string& settingPath) { } } +void RosterController::handleBlockingStateChanged() { + if (clientBlockListManager_->getBlockList()->getState() == BlockList::Available) { + roster_->setBlockedState(clientBlockListManager_->getBlockList()->getItems(), ContactRosterItem::IsBlocked); + } +} + +void RosterController::handleBlockingItemAdded(const JID& jid) { + roster_->setBlockedState(std::vector<JID>(1, jid), ContactRosterItem::IsBlocked); +} + +void RosterController::handleBlockingItemRemoved(const JID& jid) { + roster_->setBlockedState(std::vector<JID>(1, jid), ContactRosterItem::IsUnblocked); +} + void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { if (boost::shared_ptr<AddContactUIEvent> addContactEvent = boost::dynamic_pointer_cast<AddContactUIEvent>(event)) { RosterItemPayload item; @@ -256,6 +272,18 @@ void RosterController::updateItem(const XMPPRosterItem& item) { request->send(); } +void RosterController::initBlockingCommand() { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + + blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&RosterController::handleBlockingStateChanged, this)); + blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&RosterController::handleBlockingItemAdded, this, _1)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&RosterController::handleBlockingItemRemoved, this, _1)); + + if (blockList->getState() == BlockList::Available) { + roster_->setBlockedState(blockList->getItems(), ContactRosterItem::IsBlocked); + } +} + void RosterController::handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload) { if (!error) { return; @@ -299,10 +327,10 @@ void RosterController::handleSubscriptionRequestDeclined(SubscriptionRequestEven } void RosterController::handleAvatarChanged(const JID& jid) { - std::string path = avatarManager_->getAvatarPath(jid).string(); + boost::filesystem::path path = avatarManager_->getAvatarPath(jid); roster_->applyOnItems(SetAvatar(jid, path)); if (jid.equals(myJID_, JID::WithoutResource)) { - mainWindow_->setMyAvatarPath(path); + mainWindow_->setMyAvatarPath(pathToString(path)); } } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 5e40124..06b551e 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -39,13 +39,14 @@ namespace Swift { class NickManager; class EntityCapsProvider; class FileTransferManager; - + class ClientBlockListManager; + 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, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview); + 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, ClientBlockListManager* clientBlockListManager); ~RosterController(); void showRosterWindow(); - MainWindow* getWindow() {return mainWindow_;}; + MainWindow* getWindow() {return mainWindow_;} boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signal<void ()> onSignOutRequest; void handleAvatarChanged(const JID& jid); @@ -57,6 +58,8 @@ namespace Swift { void setContactGroups(const JID& jid, const std::vector<std::string>& groups); void updateItem(const XMPPRosterItem&); + void initBlockingCommand(); + private: void handleOnJIDAdded(const JID &jid); void handleRosterCleared(); @@ -76,6 +79,10 @@ namespace Swift { void handleOnCapsChanged(const JID& jid); void handleSettingChanged(const std::string& settingPath); + void handleBlockingStateChanged(); + void handleBlockingItemAdded(const JID& jid); + void handleBlockingItemRemoved(const JID& jid); + JID myJID_; XMPPRoster* xmppRoster_; MainWindowFactory* mainWindowFactory_; @@ -94,7 +101,11 @@ namespace Swift { UIEventStream* uiEventStream_; EntityCapsProvider* entityCapsManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; + boost::bsignals::scoped_connection blockingOnStateChangedConnection_; + boost::bsignals::scoped_connection blockingOnItemAddedConnection_; + boost::bsignals::scoped_connection blockingOnItemRemovedConnection_; boost::bsignals::scoped_connection changeStatusConnection_; boost::bsignals::scoped_connection signOutConnection_; boost::bsignals::scoped_connection uiEventConnection_; diff --git a/Swift/Controllers/Roster/RosterItemOperation.h b/Swift/Controllers/Roster/RosterItemOperation.h index 691c8ef..f1dff8d 100644 --- a/Swift/Controllers/Roster/RosterItemOperation.h +++ b/Swift/Controllers/Roster/RosterItemOperation.h @@ -12,10 +12,10 @@ namespace Swift { class RosterItemOperation { public: - RosterItemOperation(bool requiresLookup = false, const JID& lookupJID = JID()) : requiresLookup_(requiresLookup), lookupJID_(lookupJID) {}; - virtual ~RosterItemOperation() {}; - bool requiresLookup() const {return requiresLookup_;}; - const JID& lookupJID() const {return lookupJID_;}; + RosterItemOperation(bool requiresLookup = false, const JID& lookupJID = JID()) : requiresLookup_(requiresLookup), lookupJID_(lookupJID) {} + virtual ~RosterItemOperation() {} + bool requiresLookup() const {return requiresLookup_;} + const JID& lookupJID() const {return lookupJID_;} /** * This is called when iterating over possible subjects, so must check it's * applying to the right items - even if requiresLookup() is true an item diff --git a/Swift/Controllers/Roster/SetAvatar.h b/Swift/Controllers/Roster/SetAvatar.h index 241b741..424f0b3 100644 --- a/Swift/Controllers/Roster/SetAvatar.h +++ b/Swift/Controllers/Roster/SetAvatar.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,6 +10,7 @@ #include "Swiften/JID/JID.h" #include "Swift/Controllers/Roster/RosterItemOperation.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <boost/filesystem/path.hpp> namespace Swift { @@ -17,7 +18,7 @@ class RosterItem; class SetAvatar : public RosterItemOperation { public: - SetAvatar(const JID& jid, const std::string& path, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), path_(path), compareType_(compareType) { + SetAvatar(const JID& jid, const boost::filesystem::path& path, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), path_(path), compareType_(compareType) { } virtual void operator() (RosterItem* item) const { @@ -29,7 +30,7 @@ class SetAvatar : public RosterItemOperation { private: JID jid_; - std::string path_; + boost::filesystem::path path_; JID::CompareType compareType_; }; diff --git a/Swift/Controllers/Roster/TableRoster.cpp b/Swift/Controllers/Roster/TableRoster.cpp index c00bf4f..eb036db 100644 --- a/Swift/Controllers/Roster/TableRoster.cpp +++ b/Swift/Controllers/Roster/TableRoster.cpp @@ -9,6 +9,7 @@ #include <boost/cast.hpp> #include <cassert> #include <algorithm> +#include <boost/numeric/conversion/cast.hpp> #include <Swiften/Base/foreach.h> #include <Swiften/Network/TimerFactory.h> @@ -132,13 +133,13 @@ void TableRoster::handleUpdateTimerTick() { 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])); + std::transform(itemInserts.begin(), itemInserts.end(), update.insertedRows.begin() + boost::numeric_cast<long long>(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])); + std::transform(itemRemoves.begin(), itemRemoves.end(), update.deletedRows.begin() + boost::numeric_cast<long long>(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])); + std::transform(itemUpdates.begin(), itemUpdates.end(), update.updatedRows.begin() + boost::numeric_cast<long long>(end), CreateIndexForSection(sectionPostUpdates[i])); } // Switch the old model with the new diff --git a/Swift/Controllers/Roster/TableRoster.h b/Swift/Controllers/Roster/TableRoster.h index d4612ed..f447760 100644 --- a/Swift/Controllers/Roster/TableRoster.h +++ b/Swift/Controllers/Roster/TableRoster.h @@ -12,6 +12,7 @@ #include <Swiften/JID/JID.h> #include <Swiften/Elements/StatusShow.h> +#include <boost/filesystem/path.hpp> namespace Swift { class Roster; @@ -21,13 +22,13 @@ namespace Swift { 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) { + Item(const std::string& name, const std::string& description, const JID& jid, StatusShow::Type status, const boost::filesystem::path& 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; + boost::filesystem::path avatarPath; }; struct Index { diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index fbee894..b0034e6 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -37,6 +37,7 @@ #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> #include <Swiften/Base/Algorithm.h> #include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/Client/ClientBlockListManager.h> using namespace Swift; @@ -82,12 +83,14 @@ class RosterControllerTest : public CppUnit::TestFixture { ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_); + clientBlockListManager_ = new ClientBlockListManager(router_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_); mainWindow_ = mainWindowFactory_->last; - }; + } void tearDown() { delete rosterController_; + delete clientBlockListManager_; delete ftManager_; delete jingleSessionManager_; @@ -105,7 +108,7 @@ class RosterControllerTest : public CppUnit::TestFixture { delete uiEventStream_; delete settings_; delete xmppRoster_; - }; + } GroupRosterItem* groupChild(size_t i) { return dynamic_cast<GroupRosterItem*>(CHILDREN[i]); @@ -133,7 +136,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(item2); CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item2->getStatusText()); - }; + } void testHighestPresence() { std::vector<std::string> groups; @@ -153,7 +156,7 @@ class RosterControllerTest : public CppUnit::TestFixture { ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); - }; + } void testNotHighestPresence() { std::vector<std::string> groups; @@ -173,7 +176,7 @@ class RosterControllerTest : public CppUnit::TestFixture { ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); - }; + } void testUnavailablePresence() { std::vector<std::string> groups; @@ -215,7 +218,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(lowPresenceOffline->getStatus(), high->getStatus()); CPPUNIT_ASSERT_EQUAL(StatusShow::None, item->getStatusShow()); CPPUNIT_ASSERT_EQUAL(lowPresenceOffline->getStatus(), item->getStatusText()); - }; + } void testAdd() { std::vector<std::string> groups; @@ -225,7 +228,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(CHILDREN.size())); //CPPUNIT_ASSERT_EQUAL(std::string("Bob"), xmppRoster_->getNameForJID(JID("foo@bar.com"))); - }; + } void testAddSubscription() { std::vector<std::string> groups; @@ -242,7 +245,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); - }; + } void testReceiveRename() { std::vector<std::string> groups; @@ -256,7 +259,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("NewName"), groupChild(0)->getChildren()[0]->getDisplayName()); - }; + } void testReceiveRegroup() { std::vector<std::string> oldGroups; @@ -282,7 +285,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("new name"), groupChild(0)->getChildren()[0]->getDisplayName()); CPPUNIT_ASSERT_EQUAL(std::string("Best Group"), groupChild(0)->getDisplayName()); - }; + } void testSendRename() { JID jid("testling@wonderland.lit"); @@ -337,6 +340,7 @@ class RosterControllerTest : public CppUnit::TestFixture { JingleSessionManager* jingleSessionManager_; FileTransferManager* ftManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; }; CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest); diff --git a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp index e433b50..db8a2fd 100644 --- a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp @@ -6,6 +6,8 @@ #include <Swift/Controllers/Roster/TableRoster.h> +std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i); + std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { os << "(" << i.section << ", " << i.row << ")"; return os; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 7cd017b..ea52084 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -28,8 +28,11 @@ if env["SCONS_STAGE"] == "build" : "Chat/MUCController.cpp", "Chat/MUCSearchController.cpp", "Chat/UserSearchController.cpp", + "Chat/ChatMessageParser.cpp", + "ContactSuggester.cpp", "MainController.cpp", "ProfileController.cpp", + "ShowProfileController.cpp", "ContactEditController.cpp", "FileTransfer/FileTransferController.cpp", "FileTransfer/FileTransferOverview.cpp", @@ -48,6 +51,7 @@ if env["SCONS_STAGE"] == "build" : "HistoryViewController.cpp", "HistoryController.cpp", "FileTransferListController.cpp", + "BlockListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", @@ -74,7 +78,16 @@ if env["SCONS_STAGE"] == "build" : "XMPPURIController.cpp", "ChatMessageSummarizer.cpp", "SettingConstants.cpp", - "WhiteboardManager.cpp" + "WhiteboardManager.cpp", + "StatusCache.cpp", + "HighlightAction.cpp", + "HighlightEditorController.cpp", + "HighlightManager.cpp", + "HighlightRule.cpp", + "Highlighter.cpp", + "ContactsFromXMPPRoster.cpp", + "ContactProvider.cpp", + "Contact.cpp" ]) env.Append(UNITTEST_SOURCES = [ @@ -86,7 +99,9 @@ if env["SCONS_STAGE"] == "build" : File("UnitTest/PresenceNotifierTest.cpp"), File("Chat/UnitTest/ChatsManagerTest.cpp"), File("Chat/UnitTest/MUCControllerTest.cpp"), + File("Chat/UnitTest/ChatMessageParserTest.cpp"), File("UnitTest/MockChatWindow.cpp"), File("UnitTest/ChatMessageSummarizerTest.cpp"), File("Settings/UnitTest/SettingsProviderHierachyTest.cpp"), + File("UnitTest/HighlightRuleTest.cpp"), ]) diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp index 7ab4ac4..a5328a4 100644 --- a/Swift/Controllers/SettingConstants.cpp +++ b/Swift/Controllers/SettingConstants.cpp @@ -19,4 +19,10 @@ const SettingsProvider::Setting<bool> SettingConstants::LOGIN_AUTOMATICALLY = Se const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOffline", false); const SettingsProvider::Setting<std::string> SettingConstants::EXPANDED_ROSTER_GROUPS("GroupExpandiness", ""); const SettingsProvider::Setting<bool> SettingConstants::PLAY_SOUNDS("playSounds", true); +const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES("highlightRules", "@"); +const SettingsProvider::Setting<bool> SettingConstants::SPELL_CHECKER("spellChecker", false); +const SettingsProvider::Setting<std::string> SettingConstants::DICT_PATH("dictPath", "/usr/share/myspell/dicts/"); +const SettingsProvider::Setting<std::string> SettingConstants::PERSONAL_DICT_PATH("personaldictPath", "/home/"); +const SettingsProvider::Setting<std::string> SettingConstants::DICT_FILE("dictFile", "en_US.dic"); +const SettingsProvider::Setting<std::string> SettingConstants::INVITE_AUTO_ACCEPT_MODE("inviteAutoAcceptMode", "presence"); } diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h index ff1ed72..4d25bde 100644 --- a/Swift/Controllers/SettingConstants.h +++ b/Swift/Controllers/SettingConstants.h @@ -22,5 +22,11 @@ namespace Swift { static const SettingsProvider::Setting<bool> SHOW_OFFLINE; static const SettingsProvider::Setting<std::string> EXPANDED_ROSTER_GROUPS; static const SettingsProvider::Setting<bool> PLAY_SOUNDS; + static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES; + static const SettingsProvider::Setting<bool> SPELL_CHECKER; + static const SettingsProvider::Setting<std::string> DICT_PATH; + static const SettingsProvider::Setting<std::string> PERSONAL_DICT_PATH; + static const SettingsProvider::Setting<std::string> DICT_FILE; + static const SettingsProvider::Setting<std::string> INVITE_AUTO_ACCEPT_MODE; }; } diff --git a/Swift/Controllers/Settings/DummySettingsProvider.h b/Swift/Controllers/Settings/DummySettingsProvider.h index 1d6059f..0183dd3 100644 --- a/Swift/Controllers/Settings/DummySettingsProvider.h +++ b/Swift/Controllers/Settings/DummySettingsProvider.h @@ -17,25 +17,25 @@ class DummySettingsProvider : public SettingsProvider { virtual ~DummySettingsProvider() {} virtual std::string getSetting(const Setting<std::string>& setting) { return stringValues.find(setting.getKey()) != stringValues.end() ? stringValues[setting.getKey()] : setting.getDefaultValue(); - }; + } virtual void storeSetting(const Setting<std::string>& setting, const std::string& value) { stringValues[setting.getKey()] = value; onSettingChanged(setting.getKey()); - }; + } virtual bool getSetting(const Setting<bool>& setting) { return boolValues.find(setting.getKey()) != boolValues.end() ? boolValues[setting.getKey()] : setting.getDefaultValue(); - }; + } virtual void storeSetting(const Setting<bool>& setting, const bool& value) { boolValues[setting.getKey()] = value; onSettingChanged(setting.getKey()); - }; + } virtual int getSetting(const Setting<int>& setting) { return intValues.find(setting.getKey()) != intValues.end() ? intValues[setting.getKey()] : setting.getDefaultValue(); - }; + } virtual void storeSetting(const Setting<int>& setting, const int& value) { intValues[setting.getKey()] = value; onSettingChanged(setting.getKey()); - }; + } virtual std::vector<std::string> getAvailableProfiles() {return std::vector<std::string>();} virtual void createProfile(const std::string& ) {} virtual void removeProfile(const std::string& ) {} diff --git a/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp b/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp index aa4d14f..2b637a0 100644 --- a/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp +++ b/Swift/Controllers/Settings/UnitTest/SettingsProviderHierachyTest.cpp @@ -26,7 +26,7 @@ class SettingsProviderHierachyTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE_END(); public: - SettingsProviderHierachyTest() : setting1("somekey", 42) {}; + SettingsProviderHierachyTest() : setting1("somekey", 42) {} void setUp() { bottom = new DummySettingsProvider(); diff --git a/Swift/Controllers/ShowProfileController.cpp b/Swift/Controllers/ShowProfileController.cpp new file mode 100644 index 0000000..15b7b26 --- /dev/null +++ b/Swift/Controllers/ShowProfileController.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "ShowProfileController.h" + +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/VCards/VCardManager.h> + +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> + +namespace Swift { + +ShowProfileController::ShowProfileController(VCardManager* vcardManager, ProfileWindowFactory* profileWindowFactory, UIEventStream* uiEventStream) : vcardManager(vcardManager), profileWindowFactory(profileWindowFactory), uiEventStream(uiEventStream) { + uiEventStream->onUIEvent.connect(boost::bind(&ShowProfileController::handleUIEvent, this, _1)); + vcardManager->onVCardChanged.connect(boost::bind(&ShowProfileController::handleVCardChanged, this, _1, _2)); +} + +ShowProfileController::~ShowProfileController() { + typedef std::pair<JID, ProfileWindow*> JIDProfileWindowPair; + foreach(const JIDProfileWindowPair& jidWndPair, openedProfileWindows) { + jidWndPair.second->onWindowAboutToBeClosed.disconnect(boost::bind(&ShowProfileController::handleProfileWindowAboutToBeClosed, this, _1)); + delete jidWndPair.second; + } + + vcardManager->onVCardChanged.disconnect(boost::bind(&ShowProfileController::handleVCardChanged, this, _1, _2)); + uiEventStream->onUIEvent.disconnect(boost::bind(&ShowProfileController::handleUIEvent, this, _1)); +} + +void ShowProfileController::handleUIEvent(UIEvent::ref event) { + ShowProfileForRosterItemUIEvent::ref showProfileEvent = boost::dynamic_pointer_cast<ShowProfileForRosterItemUIEvent>(event); + if (!showProfileEvent) { + return; + } + + if (openedProfileWindows.find(showProfileEvent->getJID()) == openedProfileWindows.end()) { + ProfileWindow* newProfileWindow = profileWindowFactory->createProfileWindow(); + newProfileWindow->setJID(showProfileEvent->getJID()); + newProfileWindow->onWindowAboutToBeClosed.connect(boost::bind(&ShowProfileController::handleProfileWindowAboutToBeClosed, this, _1)); + openedProfileWindows[showProfileEvent->getJID()] = newProfileWindow; + VCard::ref vcard = vcardManager->getVCardAndRequestWhenNeeded(showProfileEvent->getJID()); + if (vcard) { + newProfileWindow->setVCard(vcard); + } else { + newProfileWindow->setProcessing(true); + } + newProfileWindow->show(); + } else { + openedProfileWindows[showProfileEvent->getJID()]->show(); + } +} + +void ShowProfileController::handleVCardChanged(const JID& jid, VCard::ref vcard) { + if (openedProfileWindows.find(jid) == openedProfileWindows.end()) { + return; + } + + ProfileWindow* profileWindow = openedProfileWindows[jid]; + profileWindow->setVCard(vcard); + profileWindow->setProcessing(false); + profileWindow->show(); +} + +void ShowProfileController::handleProfileWindowAboutToBeClosed(const JID& profileJid) { + openedProfileWindows.erase(profileJid); +} + +} diff --git a/Swift/Controllers/ShowProfileController.h b/Swift/Controllers/ShowProfileController.h new file mode 100644 index 0000000..27a0cf4 --- /dev/null +++ b/Swift/Controllers/ShowProfileController.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/VCard.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class VCardManager; + class ProfileWindow; + class ProfileWindowFactory; + class UIEventStream; + + class ShowProfileController { + public: + ShowProfileController(VCardManager* vcardManager, ProfileWindowFactory* profileWindowFactory, UIEventStream* uiEventStream); + ~ShowProfileController(); + + private: + void handleUIEvent(UIEvent::ref event); + void handleVCardChanged(const JID&, VCard::ref); + void handleProfileWindowAboutToBeClosed(const JID& profileJid); + + private: + VCardManager* vcardManager; + ProfileWindowFactory* profileWindowFactory; + UIEventStream* uiEventStream; + std::map<JID, ProfileWindow*> openedProfileWindows; + }; +} diff --git a/Swift/Controllers/SoundEventController.cpp b/Swift/Controllers/SoundEventController.cpp index d056990..a5171e2 100644 --- a/Swift/Controllers/SoundEventController.cpp +++ b/Swift/Controllers/SoundEventController.cpp @@ -12,22 +12,33 @@ #include <Swift/Controllers/SoundPlayer.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/HighlightManager.h> namespace Swift { -SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings) { +SoundEventController::SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager) { settings_ = settings; eventController_ = eventController; soundPlayer_ = soundPlayer; eventController_->onEventQueueEventAdded.connect(boost::bind(&SoundEventController::handleEventQueueEventAdded, this, _1)); + highlightManager_ = highlightManager; + highlightManager_->onHighlight.connect(boost::bind(&SoundEventController::handleHighlight, this, _1)); + settings_->onSettingChanged.connect(boost::bind(&SoundEventController::handleSettingChanged, this, _1)); playSounds_ = settings->getSetting(SettingConstants::PLAY_SOUNDS); } -void SoundEventController::handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> event) { - if (playSounds_ && !event->getConcluded()) { - soundPlayer_->playSound(SoundPlayer::MessageReceived); +void SoundEventController::handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> /*event*/) { + // message received sound is now played via highlighting + //if (playSounds_ && !event->getConcluded()) { + // soundPlayer_->playSound(SoundPlayer::MessageReceived); + //} +} + +void SoundEventController::handleHighlight(const HighlightAction& action) { + if (playSounds_ && action.playSound()) { + soundPlayer_->playSound(SoundPlayer::MessageReceived, action.getSoundFile()); } } diff --git a/Swift/Controllers/SoundEventController.h b/Swift/Controllers/SoundEventController.h index c6dec6f..c9dcab4 100644 --- a/Swift/Controllers/SoundEventController.h +++ b/Swift/Controllers/SoundEventController.h @@ -10,21 +10,25 @@ #include <Swift/Controllers/XMPPEvents/StanzaEvent.h> #include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/HighlightAction.h> namespace Swift { class EventController; class SoundPlayer; + class HighlightManager; class SoundEventController { public: - SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings); + SoundEventController(EventController* eventController, SoundPlayer* soundPlayer, SettingsProvider* settings, HighlightManager* highlightManager); void setPlaySounds(bool playSounds); - bool getSoundEnabled() {return playSounds_;}; + bool getSoundEnabled() {return playSounds_;} private: void handleSettingChanged(const std::string& settingPath); void handleEventQueueEventAdded(boost::shared_ptr<StanzaEvent> event); + void handleHighlight(const HighlightAction& action); EventController* eventController_; SoundPlayer* soundPlayer_; bool playSounds_; SettingsProvider* settings_; + HighlightManager* highlightManager_; }; } diff --git a/Swift/Controllers/SoundPlayer.h b/Swift/Controllers/SoundPlayer.h index b71d759..f18a2c0 100644 --- a/Swift/Controllers/SoundPlayer.h +++ b/Swift/Controllers/SoundPlayer.h @@ -6,11 +6,13 @@ #pragma once +#include <string> + namespace Swift { class SoundPlayer { public: - virtual ~SoundPlayer() {}; + virtual ~SoundPlayer() {} enum SoundEffect{MessageReceived}; - virtual void playSound(SoundEffect sound) = 0; + virtual void playSound(SoundEffect sound, const std::string& soundResource) = 0; }; } diff --git a/Swift/Controllers/StatusCache.cpp b/Swift/Controllers/StatusCache.cpp new file mode 100644 index 0000000..3444189 --- /dev/null +++ b/Swift/Controllers/StatusCache.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/StatusCache.h> + +#include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/ByteArray.h> +#include <SwifTools/Application/ApplicationPathProvider.h> + +namespace lambda = boost::lambda; + +namespace Swift { + +static const size_t MAX_ENTRIES = 200; + +StatusCache::StatusCache(ApplicationPathProvider* paths) { + paths_ = paths; + path_ = paths_->getDataDir() / "StatusCache"; + loadRecents(); +} + +StatusCache::~StatusCache() { + +} + +std::vector<StatusCache::PreviousStatus> StatusCache::getMatches(const std::string& substring, size_t maxCount) const { + std::vector<PreviousStatus> matches; + foreach (const PreviousStatus& status, previousStatuses_) { + if (substring.empty() || (boost::algorithm::ifind_first(status.first, substring) && substring != status.first)) { + matches.push_back(status); + if (matches.size() == maxCount) { + break; + } + } + } + return matches; +} + +void StatusCache::addRecent(const std::string& text, StatusShow::Type type) { + if (text.empty()) { + return; + } + previousStatuses_.remove_if(lambda::bind(&PreviousStatus::first, lambda::_1) == text && lambda::bind(&PreviousStatus::second, lambda::_1) == type); + previousStatuses_.push_front(PreviousStatus(text, type)); + for (size_t i = previousStatuses_.size(); i > MAX_ENTRIES; i--) { + previousStatuses_.pop_back(); + } + saveRecents(); +} + +void StatusCache::loadRecents() { + try { + if (boost::filesystem::exists(path_)) { + ByteArray data; + readByteArrayFromFile(data, path_); + std::string stringData = byteArrayToString(data); + std::vector<std::string> lines; + boost::split(lines, stringData, boost::is_any_of("\n")); + foreach (const std::string& line, lines) { + std::vector<std::string> bits; + boost::split(bits, line, boost::is_any_of("\t")); + if (bits.size() < 2) { + continue; + } + StatusShow::Type type; + type = static_cast<StatusShow::Type>(boost::lexical_cast<size_t>(bits[0])); + previousStatuses_.push_back(PreviousStatus(boost::trim_copy(bits[1]), type)); + } + } + } + catch (const boost::filesystem::filesystem_error& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + } +} + +void StatusCache::saveRecents() { + try { + if (!boost::filesystem::exists(path_.parent_path())) { + boost::filesystem::create_directories(path_.parent_path()); + } + boost::filesystem::ofstream file(path_); + foreach (const PreviousStatus& recent, previousStatuses_) { + std::string message = recent.first; + boost::replace_all(message, "\t", " "); + file << recent.second << "\t" << message << std::endl; + } + file.close(); + } + catch (const boost::filesystem::filesystem_error& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + } +} + +} + + + + diff --git a/Swift/Controllers/StatusCache.h b/Swift/Controllers/StatusCache.h new file mode 100644 index 0000000..35b3674 --- /dev/null +++ b/Swift/Controllers/StatusCache.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <utility> +#include <vector> +#include <list> +#include <iostream> + +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/StatusShow.h> + +namespace Swift { + class ApplicationPathProvider; + class StatusCache { + public: + typedef std::pair<std::string, StatusShow::Type> PreviousStatus; + public: + StatusCache(ApplicationPathProvider* paths); + ~StatusCache(); + + std::vector<PreviousStatus> getMatches(const std::string& substring, size_t maxCount) const; + void addRecent(const std::string& text, StatusShow::Type type); + private: + void saveRecents(); + void loadRecents(); + private: + boost::filesystem::path path_; + std::list<PreviousStatus> previousStatuses_; + ApplicationPathProvider* paths_; + }; +} + + diff --git a/Swift/Controllers/StatusTracker.cpp b/Swift/Controllers/StatusTracker.cpp index 0c88f4d..6766c2e 100644 --- a/Swift/Controllers/StatusTracker.cpp +++ b/Swift/Controllers/StatusTracker.cpp @@ -8,6 +8,8 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Elements/Idle.h> + namespace Swift { StatusTracker::StatusTracker() { @@ -21,6 +23,7 @@ boost::shared_ptr<Presence> StatusTracker::getNextPresence() { presence = boost::make_shared<Presence>(); presence->setShow(StatusShow::Away); presence->setStatus(queuedPresence_->getStatus()); + presence->addPayload(boost::make_shared<Idle>(isAutoAwaySince_)); } else { presence = queuedPresence_; } @@ -35,11 +38,12 @@ void StatusTracker::setRequestedPresence(boost::shared_ptr<Presence> presence) { // } } -bool StatusTracker::goAutoAway() { +bool StatusTracker::goAutoAway(const int& seconds) { if (queuedPresence_->getShow() != StatusShow::Online) { return false; } isAutoAway_ = true; + isAutoAwaySince_ = boost::posix_time::second_clock::universal_time() - boost::posix_time::seconds(seconds); return true; } diff --git a/Swift/Controllers/StatusTracker.h b/Swift/Controllers/StatusTracker.h index 4f4e880..10a5c0c 100644 --- a/Swift/Controllers/StatusTracker.h +++ b/Swift/Controllers/StatusTracker.h @@ -10,6 +10,8 @@ #include "Swiften/Elements/Presence.h" +#include <boost/date_time/posix_time/posix_time_types.hpp> + namespace Swift { class StatusTracker { @@ -17,10 +19,11 @@ namespace Swift { StatusTracker(); boost::shared_ptr<Presence> getNextPresence(); void setRequestedPresence(boost::shared_ptr<Presence> presence); - bool goAutoAway(); + bool goAutoAway(const int& seconds); bool goAutoUnAway(); private: boost::shared_ptr<Presence> queuedPresence_; bool isAutoAway_; + boost::posix_time::ptime isAutoAwaySince_; }; } diff --git a/Swift/Controllers/StatusUtil.cpp b/Swift/Controllers/StatusUtil.cpp index fd1fea3..a72f340 100644 --- a/Swift/Controllers/StatusUtil.cpp +++ b/Swift/Controllers/StatusUtil.cpp @@ -6,6 +6,7 @@ #include <Swift/Controllers/StatusUtil.h> +#include <cassert> #include <Swift/Controllers/Intl.h> namespace Swift { @@ -19,6 +20,7 @@ std::string statusShowTypeToFriendlyName(StatusShow::Type type) { case StatusShow::DND: return QT_TRANSLATE_NOOP("", "Busy"); case StatusShow::None: return QT_TRANSLATE_NOOP("", "Offline"); } + assert(false); return ""; } diff --git a/Swift/Controllers/Storages/AvatarFileStorage.cpp b/Swift/Controllers/Storages/AvatarFileStorage.cpp index b39e586..671e0cb 100644 --- a/Swift/Controllers/Storages/AvatarFileStorage.cpp +++ b/Swift/Controllers/Storages/AvatarFileStorage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -12,12 +12,12 @@ #include <Swiften/Base/foreach.h> #include <Swiften/Base/String.h> -#include <Swiften/StringCodecs/SHA1.h> #include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/Crypto/CryptoProvider.h> namespace Swift { -AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile) : avatarsDir(avatarsDir), avatarsFile(avatarsFile) { +AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile, CryptoProvider* crypto) : avatarsDir(avatarsDir), avatarsFile(avatarsFile), crypto(crypto) { if (boost::filesystem::exists(avatarsFile)) { try { boost::filesystem::ifstream file(avatarsFile); @@ -47,7 +47,7 @@ bool AvatarFileStorage::hasAvatar(const std::string& hash) const { } void AvatarFileStorage::addAvatar(const std::string& hash, const ByteArray& avatar) { - assert(Hexify::hexify(SHA1::getHash(avatar)) == hash); + assert(Hexify::hexify(crypto->getSHA1Hash(avatar)) == hash); boost::filesystem::path avatarPath = getAvatarPath(hash); if (!boost::filesystem::exists(avatarPath.parent_path())) { @@ -69,7 +69,7 @@ boost::filesystem::path AvatarFileStorage::getAvatarPath(const std::string& hash ByteArray AvatarFileStorage::getAvatar(const std::string& hash) const { ByteArray data; - readByteArrayFromFile(data, getAvatarPath(hash).string()); + readByteArrayFromFile(data, getAvatarPath(hash)); return data; } diff --git a/Swift/Controllers/Storages/AvatarFileStorage.h b/Swift/Controllers/Storages/AvatarFileStorage.h index b7e73f5..85a6463 100644 --- a/Swift/Controllers/Storages/AvatarFileStorage.h +++ b/Swift/Controllers/Storages/AvatarFileStorage.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -15,9 +15,11 @@ #include "Swiften/Avatars/AvatarStorage.h" namespace Swift { + class CryptoProvider; + class AvatarFileStorage : public AvatarStorage { public: - AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile); + AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile, CryptoProvider* crypto); virtual bool hasAvatar(const std::string& hash) const; virtual void addAvatar(const std::string& hash, const ByteArray& avatar); @@ -34,6 +36,7 @@ namespace Swift { private: boost::filesystem::path avatarsDir; boost::filesystem::path avatarsFile; + CryptoProvider* crypto; typedef std::map<JID, std::string> JIDAvatarMap; JIDAvatarMap jidAvatars; }; diff --git a/Swift/Controllers/Storages/CertificateFileStorage.cpp b/Swift/Controllers/Storages/CertificateFileStorage.cpp index a4a95c7..34d1f76 100644 --- a/Swift/Controllers/Storages/CertificateFileStorage.cpp +++ b/Swift/Controllers/Storages/CertificateFileStorage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -8,22 +8,23 @@ #include <iostream> #include <boost/filesystem/fstream.hpp> +#include <boost/numeric/conversion/cast.hpp> -#include <Swiften/StringCodecs/SHA1.h> #include <Swiften/StringCodecs/Hexify.h> #include <Swiften/TLS/CertificateFactory.h> #include <Swiften/Base/Log.h> +#include <Swiften/Crypto/CryptoProvider.h> namespace Swift { -CertificateFileStorage::CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory) : path(path), certificateFactory(certificateFactory) { +CertificateFileStorage::CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory, CryptoProvider* crypto) : path(path), certificateFactory(certificateFactory), crypto(crypto) { } bool CertificateFileStorage::hasCertificate(Certificate::ref certificate) const { boost::filesystem::path certificatePath = getCertificatePath(certificate); if (boost::filesystem::exists(certificatePath)) { ByteArray data; - readByteArrayFromFile(data, certificatePath.string()); + readByteArrayFromFile(data, certificatePath); Certificate::ref storedCertificate = certificateFactory->createCertificateFromDER(data); if (storedCertificate && storedCertificate->toDER() == certificate->toDER()) { return true; @@ -50,12 +51,12 @@ 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*>(vecptr(data)), data.size()); + file.write(reinterpret_cast<const char*>(vecptr(data)), boost::numeric_cast<std::streamsize>(data.size())); file.close(); } boost::filesystem::path CertificateFileStorage::getCertificatePath(Certificate::ref certificate) const { - return path / Hexify::hexify(SHA1::getHash(certificate->toDER())); + return path / Hexify::hexify(crypto->getSHA1Hash(certificate->toDER())); } } diff --git a/Swift/Controllers/Storages/CertificateFileStorage.h b/Swift/Controllers/Storages/CertificateFileStorage.h index f7a60b9..12151d0 100644 --- a/Swift/Controllers/Storages/CertificateFileStorage.h +++ b/Swift/Controllers/Storages/CertificateFileStorage.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -12,10 +12,11 @@ namespace Swift { class CertificateFactory; + class CryptoProvider; class CertificateFileStorage : public CertificateStorage { public: - CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory); + CertificateFileStorage(const boost::filesystem::path& path, CertificateFactory* certificateFactory, CryptoProvider* crypto); virtual bool hasCertificate(Certificate::ref certificate) const; virtual void addCertificate(Certificate::ref certificate); @@ -26,6 +27,7 @@ namespace Swift { private: boost::filesystem::path path; CertificateFactory* certificateFactory; + CryptoProvider* crypto; }; } diff --git a/Swift/Controllers/Storages/CertificateFileStorageFactory.h b/Swift/Controllers/Storages/CertificateFileStorageFactory.h index b215165..6834619 100644 --- a/Swift/Controllers/Storages/CertificateFileStorageFactory.h +++ b/Swift/Controllers/Storages/CertificateFileStorageFactory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,18 +11,20 @@ namespace Swift { class CertificateFactory; + class CryptoProvider; class CertificateFileStorageFactory : public CertificateStorageFactory { public: - CertificateFileStorageFactory(const boost::filesystem::path& basePath, CertificateFactory* certificateFactory) : basePath(basePath), certificateFactory(certificateFactory) {} + CertificateFileStorageFactory(const boost::filesystem::path& basePath, CertificateFactory* certificateFactory, CryptoProvider* crypto) : basePath(basePath), certificateFactory(certificateFactory), crypto(crypto) {} virtual CertificateStorage* createCertificateStorage(const JID& profile) const { boost::filesystem::path profilePath = basePath / profile.toString(); - return new CertificateFileStorage(profilePath / "certificates", certificateFactory); + return new CertificateFileStorage(profilePath / "certificates", certificateFactory, crypto); } private: boost::filesystem::path basePath; CertificateFactory* certificateFactory; + CryptoProvider* crypto; }; } diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp index cff87d3..52a5e00 100644 --- a/Swift/Controllers/Storages/FileStorages.cpp +++ b/Swift/Controllers/Storages/FileStorages.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,17 +10,18 @@ #include "Swift/Controllers/Storages/CapsFileStorage.h" #include "Swift/Controllers/Storages/RosterFileStorage.h" #include <Swiften/History/SQLiteHistoryStorage.h> +#include <Swiften/Base/Path.h> namespace Swift { -FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid) { - std::string profile = jid.toBare(); - vcardStorage = new VCardFileStorage(baseDir / profile / "vcards"); +FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid, CryptoProvider* crypto) { + boost::filesystem::path profile = stringToPath(jid.toBare()); + vcardStorage = new VCardFileStorage(baseDir / profile / "vcards", crypto); capsStorage = new CapsFileStorage(baseDir / "caps"); - avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars"); + avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars", crypto); rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml"); #ifdef SWIFT_EXPERIMENTAL_HISTORY - historyStorage = new SQLiteHistoryStorage((baseDir / "history.db").string()); + historyStorage = new SQLiteHistoryStorage(baseDir / "history.db"); #endif } diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h index 5e89db8..1e914b9 100644 --- a/Swift/Controllers/Storages/FileStorages.h +++ b/Swift/Controllers/Storages/FileStorages.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -8,7 +8,7 @@ #include <boost/filesystem/path.hpp> -#include "Swiften/Client/Storages.h" +#include <Swiften/Client/Storages.h> namespace Swift { class VCardFileStorage; @@ -17,6 +17,7 @@ namespace Swift { class RosterFileStorage; class HistoryStorage; class JID; + class CryptoProvider; /** * A storages implementation that stores all controller data on disk. @@ -37,7 +38,7 @@ namespace Swift { * \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(const boost::filesystem::path& baseDir, const JID& jid, CryptoProvider*); ~FileStorages(); virtual VCardStorage* getVCardStorage() const; diff --git a/Swift/Controllers/Storages/FileStoragesFactory.h b/Swift/Controllers/Storages/FileStoragesFactory.h index 0676bc3..c119dcf 100644 --- a/Swift/Controllers/Storages/FileStoragesFactory.h +++ b/Swift/Controllers/Storages/FileStoragesFactory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,15 +10,18 @@ #include "Swift/Controllers/Storages/FileStorages.h" namespace Swift { + class CryptoProvider; + class FileStoragesFactory : public StoragesFactory { public: - FileStoragesFactory(const boost::filesystem::path& basePath) : basePath(basePath) {} + FileStoragesFactory(const boost::filesystem::path& basePath, CryptoProvider* crypto) : basePath(basePath), crypto(crypto) {} virtual Storages* createStorages(const JID& profile) const { - return new FileStorages(basePath, profile); + return new FileStorages(basePath, profile, crypto); } private: boost::filesystem::path basePath; + CryptoProvider* crypto; }; } diff --git a/Swift/Controllers/Storages/VCardFileStorage.cpp b/Swift/Controllers/Storages/VCardFileStorage.cpp index d799a90..b22e235 100644 --- a/Swift/Controllers/Storages/VCardFileStorage.cpp +++ b/Swift/Controllers/Storages/VCardFileStorage.cpp @@ -13,8 +13,9 @@ #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/Base/Path.h> +#include <Swiften/Crypto/CryptoProvider.h> #include "Swiften/JID/JID.h" #include "Swiften/Elements/VCard.h" #include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h" @@ -25,7 +26,7 @@ using namespace Swift; typedef GenericPayloadPersister<VCard, VCardParser, VCardSerializer> VCardPersister; -VCardFileStorage::VCardFileStorage(boost::filesystem::path dir) : vcardsPath(dir) { +VCardFileStorage::VCardFileStorage(boost::filesystem::path dir, CryptoProvider* crypto) : VCardStorage(crypto), vcardsPath(dir), crypto(crypto) { cacheFile = vcardsPath / "phashes"; if (boost::filesystem::exists(cacheFile)) { try { @@ -66,7 +67,7 @@ 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")); + return boost::filesystem::path(vcardsPath / stringToPath(file + ".xml")); } catch (const boost::filesystem::filesystem_error& e) { std::cerr << "ERROR: " << e.what() << std::endl; @@ -88,7 +89,7 @@ std::string VCardFileStorage::getPhotoHash(const JID& jid) const { 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())); + hash = Hexify::hexify(crypto->getSHA1Hash(vCard->getPhoto())); } std::pair<PhotoHashMap::iterator, bool> r = photoHashes.insert(std::make_pair(jid, hash)); if (r.second) { diff --git a/Swift/Controllers/Storages/VCardFileStorage.h b/Swift/Controllers/Storages/VCardFileStorage.h index ba422f4..2c3af3d 100644 --- a/Swift/Controllers/Storages/VCardFileStorage.h +++ b/Swift/Controllers/Storages/VCardFileStorage.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -14,9 +14,11 @@ #include "Swiften/VCards/VCardStorage.h" namespace Swift { + class CryptoProvider; + class VCardFileStorage : public VCardStorage { public: - VCardFileStorage(boost::filesystem::path dir); + VCardFileStorage(boost::filesystem::path dir, CryptoProvider* crypto); virtual VCard::ref getVCard(const JID& jid) const; virtual void setVCard(const JID& jid, VCard::ref v); @@ -31,6 +33,7 @@ namespace Swift { private: boost::filesystem::path vcardsPath; + CryptoProvider* crypto; boost::filesystem::path cacheFile; typedef std::map<JID, std::string> PhotoHashMap; mutable PhotoHashMap photoHashes; diff --git a/Swift/Controllers/SystemTray.h b/Swift/Controllers/SystemTray.h index 736b1fa..b71a783 100644 --- a/Swift/Controllers/SystemTray.h +++ b/Swift/Controllers/SystemTray.h @@ -11,7 +11,7 @@ namespace Swift { class SystemTray { public: - virtual ~SystemTray(){}; + virtual ~SystemTray(){} virtual void setUnreadMessages(bool some) = 0; virtual void setStatusType(StatusShow::Type type) = 0; virtual void setConnecting() = 0; diff --git a/Swift/Controllers/Translator.cpp b/Swift/Controllers/Translator.cpp index 82fc46e..b7766ca 100644 --- a/Swift/Controllers/Translator.cpp +++ b/Swift/Controllers/Translator.cpp @@ -10,7 +10,7 @@ namespace Swift { -struct DefaultTranslator : public Translator { +static struct DefaultTranslator : public Translator { virtual std::string translate(const std::string& text, const std::string&) { return text; } diff --git a/Swift/Controllers/UIEvents/AddContactUIEvent.h b/Swift/Controllers/UIEvents/AddContactUIEvent.h index d92d3af..6b70b76 100644 --- a/Swift/Controllers/UIEvents/AddContactUIEvent.h +++ b/Swift/Controllers/UIEvents/AddContactUIEvent.h @@ -14,15 +14,15 @@ namespace Swift { class AddContactUIEvent : public UIEvent { public: - AddContactUIEvent(const JID& jid, const std::string& name, const std::set<std::string>& groups) : jid_(jid), name_(name), groups_(groups) {}; + AddContactUIEvent(const JID& jid, const std::string& name, const std::set<std::string>& groups) : jid_(jid), name_(name), groups_(groups) {} const std::string& getName() const { return name_; - }; + } const JID& getJID() const { return jid_; - }; + } const std::set<std::string>& getGroups() const { return groups_; diff --git a/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h b/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h index 210da3e..df01d6c 100644 --- a/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h +++ b/Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h @@ -14,7 +14,7 @@ namespace Swift { class AddMUCBookmarkUIEvent : public UIEvent { public: - AddMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {}; + AddMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {} const MUCBookmark& getBookmark() { return bookmark; } private: diff --git a/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h b/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h new file mode 100644 index 0000000..57e181d --- /dev/null +++ b/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013 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 CreateImpromptuMUCUIEvent : public UIEvent { + public: + CreateImpromptuMUCUIEvent(const std::vector<JID>& jids, const JID& roomJID = JID(), const std::string reason = "") : jids_(jids), roomJID_(roomJID), reason_(reason) { } + + std::vector<JID> getJIDs() const { return jids_; } + JID getRoomJID() const { return roomJID_; } + std::string getReason() const { return reason_; } + private: + std::vector<JID> jids_; + JID roomJID_; + std::string reason_; +}; + +} diff --git a/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h b/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h index 2b10f09..7723d89 100644 --- a/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h +++ b/Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h @@ -14,10 +14,10 @@ namespace Swift { class EditMUCBookmarkUIEvent : public UIEvent { public: - EditMUCBookmarkUIEvent(const MUCBookmark& oldBookmark, const MUCBookmark& newBookmark) : oldBookmark(oldBookmark) , newBookmark(newBookmark) {}; + EditMUCBookmarkUIEvent(const MUCBookmark& oldBookmark, const MUCBookmark& newBookmark) : oldBookmark(oldBookmark) , newBookmark(newBookmark) {} - const MUCBookmark& getOldBookmark() {return oldBookmark;}; - const MUCBookmark& getNewBookmark() {return newBookmark;}; + const MUCBookmark& getOldBookmark() {return oldBookmark;} + const MUCBookmark& getNewBookmark() {return newBookmark;} private: MUCBookmark oldBookmark; diff --git a/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h new file mode 100644 index 0000000..cb9d20b --- /dev/null +++ b/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 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 <vector> + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + class InviteToMUCUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<InviteToMUCUIEvent> ref; + + InviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite, const std::string& reason) : room_(room), invite_(JIDsToInvite), reason_(reason) { + } + + const JID& getRoom() const { + return room_; + } + + const std::vector<JID> getInvites() const { + return invite_; + } + + const std::string getReason() const { + return reason_; + } + + private: + JID room_; + std::vector<JID> invite_; + std::string reason_; + }; +} diff --git a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h index b3ff8c7..e046942 100644 --- a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h +++ b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h @@ -18,17 +18,22 @@ namespace Swift { class JoinMUCUIEvent : public UIEvent { public: typedef boost::shared_ptr<JoinMUCUIEvent> ref; - JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& password = boost::optional<std::string>(), const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false, bool createAsReservedRoomIfNew = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture), createAsReservedRoomIfNew_(createAsReservedRoomIfNew), password_(password) {}; + JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& password = boost::optional<std::string>(), const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false, bool createAsReservedRoomIfNew = false, bool isImpromptu = false, bool isContinuation = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture), createAsReservedRoomIfNew_(createAsReservedRoomIfNew), password_(password), isImpromptuMUC_(isImpromptu), isContinuation_(isContinuation) {} const boost::optional<std::string>& getNick() const {return nick_;} const JID& getJID() const {return jid_;} bool getShouldJoinAutomatically() const {return joinAutomatically_;} bool getCreateAsReservedRoomIfNew() const {return createAsReservedRoomIfNew_;} const boost::optional<std::string>& getPassword() const {return password_;} + bool isImpromptu() const {return isImpromptuMUC_;} + bool isContinuation() const {return isContinuation_;} + private: JID jid_; boost::optional<std::string> nick_; bool joinAutomatically_; bool createAsReservedRoomIfNew_; boost::optional<std::string> password_; + bool isImpromptuMUC_; + bool isContinuation_; }; } diff --git a/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h b/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h index ea2e609..0df40f9 100644 --- a/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h +++ b/Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h @@ -14,7 +14,7 @@ namespace Swift { class RemoveMUCBookmarkUIEvent : public UIEvent { public: - RemoveMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {}; + RemoveMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {} const MUCBookmark& getBookmark() { return bookmark; } private: diff --git a/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h b/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h index 7e5236a..617daf3 100644 --- a/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h +++ b/Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h @@ -13,9 +13,9 @@ namespace Swift { class RemoveRosterItemUIEvent : public UIEvent { public: - RemoveRosterItemUIEvent(const JID& jid) : jid_(jid) {}; - virtual ~RemoveRosterItemUIEvent() {}; - JID getJID() {return jid_;}; + RemoveRosterItemUIEvent(const JID& jid) : jid_(jid) {} + virtual ~RemoveRosterItemUIEvent() {} + JID getJID() {return jid_;} private: JID jid_; diff --git a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h index c3b4b49..a0b51f2 100644 --- a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h @@ -13,7 +13,7 @@ namespace Swift { class RequestAdHocUIEvent : public UIEvent { public: - RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {}; + RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {} const DiscoItems::Item& getCommand() const {return command_;} private: DiscoItems::Item command_; diff --git a/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h b/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h index 5a071cf..7fe1926 100644 --- a/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h @@ -15,11 +15,11 @@ namespace Swift { class RequestAddUserDialogUIEvent : public UIEvent { public: - RequestAddUserDialogUIEvent(const JID& predefinedJID, const std::string& predefinedName) : preJID_(predefinedJID), preName_(predefinedName) {}; - RequestAddUserDialogUIEvent() : preJID_(), preName_() {}; + RequestAddUserDialogUIEvent(const JID& predefinedJID, const std::string& predefinedName) : preJID_(predefinedJID), preName_(predefinedName) {} + RequestAddUserDialogUIEvent() : preJID_(), preName_() {} - const JID& getPredefinedJID() const { return preJID_; }; - const std::string& getPredefinedName() const { return preName_; }; + const JID& getPredefinedJID() const { return preJID_; } + const std::string& getPredefinedName() const { return preName_; } private: JID preJID_; diff --git a/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h new file mode 100644 index 0000000..d29cb4f --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 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 RequestBlockListDialogUIEvent : public UIEvent { +}; + +} diff --git a/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h new file mode 100644 index 0000000..9b7abcb --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 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> + +#include <Swiften/JID/JID.h> + +namespace Swift { + +class RequestChangeBlockStateUIEvent : public UIEvent { + public: + enum BlockState { + Blocked, + Unblocked + }; + + public: + RequestChangeBlockStateUIEvent(BlockState newState, const JID& contact) : state_(newState), contact_(contact) {} + + BlockState getBlockState() const { + return state_; + } + + JID getContact() const { + return contact_; + } + private: + BlockState state_; + JID contact_; +}; + +} diff --git a/Swift/Controllers/UIEvents/RequestChatUIEvent.h b/Swift/Controllers/UIEvents/RequestChatUIEvent.h index b1e86ed..4ef954f 100644 --- a/Swift/Controllers/UIEvents/RequestChatUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestChatUIEvent.h @@ -13,7 +13,7 @@ namespace Swift { class RequestChatUIEvent : public UIEvent { public: - RequestChatUIEvent(const JID& contact) : contact_(contact) {}; + RequestChatUIEvent(const JID& contact) : contact_(contact) {} JID getContact() {return contact_;} private: JID contact_; diff --git a/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h b/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h new file mode 100644 index 0000000..42e22a2 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * 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 RequestHighlightEditorUIEvent : public UIEvent { + }; + +} diff --git a/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h new file mode 100644 index 0000000..69aa0cd --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013 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 <vector> + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + class RequestInviteToMUCUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<RequestInviteToMUCUIEvent> ref; + + RequestInviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite) : room_(room), invite_(JIDsToInvite) { + } + + const JID& getRoom() const { + return room_; + } + + const std::vector<JID> getInvites() const { + return invite_; + } + + private: + JID room_; + std::vector<JID> invite_; + }; +} diff --git a/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h index f5b995b..5c44da7 100644 --- a/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h @@ -13,7 +13,7 @@ namespace Swift { class RequestWhiteboardUIEvent : public UIEvent { public: - RequestWhiteboardUIEvent(const JID& contact) : contact_(contact) {}; + RequestWhiteboardUIEvent(const JID& contact) : contact_(contact) {} const JID& getContact() const {return contact_;} private: JID contact_; diff --git a/Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h b/Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h new file mode 100644 index 0000000..4a603ea --- /dev/null +++ b/Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + +class ShowProfileForRosterItemUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<ShowProfileForRosterItemUIEvent> ref; + public: + ShowProfileForRosterItemUIEvent(const JID& jid) : jid_(jid) {} + virtual ~ShowProfileForRosterItemUIEvent() {} + JID getJID() const {return jid_;} + private: + JID jid_; +}; + +} diff --git a/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h index 265bf7d..bb72d9b 100644 --- a/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h +++ b/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h @@ -13,7 +13,7 @@ namespace Swift { class ShowWhiteboardUIEvent : public UIEvent { public: - ShowWhiteboardUIEvent(const JID& contact) : contact_(contact) {}; + ShowWhiteboardUIEvent(const JID& contact) : contact_(contact) {} const JID& getContact() const {return contact_;} private: JID contact_; diff --git a/Swift/Controllers/UIEvents/UIEventStream.h b/Swift/Controllers/UIEvents/UIEventStream.h index e174029..b1337a2 100644 --- a/Swift/Controllers/UIEvents/UIEventStream.h +++ b/Swift/Controllers/UIEvents/UIEventStream.h @@ -18,6 +18,6 @@ namespace Swift { void send(boost::shared_ptr<UIEvent> event) { onUIEvent(event); - }; + } }; } diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h index f7a5d39..835defe 100644 --- a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h @@ -9,6 +9,6 @@ namespace Swift { class AdHocCommandWindow { public: - virtual ~AdHocCommandWindow() {}; + virtual ~AdHocCommandWindow() {} }; } diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h new file mode 100644 index 0000000..60a1c11 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swiften/JID/JID.h> +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + + class ClientBlockListManager; + + class BlockListEditorWidget { + public: + virtual ~BlockListEditorWidget() {} + + virtual void show() = 0; + + virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs) = 0; + virtual void setBusy(bool isBusy) = 0; + + virtual std::vector<JID> getCurrentBlockList() const = 0; + + boost::signal<void (const std::vector<JID>& /* blockedJID */)> onSetNewBlockList; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h new file mode 100644 index 0000000..eb91ac1 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class BlockListEditorWidget; + + class BlockListEditorWidgetFactory { + public: + virtual ~BlockListEditorWidgetFactory() {} + + virtual BlockListEditorWidget* createBlockListEditorWidget() = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h index cb55bb3..b189e72 100644 --- a/Swift/Controllers/UIInterfaces/ChatListWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h @@ -7,11 +7,13 @@ #pragma once #include <list> +#include <set> +#include <map> #include <boost/shared_ptr.hpp> #include <Swiften/MUC/MUCBookmark.h> #include <Swiften/Elements/StatusShow.h> #include <boost/filesystem/path.hpp> - +#include <Swiften/Base/foreach.h> #include <Swiften/Base/boost_bsignals.h> namespace Swift { @@ -19,13 +21,14 @@ namespace Swift { public: class Chat { public: + Chat() : statusType(StatusShow::None), isMUC(false), unreadCount(0) {} 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; } @@ -35,6 +38,18 @@ namespace Swift { void setAvatarPath(const boost::filesystem::path& path) { avatarPath = path; } + std::string getImpromptuTitle() const { + typedef std::pair<std::string, JID> StringJIDPair; + std::string title; + foreach(StringJIDPair pair, impromptuJIDs) { + if (title.empty()) { + title += pair.first; + } else { + title += ", " + pair.first; + } + } + return title; + } JID jid; std::string chatName; std::string activity; @@ -43,6 +58,7 @@ namespace Swift { std::string nick; int unreadCount; boost::filesystem::path avatarPath; + std::map<std::string, JID> impromptuJIDs; }; virtual ~ChatListWindow(); diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index 5db1a54..e6f61ca 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -6,17 +6,20 @@ #pragma once +#include <vector> +#include <string> + #include <boost/optional.hpp> -#include <Swiften/Base/boost_bsignals.h> #include <boost/shared_ptr.hpp> +#include <boost/make_shared.hpp> #include <boost/date_time/posix_time/posix_time.hpp> -#include <vector> -#include <string> +#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/SecurityLabelsCatalog.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/Form.h> #include <Swiften/Elements/MUCOccupant.h> +#include <Swift/Controllers/HighlightManager.h> namespace Swift { @@ -27,39 +30,93 @@ namespace Swift { class RosterItem; class ContactRosterItem; class FileTransferController; - class InviteToChatWindow; + class UserSearchWindow; + class ChatWindow { public: + class ChatMessagePart { + public: + virtual ~ChatMessagePart() {} + }; + + class ChatMessage { + public: + ChatMessage() {} + ChatMessage(const std::string& text) { + append(boost::make_shared<ChatTextMessagePart>(text)); + } + void append(const boost::shared_ptr<ChatMessagePart>& part) { + parts_.push_back(part); + } + + const std::vector<boost::shared_ptr<ChatMessagePart> >& getParts() const { + return parts_; + } + private: + std::vector<boost::shared_ptr<ChatMessagePart> > parts_; + }; + + class ChatTextMessagePart : public ChatMessagePart { + public: + ChatTextMessagePart(const std::string& text) : text(text) {} + std::string text; + }; + + class ChatURIMessagePart : public ChatMessagePart { + public: + ChatURIMessagePart(const std::string& target) : target(target) {} + std::string target; + }; + + class ChatEmoticonMessagePart : public ChatMessagePart { + public: + std::string imagePath; + std::string alternativeText; + }; + + class ChatHighlightingMessagePart : public ChatMessagePart { + public: + std::string foregroundColor; + std::string backgroundColor; + std::string text; + }; + + enum AckState {Pending, Received, Failed}; enum ReceiptState {ReceiptRequested, ReceiptReceived}; enum Tristate {Yes, No, Maybe}; - enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor, AddContact}; + enum OccupantAction {Kick, Ban, MakeModerator, MakeParticipant, MakeVisitor, AddContact, ShowProfile}; enum RoomAction {ChangeSubject, Configure, Affiliations, Destroy, Invite}; enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; enum WhiteboardSessionState {WhiteboardAccepted, WhiteboardTerminated, WhiteboardRejected}; + enum BlockingState {BlockingUnsupported, IsBlocked, IsUnblocked}; + enum Direction { UnknownDirection, DefaultDirection }; + ChatWindow() {} - virtual ~ChatWindow() {}; + virtual ~ChatWindow() {} /** Add message to window. * @return id of added message (for acks). */ - 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& time) = 0; + virtual std::string addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; /** Adds action to window. * @return id of added message (for acks); */ - virtual std::string addAction(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0; - 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; - virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + virtual std::string addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + + virtual void addSystemMessage(const ChatMessage& message, Direction direction) = 0; + virtual void addPresenceMessage(const ChatMessage& message, Direction direction) = 0; + + virtual void addErrorMessage(const ChatMessage& message) = 0; + virtual void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + virtual void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 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 addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true) = 0; + virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false) = 0; virtual std::string addWhiteboardRequest(bool senderIsSelf) = 0; virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0; @@ -75,19 +132,21 @@ namespace Swift { virtual void setSecurityLabelsEnabled(bool enabled) = 0; virtual void setCorrectionEnabled(Tristate enabled) = 0; virtual void setUnreadMessageCount(int count) = 0; - virtual void convertToMUC() = 0; + virtual void convertToMUC(bool impromptuMUC = false) = 0; // virtual TreeWidget *getTreeWidget() = 0; virtual void setSecurityLabelsError() = 0; virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() = 0; virtual void setInputEnabled(bool enabled) = 0; virtual void setRosterModel(Roster* model) = 0; virtual void setTabComplete(TabComplete* completer) = 0; - virtual void replaceLastMessage(const std::string& message) = 0; + virtual void replaceLastMessage(const ChatMessage& message) = 0; virtual void setAckState(const std::string& id, AckState state) = 0; virtual void flash() = 0; virtual void setSubject(const std::string& subject) = 0; virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) = 0; virtual void setAvailableRoomActions(const std::vector<RoomAction> &actions) = 0; + virtual void setBlockingState(BlockingState state) = 0; + virtual void setCanInitiateImpromptuChats(bool supportsImpromptu) = 0; /** * Set an alert on the window. * @param alertText Description of alert (required). @@ -110,8 +169,6 @@ namespace Swift { */ virtual void showRoomConfigurationForm(Form::ref) = 0; - virtual InviteToChatWindow* createInviteToChatWindow() = 0; - boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest; @@ -124,7 +181,7 @@ namespace Swift { boost::signal<void (const std::string&)> onChangeSubjectRequest; boost::signal<void (Form::ref)> onConfigureRequest; boost::signal<void ()> onDestroyRequest; - boost::signal<void ()> onInvitePersonToThisMUCRequest; + boost::signal<void (const std::vector<JID>&)> onInviteToChat; boost::signal<void ()> onConfigurationFormCancelled; boost::signal<void ()> onGetAffiliationsRequest; boost::signal<void (MUCOccupant::Affiliation, const JID&)> onSetAffiliationRequest; @@ -141,6 +198,10 @@ namespace Swift { boost::signal<void ()> onWhiteboardSessionAccept; boost::signal<void ()> onWhiteboardSessionCancel; boost::signal<void ()> onWhiteboardWindowShow; + + // Blocking Command related + boost::signal<void ()> onBlockUserRequest; + boost::signal<void ()> onUnblockUserRequest; }; } diff --git a/Swift/Controllers/UIInterfaces/ChatWindowFactory.h b/Swift/Controllers/UIInterfaces/ChatWindowFactory.h index b7b4479..62e6621 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/ChatWindowFactory.h @@ -14,7 +14,7 @@ namespace Swift { class UIEventStream; class ChatWindowFactory { public: - virtual ~ChatWindowFactory() {}; + virtual ~ChatWindowFactory() {} /** * Transfers ownership of result. */ diff --git a/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h b/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h index 8ad56c0..9d47aef 100644 --- a/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h @@ -11,7 +11,7 @@ namespace Swift { class ContactEditWindowFactory { public: - virtual ~ContactEditWindowFactory() {}; + virtual ~ContactEditWindowFactory() {} virtual ContactEditWindow* createContactEditWindow() = 0; }; diff --git a/Swift/Controllers/UIInterfaces/EventWindow.h b/Swift/Controllers/UIInterfaces/EventWindow.h index 3ca2c82..b3af3d3 100644 --- a/Swift/Controllers/UIInterfaces/EventWindow.h +++ b/Swift/Controllers/UIInterfaces/EventWindow.h @@ -19,7 +19,7 @@ namespace Swift { return canDelete_; } - virtual ~EventWindow() {}; + virtual ~EventWindow() {} virtual void addEvent(boost::shared_ptr<StanzaEvent> event, bool active) = 0; virtual void removeEvent(boost::shared_ptr<StanzaEvent> event) = 0; diff --git a/Swift/Controllers/UIInterfaces/EventWindowFactory.h b/Swift/Controllers/UIInterfaces/EventWindowFactory.h index 1ff3ada..0b9c28e 100644 --- a/Swift/Controllers/UIInterfaces/EventWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/EventWindowFactory.h @@ -11,7 +11,7 @@ namespace Swift { class EventWindowFactory { public: - virtual ~EventWindowFactory() {}; + virtual ~EventWindowFactory() {} /** * Transfers ownership of result. */ diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h b/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h new file mode 100644 index 0000000..4745035 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWidget.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class HighlightManager; + + class HighlightEditorWidget { + public: + virtual ~HighlightEditorWidget() {} + + virtual void show() = 0; + + virtual void setHighlightManager(HighlightManager* highlightManager) = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h new file mode 100644 index 0000000..ade575b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + + class HighlightEditorWidget; + + class HighlightEditorWidgetFactory { + public: + virtual ~HighlightEditorWidgetFactory() {} + + virtual HighlightEditorWidget* createHighlightEditorWidget() = 0; + }; + +} diff --git a/Swift/Controllers/UIInterfaces/HistoryWindow.h b/Swift/Controllers/UIInterfaces/HistoryWindow.h index ffb0ad5..6d50f4b 100644 --- a/Swift/Controllers/UIInterfaces/HistoryWindow.h +++ b/Swift/Controllers/UIInterfaces/HistoryWindow.h @@ -11,7 +11,7 @@ namespace Swift { class HistoryWindow { public: - virtual ~HistoryWindow() {}; + virtual ~HistoryWindow() {} virtual void activate() = 0; virtual void setRosterModel(Roster*) = 0; diff --git a/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h index e91bc37..807fec5 100644 --- a/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h @@ -12,7 +12,7 @@ namespace Swift { class UIEventStream; class HistoryWindowFactory { public: - virtual ~HistoryWindowFactory() {}; + virtual ~HistoryWindowFactory() {} virtual HistoryWindow* createHistoryWindow(UIEventStream* eventStream) = 0; }; } diff --git a/Swift/Controllers/UIInterfaces/InviteToChatWindow.h b/Swift/Controllers/UIInterfaces/InviteToChatWindow.h deleted file mode 100644 index 4070e01..0000000 --- a/Swift/Controllers/UIInterfaces/InviteToChatWindow.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2012 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> -#include <Swiften/Base/boost_bsignals.h> -#include <Swiften/JID/JID.h> - -namespace Swift { - class InviteToChatWindow { - public: - virtual ~InviteToChatWindow() {}; - - virtual void setAutoCompletions(std::vector<std::pair<JID, std::string> > completions) = 0; - - virtual std::string getReason() const = 0; - - virtual std::vector<JID> getJIDs() const = 0; - - boost::signal<void ()> onCompleted; - boost::signal<void ()> onDismissed; - }; -} - diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h index 4873c9b..56a9587 100644 --- a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h +++ b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h @@ -15,7 +15,7 @@ namespace Swift { class JoinMUCWindow { public: - virtual ~JoinMUCWindow() {}; + virtual ~JoinMUCWindow() {} virtual void setNick(const std::string& nick) = 0; virtual void setMUC(const std::string& nick) = 0; diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h index cd8021b..494b418 100644 --- a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h @@ -12,7 +12,7 @@ namespace Swift { class UIEventStream; class JoinMUCWindowFactory { public: - virtual ~JoinMUCWindowFactory() {}; + virtual ~JoinMUCWindowFactory() {} virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream) = 0; }; diff --git a/Swift/Controllers/UIInterfaces/LoginWindow.h b/Swift/Controllers/UIInterfaces/LoginWindow.h index dbc77c4..e27c385 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindow.h +++ b/Swift/Controllers/UIInterfaces/LoginWindow.h @@ -18,7 +18,7 @@ namespace Swift { class MainWindow; class LoginWindow { public: - virtual ~LoginWindow() {}; + virtual ~LoginWindow() {} virtual void selectUser(const std::string&) = 0; virtual void morphInto(MainWindow *mainWindow) = 0; virtual void loggedOut() = 0; diff --git a/Swift/Controllers/UIInterfaces/LoginWindowFactory.h b/Swift/Controllers/UIInterfaces/LoginWindowFactory.h index 1cead2a..7b8b7ec 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/LoginWindowFactory.h @@ -16,7 +16,7 @@ namespace Swift { class LoginWindowFactory { public: - virtual ~LoginWindowFactory() {}; + virtual ~LoginWindowFactory() {} /** * Transfers ownership of result. diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h index 5814b06..43a61a1 100644 --- a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h @@ -19,7 +19,7 @@ namespace Swift { class MUCSearchWindow { public: - virtual ~MUCSearchWindow() {}; + virtual ~MUCSearchWindow() {} virtual void clearList() = 0; virtual void addService(const MUCService& service) = 0; diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h b/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h index d334dff..46488eb 100644 --- a/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h @@ -12,7 +12,7 @@ namespace Swift { class UIEventStream; class MUCSearchWindowFactory { public: - virtual ~MUCSearchWindowFactory() {}; + virtual ~MUCSearchWindowFactory() {} virtual MUCSearchWindow* createMUCSearchWindow() = 0; }; diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h index 23328b4..3b10041 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -20,7 +20,7 @@ namespace Swift { class MainWindow { public: MainWindow(bool candelete = true) : canDelete_(candelete) {} - virtual ~MainWindow() {}; + virtual ~MainWindow() {} bool canDelete() const { return canDelete_; @@ -34,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 setBlockingCommandAvailable(bool isAvailable) = 0; virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) = 0; virtual void setStreamEncryptionStatus(bool tlsInPlaceAndValid) = 0; virtual void openCertificateDialog(const std::vector<Certificate::ref>& chain) = 0; diff --git a/Swift/Controllers/UIInterfaces/MainWindowFactory.h b/Swift/Controllers/UIInterfaces/MainWindowFactory.h index c5cdfef..6bd34b4 100644 --- a/Swift/Controllers/UIInterfaces/MainWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/MainWindowFactory.h @@ -15,7 +15,7 @@ namespace Swift { class MainWindowFactory { public: - virtual ~MainWindowFactory() {}; + virtual ~MainWindowFactory() {} /** * Transfers ownership of result. */ diff --git a/Swift/Controllers/UIInterfaces/ProfileWindow.h b/Swift/Controllers/UIInterfaces/ProfileWindow.h index 5d5c754..5c158e1 100644 --- a/Swift/Controllers/UIInterfaces/ProfileWindow.h +++ b/Swift/Controllers/UIInterfaces/ProfileWindow.h @@ -12,19 +12,24 @@ #include <Swiften/Elements/VCard.h> namespace Swift { + class JID; + class ProfileWindow { public: - virtual ~ProfileWindow() {}; + virtual ~ProfileWindow() {} + virtual void setJID(const JID& jid) = 0; virtual void setVCard(VCard::ref vcard) = 0; virtual void setEnabled(bool b) = 0; virtual void setProcessing(bool b) = 0; virtual void setError(const std::string&) = 0; + virtual void setEditable(bool b) = 0; virtual void show() = 0; virtual void hide() = 0; boost::signal<void (VCard::ref)> onVCardChangeRequest; + boost::signal<void (const JID&)> onWindowAboutToBeClosed; }; } diff --git a/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h b/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h index 022c3eb..45a340a 100644 --- a/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/ProfileWindowFactory.h @@ -11,7 +11,7 @@ namespace Swift { class ProfileWindowFactory { public: - virtual ~ProfileWindowFactory() {}; + virtual ~ProfileWindowFactory() {} virtual ProfileWindow* createProfileWindow() = 0; }; diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 6b4efd8..990dc98 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -21,6 +21,8 @@ #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> #include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWidgetFactory.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h> namespace Swift { class UIFactory : @@ -38,7 +40,9 @@ namespace Swift { public ContactEditWindowFactory, public AdHocCommandWindowFactory, public FileTransferListWidgetFactory, - public WhiteboardWindowFactory { + public WhiteboardWindowFactory, + public HighlightEditorWidgetFactory, + public BlockListEditorWidgetFactory { public: virtual ~UIFactory() {} }; diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h index a3d69d6..9dd1811 100644 --- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h @@ -6,19 +6,20 @@ #pragma once -#include "Swiften/Base/boost_bsignals.h" +#include <Swiften/Base/boost_bsignals.h> #include <vector> #include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Chat/UserSearchController.h" +#include <Swiften/JID/JID.h> +#include <Swift/Controllers/Chat/UserSearchController.h> +#include <Swift/Controllers/Contact.h> namespace Swift { class UserSearchWindow { public: - enum Type {AddContact, ChatToContact}; + enum Type {AddContact, ChatToContact, InviteToChat}; virtual ~UserSearchWindow() {} virtual void clear() = 0; @@ -31,10 +32,20 @@ namespace Swift { virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields) = 0; virtual void setNameSuggestions(const std::vector<std::string>& suggestions) = 0; virtual void prepopulateJIDAndName(const JID& jid, const std::string& name) = 0; + virtual void setContactSuggestions(const std::vector<Contact>& suggestions) = 0; + virtual void setJIDs(const std::vector<JID>&) = 0; + virtual void setRoomJID(const JID& roomJID) = 0; + virtual std::string getReason() const = 0; + virtual std::vector<JID> getJIDs() const = 0; + virtual void setCanStartImpromptuChats(bool supportsImpromptu) = 0; + virtual void updateContacts(const std::vector<Contact>& contacts) = 0; + virtual void show() = 0; boost::signal<void (const JID&)> onFormRequested; boost::signal<void (boost::shared_ptr<SearchPayload>, const JID&)> onSearchRequested; boost::signal<void (const JID&)> onNameSuggestionRequested; + boost::signal<void (const std::string&)> onContactSuggestionsRequested; + boost::signal<void (const std::vector<JID>&)> onJIDUpdateRequested; }; } diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h b/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h index 2a15806..331d6dd 100644 --- a/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h @@ -14,7 +14,7 @@ namespace Swift { class UIEventStream; class UserSearchWindowFactory { public: - virtual ~UserSearchWindowFactory() {}; + virtual ~UserSearchWindowFactory() {} virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) = 0; }; diff --git a/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h b/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h index c2d2f6c..2be0f9c 100644 --- a/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h @@ -12,7 +12,7 @@ namespace Swift { class WhiteboardWindowFactory { public : - virtual ~WhiteboardWindowFactory() {}; + virtual ~WhiteboardWindowFactory() {} virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) = 0; }; diff --git a/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h b/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h index e27fe2e..3cba597 100644 --- a/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h +++ b/Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h @@ -12,7 +12,7 @@ namespace Swift { class UIEventStream; class XMLConsoleWidgetFactory { public: - virtual ~XMLConsoleWidgetFactory() {}; + virtual ~XMLConsoleWidgetFactory() {} virtual XMLConsoleWidget* createXMLConsoleWidget() = 0; }; diff --git a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp index ee0ee9f..dd2a8e5 100644 --- a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp +++ b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp @@ -26,7 +26,7 @@ class ChatMessageSummarizerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE_END(); public: - ChatMessageSummarizerTest() {}; + ChatMessageSummarizerTest() {} void setUp() { @@ -72,7 +72,7 @@ public: 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)); + CPPUNIT_ASSERT_EQUAL(string("Bob (3); Betty (7)"), summary.getSummary(current, unreads)); } void testCurrentNoneOtherCount() { @@ -82,7 +82,7 @@ public: 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)); + CPPUNIT_ASSERT_EQUAL(string("Bob; Betty (7)"), summary.getSummary(current, unreads)); } void testCurrentNoneOthersCount() { diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp new file mode 100644 index 0000000..ec81227 --- /dev/null +++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <vector> +#include <string> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swift/Controllers/HighlightRule.h> + +using namespace Swift; + +class HighlightRuleTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(HighlightRuleTest); + CPPUNIT_TEST(testEmptyRuleNeverMatches); + CPPUNIT_TEST(testKeyword); + CPPUNIT_TEST(testNickKeyword); + CPPUNIT_TEST(testNickWithoutOtherKeywords); + CPPUNIT_TEST(testSender); + CPPUNIT_TEST(testSenderAndKeyword); + CPPUNIT_TEST(testWholeWords); + CPPUNIT_TEST(testCase); + CPPUNIT_TEST(testWholeWordsAndCase); + CPPUNIT_TEST(testMUC); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + std::vector<std::string> keywords; + keywords.push_back("keyword1"); + keywords.push_back("KEYWORD2"); + + std::vector<std::string>senders; + senders.push_back("sender1"); + senders.push_back("SENDER2"); + + emptyRule = new HighlightRule(); + + keywordRule = new HighlightRule(); + keywordRule->setKeywords(keywords); + + keywordChatRule = new HighlightRule(); + keywordChatRule->setKeywords(keywords); + keywordChatRule->setMatchChat(true); + + keywordNickChatRule = new HighlightRule(); + keywordNickChatRule->setKeywords(keywords); + keywordNickChatRule->setNickIsKeyword(true); + keywordNickChatRule->setMatchChat(true); + + nickChatRule = new HighlightRule(); + nickChatRule->setNickIsKeyword(true); + nickChatRule->setMatchChat(true); + + nickRule = new HighlightRule(); + nickRule->setNickIsKeyword(true); + + senderRule = new HighlightRule(); + senderRule->setSenders(senders); + + senderChatRule = new HighlightRule(); + senderChatRule->setSenders(senders); + senderChatRule->setMatchChat(true); + + senderKeywordChatRule = new HighlightRule(); + senderKeywordChatRule->setSenders(senders); + senderKeywordChatRule->setKeywords(keywords); + senderKeywordChatRule->setMatchChat(true); + + senderKeywordNickChatRule = new HighlightRule(); + senderKeywordNickChatRule->setSenders(senders); + senderKeywordNickChatRule->setKeywords(keywords); + senderKeywordNickChatRule->setNickIsKeyword(true); + senderKeywordNickChatRule->setMatchChat(true); + + senderKeywordNickWordChatRule = new HighlightRule(); + senderKeywordNickWordChatRule->setSenders(senders); + senderKeywordNickWordChatRule->setKeywords(keywords); + senderKeywordNickWordChatRule->setNickIsKeyword(true); + senderKeywordNickWordChatRule->setMatchWholeWords(true); + senderKeywordNickWordChatRule->setMatchChat(true); + + senderKeywordNickCaseChatRule = new HighlightRule(); + senderKeywordNickCaseChatRule->setSenders(senders); + senderKeywordNickCaseChatRule->setKeywords(keywords); + senderKeywordNickCaseChatRule->setNickIsKeyword(true); + senderKeywordNickCaseChatRule->setMatchCase(true); + senderKeywordNickCaseChatRule->setMatchChat(true); + + senderKeywordNickCaseWordChatRule = new HighlightRule(); + senderKeywordNickCaseWordChatRule->setSenders(senders); + senderKeywordNickCaseWordChatRule->setKeywords(keywords); + senderKeywordNickCaseWordChatRule->setNickIsKeyword(true); + senderKeywordNickCaseWordChatRule->setMatchCase(true); + senderKeywordNickCaseWordChatRule->setMatchWholeWords(true); + senderKeywordNickCaseWordChatRule->setMatchChat(true); + + senderKeywordNickMUCRule = new HighlightRule(); + senderKeywordNickMUCRule->setSenders(senders); + senderKeywordNickMUCRule->setKeywords(keywords); + senderKeywordNickMUCRule->setNickIsKeyword(true); + senderKeywordNickMUCRule->setMatchMUC(true); + } + + void tearDown() { + delete emptyRule; + + delete keywordRule; + delete keywordChatRule; + delete keywordNickChatRule; + delete nickChatRule; + delete nickRule; + + delete senderRule; + delete senderChatRule; + delete senderKeywordChatRule; + delete senderKeywordNickChatRule; + + delete senderKeywordNickWordChatRule; + delete senderKeywordNickCaseChatRule; + delete senderKeywordNickCaseWordChatRule; + + delete senderKeywordNickMUCRule; + } + + void testEmptyRuleNeverMatches() { + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "from", "", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("body", "", "", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "from", "", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(emptyRule->isMatch("", "", "", HighlightRule::MUCMessage), false); + } + + void testKeyword() { + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body", "sender contains keyword1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc keyword1 xyz", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), true); + } + + void testNickKeyword() { + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "sender contains nick", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body contains mixed-case NiCk", "sender", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(keywordNickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), false); + } + + void testNickWithoutOtherKeywords() { + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains nick", "from", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(nickRule->isMatch("body contains nick", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "sender contains nick but it does't matter", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body contains mixed-case NiCk", "from", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("nickname", "from", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("NIckNAME", "from", "nick", HighlightRule::ChatMessage), true); + + // there are no keywords in this rule and empty nick is not treated as a keyword, so we don't check for keywords to get a match + CPPUNIT_ASSERT_EQUAL(nickChatRule->isMatch("body", "from", "", HighlightRule::ChatMessage), true); + } + + void testSender() { + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "from", "nick", HighlightRule::MUCMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender1", "nick", HighlightRule::MUCMessage), false); + CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderRule->isMatch("body contains sender1", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc sender1 xyz", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcsender1xyz", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "SENDer1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abc SENDer1 xyz", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "abcSENDer1xyz", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderChatRule->isMatch("body", "sender2", "nick", HighlightRule::ChatMessage), true); + } + + void testSenderAndKeyword() { + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + } + + void testWholeWords() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), true); + } + + void testCase() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), true); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false); + } + + void testWholeWordsAndCase() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("xkeyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::ChatMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains xnick", "sender1", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("keyword1", "SENDer1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("KEYword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseWordChatRule->isMatch("body contains NiCk", "sender1", "nick", HighlightRule::ChatMessage), false); + } + + void testMUC() { + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("keyword1", "sender1", "nick", HighlightRule::MUCMessage), true); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickMUCRule->isMatch("body contains nick", "sender1", "nick", HighlightRule::MUCMessage), true); + } + + private: + HighlightRule* emptyRule; + + HighlightRule* keywordRule; + HighlightRule* keywordChatRule; + HighlightRule* keywordNickChatRule; + HighlightRule* nickChatRule; + HighlightRule* nickRule; + + HighlightRule* senderRule; + HighlightRule* senderChatRule; + HighlightRule* senderKeywordChatRule; + HighlightRule* senderKeywordNickChatRule; + + HighlightRule* senderKeywordNickWordChatRule; + HighlightRule* senderKeywordNickCaseChatRule; + HighlightRule* senderKeywordNickCaseWordChatRule; + + HighlightRule* senderKeywordNickMUCRule; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(HighlightRuleTest); diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 998a4eb..43779c5 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -6,59 +6,80 @@ #pragma once -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swiften/Base/foreach.h> + namespace Swift { class MockChatWindow : public ChatWindow { public: - MockChatWindow() : labelsEnabled_(false) {}; + 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 "";}; - virtual std::string addAction(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 "";}; - virtual void addSystemMessage(const std::string& /*message*/) {}; - virtual void addErrorMessage(const std::string& /*message*/) {}; - virtual void addPresenceMessage(const std::string& /*message*/) {}; + virtual std::string addMessage(const ChatMessage& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) { + lastMessageBody_ = bodyFromMessage(message); return "id";} + + virtual std::string addAction(const ChatMessage& /*message*/, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {return "id";} + + virtual void addSystemMessage(const ChatMessage& /*message*/, Direction /*direction*/) {} + virtual void addPresenceMessage(const ChatMessage& /*message*/, Direction /*direction*/) {} + + virtual void addErrorMessage(const ChatMessage& /*message*/) {} + virtual void replaceMessage(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {} + virtual void replaceWithAction(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {} + virtual void replaceLastMessage(const ChatMessage& /*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 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 setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { } - virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}; - virtual void setName(const std::string& name) {name_ = name;}; - virtual void show() {}; - virtual void activate() {}; - virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;}; - virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;}; - virtual void setUnreadMessageCount(int /*count*/) {}; - virtual void convertToMUC() {}; - virtual void setSecurityLabelsError() {}; + virtual void setContactChatState(ChatState::ChatStateType /*state*/) {} + virtual void setName(const std::string& name) {name_ = name;} + virtual void show() {} + virtual void activate() {} + virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;} + virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;} + virtual void setUnreadMessageCount(int /*count*/) {} + virtual void convertToMUC(bool /*impromptuMUC*/) {} + virtual void setSecurityLabelsError() {} virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;} - virtual void setInputEnabled(bool /*enabled*/) {}; - 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&) {}; - virtual void replaceWithAction(const std::string& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/) {}; - 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 setInputEnabled(bool /*enabled*/) {} + virtual void setRosterModel(Roster* /*roster*/) {} + virtual void setTabComplete(TabComplete*) {} + + 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) {} - virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true) {}; + virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true, bool = false, bool = false) {} - virtual std::string addWhiteboardRequest(bool) {return "";}; - virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){}; + virtual std::string addWhiteboardRequest(bool) {return "";} + virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){} virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) {} - virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {}; - virtual InviteToChatWindow* createInviteToChatWindow() {return NULL;} + virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {} + + virtual void setBlockingState(BlockingState) {} + virtual void setCanInitiateImpromptuChats(bool /*supportsImpromptu*/) {} + + std::string bodyFromMessage(const ChatMessage& message) { + boost::shared_ptr<ChatTextMessagePart> text; + foreach (boost::shared_ptr<ChatMessagePart> part, message.getParts()) { + if ((text = boost::dynamic_pointer_cast<ChatTextMessagePart>(part))) { + return text->text; + } + } + return ""; + } std::string name_; std::string lastMessageBody_; diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h index be1a932..69a4e25 100644 --- a/Swift/Controllers/UnitTest/MockMainWindow.h +++ b/Swift/Controllers/UnitTest/MockMainWindow.h @@ -12,18 +12,19 @@ namespace Swift { class Roster; class MockMainWindow : public MainWindow { public: - MockMainWindow() : roster(NULL) {}; - virtual ~MockMainWindow() {}; - virtual void setRosterModel(Roster* roster) {this->roster = roster;}; - virtual void setMyNick(const std::string& /*name*/) {};; - virtual void setMyJID(const JID& /*jid*/) {};; - 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() {}; + MockMainWindow() : roster(NULL) {} + virtual ~MockMainWindow() {} + virtual void setRosterModel(Roster* roster) {this->roster = roster;} + virtual void setMyNick(const std::string& /*name*/) {} + virtual void setMyJID(const JID& /*jid*/) {} + 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() {} virtual void setStreamEncryptionStatus(bool /*tlsInPlaceAndValid*/) {} virtual void openCertificateDialog(const std::vector<Certificate::ref>& /*chain*/) {} + virtual void setBlockingCommandAvailable(bool /*isAvailable*/) {} Roster* roster; }; diff --git a/Swift/Controllers/UnitTest/MockMainWindowFactory.h b/Swift/Controllers/UnitTest/MockMainWindowFactory.h index d130b39..279a6dd 100644 --- a/Swift/Controllers/UnitTest/MockMainWindowFactory.h +++ b/Swift/Controllers/UnitTest/MockMainWindowFactory.h @@ -13,14 +13,14 @@ namespace Swift { class MockMainWindowFactory : public MainWindowFactory { public: - MockMainWindowFactory() : last(NULL) {}; + MockMainWindowFactory() : last(NULL) {} - virtual ~MockMainWindowFactory() {}; + virtual ~MockMainWindowFactory() {} /** * Transfers ownership of result. */ - virtual MainWindow* createMainWindow(UIEventStream*) {last = new MockMainWindow();return last;}; + virtual MainWindow* createMainWindow(UIEventStream*) {last = new MockMainWindow();return last;} MockMainWindow* last; }; } diff --git a/Swift/Controllers/XMPPEvents/ErrorEvent.h b/Swift/Controllers/XMPPEvents/ErrorEvent.h index cbfc471..ac09de9 100644 --- a/Swift/Controllers/XMPPEvents/ErrorEvent.h +++ b/Swift/Controllers/XMPPEvents/ErrorEvent.h @@ -18,10 +18,10 @@ namespace Swift { class ErrorEvent : public StanzaEvent { public: - ErrorEvent(const JID& jid, const std::string& text) : jid_(jid), text_(text){}; - virtual ~ErrorEvent(){}; - const JID& getJID() const {return jid_;}; - const std::string& getText() const {return text_;}; + ErrorEvent(const JID& jid, const std::string& text) : jid_(jid), text_(text){} + virtual ~ErrorEvent(){} + const JID& getJID() const {return jid_;} + const std::string& getText() const {return text_;} private: JID jid_; diff --git a/Swift/Controllers/XMPPEvents/EventController.cpp b/Swift/Controllers/XMPPEvents/EventController.cpp index d84dfe3..8cb259b 100644 --- a/Swift/Controllers/XMPPEvents/EventController.cpp +++ b/Swift/Controllers/XMPPEvents/EventController.cpp @@ -7,6 +7,7 @@ #include <Swift/Controllers/XMPPEvents/EventController.h> #include <boost/bind.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <algorithm> #include <Swiften/Base/foreach.h> @@ -48,7 +49,7 @@ void EventController::handleIncomingEvent(boost::shared_ptr<StanzaEvent> sourceE if ((messageEvent && messageEvent->isReadable()) || subscriptionEvent || errorEvent || mucInviteEvent) { events_.push_back(sourceEvent); sourceEvent->onConclusion.connect(boost::bind(&EventController::handleEventConcluded, this, sourceEvent)); - onEventQueueLengthChange(events_.size()); + onEventQueueLengthChange(boost::numeric_cast<int>(events_.size())); onEventQueueEventAdded(sourceEvent); if (sourceEvent->getConcluded()) { handleEventConcluded(sourceEvent); @@ -59,7 +60,7 @@ void EventController::handleIncomingEvent(boost::shared_ptr<StanzaEvent> sourceE void EventController::handleEventConcluded(boost::shared_ptr<StanzaEvent> event) { event->onConclusion.disconnect(boost::bind(&EventController::handleEventConcluded, this, event)); events_.erase(std::remove(events_.begin(), events_.end(), event), events_.end()); - onEventQueueLengthChange(events_.size()); + onEventQueueLengthChange(boost::numeric_cast<int>(events_.size())); } void EventController::disconnectAll() { diff --git a/Swift/Controllers/XMPPEvents/MUCInviteEvent.h b/Swift/Controllers/XMPPEvents/MUCInviteEvent.h index 0b430cd..65ecece 100644 --- a/Swift/Controllers/XMPPEvents/MUCInviteEvent.h +++ b/Swift/Controllers/XMPPEvents/MUCInviteEvent.h @@ -13,13 +13,14 @@ namespace Swift { typedef boost::shared_ptr<MUCInviteEvent> ref; public: - MUCInviteEvent(const JID& inviter, const JID& roomJID, const std::string& reason, const std::string& password, bool direct) : inviter_(inviter), roomJID_(roomJID), reason_(reason), password_(password), direct_(direct) {} + MUCInviteEvent(const JID& inviter, const JID& roomJID, const std::string& reason, const std::string& password, bool direct, bool impromptu) : inviter_(inviter), roomJID_(roomJID), reason_(reason), password_(password), direct_(direct), impromptu_(impromptu) {} const JID& getInviter() const { return inviter_; } const JID& getRoomJID() const { return roomJID_; } const std::string& getReason() const { return reason_; } const std::string& getPassword() const { return password_; } bool getDirect() const { return direct_; } + bool getImpromptu() const { return impromptu_; } private: JID inviter_; @@ -27,5 +28,6 @@ namespace Swift { std::string reason_; std::string password_; bool direct_; + bool impromptu_; }; } diff --git a/Swift/Controllers/XMPPEvents/MessageEvent.h b/Swift/Controllers/XMPPEvents/MessageEvent.h index 1093470..a9214f5 100644 --- a/Swift/Controllers/XMPPEvents/MessageEvent.h +++ b/Swift/Controllers/XMPPEvents/MessageEvent.h @@ -17,7 +17,7 @@ namespace Swift { public: typedef boost::shared_ptr<MessageEvent> ref; - MessageEvent(boost::shared_ptr<Message> stanza) : stanza_(stanza), targetsMe_(true) {}; + MessageEvent(boost::shared_ptr<Message> stanza) : stanza_(stanza), targetsMe_(true) {} boost::shared_ptr<Message> getStanza() {return stanza_;} diff --git a/Swift/Controllers/XMPPEvents/StanzaEvent.h b/Swift/Controllers/XMPPEvents/StanzaEvent.h index 321d23d..a15afc1 100644 --- a/Swift/Controllers/XMPPEvents/StanzaEvent.h +++ b/Swift/Controllers/XMPPEvents/StanzaEvent.h @@ -14,13 +14,13 @@ namespace Swift { class StanzaEvent { public: - StanzaEvent() : time_(boost::posix_time::microsec_clock::universal_time()) {concluded_ = false;}; - virtual ~StanzaEvent() {}; - void conclude() {concluded_ = true; onConclusion();}; + StanzaEvent() : time_(boost::posix_time::microsec_clock::universal_time()) {concluded_ = false;} + virtual ~StanzaEvent() {} + void conclude() {concluded_ = true; onConclusion();} /** Do not call this directly from outside the class. * If you connect to this signal, you *must* disconnect from it manually. */ boost::signal<void()> onConclusion; - bool getConcluded() {return concluded_;}; + bool getConcluded() {return concluded_;} boost::posix_time::ptime getTime() {return time_;} private: bool concluded_; diff --git a/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h b/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h index 1f7812e..fb7a05e 100644 --- a/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h +++ b/Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h @@ -18,21 +18,21 @@ namespace Swift { class SubscriptionRequestEvent : public StanzaEvent { public: - SubscriptionRequestEvent(const JID& jid, const std::string& reason) : jid_(jid), reason_(reason){}; - virtual ~SubscriptionRequestEvent(){}; - const JID& getJID() const {return jid_;}; - const std::string& getReason() const {return reason_;}; + SubscriptionRequestEvent(const JID& jid, const std::string& reason) : jid_(jid), reason_(reason){} + virtual ~SubscriptionRequestEvent(){} + const JID& getJID() const {return jid_;} + const std::string& getReason() const {return reason_;} boost::signal<void()> onAccept; boost::signal<void()> onDecline; void accept() { onAccept(); conclude(); - }; + } void decline() { onDecline(); conclude(); - }; + } void defer() { conclude(); diff --git a/Swift/Packaging/Debian/debian/control.in b/Swift/Packaging/Debian/debian/control.in index bd04e97..fa734f6 100644 --- a/Swift/Packaging/Debian/debian/control.in +++ b/Swift/Packaging/Debian/debian/control.in @@ -3,7 +3,7 @@ Section: net Priority: optional Maintainer: Swift Package Maintainer <packages@swift.im> Uploaders: Remko Tronçon <dev@el-tramo.be>, Kevin Smith <kevin@kismith.co.uk> -Build-Depends: debhelper (>= 7), scons (>= 1.2.0), libssl-dev (>= 0.9.8g), libqt4-dev (>= 4.5.0), libxml2-dev (>= 2.7.6), libxss-dev (>= 1.2.0), libboost-dev (>= 1.34.1), libboost-filesystem-dev (>= 1.34.1), libboost-program-options-dev (>= 1.34.1), libboost-regex-dev (>= 1.34.1), libboost-signals-dev (>= 1.34.1), libboost-system-dev (>= 1.34.1), libboost-thread-dev (>= 1.34.1), libboost-date-time-dev (>= 1.34.1), libidn11-dev (>= 1.10), docbook-xsl (>= 1.75.0), docbook-xml (>= 4.5), xsltproc, libxml2-utils %WEBKIT_DEPENDENCY% +Build-Depends: debhelper (>= 7), scons (>= 1.2.0), libssl-dev (>= 0.9.8g), libqt4-dev (>= 4.5.0), libxml2-dev (>= 2.7.6), libxss-dev (>= 1.2.0), libboost-dev (>= 1.34.1), libboost-filesystem-dev (>= 1.34.1), libboost-program-options-dev (>= 1.34.1), libboost-regex-dev (>= 1.34.1), libboost-signals-dev (>= 1.34.1), libboost-system-dev (>= 1.34.1), libboost-thread-dev (>= 1.34.1), libboost-date-time-dev (>= 1.34.1), libidn11-dev (>= 1.10), docbook-xsl (>= 1.75.0), docbook-xml (>= 4.5), xsltproc, libxml2-utils, libhunspell-dev, libnatpmp-dev, libminiupnpc-dev, libsqlite3-dev %WEBKIT_DEPENDENCY% Standards-Version: 3.9.4 Vcs-Git: git://swift.im/swift Vcs-Browser: http://swift.im/git/swift diff --git a/Swift/Packaging/Debian/package.sh b/Swift/Packaging/Debian/package.sh index d4b6484..8c7c89f 100755 --- a/Swift/Packaging/Debian/package.sh +++ b/Swift/Packaging/Debian/package.sh @@ -50,9 +50,9 @@ else rm -rf $DIRNAME/.git find $DIRNAME -name .gitignore | xargs rm -f if [ -z "$SWIFT_COPY_UUID" ]; then - find $DIRNAME/3rdParty -type f | grep -v SConscript | xargs rm -f + find $DIRNAME/3rdParty -type f | grep -v SConscript | grep -v miniupnp | grep -v natpmp |xargs rm -f else - find $DIRNAME/3rdParty -type f | grep -v uuid | grep -v SConscript | xargs rm -f + find $DIRNAME/3rdParty -type f | grep -v uuid | grep -v SConscript | grep -v miniupnp | grep -v natpmp || xargs rm -f fi find $DIRNAME/3rdParty -depth -empty -type d -exec rmdir {} \; rm -rf $DIRNAME/3rdParty/SCons diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp index 5b879df..5b03ac5 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.cpp +++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp @@ -120,7 +120,7 @@ void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem 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, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, item->getChat().unreadCount, compact_); } void ChatListDelegate::paintWhiteboard(QPainter* painter, const QStyleOptionViewItem& option, ChatListWhiteboardItem* item) const { @@ -135,7 +135,8 @@ void ChatListDelegate::paintWhiteboard(QPainter* painter, const QStyleOptionView QString name = item->data(Qt::DisplayRole).toString(); //qDebug() << "Avatar for " << name << " = " << avatarPath; QString statusText = item->data(ChatListWhiteboardItem::DetailTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, item->getChat().unreadCount, compact_); + } } diff --git a/Swift/QtUI/ChatList/ChatListGroupItem.h b/Swift/QtUI/ChatList/ChatListGroupItem.h index a1e479f..17defea 100644 --- a/Swift/QtUI/ChatList/ChatListGroupItem.h +++ b/Swift/QtUI/ChatList/ChatListGroupItem.h @@ -13,14 +13,14 @@ namespace Swift { class ChatListGroupItem : public ChatListItem { public: - 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];}; - int row(ChatListItem* item) {return items_.indexOf(item);}; - QVariant data(int role) const {return (role == Qt::DisplayRole) ? name_ : QVariant();}; - void clear() {items_.clear();}; + 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];} + int row(ChatListItem* item) {return items_.indexOf(item);} + QVariant data(int role) const {return (role == Qt::DisplayRole) ? name_ : QVariant();} + void clear() {items_.clear();} private: static bool pointerItemLessThan(const ChatListItem* first, const ChatListItem* second) { QString myName = first->data(Qt::DisplayRole).toString().toLower(); diff --git a/Swift/QtUI/ChatList/ChatListItem.h b/Swift/QtUI/ChatList/ChatListItem.h index e7be614..28c0f9c 100644 --- a/Swift/QtUI/ChatList/ChatListItem.h +++ b/Swift/QtUI/ChatList/ChatListItem.h @@ -13,10 +13,10 @@ namespace Swift { class ChatListGroupItem; class ChatListItem { public: - ChatListItem(ChatListGroupItem* parent) {parent_ = parent;}; + ChatListItem(ChatListGroupItem* parent) {parent_ = parent;} virtual ~ChatListItem() {} - ChatListGroupItem* parent() {return parent_;}; + ChatListGroupItem* parent() {return parent_;} virtual QVariant data(int role) const = 0; private: diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h index e384a04..04e369a 100644 --- a/Swift/QtUI/ChatList/ChatListModel.h +++ b/Swift/QtUI/ChatList/ChatListModel.h @@ -6,8 +6,6 @@ #pragma once -#include <boost/shared_ptr.hpp> - #include <QAbstractItemModel> #include <QList> diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.cpp b/Swift/QtUI/ChatList/ChatListRecentItem.cpp index 6c9807f..e9ecec8 100644 --- a/Swift/QtUI/ChatList/ChatListRecentItem.cpp +++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -7,6 +7,7 @@ #include <Swift/QtUI/ChatList/ChatListRecentItem.h> #include <Swift/QtUI/QtSwiftUtil.h> +#include <Swiften/Base/Path.h> namespace Swift { ChatListRecentItem::ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) { @@ -19,13 +20,13 @@ const ChatListWindow::Chat& ChatListRecentItem::getChat() const { QVariant ChatListRecentItem::data(int role) const { switch (role) { - case Qt::DisplayRole: return P2QSTRING(chat_.chatName); + case Qt::DisplayRole: return chat_.impromptuJIDs.empty() ? P2QSTRING(chat_.chatName) : P2QSTRING(chat_.getImpromptuTitle()); 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 AvatarRole: return QVariant(P2QSTRING(pathToString(chat_.avatarPath))); case PresenceIconRole: return getPresenceIcon(); default: return QVariant(); } diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.h b/Swift/QtUI/ChatList/ChatListRecentItem.h index 4e7bc3e..3f27a68 100644 --- a/Swift/QtUI/ChatList/ChatListRecentItem.h +++ b/Swift/QtUI/ChatList/ChatListRecentItem.h @@ -23,7 +23,8 @@ namespace Swift { DetailTextRole = Qt::UserRole, AvatarRole = Qt::UserRole + 1, PresenceIconRole = Qt::UserRole + 2/*, - StatusShowTypeRole = Qt::UserRole + 3*/ + StatusShowTypeRole = Qt::UserRole + 3, + IdleRole = Qt::UserRole + 4*/ }; ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent); const ChatListWindow::Chat& getChat() const; diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp index 41648b6..6791aa5 100644 --- a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp +++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp @@ -4,9 +4,16 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + #include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h> #include <Swift/QtUI/QtSwiftUtil.h> +#include <Swiften/Base/Path.h> namespace Swift { ChatListWhiteboardItem::ChatListWhiteboardItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) { @@ -25,7 +32,7 @@ namespace Swift { 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 AvatarRole: return QVariant(P2QSTRING(pathToString(chat_.avatarPath))); case PresenceIconRole: return getPresenceIcon(); default: return QVariant(); } diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp index 9692c9c..4d1f19b 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.cpp +++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp @@ -30,7 +30,7 @@ namespace Swift { QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) { eventStream_ = uiEventStream; - settings_ = settings;; + settings_ = settings; bookmarksEnabled_ = false; model_ = new ChatListModel(); setModel(model_); diff --git a/Swift/QtUI/ChatSnippet.cpp b/Swift/QtUI/ChatSnippet.cpp index ab31d29..3436531 100644 --- a/Swift/QtUI/ChatSnippet.cpp +++ b/Swift/QtUI/ChatSnippet.cpp @@ -1,12 +1,14 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ +#include <Swift/QtUI/ChatSnippet.h> + #include <QFile> -#include "ChatSnippet.h" +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -39,4 +41,58 @@ QString ChatSnippet::wrapResizable(const QString& text) { return "<span class='swift_resizable'>" + text + "</span>"; } -}; +QString ChatSnippet::directionToCSS(Direction direction) { + return direction == RTL ? QString("rtl") : QString("ltr"); +} + +ChatSnippet::Direction ChatSnippet::getDirection(const ChatWindow::ChatMessage& message) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + std::string text = ""; + foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) { + if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + text = textPart->text; + break; + } + } + return getDirection(text); +} + +ChatSnippet::Direction ChatSnippet::getDirection(const std::string& message) { + return getDirection(P2QSTRING(message)); +} + +ChatSnippet::Direction ChatSnippet::getDirection(const QString& message) { + /* + for (int i = 0; i < message.size(); ++i) { + switch (message.at(i).direction()) { + case QChar::DirL: + case QChar::DirLRE: + case QChar::DirLRO: + return ChatSnippet::LTR; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirRLE: + case QChar::DirRLO: + return ChatSnippet::RTL; + case QChar::DirEN: + case QChar::DirES: + case QChar::DirET: + case QChar::DirAN: + case QChar::DirCS: + case QChar::DirB: + case QChar::DirWS: + case QChar::DirON: + case QChar::DirS: + case QChar::DirPDF: + case QChar::DirNSM: + case QChar::DirBN: + break; + } + } + return ChatSnippet::LTR; + */ + return message.isRightToLeft() ? ChatSnippet::RTL : ChatSnippet::LTR; +} + + +} diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h index 78e0b88..f60d486 100644 --- a/Swift/QtUI/ChatSnippet.h +++ b/Swift/QtUI/ChatSnippet.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,11 +10,20 @@ #include <QString> #include <QDateTime> -#include "QtChatTheme.h" + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/QtUI/QtChatTheme.h> + namespace Swift { class ChatSnippet { public: + enum Direction { + RTL, + LTR + }; + ChatSnippet(bool appendToPrevious); virtual ~ChatSnippet(); @@ -42,7 +51,13 @@ namespace Swift { static QString timeToEscapedString(const QDateTime& time); + static Direction getDirection(const std::string& message); + static Direction getDirection(const ChatWindow::ChatMessage& message); + static Direction getDirection(const QString& message); + protected: + static QString directionToCSS(Direction direction); + QString wrapResizable(const QString& text); void setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet> continuationFallback) { continuationFallback_ = continuationFallback; diff --git a/Swift/QtUI/EventViewer/EventDelegate.cpp b/Swift/QtUI/EventViewer/EventDelegate.cpp index 9ecdd34..c0904b3 100644 --- a/Swift/QtUI/EventViewer/EventDelegate.cpp +++ b/Swift/QtUI/EventViewer/EventDelegate.cpp @@ -25,12 +25,13 @@ QSize EventDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIn return QStyledItemDelegate::sizeHint(option, index); } switch (getEventType(item->getEvent())) { - case MessageEventType: return messageDelegate_.sizeHint(option, item); - case SubscriptionEventType: return subscriptionDelegate_.sizeHint(option, item); - case ErrorEventType: return errorDelegate_.sizeHint(option, item); - case MUCInviteEventType: return mucInviteDelegate_.sizeHint(option, item); - default: return QStyledItemDelegate::sizeHint(option, index); + case MessageEventType: return messageDelegate_.sizeHint(option, item); + case SubscriptionEventType: return subscriptionDelegate_.sizeHint(option, item); + case ErrorEventType: return errorDelegate_.sizeHint(option, item); + case MUCInviteEventType: return mucInviteDelegate_.sizeHint(option, item); } + assert(false); + return QSize(); } void EventDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { @@ -40,23 +41,30 @@ void EventDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, return; } switch (getEventType(item->getEvent())) { - case MessageEventType: messageDelegate_.paint(painter, option, item);break; - case SubscriptionEventType: subscriptionDelegate_.paint(painter, option, item);break; - case ErrorEventType: errorDelegate_.paint(painter, option, item);break; - case MUCInviteEventType: mucInviteDelegate_.paint(painter, option, item);break; - default: QStyledItemDelegate::paint(painter, option, index); + case MessageEventType: messageDelegate_.paint(painter, option, item);break; + case SubscriptionEventType: subscriptionDelegate_.paint(painter, option, item);break; + case ErrorEventType: errorDelegate_.paint(painter, option, item);break; + case MUCInviteEventType: mucInviteDelegate_.paint(painter, option, item);break; } } EventType EventDelegate::getEventType(boost::shared_ptr<StanzaEvent> event) const { boost::shared_ptr<MessageEvent> messageEvent = boost::dynamic_pointer_cast<MessageEvent>(event); - if (messageEvent) return MessageEventType; + if (messageEvent) { + return MessageEventType; + } boost::shared_ptr<SubscriptionRequestEvent> subscriptionEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(event); - if (subscriptionEvent) return SubscriptionEventType; + if (subscriptionEvent) { + return SubscriptionEventType; + } boost::shared_ptr<ErrorEvent> errorEvent = boost::dynamic_pointer_cast<ErrorEvent>(event); - if (errorEvent) return ErrorEventType; + if (errorEvent) { + return ErrorEventType; + } boost::shared_ptr<MUCInviteEvent> mucInviteEvent = boost::dynamic_pointer_cast<MUCInviteEvent>(event); - if (mucInviteEvent) return MUCInviteEventType; + if (mucInviteEvent) { + return MUCInviteEventType; + } //I don't know what this is. assert(false); return MessageEventType; diff --git a/Swift/QtUI/EventViewer/QtEvent.cpp b/Swift/QtUI/EventViewer/QtEvent.cpp index 3c6f16c..c3ff944 100644 --- a/Swift/QtUI/EventViewer/QtEvent.cpp +++ b/Swift/QtUI/EventViewer/QtEvent.cpp @@ -7,6 +7,7 @@ #include "Swift/QtUI/EventViewer/QtEvent.h" #include <QDateTime> +#include <QColor> #include "Swift/Controllers/XMPPEvents/MessageEvent.h" #include "Swift/Controllers/XMPPEvents/ErrorEvent.h" @@ -25,8 +26,8 @@ QVariant QtEvent::data(int role) { switch (role) { case Qt::ToolTipRole: return QVariant(text()).toString() + "\n" + B2QDATE(event_->getTime()).toString(); case Qt::DisplayRole: return QVariant(text()); - case Qt::TextColorRole: return active_ ? Qt::black : Qt::darkGray; - case Qt::BackgroundColorRole: return active_ ? Qt::white : Qt::lightGray; + case Qt::TextColorRole: return QColor(active_ ? Qt::black : Qt::darkGray); + case Qt::BackgroundColorRole: return QColor(active_ ? Qt::white : Qt::lightGray); case SenderRole: return QVariant(sender()); /*case StatusTextRole: return statusText_; case AvatarRole: return avatar_; diff --git a/Swift/QtUI/EventViewer/QtEvent.h b/Swift/QtUI/EventViewer/QtEvent.h index f5e3dee..11efd60 100644 --- a/Swift/QtUI/EventViewer/QtEvent.h +++ b/Swift/QtUI/EventViewer/QtEvent.h @@ -17,7 +17,7 @@ namespace Swift { public: QtEvent(boost::shared_ptr<StanzaEvent> event, bool active); QVariant data(int role); - boost::shared_ptr<StanzaEvent> getEvent() { return event_; }; + boost::shared_ptr<StanzaEvent> getEvent() { return event_; } enum EventRoles { SenderRole = Qt::UserRole diff --git a/Swift/QtUI/FreeDesktopNotifier.cpp b/Swift/QtUI/FreeDesktopNotifier.cpp index 2393340..1f1ccda 100644 --- a/Swift/QtUI/FreeDesktopNotifier.cpp +++ b/Swift/QtUI/FreeDesktopNotifier.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -13,6 +13,9 @@ #include <QStringList> #include <QtDBus/QtDBus> #include <algorithm> +#include <Swiften/Base/Path.h> + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -43,7 +46,7 @@ void FreeDesktopNotifier::showMessage(Type type, const std::string& subject, con hints["x-canonical-append"] = QString("allowed"); msg << applicationName.c_str(); msg << quint32(0); // ID of previous notification to replace - msg << imageScaler.getScaledImage(picture, 48).string().c_str(); // Icon to display + msg << P2QSTRING(pathToString(imageScaler.getScaledImage(picture, 48))); // Icon to display msg << subject.c_str(); // Summary / Header of the message to display msg << body; // Body of the message to display msg << actions; // Actions from which the user may choose diff --git a/Swift/QtUI/FreeDesktopNotifier.h b/Swift/QtUI/FreeDesktopNotifier.h index 4ba02b4..6da7621 100644 --- a/Swift/QtUI/FreeDesktopNotifier.h +++ b/Swift/QtUI/FreeDesktopNotifier.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010 -2012 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -15,9 +15,8 @@ namespace Swift { FreeDesktopNotifier(const std::string& name); virtual void showMessage(Type type, const std::string& subject, const std::string& description, const boost::filesystem::path& picture, boost::function<void()> callback); - virtual void purgeCallbacks() { -#warning FIXME implement. - }; + virtual void purgeCallbacks() {} + private: std::string applicationName; QtCachedImageScaler imageScaler; diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp index 2fa24c2..7bd16e3 100644 --- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp +++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp @@ -23,7 +23,7 @@ namespace Swift { QtMUCSearchWindow::QtMUCSearchWindow() { ui_.setupUi(this); -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); #endif setModal(true); diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp index 47aa9f8..28c44c4 100644 --- a/Swift/QtUI/MessageSnippet.cpp +++ b/Swift/QtUI/MessageSnippet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,9 +11,9 @@ namespace Swift { -MessageSnippet::MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme, const QString& id) : ChatSnippet(appendToPrevious) { +MessageSnippet::MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme, const QString& id, Direction direction) : ChatSnippet(appendToPrevious) { if (appendToPrevious) { - setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet>(new MessageSnippet(message, sender, time, iconURI, isIncoming, false, theme, id))); + setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet>(new MessageSnippet(message, sender, time, iconURI, isIncoming, false, theme, id, direction))); } if (isIncoming) { if (appendToPrevious) { @@ -32,12 +32,13 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co } } + content_.replace("%direction%", directionToCSS(direction)); content_.replace("%message%", wrapResizable("<span class='swift_message'>" + escape(message) + "</span><span class='swift_ack'></span><span class='swift_receipt'></span>")); content_.replace("%wrapped_sender%", wrapResizable(escape(sender))); content_.replace("%sender%", escape(sender)); content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); content_.replace("%userIconPath%", escape(iconURI)); - content_ = "<div id='" + id + "'>" + content_ + "</div>"; + content_ = QString("<div id='%1'>%2</div>").arg(id).arg(content_); content_ = "<span class='date" + time.date().toString(Qt::ISODate) + "'>" + content_ + "</span>"; } diff --git a/Swift/QtUI/MessageSnippet.h b/Swift/QtUI/MessageSnippet.h index c7425e9..8186d19 100644 --- a/Swift/QtUI/MessageSnippet.h +++ b/Swift/QtUI/MessageSnippet.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -15,7 +15,7 @@ class QDateTime; namespace Swift { class MessageSnippet : public ChatSnippet { public: - MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme, const QString& id); + MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme, const QString& id, Direction direction); virtual ~MessageSnippet(); const QString& getContent() const { return content_; @@ -23,7 +23,7 @@ namespace Swift { QString getContinuationElementID() const { return "insert"; - }; + } private: QString content_; diff --git a/Swift/QtUI/QtAboutWidget.cpp b/Swift/QtUI/QtAboutWidget.cpp index acdc61e..c00acf7 100644 --- a/Swift/QtUI/QtAboutWidget.cpp +++ b/Swift/QtUI/QtAboutWidget.cpp @@ -19,7 +19,7 @@ namespace Swift { QtAboutWidget::QtAboutWidget() : QDialog() { -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowTitle(QString(tr("About %1")).arg("Swift")); #endif setWindowIcon(QIcon(":/logo-icon-16.png")); diff --git a/Swift/QtUI/QtAffiliationEditor.cpp b/Swift/QtUI/QtAffiliationEditor.cpp index ed03c23..0896b92 100644 --- a/Swift/QtUI/QtAffiliationEditor.cpp +++ b/Swift/QtUI/QtAffiliationEditor.cpp @@ -76,4 +76,4 @@ MUCOccupant::Affiliation QtAffiliationEditor::affiliationFromIndex(int affiliati } } -}
\ No newline at end of file +} diff --git a/Swift/QtUI/QtAffiliationEditor.h b/Swift/QtUI/QtAffiliationEditor.h index 913b2cc..96536eb 100644 --- a/Swift/QtUI/QtAffiliationEditor.h +++ b/Swift/QtUI/QtAffiliationEditor.h @@ -34,4 +34,4 @@ namespace Swift { std::map<MUCOccupant::Affiliation, std::vector<JID> > affiliations_; std::vector<ChangePair> changes_; }; -}
\ No newline at end of file +} diff --git a/Swift/QtUI/QtAvatarWidget.cpp b/Swift/QtUI/QtAvatarWidget.cpp index f0bdf3c..015c2da 100644 --- a/Swift/QtUI/QtAvatarWidget.cpp +++ b/Swift/QtUI/QtAvatarWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 Remko Tronçon + * Copyright (c) 2011-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -19,6 +19,7 @@ #include <QPainter> #include <QtSwiftUtil.h> +#include <Swiften/Base/Path.h> namespace Swift { @@ -68,6 +69,9 @@ void QtAvatarWidget::setAvatar(const ByteArray& data, const std::string& type) { } void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { + if (!editable) { + return; + } QMenu menu; QAction* selectPicture = new QAction(tr("Select picture ..."), this); @@ -81,7 +85,7 @@ void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { QString fileName = QFileDialog::getOpenFileName(this, tr("Select picture"), "", tr("Image Files (*.png *.jpg *.jpeg *.gif)")); if (!fileName.isEmpty()) { ByteArray data; - readByteArrayFromFile(data, Q2PSTRING(fileName)); + readByteArrayFromFile(data, stringToPath(Q2PSTRING(fileName))); QBuffer buffer; buffer.setData(reinterpret_cast<const char*>(vecptr(data)), data.size()); diff --git a/Swift/QtUI/QtAvatarWidget.h b/Swift/QtUI/QtAvatarWidget.h index 8830d82..f4ac4cf 100644 --- a/Swift/QtUI/QtAvatarWidget.h +++ b/Swift/QtUI/QtAvatarWidget.h @@ -15,6 +15,7 @@ class QLabel; namespace Swift { class QtAvatarWidget : public QWidget { Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) public: QtAvatarWidget(QWidget* parent); @@ -28,9 +29,18 @@ namespace Swift { return type; } + void setEditable(bool b) { + editable = b; + } + + bool isEditable() const { + return editable; + } + void mousePressEvent(QMouseEvent* event); private: + bool editable; ByteArray data; std::string type; QLabel* label; diff --git a/Swift/QtUI/QtBlockListEditorWindow.cpp b/Swift/QtUI/QtBlockListEditorWindow.cpp new file mode 100644 index 0000000..a759a3f --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <QtBlockListEditorWindow.h> +#include <ui_QtBlockListEditorWindow.h> + +#include <boost/bind.hpp> + +#include <QLineEdit> +#include <QMovie> +#include <QShortcut> +#include <QStyledItemDelegate> +#include <QValidator> + +#include <Swift/QtUI/QtUtilities.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Base/foreach.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class QtJIDValidator : public QValidator { + public: + QtJIDValidator(QObject* parent) : QValidator(parent) {} + virtual ~QtJIDValidator() {} + virtual QValidator::State validate(QString& input, int&) const { + return JID(Q2PSTRING(input)).isValid() ? Acceptable : Intermediate; + } +}; + +class QtJIDValidatedItemDelegate : public QItemDelegate { + public: + QtJIDValidatedItemDelegate(QObject* parent) : QItemDelegate(parent) {} + virtual ~QtJIDValidatedItemDelegate() {} + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const { + QLineEdit *editor = new QLineEdit(parent); + editor->setValidator(new QtJIDValidator(editor)); + return editor; + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const { + QString value = index.model()->data(index, Qt::EditRole).toString(); + + QLineEdit *lineEdit = static_cast<QLineEdit*>(editor); + lineEdit->setText(value); + } + + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { + QLineEdit *lineEdit = static_cast<QLineEdit*>(editor); + QString currentValue = lineEdit->text(); + int pos = 0; + if (lineEdit->validator()->validate(currentValue, pos) == QValidator::Acceptable) { + model->setData(index, lineEdit->text(), Qt::EditRole); + } else { + model->setData(index, QString(), Qt::EditRole); + } + } + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { + editor->setGeometry(option.rect); + } +}; + +QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow) { + ui->setupUi(this); + new QShortcut(QKeySequence::Close, this, SLOT(close())); + ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); + + itemDelegate = new QtRemovableItemDelegate(style()); + + connect(ui->savePushButton, SIGNAL(clicked()), SLOT(applyChanges())); + + ui->blockListTreeWidget->setColumnCount(2); + ui->blockListTreeWidget->header()->setStretchLastSection(false); + ui->blockListTreeWidget->header()->resizeSection(1, itemDelegate->sizeHint(QStyleOptionViewItem(), QModelIndex()).width()); + +#if QT_VERSION >= 0x050000 + ui->blockListTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); +#else + ui->blockListTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch); +#endif + + ui->blockListTreeWidget->setHeaderHidden(true); + ui->blockListTreeWidget->setRootIsDecorated(false); + ui->blockListTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); + ui->blockListTreeWidget->setItemDelegateForColumn(0, new QtJIDValidatedItemDelegate(this)); + ui->blockListTreeWidget->setItemDelegateForColumn(1, itemDelegate); + connect(ui->blockListTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int))); + + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); +} + +QtBlockListEditorWindow::~QtBlockListEditorWindow() { +} + +void QtBlockListEditorWindow::show() { + QWidget::show(); + QWidget::activateWindow(); +} + +void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *, int) { + bool hasEmptyRow = false; + QList<QTreeWidgetItem*> rows = ui->blockListTreeWidget->findItems("", Qt::MatchFixedString); + foreach(QTreeWidgetItem* row, rows) { + if (row->text(0).isEmpty()) { + hasEmptyRow = true; + } + } + + if (!hasEmptyRow) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); + } +} + +void QtBlockListEditorWindow::applyChanges() { + onSetNewBlockList(getCurrentBlockList()); +} + +void Swift::QtBlockListEditorWindow::setCurrentBlockList(const std::vector<JID> &blockedJIDs) { + ui->blockListTreeWidget->clear(); + + foreach(const JID& jid, blockedJIDs) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(P2QSTRING(jid.toString())) << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); + } + handleItemChanged(0,0); +} + +void Swift::QtBlockListEditorWindow::setBusy(bool isBusy) { + if (isBusy) { + ui->throbberLabel->movie()->start(); + ui->throbberLabel->show(); + } else { + ui->throbberLabel->movie()->stop(); + ui->throbberLabel->hide(); + } +} + +std::vector<JID> Swift::QtBlockListEditorWindow::getCurrentBlockList() const { + std::vector<JID> futureBlockedJIDs; + + for(int i=0; i < ui->blockListTreeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem* row = ui->blockListTreeWidget->topLevelItem(i); + if (!row->text(0).isEmpty()) { + futureBlockedJIDs.push_back(JID(Q2PSTRING(row->text(0)))); + } + } + return futureBlockedJIDs; +} + +} diff --git a/Swift/QtUI/QtBlockListEditorWindow.h b/Swift/QtUI/QtBlockListEditorWindow.h new file mode 100644 index 0000000..4b124a3 --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h> +#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> + +#include <QWidget> +#include <QTreeWidgetItem> + +namespace Ui { + class QtBlockListEditorWindow; +} + +namespace Swift { + +class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { + Q_OBJECT + + public: + QtBlockListEditorWindow(); + virtual ~QtBlockListEditorWindow(); + + virtual void show(); + virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs); + virtual void setBusy(bool isBusy); + virtual std::vector<JID> getCurrentBlockList() const; + + private slots: + void handleItemChanged(QTreeWidgetItem*, int); + void applyChanges(); + + private: + Ui::QtBlockListEditorWindow* ui; + QtRemovableItemDelegate* itemDelegate; +}; + +} diff --git a/Swift/QtUI/QtBlockListEditorWindow.ui b/Swift/QtUI/QtBlockListEditorWindow.ui new file mode 100644 index 0000000..f71bbae --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.ui @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtBlockListEditorWindow</class> + <widget class="QWidget" name="QtBlockListEditorWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>348</width> + <height>262</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Block List</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>5</number> + </property> + <property name="margin"> + <number>5</number> + </property> + <item> + <widget class="QTreeWidget" name="blockListTreeWidget"> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="errorLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="throbberLabel"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="savePushButton"> + <property name="text"> + <string>Save</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtCachedImageScaler.cpp b/Swift/QtUI/QtCachedImageScaler.cpp index 7307577..45375e7 100644 --- a/Swift/QtUI/QtCachedImageScaler.cpp +++ b/Swift/QtUI/QtCachedImageScaler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -8,6 +8,8 @@ #include <QImage> #include <boost/lexical_cast.hpp> +#include <Swiften/Base/Path.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -15,16 +17,18 @@ QtCachedImageScaler::QtCachedImageScaler() { } boost::filesystem::path QtCachedImageScaler::getScaledImage(const boost::filesystem::path& imagePath, int size) { - boost::filesystem::path scaledImagePath(imagePath.string() + "." + boost::lexical_cast<std::string>(size)); + boost::filesystem::path scaledImagePath(imagePath); + std::string suffix = "." + boost::lexical_cast<std::string>(size); + scaledImagePath = stringToPath(pathToString(scaledImagePath) + suffix); if (!boost::filesystem::exists(scaledImagePath)) { - QImage image(imagePath.string().c_str()); + QImage image(P2QSTRING(pathToString(imagePath))); if (!image.isNull()) { if (image.width() > size || image.height() > size) { QImage scaledImage = image.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - scaledImage.save(QString::fromUtf8(scaledImagePath.string().c_str()), "PNG"); + scaledImage.save(P2QSTRING(pathToString(scaledImagePath)), "PNG"); } else { - image.save(QString::fromUtf8(scaledImagePath.string().c_str()), "PNG"); + image.save(P2QSTRING(pathToString(scaledImagePath)), "PNG"); } } else { diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp index d3a5676..de1ee7c 100644 --- a/Swift/QtUI/QtChatTabs.cpp +++ b/Swift/QtUI/QtChatTabs.cpp @@ -22,8 +22,8 @@ #include <qdebug.h> namespace Swift { -QtChatTabs::QtChatTabs() : QWidget() { -#ifndef Q_WS_MAC +QtChatTabs::QtChatTabs(bool singleWindow) : QWidget(), singleWindow_(singleWindow) { +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-chat-16.png")); #else setAttribute(Qt::WA_ShowWithoutActivating); @@ -46,7 +46,6 @@ QtChatTabs::QtChatTabs() : QWidget() { layout->setContentsMargins(0, 3, 0, 0); layout->addWidget(tabs_); setLayout(layout); - //resize(400, 300); } void QtChatTabs::closeEvent(QCloseEvent* event) { @@ -114,7 +113,13 @@ void QtChatTabs::handleTabClosing() { if (widget && ((index = tabs_->indexOf(widget)) >= 0)) { tabs_->removeTab(index); if (tabs_->count() == 0) { - hide(); + if (!singleWindow_) { + hide(); + } + else { + setWindowTitle(""); + onTitleChanged(""); + } } else { handleTabTitleUpdated(tabs_->currentWidget()); @@ -176,10 +181,9 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { } QString tabText = tabbable->windowTitle().simplified(); - // look for spectrum-generated and other long JID localparts, and // try to abbreviate the resulting long tab texts - QRegExp hasTrailingGarbage("^(.[-\\w\\s&]+)([^\\s\\w].*)$"); + QRegExp hasTrailingGarbage("^(.[-\\w\\s,&]+)([^\\s\\,w].*)$"); if (hasTrailingGarbage.exactMatch(tabText) && hasTrailingGarbage.cap(1).simplified().length() >= 2 && hasTrailingGarbage.cap(2).length() >= 7) { @@ -188,10 +192,8 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { // least a couple of characters. tabText = hasTrailingGarbage.cap(1).simplified(); } - // QTabBar interprets &, so escape that tabText.replace("&", "&&"); - // see which alt[a-z] keys other tabs use bool accelsTaken[26]; int i = 0; @@ -237,7 +239,7 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { switch (tabbable->getWidgetAlertState()) { case QtTabbable::WaitingActivity : tabTextColor = QColor(217, 20, 43); break; case QtTabbable::ImpendingActivity : tabTextColor = QColor(27, 171, 32); break; - default : tabTextColor = QColor(); + case QtTabbable::NoActivity : tabTextColor = QColor(); break; } tabs_->tabBar()->setTabTextColor(index, tabTextColor); @@ -251,7 +253,9 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(tabs_->currentWidget())->windowTitle())); ChatMessageSummarizer summary; - setWindowTitle(summary.getSummary(current, unreads).c_str()); + QString title = summary.getSummary(current, unreads).c_str(); + setWindowTitle(title); + emit onTitleChanged(title); } void QtChatTabs::flash() { @@ -270,7 +274,7 @@ void QtChatTabs::moveEvent(QMoveEvent*) { void QtChatTabs::checkForFirstShow() { if (!isVisible()) { -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC showMinimized(); #else /* https://bugreports.qt-project.org/browse/QTBUG-19194 diff --git a/Swift/QtUI/QtChatTabs.h b/Swift/QtUI/QtChatTabs.h index 233c574..f9cd685 100644 --- a/Swift/QtUI/QtChatTabs.h +++ b/Swift/QtUI/QtChatTabs.h @@ -17,12 +17,13 @@ namespace Swift { class QtChatTabs : public QWidget { Q_OBJECT public: - QtChatTabs(); + QtChatTabs(bool singleWindow); void addTab(QtTabbable* tab); void minimise(); QtTabbable* getCurrentTab(); signals: void geometryChanged(); + void onTitleChanged(const QString& title); protected slots: void closeEvent(QCloseEvent* event); @@ -44,6 +45,7 @@ namespace Swift { private: void checkForFirstShow(); QtTabWidget* tabs_; + bool singleWindow_; }; } diff --git a/Swift/QtUI/QtChatTheme.h b/Swift/QtUI/QtChatTheme.h index c6b02a0..f72a48b 100644 --- a/Swift/QtUI/QtChatTheme.h +++ b/Swift/QtUI/QtChatTheme.h @@ -13,20 +13,20 @@ namespace Swift { class QtChatTheme { public: QtChatTheme(const QString& themePath); - 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 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; diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 81820a3..db4fe51 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -1,487 +1,20 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtChatView.h" - -#include <QtDebug> -#include <QEventLoop> -#include <QFile> -#include <QDesktopServices> -#include <QVBoxLayout> -#include <QWebFrame> -#include <QKeyEvent> -#include <QStackedWidget> -#include <QTimer> -#include <QMessageBox> -#include <QApplication> - -#include <Swiften/Base/Log.h> - -#include "QtWebView.h" -#include "QtChatTheme.h" -#include "QtChatWindow.h" -#include "QtSwiftUtil.h" +#include <Swift/QtUI/QtChatView.h> namespace Swift { -QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QWidget(parent), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll) { - theme_ = theme; - - QVBoxLayout* mainLayout = new QVBoxLayout(this); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0,0,0,0); - webView_ = new QtWebView(this); - connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&))); - 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); - stack->addWidget(webView_); - stack->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); - stack->setLineWidth(2); - mainLayout->addWidget(stack); -#else - mainLayout->addWidget(webView_); -#endif - -#ifdef SWIFT_EXPERIMENTAL_FT - setAcceptDrops(true); -#endif - - webPage_ = new QWebPage(this); - webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); - //webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); - webView_->setPage(webPage_); - connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); - connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&))); - - viewReady_ = false; - isAtBottom_ = true; - resetView(); -} - -void QtChatView::handleClearRequested() { - QMessageBox messageBox(this); - messageBox.setWindowTitle(tr("Clear log")); - messageBox.setText(tr("You are about to clear the contents of your chat log.")); - messageBox.setInformativeText(tr("Are you sure?")); - messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - messageBox.setDefaultButton(QMessageBox::Yes); - int button = messageBox.exec(); - if (button == QMessageBox::Yes) { - logCleared(); - resetView(); - } -} - -void QtChatView::handleKeyPressEvent(QKeyEvent* event) { - webView_->keyPressEvent(event); -} - -void QtChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) { - if (viewReady_) { - addToDOM(snippet); - } else { - /* If this asserts, the previous queuing code was necessary and should be reinstated */ - assert(false); - } -} - -void QtChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) { - // save scrollbar maximum value - if (!topMessageAdded_) { - scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); - } - topMessageAdded_ = true; - - QWebElement continuationElement = firstElement_.findFirst("#insert"); - - bool insert = snippet->getAppendToPrevious(); - bool fallback = continuationElement.isNull(); - - boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet; - QWebElement newElement = snippetToDOM(newSnippet); - - if (insert && !fallback) { - Q_ASSERT(!continuationElement.isNull()); - continuationElement.replace(newElement); - } else { - continuationElement.removeFromDocument(); - topInsertPoint_.prependOutside(newElement); - } - - firstElement_ = newElement; - - if (lastElement_.isNull()) { - lastElement_ = firstElement_; - } - - if (fontSizeSteps_ != 0) { - double size = 1.0 + 0.2 * fontSizeSteps_; - QString sizeString(QString().setNum(size, 'g', 3) + "em"); - const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable"); - foreach (QWebElement span, spans) { - span.setStyleProperty("font-size", sizeString); - } - } -} - -QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) { - QWebElement newElement = newInsertPoint_.clone(); - newElement.setInnerXml(snippet->getContent()); - Q_ASSERT(!newElement.isNull()); - return newElement; -} - -void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) { - rememberScrolledToBottom(); - bool insert = snippet->getAppendToPrevious(); - QWebElement continuationElement = lastElement_.findFirst("#insert"); - bool fallback = insert && continuationElement.isNull(); - boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet; - QWebElement newElement = snippetToDOM(newSnippet); - if (insert && !fallback) { - Q_ASSERT(!continuationElement.isNull()); - continuationElement.replace(newElement); - } else { - continuationElement.removeFromDocument(); - newInsertPoint_.prependOutside(newElement); - } - lastElement_ = newElement; - if (fontSizeSteps_ != 0) { - double size = 1.0 + 0.2 * fontSizeSteps_; - QString sizeString(QString().setNum(size, 'g', 3) + "em"); - const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable"); - 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_); - rememberScrolledToBottom(); - assert(!lastElement_.isNull()); - QWebElement replace = lastElement_.findFirst("span.swift_message"); - assert(!replace.isNull()); - QString old = lastElement_.toOuterXml(); - replace.setInnerXml(ChatSnippet::escape(newMessage)); -} - -void QtChatView::replaceLastMessage(const QString& newMessage, const QString& note) { - rememberScrolledToBottom(); - replaceLastMessage(newMessage); - QWebElement replace = lastElement_.findFirst("span.swift_time"); - assert(!replace.isNull()); - 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_inner_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)))); - } - else { - qWarning() << "Trying to replace element with id " << id << " but it's not there."; - } -} - -void QtChatView::showEmoticons(bool show) { - { - const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image"); - foreach (QWebElement span, spans) { - span.setStyleProperty("display", show ? "inline" : "none"); - } - } - { - const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text"); - foreach (QWebElement span, spans) { - span.setStyleProperty("display", show ? "none" : "inline"); - } - } -} - -void QtChatView::copySelectionToClipboard() { - if (!webPage_->selectedText().isEmpty()) { - webPage_->triggerAction(QWebPage::Copy); - } -} - -void QtChatView::setAckXML(const QString& id, const QString& xml) { - QWebElement message = document_.findFirst("#" + id); - /* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */ - if (message.isNull()) return; - QWebElement ackElement = message.findFirst("span.swift_ack"); - assert(!ackElement.isNull()); - ackElement.setInnerXml(xml); -} - -void QtChatView::setReceiptXML(const QString& id, const QString& xml) { - QWebElement message = document_.findFirst("#" + id); - if (message.isNull()) return; - QWebElement receiptElement = message.findFirst("span.swift_receipt"); - assert(!receiptElement.isNull()); - receiptElement.setInnerXml(xml); -} - -void QtChatView::displayReceiptInfo(const QString& id, bool showIt) { - QWebElement message = document_.findFirst("#" + id); - if (message.isNull()) return; - QWebElement receiptElement = message.findFirst("span.swift_receipt"); - assert(!receiptElement.isNull()); - receiptElement.setStyleProperty("display", showIt ? "inline" : "none"); -} - -void QtChatView::rememberScrolledToBottom() { - isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1); -} - -void QtChatView::scrollToBottom() { - isAtBottom_ = true; - webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)); - webView_->update(); /* Work around redraw bug in some versions of Qt. */ -} - -void QtChatView::handleFrameSizeChanged() { - if (topMessageAdded_) { - // adjust new scrollbar position - int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); - webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_); - topMessageAdded_ = false; - } +QtChatView::QtChatView(QWidget* parent) : QWidget(parent) { - if (isAtBottom_ && !disableAutoScroll_) { - scrollToBottom(); - } } -void QtChatView::handleLinkClicked(const QUrl& url) { - QDesktopServices::openUrl(url); -} - -void QtChatView::handleViewLoadFinished(bool ok) { - Q_ASSERT(ok); - viewReady_ = true; -} - -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.swift_resizable"); - foreach (QWebElement span, spans) { - span.setStyleProperty("font-size", sizeString); - } - webView_->setFontSizeIsMinimal(size == 1.0); -} - -void QtChatView::resetView() { - lastElement_ = QWebElement(); - firstElement_ = lastElement_; - topMessageAdded_ = false; - scrollBarMaximum_ = 0; - QString pageHTML = theme_->getTemplate(); - pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3"); - pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase()); - if (pageHTML.count("%@") > 3) { - pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getMainCSS()); - } - pageHTML.replace(pageHTML.indexOf("%@"), 2, "Variants/Blue on Green.css"); - pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*headerSnippet.getContent()*/); - pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/); - QEventLoop syncLoop; - connect(webView_, SIGNAL(loadFinished(bool)), &syncLoop, SLOT(quit())); - webPage_->mainFrame()->setHtml(pageHTML); - while (!viewReady_) { - QTimer t; - t.setSingleShot(true); - connect(&t, SIGNAL(timeout()), &syncLoop, SLOT(quit())); - t.start(50); - syncLoop.exec(); - } - document_ = webPage_->mainFrame()->documentElement(); - - resetTopInsertPoint(); - QWebElement chatElement = document_.findFirst("#Chat"); - newInsertPoint_ = chatElement.clone(); - newInsertPoint_.setOuterXml("<div id='swift_insert'/>"); - chatElement.appendInside(newInsertPoint_); - Q_ASSERT(!newInsertPoint_.isNull()); - - scrollToBottom(); - - connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); -} - -QWebElement findElementWithID(QWebElement document, QString elementName, QString id) { - QWebElementCollection elements = document.findAll(elementName); - foreach(QWebElement element, elements) { - if (element.attribute("id") == id) { - return element; - } - } - return QWebElement(); -} - -void QtChatView::setFileTransferProgress(QString id, const int percentageDone) { - QWebElement ftElement = findElementWithID(document_, "div", 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 = findElementWithID(document_, "div", 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 = tr("Waiting for other side to accept the transfer.") + "<br/>" + - QtChatWindow::buildChatWindowButton(tr("Cancel"), QtChatWindow::ButtonFileTransferCancel, id); - } - if (state == ChatWindow::Negotiating) { - // replace with text "Negotiaging" + Cancel button - newInnerHTML = tr("Negotiating...") + "<br/>" + - QtChatWindow::buildChatWindowButton(tr("Cancel"), QtChatWindow::ButtonFileTransferCancel, 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>" + - QtChatWindow::buildChatWindowButton(tr("Cancel"), QtChatWindow::ButtonFileTransferCancel, id); - } - else if (state == ChatWindow::Canceled) { - newInnerHTML = tr("Transfer has been canceled!"); - } - else if (state == ChatWindow::Finished) { - // text "Successful transfer" - newInnerHTML = tr("Transfer completed successfully."); - } - else if (state == ChatWindow::FTFailed) { - newInnerHTML = tr("Transfer failed."); - } - - ftElement.setInnerXml(newInnerHTML); -} - -void QtChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) { - QWebElement divElement = findElementWithID(document_, "div", id); - QString newInnerHTML; - if (state == ChatWindow::WhiteboardAccepted) { - newInnerHTML = tr("Started whiteboard chat") + "<br/>" + - QtChatWindow::buildChatWindowButton(tr("Show whiteboard"), QtChatWindow::ButtonWhiteboardShowWindow, id); - } else if (state == ChatWindow::WhiteboardTerminated) { - newInnerHTML = tr("Whiteboard chat has been canceled"); - } else if (state == ChatWindow::WhiteboardRejected) { - newInnerHTML = tr("Whiteboard chat request has been rejected"); - } - divElement.setInnerXml(newInnerHTML); -} - -void QtChatView::setMUCInvitationJoined(QString id) { - QWebElement divElement = findElementWithID(document_, "div", id); - QWebElement buttonElement = findElementWithID(divElement, "input", "mucinvite"); - if (!buttonElement.isNull()) { - buttonElement.setAttribute("value", tr("Return to room")); - } -} - -void QtChatView::handleScrollRequested(int, int dy, const QRect&) { - rememberScrolledToBottom(); - - int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy; - emit scrollRequested(pos); - - if (pos == 0) { - emit scrollReachedTop(); - } - else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) { - emit scrollReachedBottom(); - } -} - -int QtChatView::getSnippetPositionByDate(const QDate& date) { - QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate)); - - return message.geometry().top(); -} - -void QtChatView::resetTopInsertPoint() { - QWebElement continuationElement = firstElement_.findFirst("#insert"); - continuationElement.removeFromDocument(); - firstElement_ = QWebElement(); - - topInsertPoint_.removeFromDocument(); - QWebElement chatElement = document_.findFirst("#Chat"); - topInsertPoint_ = chatElement.clone(); - topInsertPoint_.setOuterXml("<div id='swift_insert'/>"); - chatElement.prependInside(topInsertPoint_); +QtChatView::~QtChatView() { + } } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index 9080808..c8519b7 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -1,101 +1,62 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFT_QtChatView_H -#define SWIFT_QtChatView_H - -#include <QString> -#include <QWidget> -#include <QList> -#include <QWebElement> +#pragma once +#include <string> #include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> -#include "ChatSnippet.h" +#include <QWidget> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> -class QWebPage; -class QUrl; -class QDate; - namespace Swift { - class QtWebView; - class QtChatTheme; + class HighlightAction; + class SecurityLabel; + class QtChatView : public QWidget { - Q_OBJECT + Q_OBJECT public: - QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false); - void addMessageTop(boost::shared_ptr<ChatSnippet> snippet); - void addMessageBottom(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); - void setReceiptXML(const QString& id, const QString& xml); - void displayReceiptInfo(const QString& id, bool showIt); - - 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); - void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state); - void setMUCInvitationJoined(QString id); - void showEmoticons(bool show); - int getSnippetPositionByDate(const QDate& date); - - signals: - void gotFocus(); - void fontResized(int); - void logCleared(); - void scrollRequested(int pos); - void scrollReachedTop(); - void scrollReachedBottom(); + QtChatView(QWidget* parent); + virtual ~QtChatView(); + + /** Add message to window. + * @return id of added message (for acks). + */ + virtual std::string addMessage(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + /** Adds action to window. + * @return id of added message (for acks); + */ + virtual std::string addAction(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + + virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; + virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; + + virtual void addErrorMessage(const ChatWindow::ChatMessage& message) = 0; + virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; + virtual void replaceLastMessage(const ChatWindow::ChatMessage& message) = 0; + virtual void setAckState(const std::string& id, ChatWindow::AckState state) = 0; + + 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 ChatWindow::FileTransferState state, const std::string& msg = "") = 0; + virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) = 0; + virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) = 0; + virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0; + virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0; + + virtual void showEmoticons(bool show) = 0; + virtual void addLastSeenLine() = 0; public slots: - void copySelectionToClipboard(); - void scrollToBottom(); - void handleLinkClicked(const QUrl&); - void handleKeyPressEvent(QKeyEvent* event); - void resetView(); - void resetTopInsertPoint(); - void increaseFontSize(int numSteps = 1); - void decreaseFontSize(); - void resizeFont(int fontSizeSteps); - - private slots: - void handleViewLoadFinished(bool); - void handleFrameSizeChanged(); - void handleClearRequested(); - void handleScrollRequested(int dx, int dy, const QRect& rectToScroll); + virtual void resizeFont(int fontSizeSteps) = 0; + virtual void scrollToBottom() = 0; + virtual void handleKeyPressEvent(QKeyEvent* event) = 0; - private: - void headerEncode(); - void messageEncode(); - void addToDOM(boost::shared_ptr<ChatSnippet> snippet); - QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); - - bool viewReady_; - bool isAtBottom_; - bool topMessageAdded_; - int scrollBarMaximum_; - QtWebView* webView_; - QWebPage* webPage_; - int fontSizeSteps_; - QtChatTheme* theme_; - QWebElement newInsertPoint_; - QWebElement topInsertPoint_; - QWebElement lineSeparator_; - QWebElement lastElement_; - QWebElement firstElement_; - QWebElement document_; - bool disableAutoScroll_; }; } - -#endif diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 28549f8..49f57c9 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -1,73 +1,62 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtChatWindow.h" -#include "Swift/Controllers/Roster/Roster.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 "QtInviteToChatWindow.h" -#include <Swift/QtUI/QtUISettingConstants.h> - -#include <Swiften/StringCodecs/Base64.h> -#include "SwifTools/TabComplete.h" -#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> -#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> -#include "QtChatWindowJSBridge.h" +#include <Swift/QtUI/QtChatWindow.h> #include <boost/cstdint.hpp> -#include <boost/format.hpp> #include <boost/lexical_cast.hpp> +#include <boost/smart_ptr/make_shared.hpp> -#include <QLabel> #include <qdebug.h> -#include <QMessageBox> -#include <QInputDialog> #include <QApplication> #include <QBoxLayout> #include <QCloseEvent> #include <QComboBox> +#include <QFileDialog> #include <QFileInfo> +#include <QInputDialog> +#include <QLabel> #include <QLineEdit> +#include <QMenu> +#include <QMessageBox> +#include <QMimeData> +#include <QPushButton> #include <QSplitter> #include <QString> +#include <QTextDocument> #include <QTextEdit> #include <QTime> +#include <QToolButton> #include <QUrl> -#include <QPushButton> -#include <QFileDialog> -#include <QMenu> -#include <QTextDocument> -#include <Swift/Controllers/Settings/SettingsProvider.h> + #include <Swiften/Base/Log.h> -namespace Swift { +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/RosterItem.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> -const QString QtChatWindow::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel"); -const QString QtChatWindow::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest"); -const QString QtChatWindow::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow"); -const QString QtChatWindow::ButtonFileTransferCancel = QString("filetransfer-cancel"); -const QString QtChatWindow::ButtonFileTransferSetDescription = QString("filetransfer-setdescription"); -const QString QtChatWindow::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest"); -const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest"); -const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite"); +#include <SwifTools/TabComplete.h> +#include <Swift/QtUI/Roster/QtOccupantListWidget.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtTextEdit.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/QtWebKitChatView.h> -QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons) { +namespace Swift { + +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) { settings_ = settings; unreadCount_ = 0; - idCounter_ = 0; inputEnabled_ = true; completer_ = NULL; affiliationEditor_ = NULL; @@ -75,7 +64,6 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt isCorrection_ = false; labelModel_ = NULL; correctionEnabled_ = Maybe; - showEmoticons_ = true; updateTitleWithUnreadCount(); #ifdef SWIFT_EXPERIMENTAL_FT @@ -103,26 +91,23 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt alertLabel_->setStyleSheet(alertStyleSheet_); alertWidget_->hide(); - QBoxLayout* subjectLayout = new QBoxLayout(QBoxLayout::LeftToRight); + subjectLayout_ = new QBoxLayout(QBoxLayout::LeftToRight); subject_ = new QLineEdit(this); - subjectLayout->addWidget(subject_); + subjectLayout_->addWidget(subject_); setSubject(""); subject_->setReadOnly(true); - actionButton_ = new QPushButton(this); + QPushButton* actionButton_ = new QPushButton(this); actionButton_->setIcon(QIcon(":/icons/actions.png")); connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked())); - subjectLayout->addWidget(actionButton_); - subject_->hide(); - actionButton_->hide(); - layout->addLayout(subjectLayout); + layout->addLayout(subjectLayout_); logRosterSplitter_ = new QSplitter(this); logRosterSplitter_->setAutoFillBackground(true); layout->addWidget(logRosterSplitter_); - messageLog_ = new QtChatView(theme, this); + messageLog_ = new QtWebKitChatView(this, eventStream_, theme, this); // I accept that passing the ChatWindow in so that the view can call the signals is somewhat inelegant, but it saves a lot of boilerplate. This patch is unpleasant enough already. So let's fix this soon (it at least needs fixing by the time history is sorted), but not now. logRosterSplitter_->addWidget(messageLog_); treeWidget_ = new QtOccupantListWidget(eventStream_, settings_, this); @@ -150,13 +135,19 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt QHBoxLayout* inputBarLayout = new QHBoxLayout(); inputBarLayout->setContentsMargins(0,0,0,0); inputBarLayout->setSpacing(2); - input_ = new QtTextEdit(this); + input_ = new QtTextEdit(settings_, this); input_->setAcceptRichText(false); inputBarLayout->addWidget(midBar_); inputBarLayout->addWidget(input_); correctingLabel_ = new QLabel(tr("Correcting"), this); inputBarLayout->addWidget(correctingLabel_); correctingLabel_->hide(); + + // using an extra layout to work around Qt margin glitches on OS X + QHBoxLayout* actionLayout = new QHBoxLayout(); + actionLayout->addWidget(actionButton_); + + inputBarLayout->addLayout(actionLayout); layout->addLayout(inputBarLayout); inputClearing_ = false; @@ -180,17 +171,12 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1)); treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2)); - jsBridge = new QtChatWindowJSBridge(); - messageLog_->addToJSEnvironment("chatwindow", jsBridge); - connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString))); - settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1)); - showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); + messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); } QtChatWindow::~QtChatWindow() { - delete jsBridge; if (mucConfigurationWindow_) { delete mucConfigurationWindow_.data(); } @@ -198,8 +184,8 @@ QtChatWindow::~QtChatWindow() { void QtChatWindow::handleSettingChanged(const std::string& setting) { if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { - showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); - messageLog_->showEmoticons(showEmoticons_); + bool showEmoticons = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); + messageLog_->showEmoticons(showEmoticons); } } @@ -211,10 +197,6 @@ void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); } -bool QtChatWindow::appendToPreviousCheck(QtChatWindow::PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const { - return previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName))); -} - void QtChatWindow::handleFontResized(int fontSizeSteps) { messageLog_->resizeFont(fontSizeSteps); } @@ -244,7 +226,6 @@ void QtChatWindow::setTabComplete(TabComplete* completer) { void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) { event->ignore(); - QtTabbable::handleKeyPressEvent(event); if (event->isAccepted()) { return; } @@ -409,11 +390,11 @@ void QtChatWindow::closeEvent(QCloseEvent* event) { onClosed(); } -void QtChatWindow::convertToMUC() { - setAcceptDrops(false); +void QtChatWindow::convertToMUC(bool impromptuMUC) { + impromptu_ = impromptuMUC; + isMUC_ = true; treeWidget_->show(); - subject_->show(); - actionButton_->show(); + subject_->setVisible(!impromptu_); } void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { @@ -481,333 +462,16 @@ void QtChatWindow::updateTitleWithUnreadCount() { emit titleUpdated(); } -std::string QtChatWindow::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& time) { - return addMessage(linkimoticonify(P2QSTRING(message)), senderName, senderIsSelf, label, avatarPath, "", time); -} -QString QtChatWindow::linkimoticonify(const QString& message) const { - QString messageHTML(message); - messageHTML = Qt::escape(messageHTML); - QMapIterator<QString, QString> it(emoticons_); - QString textStyle = showEmoticons_ ? "style='display:none'" : ""; - QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; - if (messageHTML.length() < 500) { - while (it.hasNext()) { - it.next(); - messageHTML.replace(it.key(), "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + it.value() + "'/></span><span class='swift_emoticon_text' " + textStyle + ">"+it.key() + "</span>"); - } - messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); - } - messageHTML.replace("\n","<br/>"); - return messageHTML; -} - -std::string QtChatWindow::addMessage(const QString &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) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); - - QString htmlString; - if (label) { - htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(Qt::escape(P2QSTRING(label->getForegroundColor()))).arg(Qt::escape(P2QSTRING(label->getBackgroundColor()))); - htmlString += QString("%1</span> ").arg(Qt::escape(P2QSTRING(label->getDisplayMarking()))); - } - QString messageHTML(message); - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - htmlString += "<span class='swift_inner_message'>" + styleSpanStart + messageHTML + styleSpanEnd + "</span>" ; - - bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf); - 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" + boost::lexical_cast<std::string>(idCounter_++); - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); - - previousMessageWasSelf_ = senderIsSelf; - previousSenderName_ = P2QSTRING(senderName); - previousMessageKind_ = PreviousMessageWasMessage; - return id; -} void QtChatWindow::flash() { emit requestFlash(); } -void QtChatWindow::setAckState(std::string const& id, ChatWindow::AckState state) { - QString xml; - switch (state) { - case ChatWindow::Pending: - xml = "<img src='qrc:/icons/throbber.gif' title='" + tr("This message has not been received by your server yet.") + "'/>"; - messageLog_->displayReceiptInfo(P2QSTRING(id), false); - break; - case ChatWindow::Received: - xml = ""; - messageLog_->displayReceiptInfo(P2QSTRING(id), true); - break; - case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' title='" + tr("This message may not have been transmitted.") + "'/>"; break; - } - messageLog_->setAckXML(P2QSTRING(id), xml); -} - -void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { - QString xml; - switch (state) { - case ChatWindow::ReceiptReceived: - xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>"; - break; - case ChatWindow::ReceiptRequested: - xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>"; - break; - } - messageLog_->setReceiptXML(P2QSTRING(id), xml); -} - int QtChatWindow::getCount() { return unreadCount_; } -std::string QtChatWindow::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { - return addMessage(" *" + linkimoticonify(P2QSTRING(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] : "") ); -} - -QString encodeButtonArgument(const QString& str) { - return Qt::escape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str))))); -} - -QString decodeButtonArgument(const QString& str) { - return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str)))); -} - -QString QtChatWindow::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3) { - QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+"); - Q_ASSERT(regex.exactMatch(id)); - QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)); - return html; -} - -std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { - SWIFT_LOG(debug) << "addFileTransfer" << std::endl; - QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); - - QString htmlString; - QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes)); - if (senderIsSelf) { - // outgoing - htmlString = tr("Send file") + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" + - "<div id='" + ft_id + "'>" + - buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) + - buildChatWindowButton(tr("Set Description"), ButtonFileTransferSetDescription, ft_id) + - buildChatWindowButton(tr("Send"), ButtonFileTransferSendRequest, ft_id) + - "</div>"; - } else { - // incoming - htmlString = tr("Receiving file") + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" + - "<div id='" + ft_id + "'>" + - buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) + - buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) + - "</div>"; - } - - //addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time()); - - bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf); - 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 = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++); - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); - - previousMessageWasSelf_ = senderIsSelf; - previousSenderName_ = P2QSTRING(senderName); - previousMessageKind_ = PreviousMessageWasFileTransfer; - return Q2PSTRING(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)); -} - -std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) { - QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); - QString htmlString; - if (senderIsSelf) { - htmlString = "<div id='" + wb_id + "'>" + tr("Starting whiteboard chat") + "<br />"+ - buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) + - "</div>"; - } else { - htmlString = "<div id='" + wb_id + "'>" + tr("%1 would like to start a whiteboard chat").arg(Qt::escape(contact_)) + ": <br/>" + - buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) + - buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) + - "</div>"; - } - - 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 = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++); - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(contact_), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id)))); - - previousMessageWasSelf_ = false; - previousSenderName_ = contact_; - return Q2PSTRING(wb_id); -} - -void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) { - messageLog_->setWhiteboardSessionStatus(QString::fromStdString(id), state); -} - -void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3) { - QString arg1 = decodeButtonArgument(encodedArgument1); - QString arg2 = decodeButtonArgument(encodedArgument2); - QString arg3 = decodeButtonArgument(encodedArgument3); - - if (id.startsWith(ButtonFileTransferCancel)) { - QString ft_id = arg1; - onFileTransferCancel(Q2PSTRING(ft_id)); - } - else if (id.startsWith(ButtonFileTransferSetDescription)) { - QString ft_id = arg1; - bool ok = false; - QString text = QInputDialog::getText(this, tr("File transfer description"), - tr("Description:"), QLineEdit::Normal, "", &ok); - if (ok) { - descriptions[ft_id] = text; - } - } - else if (id.startsWith(ButtonFileTransferSendRequest)) { - QString ft_id = arg1; - QString text = descriptions.find(ft_id) == descriptions.end() ? QString() : descriptions[ft_id]; - onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text)); - } - else if (id.startsWith(ButtonFileTransferAcceptRequest)) { - QString ft_id = arg1; - QString filename = arg2; - - QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename); - if (!path.isEmpty()) { - onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path)); - } - } - else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) { - QString id = arg1; - messageLog_->setWhiteboardSessionStatus(QString::fromStdString(Q2PSTRING(id)), ChatWindow::WhiteboardAccepted); - onWhiteboardSessionAccept(); - } - else if (id.startsWith(ButtonWhiteboardSessionCancel)) { - QString id = arg1; - messageLog_->setWhiteboardSessionStatus(QString::fromStdString(Q2PSTRING(id)), ChatWindow::WhiteboardTerminated); - onWhiteboardSessionCancel(); - } - else if (id.startsWith(ButtonWhiteboardShowWindow)) { - QString id = arg1; - onWhiteboardWindowShow(); - } - else if (id.startsWith(ButtonMUCInvite)) { - QString roomJID = arg1; - QString password = arg2; - QString elementID = arg3; - - eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password))); - messageLog_->setMUCInvitationJoined(elementID); - } - else { - SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl; - } -} - -void QtChatWindow::addErrorMessage(const std::string& errorMessage) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - - QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage))); - errorMessageHTML.replace("\n","<br/>"); - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_))); - - previousMessageWasSelf_ = false; - previousMessageKind_ = PreviousMessageWasSystem; -} - -void QtChatWindow::addSystemMessage(const std::string& message) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - - QString messageHTML(P2QSTRING(message)); - messageHTML = linkimoticonify(messageHTML); - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); - - previousMessageKind_ = PreviousMessageWasSystem; -} - -void QtChatWindow::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { - replaceMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", id, time, "font-style:italic "); -} - -void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { - replaceMessage(linkimoticonify(P2QSTRING(message)), id, time, ""); -} - -void QtChatWindow::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style) { - if (!id.empty()) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - - QString messageHTML(message); - - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - messageHTML = styleSpanStart + messageHTML + styleSpanEnd; - - messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time)); - } - else { - std::cerr << "Trying to replace a message with no id"; - } -} - -void QtChatWindow::addPresenceMessage(const std::string& message) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - - QString messageHTML(P2QSTRING(message)); - messageHTML = linkimoticonify(messageHTML); - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); - - previousMessageKind_ = PreviousMessageWasPresence; -} - void QtChatWindow::returnPressed() { if (!inputEnabled_) { @@ -866,21 +530,35 @@ void QtChatWindow::moveEvent(QMoveEvent*) { void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { // TODO: check whether contact actually supports file transfer - event->acceptProposedAction(); + if (!isMUC_) { + event->acceptProposedAction(); + } + } else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid")) { + if (isMUC_ || supportsImpromptuChat_) { + 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."); + if (event->mimeData()->hasUrls()) { + if (event->mimeData()->urls().size() == 1) { + onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile())); + } else { + std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time."))); + ChatMessage message; + message.append(boost::make_shared<ChatTextMessagePart>(messageText)); + addSystemMessage(message, DefaultDirection); + } + } else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid")) { + QByteArray dataBytes = event->mimeData()->data("application/vnd.swift.contact-jid"); + QDataStream dataStream(&dataBytes, QIODevice::ReadOnly); + QString jidString; + dataStream >> jidString; + onInviteToChat(std::vector<JID>(1, JID(Q2PSTRING(jidString)))); } } -void QtChatWindow::replaceLastMessage(const std::string& message) { - messageLog_->replaceLastMessage(linkimoticonify(P2QSTRING(message))); -} void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) { treeWidget_->setAvailableOccupantActions(actions); @@ -901,15 +579,40 @@ void QtChatWindow::handleActionButtonClicked() { QAction* destroy = NULL; QAction* invite = NULL; - foreach(ChatWindow::RoomAction availableAction, availableRoomActions_) - { - switch(availableAction) + QAction* block = NULL; + QAction* unblock = NULL; + + if (availableRoomActions_.empty()) { + if (blockingState_ == IsBlocked) { + unblock = contextMenu.addAction(tr("Unblock")); + } else if (blockingState_ == IsUnblocked) { + block = contextMenu.addAction(tr("Block")); + } + + if (supportsImpromptuChat_) { + invite = contextMenu.addAction(tr("Invite person to this chat…")); + } + + } else { + foreach(ChatWindow::RoomAction availableAction, availableRoomActions_) { - case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break; - case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break; - case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break; - case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break; - case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break; + if (impromptu_) { + // hide options we don't need in impromptu chats + if (availableAction == ChatWindow::ChangeSubject || + availableAction == ChatWindow::Configure || + availableAction == ChatWindow::Affiliations || + availableAction == ChatWindow::Destroy) { + continue; + } + } + switch(availableAction) + { + case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break; + case ChatWindow::Configure: configure = contextMenu.addAction(tr("Configure room…")); break; + case ChatWindow::Affiliations: affiliations = contextMenu.addAction(tr("Edit affiliations…")); break; + case ChatWindow::Destroy: destroy = contextMenu.addAction(tr("Destroy room")); break; + case ChatWindow::Invite: invite = contextMenu.addAction(tr("Invite person to this room…")); break; + } } } @@ -947,7 +650,13 @@ void QtChatWindow::handleActionButtonClicked() { } } else if (result == invite) { - onInvitePersonToThisMUCRequest(); + onInviteToChat(std::vector<JID>()); + } + else if (result == block) { + onBlockUserRequest(); + } + else if (result == unblock) { + onUnblockUserRequest(); } } @@ -960,11 +669,18 @@ void QtChatWindow::setAffiliations(MUCOccupant::Affiliation affiliation, const s affiliationEditor_->setAffiliations(affiliation, jids); } -void QtChatWindow::setAvailableRoomActions(const std::vector<RoomAction> &actions) -{ +void QtChatWindow::setAvailableRoomActions(const std::vector<RoomAction>& actions) { availableRoomActions_ = actions; } +void QtChatWindow::setBlockingState(BlockingState state) { + blockingState_ = state; +} + +void QtChatWindow::setCanInitiateImpromptuChats(bool supportsImpromptu) { + supportsImpromptuChat_ = supportsImpromptu; +} + void QtChatWindow::showRoomConfigurationForm(Form::ref form) { if (mucConfigurationWindow_) { delete mucConfigurationWindow_.data(); @@ -974,44 +690,91 @@ void QtChatWindow::showRoomConfigurationForm(Form::ref form) { mucConfigurationWindow_->onFormCancelled.connect(boost::bind(boost::ref(onConfigurationFormCancelled))); } -void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct) { +void QtChatWindow::handleAppendedToLog() { + if (lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + messageLog_->addLastSeenLine(); + } if (isWidgetSelected()) { onAllMessagesRead(); } +} - QString htmlString = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + " <br/>"; - if (!reason.empty()) { - htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "<br/>"; - } - if (!direct) { - htmlString += QObject::tr("This person may not have really sent this invitation!") + "<br/>"; - } +void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) { + handleAppendedToLog(); + messageLog_->addMUCInvitation(senderName, jid, reason, password, direct, isImpromptu, isContinuation); +} - QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); +std::string QtChatWindow::addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + handleAppendedToLog(); + return messageLog_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time, highlight); +} - htmlString += "<div id='" + id + "'>" + - buildChatWindowButton(tr("Accept Invite"), ButtonMUCInvite, Qt::escape(P2QSTRING(jid.toString())), Qt::escape(P2QSTRING(password)), id) + - "</div>"; +std::string QtChatWindow::addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + handleAppendedToLog(); + return messageLog_->addAction(message, senderName, senderIsSelf, label, avatarPath, time, highlight); +} - bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false); - 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"; - messageLog_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id))); - previousMessageWasSelf_ = false; - previousSenderName_ = P2QSTRING(senderName); - previousMessageKind_ = PreviousMessageWasMUCInvite; +void QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) { + handleAppendedToLog(); + messageLog_->addSystemMessage(message, direction); +} + +void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) { + handleAppendedToLog(); + messageLog_->addPresenceMessage(message, direction); +} + +void QtChatWindow::addErrorMessage(const ChatMessage& message) { + handleAppendedToLog(); + messageLog_->addErrorMessage(message); +} + + +void QtChatWindow::replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + handleAppendedToLog(); + messageLog_->replaceMessage(message, id, time, highlight); +} + +void QtChatWindow::replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + handleAppendedToLog(); + messageLog_->replaceWithAction(message, id, time, highlight); } +std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { + handleAppendedToLog(); + return messageLog_->addFileTransfer(senderName, senderIsSelf, filename, sizeInBytes); +} -InviteToChatWindow* QtChatWindow::createInviteToChatWindow() { - return new QtInviteToChatWindow(this); +void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) { + messageLog_->setFileTransferProgress(id, percentageDone); +} + +void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) { + messageLog_->setFileTransferStatus(id, state, msg); } +std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) { + handleAppendedToLog(); + return messageLog_->addWhiteboardRequest(contact_, senderIsSelf); +} + +void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) { + messageLog_->setWhiteboardSessionStatus(id, state); +} + +void QtChatWindow::replaceLastMessage(const ChatMessage& message) { + messageLog_->replaceLastMessage(message); +} + +void QtChatWindow::setAckState(const std::string& id, AckState state) { + messageLog_->setAckState(id, state); +} + +void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { + messageLog_->setMessageReceiptState(id, state); +} + } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index 3416b42..ca0ecad 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -1,24 +1,28 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once +#include <map> + +#include <QPointer> +#include <QTextCursor> +#include <QMap> + +#include <SwifTools/LastLineTracker.h> + #include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/QtUI/QtMUCConfigurationWindow.h> + +#include <Swift/QtUI/ChatSnippet.h> #include <Swift/QtUI/QtAffiliationEditor.h> +#include <Swift/QtUI/QtMUCConfigurationWindow.h> #include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTabbable.h> -#include <QtTabbable.h> -#include <SwifTools/LastLineTracker.h> - -#include <map> -#include <QPointer> -#include <QTextCursor> -#include <QMap> class QTextEdit; class QLineEdit; @@ -37,6 +41,9 @@ namespace Swift { class QtChatWindowJSBridge; class SettingsProvider; + // FIXME: Move this to a different file + std::string formatSize(const boost::uintmax_t bytes); + class LabelModel : public QAbstractListModel { Q_OBJECT public: @@ -73,25 +80,17 @@ namespace Swift { Q_OBJECT public: - static const QString ButtonWhiteboardSessionCancel; - static const QString ButtonWhiteboardSessionAcceptRequest; - static const QString ButtonWhiteboardShowWindow; - static const QString ButtonFileTransferCancel; - static const QString ButtonFileTransferSetDescription; - static const QString ButtonFileTransferSendRequest; - static const QString ButtonFileTransferAcceptRequest; - static const QString ButtonMUCInvite; - - public: - QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons); + QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings); ~QtChatWindow(); - 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& time); - std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); - 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); - void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + std::string addMessage(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); + std::string addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); + + void addSystemMessage(const ChatMessage& message, Direction direction); + void addPresenceMessage(const ChatMessage& message, Direction direction); + void addErrorMessage(const ChatMessage& message); + + void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); + void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); // 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); @@ -103,7 +102,7 @@ namespace Swift { void show(); void activate(); void setUnreadMessageCount(int count); - void convertToMUC(); + void convertToMUC(bool impromptuMUC = false); // TreeWidget *getTreeWidget(); void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels); void setSecurityLabelsEnabled(bool enabled); @@ -116,7 +115,7 @@ namespace Swift { void setRosterModel(Roster* roster); void setTabComplete(TabComplete* completer); int getCount(); - void replaceLastMessage(const std::string& message); + void replaceLastMessage(const ChatMessage& message); void setAckState(const std::string& id, AckState state); // message receipts @@ -127,13 +126,11 @@ namespace Swift { virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions); void setSubject(const std::string& subject); void showRoomConfigurationForm(Form::ref); - void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true); + void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false); void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&); - void setAvailableRoomActions(const std::vector<RoomAction> &actions); - - InviteToChatWindow* createInviteToChatWindow(); - - static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString()); + void setAvailableRoomActions(const std::vector<RoomAction>& actions); + void setBlockingState(BlockingState state); + virtual void setCanInitiateImpromptuChats(bool supportsImpromptu); public slots: void handleChangeSplitterState(QByteArray state); @@ -168,32 +165,19 @@ namespace Swift { void handleSplitterMoved(int pos, int index); void handleAlertButtonClicked(); void handleActionButtonClicked(); - - void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3); void handleAffiliationEditorAccepted(); void handleCurrentLabelChanged(int); private: - enum PreviousMessageKind { - PreviosuMessageWasNone, - PreviousMessageWasMessage, - PreviousMessageWasSystem, - PreviousMessageWasPresence, - PreviousMessageWasFileTransfer, - PreviousMessageWasMUCInvite - }; - - private: void updateTitleWithUnreadCount(); void tabComplete(); void beginCorrection(); void cancelCorrection(); void handleSettingChanged(const std::string& setting); - std::string addMessage(const QString& 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 replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style); + void handleOccupantSelectionChanged(RosterItem* item); - bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const; - QString linkimoticonify(const QString& message) const; + void handleAppendedToLog(); + int unreadCount_; bool contactIsTyping_; @@ -205,6 +189,7 @@ namespace Swift { QtChatTheme* theme_; QtTextEdit* input_; QWidget* midBar_; + QBoxLayout* subjectLayout_; QComboBox* labelsWidget_; QtOccupantListWidget* treeWidget_; QLabel* correctingLabel_; @@ -213,11 +198,7 @@ namespace Swift { QPushButton* alertButton_; TabComplete* completer_; QLineEdit* subject_; - QPushButton* actionButton_; bool isCorrection_; - bool previousMessageWasSelf_; - PreviousMessageKind previousMessageKind_; - QString previousSenderName_; bool inputClearing_; bool tabCompletion_; UIEventStream* eventStream_; @@ -225,16 +206,15 @@ namespace Swift { QSplitter *logRosterSplitter_; Tristate correctionEnabled_; QString alertStyleSheet_; - std::map<QString, QString> descriptions; - QtChatWindowJSBridge* jsBridge; QPointer<QtMUCConfigurationWindow> mucConfigurationWindow_; QPointer<QtAffiliationEditor> affiliationEditor_; - int idCounter_; SettingsProvider* settings_; std::vector<ChatWindow::RoomAction> availableRoomActions_; - QMap<QString, QString> emoticons_; - bool showEmoticons_; QPalette defaultLabelsPalette_; LabelModel* labelModel_; + BlockingState blockingState_; + bool impromptu_; + bool isMUC_; + bool supportsImpromptuChat_; }; } diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp index 5f91ff8..78c04c9 100644 --- a/Swift/QtUI/QtChatWindowFactory.cpp +++ b/Swift/QtUI/QtChatWindowFactory.cpp @@ -4,23 +4,24 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtChatWindowFactory.h" +#include <Swift/QtUI/QtChatWindowFactory.h> #include <QDesktopWidget> - -#include "QtChatTabs.h" -#include "QtChatWindow.h" -#include "QtSwiftUtil.h" -#include "QtChatTheme.h" #include <qdebug.h> +#include <Swift/QtUI/QtChatTabs.h> +#include <Swift/QtUI/QtChatWindow.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtChatTheme.h> +#include <Swift/QtUI/QtSingleWindow.h> + namespace Swift { static const QString SPLITTER_STATE = "mucSplitterState"; static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry"; -QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, QMap<QString, QString> emoticons) : themePath_(themePath), emoticons_(emoticons) { +QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) { qtOnlySettings_ = qtSettings; settings_ = settings; tabs_ = tabs; @@ -49,7 +50,7 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre } } - QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_, emoticons_); + QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_); connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved())); connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray))); diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h index 4f59961..63da514 100644 --- a/Swift/QtUI/QtChatWindowFactory.h +++ b/Swift/QtUI/QtChatWindowFactory.h @@ -6,21 +6,24 @@ #pragma once -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swiften/JID/JID.h" -#include "QtSettingsProvider.h" +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> #include <QObject> #include <QSplitter> + +#include <Swiften/JID/JID.h> +#include <Swift/QtUI/QtSettingsProvider.h> + namespace Swift { class QtChatTabs; class QtChatTheme; class UIEventStream; class QtUIPreferences; + class QtSingleWindow; class QtChatWindowFactory : public QObject, public ChatWindowFactory { Q_OBJECT public: - QtChatWindowFactory(QSplitter* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, QMap<QString, QString> emoticons); + QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath); ~QtChatWindowFactory(); ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); signals: @@ -34,7 +37,6 @@ namespace Swift { QtSettingsProvider* qtOnlySettings_; QtChatTabs* tabs_; QtChatTheme* theme_; - QMap<QString, QString> emoticons_; }; } diff --git a/Swift/QtUI/QtChatWindowJSBridge.h b/Swift/QtUI/QtChatWindowJSBridge.h index 8e6f0c2..5a26302 100644 --- a/Swift/QtUI/QtChatWindowJSBridge.h +++ b/Swift/QtUI/QtChatWindowJSBridge.h @@ -20,7 +20,7 @@ public: QtChatWindowJSBridge(); virtual ~QtChatWindowJSBridge(); signals: - void buttonClicked(QString id, QString arg1, QString arg2, QString arg3); + void buttonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); }; } diff --git a/Swift/QtUI/QtColorToolButton.cpp b/Swift/QtUI/QtColorToolButton.cpp new file mode 100644 index 0000000..1d379a3 --- /dev/null +++ b/Swift/QtUI/QtColorToolButton.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <QColorDialog> +#include <QPainter> + +#include <Swift/QtUI/QtColorToolButton.h> + +namespace Swift { + +QtColorToolButton::QtColorToolButton(QWidget* parent) : + QToolButton(parent) +{ + connect(this, SIGNAL(clicked()), SLOT(onClicked())); + setColorIcon(Qt::transparent); +} + +void QtColorToolButton::setColor(const QColor& color) +{ + if (color.isValid() != color_.isValid() || (color.isValid() && color != color_)) { + color_ = color; + setColorIcon(color_); + emit colorChanged(color_); + } +} + +void QtColorToolButton::onClicked() +{ + QColor c = QColorDialog::getColor(color_, this); + if (c.isValid()) { + setColor(c); + } +} + +void QtColorToolButton::setColorIcon(const QColor& color) +{ + QPixmap pix(iconSize()); + pix.fill(color.isValid() ? color : Qt::transparent); + setIcon(pix); +} + +} diff --git a/Swift/QtUI/QtColorToolButton.h b/Swift/QtUI/QtColorToolButton.h new file mode 100644 index 0000000..33d195d --- /dev/null +++ b/Swift/QtUI/QtColorToolButton.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QToolButton> + +namespace Swift { + + class QtColorToolButton : public QToolButton { + Q_OBJECT + Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged) + public: + explicit QtColorToolButton(QWidget* parent = NULL); + void setColor(const QColor& color); + const QColor& getColor() const { return color_; } + + signals: + void colorChanged(const QColor&); + + private slots: + void onClicked(); + + private: + void setColorIcon(const QColor& color); + QColor color_; + }; + +} diff --git a/Swift/QtUI/QtFileTransferListItemModel.cpp b/Swift/QtUI/QtFileTransferListItemModel.cpp index cf1de07..00afacb 100644 --- a/Swift/QtUI/QtFileTransferListItemModel.cpp +++ b/Swift/QtUI/QtFileTransferListItemModel.cpp @@ -9,14 +9,15 @@ #include <boost/bind.hpp> #include <boost/cstdint.hpp> +#include "QtChatWindow.h" // for formatSize + #include <Swiften/Base/boost_bsignals.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include "QtSwiftUtil.h" namespace Swift { -extern std::string formatSize(const boost::uintmax_t bytes); - QtFileTransferListItemModel::QtFileTransferListItemModel(QObject *parent) : QAbstractItemModel(parent), fileTransferOverview(0) { } @@ -65,11 +66,14 @@ QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) c return controller->isIncoming() ? QVariant(QObject::tr("Incoming")) : QVariant(QObject::tr("Outgoing")); } if (index.column() == OtherParty) { - return QVariant(QString::fromStdString(controller->getOtherParty().toString())); + return QVariant(P2QSTRING(controller->getOtherParty().toString())); } if (index.column() == State) { FileTransfer::State state = controller->getState(); - switch(state.state) { + switch(state.type) { + case FileTransfer::State::Initial: + assert(false); + return QVariant(""); case FileTransfer::State::WaitingForStart: return QVariant(QObject::tr("Waiting for start")); case FileTransfer::State::WaitingForAccept: @@ -91,7 +95,7 @@ QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) c return QVariant(QString::number(controller->getProgress())); } if (index.column() == OverallSize) { - return QVariant(QString::fromStdString(formatSize((controller->getSize())))); + return QVariant(P2QSTRING(formatSize((controller->getSize())))); } return QVariant(); } @@ -105,7 +109,7 @@ int QtFileTransferListItemModel::rowCount(const QModelIndex& /* parent */) const } QModelIndex QtFileTransferListItemModel::index(int row, int column, const QModelIndex& /* parent */) const { - return createIndex(row, column, 0); + return createIndex(row, column, (void*) 0); } } diff --git a/Swift/QtUI/QtFileTransferListItemModel.h b/Swift/QtUI/QtFileTransferListItemModel.h index 1d892a5..28f13f8 100644 --- a/Swift/QtUI/QtFileTransferListItemModel.h +++ b/Swift/QtUI/QtFileTransferListItemModel.h @@ -34,7 +34,7 @@ private: State, Progress, OverallSize, - NoOfColumns, + NoOfColumns }; private: diff --git a/Swift/QtUI/QtFormResultItemModel.cpp b/Swift/QtUI/QtFormResultItemModel.cpp index 5461f05..8920128 100644 --- a/Swift/QtUI/QtFormResultItemModel.cpp +++ b/Swift/QtUI/QtFormResultItemModel.cpp @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include "QtFormResultItemModel.h" #include <boost/algorithm/string/join.hpp> @@ -35,7 +41,7 @@ QVariant QtFormResultItemModel::headerData(int section, Qt::Orientation /*orient if (!formResult_) return QVariant(); if (role != Qt::DisplayRole) return QVariant(); if (static_cast<size_t>(section) >= formResult_->getReportedFields().size()) return QVariant(); - return QVariant(QString::fromStdString(formResult_->getReportedFields().at(section)->getLabel())); + return QVariant(P2QSTRING(formResult_->getReportedFields().at(section)->getLabel())); } int QtFormResultItemModel::rowCount(const QModelIndex &/*parent*/) const { @@ -69,15 +75,16 @@ const std::string QtFormResultItemModel::getFieldValue(const Form::FormItem& ite foreach(FormField::ref field, item) { if (field->getName() == name) { std::string delimiter = ""; - if (boost::dynamic_pointer_cast<TextMultiFormField>(field)) { + if (field->getType() == FormField::TextMultiType) { delimiter = "\n"; - } else if (boost::dynamic_pointer_cast<JIDMultiFormField>(field)) { + } + else if (field->getType() == FormField::JIDMultiType) { delimiter = ", "; - } else if (boost::dynamic_pointer_cast<ListMultiFormField>(field)) { + } + else if (field->getType() == FormField::ListMultiType) { delimiter = ", "; } - - return boost::algorithm::join(field->getRawValues(), delimiter); + return boost::algorithm::join(field->getValues(), delimiter); } } diff --git a/Swift/QtUI/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp index 4216863..117696d 100644 --- a/Swift/QtUI/QtFormWidget.cpp +++ b/Swift/QtUI/QtFormWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -17,6 +17,8 @@ #include <Swift/QtUI/QtSwiftUtil.h> #include <Swiften/Base/foreach.h> +#include <boost/algorithm/string/join.hpp> +#include <boost/smart_ptr/make_shared.hpp> namespace Swift { @@ -54,18 +56,16 @@ 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); + listWidget->setSelectionMode(field->getType() == FormField::ListMultiType ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection); 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()); + if (field->getType() == FormField::ListSingleType) { + selected.push_back(!field->getValues().empty() && option.value == field->getValues()[0]); } - else if (listMultiField) { + else if (field->getType() == FormField::ListMultiType) { std::string text = option.value; - selected.push_back(std::find(listMultiField->getValue().begin(), listMultiField->getValue().end(), text) != listMultiField->getValue().end()); + selected.push_back(std::find(field->getValues().begin(), field->getValues().end(), text) != field->getValues().end()); } } @@ -78,67 +78,47 @@ QListWidget* QtFormWidget::createList(FormField::ref field) { QWidget* QtFormWidget::createWidget(FormField::ref field) { QWidget* widget = NULL; - boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field); - if (booleanField) { + if (field->getType() == FormField::BooleanType) { QCheckBox* checkWidget = new QCheckBox(this); - checkWidget->setCheckState(booleanField->getValue() ? Qt::Checked : Qt::Unchecked); + checkWidget->setCheckState(field->getBoolValue() ? 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(); + if (field->getType() == FormField::FixedType) { + QString value = field->getFixedValue().c_str(); widget = new QLabel(value, this); } - boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field); - if (listSingleField) { + if (field->getType() == FormField::ListSingleType) { widget = createList(field); } - boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field); - if (textMultiField) { - QString value = textMultiField->getValue().c_str(); + if (field->getType() == FormField::TextMultiType) { + QString value = field->getTextMultiValue().c_str(); QTextEdit* textWidget = new QTextEdit(this); textWidget->setPlainText(value); widget = textWidget; } - boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field); - if (textPrivateField) { - QString value = textPrivateField->getValue().c_str(); + if (field->getType() == FormField::TextPrivateType) { + QString value = field->getTextPrivateValue().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(); + if (field->getType() == FormField::TextSingleType) { + QString value = field->getTextSingleValue().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(); + if (field->getType() == FormField::JIDSingleType) { + QString value = field->getJIDSingleValue().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(); - } + if (field->getType() == FormField::JIDMultiType) { + QString text = boost::join(field->getValues(), "\n").c_str(); QTextEdit* textWidget = new QTextEdit(this); textWidget->setPlainText(text); widget = textWidget; } - boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field); - if (listMultiField) { + if (field->getType() == FormField::ListMultiType) { widget = createList(field); } - boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field); - if (hiddenField) { - } fields_[field->getName()] = widget; return widget; } @@ -146,99 +126,49 @@ QWidget* QtFormWidget::createWidget(FormField::ref field) { 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<FormField> resultField = boost::make_shared<FormField>(field->getType()); + if (field->getType() == FormField::BooleanType) { + resultField->setBoolValue(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())); + if (field->getType() == FormField::FixedType || field->getType() == FormField::HiddenType) { + resultField->addValue(field->getValues().empty() ? "" : field->getValues()[0]); } - boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field); - if (listSingleField) { + if (field->getType() == FormField::ListSingleType) { 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()); + resultField->addValue(field->getOptions()[i].value); } } - boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field); - if (textMultiField) { + if (field->getType() == FormField::TextMultiType) { 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))); + if (!string.isEmpty()) { + resultField->setTextMultiValue(Q2PSTRING(string)); } } - boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field); - if (textSingleField) { + if (field->getType() == FormField::TextPrivateType || field->getType() == FormField::TextSingleType || field->getType() == FormField::JIDSingleType) { 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))); + if (!string.isEmpty()) { + resultField->addValue(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) { + if (field->getType() == FormField::JIDMultiType) { QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); QString string = widget->toPlainText(); - if (string.isEmpty()) { - resultField = FormField::ref(JIDMultiFormField::create()); - } - else { + if (!string.isEmpty()) { QStringList lines = string.split("\n"); - std::vector<JID> value; foreach (QString line, lines) { - value.push_back(JID(Q2PSTRING(line))); + resultField->addValue(Q2PSTRING(line)); } - resultField = FormField::ref(JIDMultiFormField::create(value)); } } - boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field); - if (listMultiField) { + if (field->getType() == FormField::ListMultiType) { 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->addValue(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); diff --git a/Swift/QtUI/QtHighlightEditorWidget.cpp b/Swift/QtUI/QtHighlightEditorWidget.cpp new file mode 100644 index 0000000..7ff094e --- /dev/null +++ b/Swift/QtUI/QtHighlightEditorWidget.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cassert> + +#include <Swift/QtUI/QtHighlightEditorWidget.h> +#include <Swift/QtUI/QtHighlightRulesItemModel.h> + +namespace Swift { + +QtHighlightEditorWidget::QtHighlightEditorWidget(QWidget* parent) + : QWidget(parent) +{ + ui_.setupUi(this); + + itemModel_ = new QtHighlightRulesItemModel(this); + ui_.treeView->setModel(itemModel_); + ui_.ruleWidget->setModel(itemModel_); + + for (int i = 0; i < QtHighlightRulesItemModel::NumberOfColumns; ++i) { + switch (i) { + case QtHighlightRulesItemModel::ApplyTo: + case QtHighlightRulesItemModel::Sender: + case QtHighlightRulesItemModel::Keyword: + case QtHighlightRulesItemModel::Action: + ui_.treeView->showColumn(i); + break; + default: + ui_.treeView->hideColumn(i); + break; + } + } + + setHighlightManager(NULL); // setup buttons for empty rules list + + connect(ui_.treeView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), SLOT(onCurrentRowChanged(QModelIndex))); + + connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked())); + connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked())); + + connect(ui_.moveUpButton, SIGNAL(clicked()), SLOT(onMoveUpButtonClicked())); + connect(ui_.moveDownButton, SIGNAL(clicked()), SLOT(onMoveDownButtonClicked())); + + connect(ui_.closeButton, SIGNAL(clicked()), SLOT(close())); + + setWindowTitle(tr("Highlight Rules")); +} + +QtHighlightEditorWidget::~QtHighlightEditorWidget() +{ +} + +void QtHighlightEditorWidget::show() +{ + if (itemModel_->rowCount(QModelIndex())) { + selectRow(0); + } + QWidget::show(); + QWidget::activateWindow(); +} + +void QtHighlightEditorWidget::setHighlightManager(HighlightManager* highlightManager) +{ + itemModel_->setHighlightManager(highlightManager); + ui_.newButton->setEnabled(highlightManager != NULL); + + ui_.ruleWidget->setEnabled(false); + ui_.deleteButton->setEnabled(false); + ui_.moveUpButton->setEnabled(false); + ui_.moveDownButton->setEnabled(false); +} + +void QtHighlightEditorWidget::closeEvent(QCloseEvent* event) { + ui_.ruleWidget->save(); + event->accept(); +} + +void QtHighlightEditorWidget::onNewButtonClicked() +{ + int row = getSelectedRow() + 1; + itemModel_->insertRow(row, QModelIndex()); + selectRow(row); +} + +void QtHighlightEditorWidget::onDeleteButtonClicked() +{ + int row = getSelectedRow(); + assert(row >= 0); + + itemModel_->removeRow(row, QModelIndex()); + if (row == itemModel_->rowCount(QModelIndex())) { + --row; + } + selectRow(row); +} + +void QtHighlightEditorWidget::onMoveUpButtonClicked() +{ + int row = getSelectedRow(); + assert(row > 0); + + ui_.ruleWidget->save(); + ui_.ruleWidget->setActiveIndex(QModelIndex()); + itemModel_->swapRows(row, row - 1); + selectRow(row - 1); +} + +void QtHighlightEditorWidget::onMoveDownButtonClicked() +{ + int row = getSelectedRow(); + assert(row < itemModel_->rowCount(QModelIndex()) - 1); + + ui_.ruleWidget->save(); + ui_.ruleWidget->setActiveIndex(QModelIndex()); + if (itemModel_->swapRows(row, row + 1)) { + selectRow(row + 1); + } +} + +void QtHighlightEditorWidget::onCurrentRowChanged(const QModelIndex& index) +{ + ui_.ruleWidget->save(); + ui_.ruleWidget->setActiveIndex(index); + + ui_.ruleWidget->setEnabled(index.isValid()); + + ui_.deleteButton->setEnabled(index.isValid()); + + ui_.moveUpButton->setEnabled(index.isValid() && index.row() != 0); + ui_.moveDownButton->setEnabled(index.isValid() && index.row() != itemModel_->rowCount(QModelIndex()) - 1); +} + +void QtHighlightEditorWidget::selectRow(int row) +{ + QModelIndex index = itemModel_->index(row, 0, QModelIndex()); + ui_.treeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); +} + +/** Return index of selected row or -1 if none is selected */ +int QtHighlightEditorWidget::getSelectedRow() const +{ + QModelIndexList rows = ui_.treeView->selectionModel()->selectedRows(); + return rows.isEmpty() ? -1 : rows[0].row(); +} + +} diff --git a/Swift/QtUI/QtHighlightEditorWidget.h b/Swift/QtUI/QtHighlightEditorWidget.h new file mode 100644 index 0000000..1293c87 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditorWidget.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/HighlightEditorWidget.h> +#include <Swift/QtUI/ui_QtHighlightEditorWidget.h> + +namespace Swift { + + class QtHighlightRulesItemModel; + + class QtHighlightEditorWidget : public QWidget, public HighlightEditorWidget { + Q_OBJECT + + public: + QtHighlightEditorWidget(QWidget* parent = NULL); + virtual ~QtHighlightEditorWidget(); + + void show(); + + void setHighlightManager(HighlightManager* highlightManager); + + private slots: + void onNewButtonClicked(); + void onDeleteButtonClicked(); + void onMoveUpButtonClicked(); + void onMoveDownButtonClicked(); + void onCurrentRowChanged(const QModelIndex&); + + private: + virtual void closeEvent(QCloseEvent* event); + + void selectRow(int row); + int getSelectedRow() const; + + Ui::QtHighlightEditorWidget ui_; + QtHighlightRulesItemModel* itemModel_; + }; + +} diff --git a/Swift/QtUI/QtHighlightEditorWidget.ui b/Swift/QtUI/QtHighlightEditorWidget.ui new file mode 100644 index 0000000..0f39168 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditorWidget.ui @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHighlightEditorWidget</class> + <widget class="QWidget" name="QtHighlightEditorWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>419</width> + <height>373</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Incoming messages are checked against the following rules. First rule that matches will be executed.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTreeView" name="treeView"> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Swift::QtHighlightRuleWidget" name="ruleWidget" native="true"/> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QPushButton" name="newButton"> + <property name="text"> + <string>New</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="deleteButton"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="moveUpButton"> + <property name="text"> + <string>Move up</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="moveDownButton"> + <property name="text"> + <string>Move down</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="closeButton"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtHighlightRuleWidget</class> + <extends>QWidget</extends> + <header>QtHighlightRuleWidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtHighlightRuleWidget.cpp b/Swift/QtUI/QtHighlightRuleWidget.cpp new file mode 100644 index 0000000..9c0df5e --- /dev/null +++ b/Swift/QtUI/QtHighlightRuleWidget.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <QDataWidgetMapper> +#include <QStringListModel> +#include <QFileDialog> + +#include <Swift/QtUI/QtHighlightRuleWidget.h> +#include <Swift/QtUI/QtHighlightRulesItemModel.h> + +namespace Swift { + +QtHighlightRuleWidget::QtHighlightRuleWidget(QWidget* parent) + : QWidget(parent) +{ + ui_.setupUi(this); + + QStringList applyToItems; + for (int i = 0; i < QtHighlightRulesItemModel::ApplyToEOL; ++i) { + applyToItems << QtHighlightRulesItemModel::getApplyToString(i); + } + QStringListModel * applyToModel = new QStringListModel(applyToItems, this); + ui_.applyTo->setModel(applyToModel); + + connect(ui_.highlightText, SIGNAL(toggled(bool)), SLOT(onHighlightTextToggled(bool))); + connect(ui_.customColors, SIGNAL(toggled(bool)), SLOT(onCustomColorsToggled(bool))); + connect(ui_.playSound, SIGNAL(toggled(bool)), SLOT(onPlaySoundToggled(bool))); + connect(ui_.customSound, SIGNAL(toggled(bool)), SLOT(onCustomSoundToggled(bool))); + connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(onSoundFileButtonClicked())); + + mapper_ = new QDataWidgetMapper(this); + hasValidIndex_ = false; + model_ = NULL; +} + +QtHighlightRuleWidget::~QtHighlightRuleWidget() +{ +} + +/** Widget does not gain ownership over the model */ +void QtHighlightRuleWidget::setModel(QtHighlightRulesItemModel* model) +{ + model_ = model; + mapper_->setModel(model_); +} + +void QtHighlightRuleWidget::setActiveIndex(const QModelIndex& index) +{ + if (index.isValid()) { + if (!hasValidIndex_) { + mapper_->addMapping(ui_.applyTo, QtHighlightRulesItemModel::ApplyTo, "currentIndex"); + mapper_->addMapping(ui_.senders, QtHighlightRulesItemModel::Sender, "plainText"); + mapper_->addMapping(ui_.keywords, QtHighlightRulesItemModel::Keyword, "plainText"); + mapper_->addMapping(ui_.nickIsKeyword, QtHighlightRulesItemModel::NickIsKeyword); + mapper_->addMapping(ui_.matchCase, QtHighlightRulesItemModel::MatchCase); + mapper_->addMapping(ui_.matchWholeWords, QtHighlightRulesItemModel::MatchWholeWords); + mapper_->addMapping(ui_.highlightText, QtHighlightRulesItemModel::HighlightText); + mapper_->addMapping(ui_.foreground, QtHighlightRulesItemModel::TextColor, "color"); + mapper_->addMapping(ui_.background, QtHighlightRulesItemModel::TextBackground, "color"); + mapper_->addMapping(ui_.playSound, QtHighlightRulesItemModel::PlaySound); + mapper_->addMapping(ui_.soundFile, QtHighlightRulesItemModel::SoundFile); + } + mapper_->setCurrentModelIndex(index); + ui_.customColors->setChecked(ui_.foreground->getColor().isValid() || ui_.background->getColor().isValid()); + ui_.customSound->setChecked(!ui_.soundFile->text().isEmpty()); + ui_.applyTo->focusWidget(); + } else { + if (hasValidIndex_) { + mapper_->clearMapping(); + } + } + + hasValidIndex_ = index.isValid(); +} + +void QtHighlightRuleWidget::onCustomColorsToggled(bool enabled) +{ + if (!enabled) { + ui_.foreground->setColor(QColor()); + ui_.background->setColor(QColor()); + } + ui_.foreground->setEnabled(enabled); + ui_.background->setEnabled(enabled); +} + +void QtHighlightRuleWidget::onCustomSoundToggled(bool enabled) +{ + if (enabled) { + if (ui_.soundFile->text().isEmpty()) { + onSoundFileButtonClicked(); + } + } else { + ui_.soundFile->clear(); + } + ui_.soundFile->setEnabled(enabled); + ui_.soundFileButton->setEnabled(enabled); +} + +void QtHighlightRuleWidget::onSoundFileButtonClicked() +{ + QString s = QFileDialog::getOpenFileName(this, tr("Choose sound file"), QString(), tr("Sound files (*.wav)")); + if (!s.isEmpty()) { + ui_.soundFile->setText(s); + } +} + +void QtHighlightRuleWidget::onHighlightTextToggled(bool enabled) +{ + ui_.customColors->setEnabled(enabled); +} + +void QtHighlightRuleWidget::onPlaySoundToggled(bool enabled) +{ + ui_.customSound->setEnabled(enabled); +} + +void QtHighlightRuleWidget::save() +{ + if (hasValidIndex_) { + mapper_->submit(); + } +} + +void QtHighlightRuleWidget::revert() +{ + if (hasValidIndex_) { + mapper_->revert(); + } +} + +} diff --git a/Swift/QtUI/QtHighlightRuleWidget.h b/Swift/QtUI/QtHighlightRuleWidget.h new file mode 100644 index 0000000..8a59a14 --- /dev/null +++ b/Swift/QtUI/QtHighlightRuleWidget.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QWidget> +#include <QModelIndex> + +#include <Swift/QtUI/ui_QtHighlightRuleWidget.h> + +class QDataWidgetMapper; + +namespace Swift { + + class QtHighlightRulesItemModel; + + class QtHighlightRuleWidget : public QWidget + { + Q_OBJECT + + public: + explicit QtHighlightRuleWidget(QWidget* parent = NULL); + ~QtHighlightRuleWidget(); + + void setModel(QtHighlightRulesItemModel* model); + + public slots: + void setActiveIndex(const QModelIndex&); + void save(); + void revert(); + + private slots: + void onHighlightTextToggled(bool); + void onCustomColorsToggled(bool); + void onPlaySoundToggled(bool); + void onCustomSoundToggled(bool); + void onSoundFileButtonClicked(); + + private: + QDataWidgetMapper * mapper_; + QtHighlightRulesItemModel * model_; + bool hasValidIndex_; + Ui::QtHighlightRuleWidget ui_; + }; + +} diff --git a/Swift/QtUI/QtHighlightRuleWidget.ui b/Swift/QtUI/QtHighlightRuleWidget.ui new file mode 100644 index 0000000..9c465f9 --- /dev/null +++ b/Swift/QtUI/QtHighlightRuleWidget.ui @@ -0,0 +1,260 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHighlightRuleWidget</class> + <widget class="QWidget" name="QtHighlightRuleWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>361</width> + <height>524</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Rule conditions</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Choose when this rule should be applied. +If you want to provide more than one sender or keyword, input them in separate lines.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>&Apply to:</string> + </property> + <property name="buddy"> + <cstring>applyTo</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="applyTo"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>&Senders:</string> + </property> + <property name="buddy"> + <cstring>senders</cstring> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPlainTextEdit" name="senders"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>&Keywords:</string> + </property> + <property name="buddy"> + <cstring>keywords</cstring> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPlainTextEdit" name="keywords"/> + </item> + <item row="5" column="1"> + <widget class="QCheckBox" name="nickIsKeyword"> + <property name="text"> + <string>Treat &nick as a keyword (in MUC)</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QCheckBox" name="matchWholeWords"> + <property name="text"> + <string>Match whole &words</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QCheckBox" name="matchCase"> + <property name="text"> + <string>Match &case</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Actions</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="highlightText"> + <property name="text"> + <string>&Highlight text</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>28</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="customColors"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Custom c&olors:</string> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtColorToolButton" name="foreground"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Foreground</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtColorToolButton" name="background"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Background</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="playSound"> + <property name="text"> + <string>&Play sound</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>28</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="customSound"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Custom soun&d:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="soundFile"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="soundFileButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>101</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtColorToolButton</class> + <extends>QToolButton</extends> + <header>QtColorToolButton.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtHighlightRulesItemModel.cpp b/Swift/QtUI/QtHighlightRulesItemModel.cpp new file mode 100644 index 0000000..4efa712 --- /dev/null +++ b/Swift/QtUI/QtHighlightRulesItemModel.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/algorithm/string.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/numeric/conversion/cast.hpp> + +#include <QStringList> +#include <QColor> + +#include <Swift/Controllers/HighlightManager.h> +#include <Swift/QtUI/QtHighlightRulesItemModel.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtHighlightRulesItemModel::QtHighlightRulesItemModel(QObject* parent) : QAbstractItemModel(parent), highlightManager_(NULL) +{ +} + +void QtHighlightRulesItemModel::setHighlightManager(HighlightManager* highlightManager) +{ + emit layoutAboutToBeChanged(); + highlightManager_ = highlightManager; + emit layoutChanged(); +} + +QVariant QtHighlightRulesItemModel::headerData(int section, Qt::Orientation /* orientation */, int role) const +{ + if (role == Qt::DisplayRole) { + switch (section) { + case ApplyTo: return QVariant(tr("Apply to")); + case Sender: return QVariant(tr("Sender")); + case Keyword: return QVariant(tr("Keyword")); + case Action: return QVariant(tr("Action")); + case NickIsKeyword: return QVariant(tr("Nick Is Keyword")); + case MatchCase: return QVariant(tr("Match Case")); + case MatchWholeWords: return QVariant(tr("Match Whole Words")); + case HighlightText: return QVariant(tr("Highlight Text")); + case TextColor: return QVariant(tr("Text Color")); + case TextBackground: return QVariant(tr("Text Background")); + case PlaySound: return QVariant(tr("Play Sounds")); + case SoundFile: return QVariant(tr("Sound File")); + } + } + + return QVariant(); +} + +int QtHighlightRulesItemModel::columnCount(const QModelIndex& /* parent */) const +{ + return NumberOfColumns; +} + +QVariant QtHighlightRulesItemModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid() && highlightManager_ && (role == Qt::DisplayRole || role == Qt::EditRole)) { + + const char* separator = (role == Qt::DisplayRole) ? " ; " : "\n"; + + if (boost::numeric_cast<std::vector<std::string>::size_type>(index.row()) < highlightManager_->getRules().size()) { + const HighlightRule& r = highlightManager_->getRules()[index.row()]; + switch (index.column()) { + case ApplyTo: { + int applyTo = 0; + if (r.getMatchChat() && r.getMatchMUC()) { + applyTo = 1; + } else if (r.getMatchChat()) { + applyTo = 2; + } else if (r.getMatchMUC()) { + applyTo = 3; + } + + if (role == Qt::DisplayRole) { + return QVariant(getApplyToString(applyTo)); + } else { + return QVariant(applyTo); + } + } + case Sender: { + std::string s = boost::join(r.getSenders(), separator); + return QVariant(P2QSTRING(s)); + } + case Keyword: { + std::string s = boost::join(r.getKeywords(), separator); + QString qs(P2QSTRING(s)); + if (role == Qt::DisplayRole && r.getNickIsKeyword()) { + qs = tr("<nick>") + (qs.isEmpty() ? "" : separator + qs); + } + return QVariant(qs); + } + case Action: { + std::vector<std::string> v; + const HighlightAction & action = r.getAction(); + if (action.highlightText()) { + v.push_back(Q2PSTRING(tr("Highlight text"))); + } + if (action.playSound()) { + v.push_back(Q2PSTRING(tr("Play sound"))); + } + std::string s = boost::join(v, separator); + return QVariant(P2QSTRING(s)); + } + case NickIsKeyword: { + return QVariant(r.getNickIsKeyword()); + } + case MatchCase: { + return QVariant(r.getMatchCase()); + } + case MatchWholeWords: { + return QVariant(r.getMatchWholeWords()); + } + case HighlightText: { + return QVariant(r.getAction().highlightText()); + } + case TextColor: { + return QVariant(QColor(P2QSTRING(r.getAction().getTextColor()))); + } + case TextBackground: { + return QVariant(QColor(P2QSTRING(r.getAction().getTextBackground()))); + } + case PlaySound: { + return QVariant(r.getAction().playSound()); + } + case SoundFile: { + return QVariant(P2QSTRING(r.getAction().getSoundFile())); + } + } + } + } + return QVariant(); +} + +bool QtHighlightRulesItemModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && highlightManager_ && role == Qt::EditRole) { + if (boost::numeric_cast<std::vector<std::string>::size_type>(index.row()) < highlightManager_->getRules().size()) { + HighlightRule r = highlightManager_->getRule(index.row()); + std::vector<int> changedColumns; + switch (index.column()) { + case ApplyTo: { + bool ok = false; + int applyTo = value.toInt(&ok); + if (!ok) { + return false; + } + r.setMatchChat(applyTo == ApplyToAll || applyTo == ApplyToChat); + r.setMatchMUC(applyTo == ApplyToAll || applyTo == ApplyToMUC); + break; + } + case Sender: + case Keyword: { + std::string s = Q2PSTRING(value.toString()); + std::vector<std::string> v; + boost::split(v, s, boost::is_any_of("\n")); + v.erase(std::remove_if(v.begin(), v.end(), boost::lambda::_1 == ""), v.end()); + if (index.column() == Sender) { + r.setSenders(v); + } else { + r.setKeywords(v); + } + break; + } + case NickIsKeyword: { + r.setNickIsKeyword(value.toBool()); + changedColumns.push_back(Keyword); // "<nick>" + break; + } + case MatchCase: { + r.setMatchCase(value.toBool()); + break; + } + case MatchWholeWords: { + r.setMatchWholeWords(value.toBool()); + break; + } + case HighlightText: { + r.getAction().setHighlightText(value.toBool()); + changedColumns.push_back(Action); + break; + } + case TextColor: { + QColor c = value.value<QColor>(); + r.getAction().setTextColor(c.isValid() ? Q2PSTRING(c.name()) : ""); + break; + } + case TextBackground: { + QColor c = value.value<QColor>(); + r.getAction().setTextBackground(c.isValid() ? Q2PSTRING(c.name()) : ""); + break; + } + case PlaySound: { + r.getAction().setPlaySound(value.toBool()); + changedColumns.push_back(Action); + break; + } + case SoundFile: { + r.getAction().setSoundFile(Q2PSTRING(value.toString())); + break; + } + } + + highlightManager_->setRule(index.row(), r); + emit dataChanged(index, index); + foreach (int column, changedColumns) { + QModelIndex i = createIndex(index.row(), column, (void*) 0); + emit dataChanged(i, i); + } + } + } + + return false; +} + +QModelIndex QtHighlightRulesItemModel::parent(const QModelIndex& /* child */) const +{ + return QModelIndex(); +} + +int QtHighlightRulesItemModel::rowCount(const QModelIndex& /* parent */) const +{ + return highlightManager_ ? highlightManager_->getRules().size() : 0; +} + +QModelIndex QtHighlightRulesItemModel::index(int row, int column, const QModelIndex& /* parent */) const +{ + return createIndex(row, column, (void*) 0); +} + +bool QtHighlightRulesItemModel::insertRows(int row, int count, const QModelIndex& /* parent */) +{ + if (highlightManager_) { + beginInsertRows(QModelIndex(), row, row + count); + while (count--) { + highlightManager_->insertRule(row, HighlightRule()); + } + endInsertRows(); + return true; + } + return false; +} + +bool QtHighlightRulesItemModel::removeRows(int row, int count, const QModelIndex& /* parent */) +{ + if (highlightManager_) { + beginRemoveRows(QModelIndex(), row, row + count); + while (count--) { + highlightManager_->removeRule(row); + } + endRemoveRows(); + return true; + } + return false; +} + +bool QtHighlightRulesItemModel::swapRows(int row1, int row2) +{ + if (highlightManager_) { + assert(row1 >= 0 && row2 >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(row1) < highlightManager_->getRules().size() && boost::numeric_cast<std::vector<std::string>::size_type>(row2) < highlightManager_->getRules().size()); + HighlightRule r = highlightManager_->getRule(row1); + highlightManager_->setRule(row1, highlightManager_->getRule(row2)); + highlightManager_->setRule(row2, r); + emit dataChanged(index(row1, 0, QModelIndex()), index(row1, 0, QModelIndex())); + emit dataChanged(index(row2, 0, QModelIndex()), index(row2, 0, QModelIndex())); + return true; + } + return false; +} + +QString QtHighlightRulesItemModel::getApplyToString(int applyTo) +{ + switch (applyTo) { + case ApplyToNone: return tr("None"); + case ApplyToAll: return tr("Chat or MUC"); + case ApplyToChat: return tr("Chat"); + case ApplyToMUC: return tr("MUC"); + default: return ""; + } +} + +} diff --git a/Swift/QtUI/QtHighlightRulesItemModel.h b/Swift/QtUI/QtHighlightRulesItemModel.h new file mode 100644 index 0000000..ac85628 --- /dev/null +++ b/Swift/QtUI/QtHighlightRulesItemModel.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QAbstractItemModel> + +namespace Swift { + + class HighlightManager; + + class QtHighlightRulesItemModel : public QAbstractItemModel { + Q_OBJECT + + public: + QtHighlightRulesItemModel(QObject* parent = NULL); + + void setHighlightManager(HighlightManager* highlightManager); + + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + int columnCount(const QModelIndex& parent) const; + QVariant data(const QModelIndex& index, int role) const; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + QModelIndex parent(const QModelIndex& child) const; + int rowCount(const QModelIndex& parent) const; + QModelIndex index(int row, int column, const QModelIndex& parent) const; + bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); + bool swapRows(int row1, int row2); + + static QString getApplyToString(int); + + enum Columns { + ApplyTo = 0, + Sender, + Keyword, + Action, + NickIsKeyword, + MatchCase, + MatchWholeWords, + HighlightText, + TextColor, + TextBackground, + PlaySound, + SoundFile, + NumberOfColumns // end of list marker + }; + + enum ApplyToValues { + ApplyToNone = 0, + ApplyToAll, + ApplyToChat, + ApplyToMUC, + ApplyToEOL // end of list marker + }; + + private: + HighlightManager* highlightManager_; + }; + +} diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp index e54bd51..9f88258 100644 --- a/Swift/QtUI/QtHistoryWindow.cpp +++ b/Swift/QtUI/QtHistoryWindow.cpp @@ -1,29 +1,43 @@ /* - * Copyright (c) 2012 Catalin Badea + * Copyright (c) 2012-2013 Catalin Badea * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include <QtHistoryWindow.h> -#include <QtTabbable.h> -#include <QtSwiftUtil.h> -#include <MessageSnippet.h> -#include <Swiften/History/HistoryMessage.h> #include <string> +#include <boost/date_time/gregorian/gregorian.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> #include <QTime> #include <QUrl> #include <QMenu> #include <QTextDocument> #include <QDateTime> -#include <Swift/QtUI/QtScaledAvatarCache.h> #include <QLineEdit> -#include <boost/smart_ptr/make_shared.hpp> -#include <boost/date_time/gregorian/gregorian.hpp> +#include <Swiften/History/HistoryMessage.h> + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/MessageSnippet.h> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/ChatSnippet.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <Swift/QtUI/QtWebKitChatView.h> namespace Swift { @@ -36,7 +50,7 @@ QtHistoryWindow::QtHistoryWindow(SettingsProvider* settings, UIEventStream* even idCounter_ = 0; delete ui_.conversation_; - conversation_ = new QtChatView(theme_, this, true); + conversation_ = new QtWebKitChatView(NULL, NULL, theme_, this, true); // Horrible unsafe. Do not do this. FIXME QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(80); sizePolicy.setVerticalStretch(0); @@ -107,7 +121,7 @@ void QtHistoryWindow::addMessage(const std::string &message, const std::string & QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); QString messageHTML(P2QSTRING(message)); - messageHTML = Qt::escape(messageHTML); + messageHTML = QtUtilities::htmlEscape(messageHTML); QString searchTerm = ui_.searchBox_->lineEdit()->text(); if (searchTerm.length()) { messageHTML.replace(searchTerm, "<span style='background-color: yellow'>" + searchTerm + "</span>"); @@ -124,14 +138,14 @@ void QtHistoryWindow::addMessage(const std::string &message, const std::string & if (addAtTheTop) { bool appendToPrevious = ((senderIsSelf && previousTopMessageWasSelf_) || (!senderIsSelf && !previousTopMessageWasSelf_&& previousTopSenderName_ == P2QSTRING(senderName))); - conversation_->addMessageTop(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, Qt::escape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + conversation_->addMessageTop(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, QtUtilities::htmlEscape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(message)))); previousTopMessageWasSelf_ = senderIsSelf; previousTopSenderName_ = P2QSTRING(senderName); } else { bool appendToPrevious = ((senderIsSelf && previousBottomMessageWasSelf_) || (!senderIsSelf && !previousBottomMessageWasSelf_&& previousBottomSenderName_ == P2QSTRING(senderName))); - conversation_->addMessageBottom(boost::shared_ptr<ChatSnippet>(new MessageSnippet(messageHTML, Qt::escape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + conversation_->addMessageBottom(boost::make_shared<MessageSnippet>(messageHTML, QtUtilities::htmlEscape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(message))); previousBottomMessageWasSelf_ = senderIsSelf; previousBottomSenderName_ = P2QSTRING(senderName); } @@ -183,7 +197,7 @@ void QtHistoryWindow::handleScrollReachedTop() { int year, month, day; QDate firstDate = *dates_.begin(); firstDate.getDate(&year, &month, &day); - onScrollReachedTop(boost::gregorian::date(year, month, day)); + onScrollReachedTop(boost::gregorian::date(boost::numeric_cast<unsigned short>(year), boost::numeric_cast<unsigned short>(month), boost::numeric_cast<unsigned short>(day))); } void QtHistoryWindow::handleScrollReachedBottom() { @@ -194,7 +208,7 @@ void QtHistoryWindow::handleScrollReachedBottom() { int year, month, day; QDate lastDate = *dates_.rbegin(); lastDate.getDate(&year, &month, &day); - onScrollReachedBottom(boost::gregorian::date(year, month, day)); + onScrollReachedBottom(boost::gregorian::date(boost::numeric_cast<unsigned short>(year), boost::numeric_cast<unsigned short>(month), boost::numeric_cast<unsigned short>(day))); } void QtHistoryWindow::handleReturnPressed() { @@ -205,7 +219,7 @@ void QtHistoryWindow::handleCalendarClicked(const QDate& date) { int year, month, day; QDate tempDate = date; // getDate discards const qualifier tempDate.getDate(&year, &month, &day); - onCalendarClicked(boost::gregorian::date(year, month, day)); + onCalendarClicked(boost::gregorian::date(boost::numeric_cast<unsigned short>(year), boost::numeric_cast<unsigned short>(month), boost::numeric_cast<unsigned short>(day))); } void QtHistoryWindow::setDate(const boost::gregorian::date& date) { @@ -242,7 +256,7 @@ boost::gregorian::date QtHistoryWindow::getLastVisibleDate() { int year, month, day; lastDate.getDate(&year, &month, &day); - return boost::gregorian::date(year, month, day); + return boost::gregorian::date(boost::numeric_cast<unsigned short>(year), boost::numeric_cast<unsigned short>(month), boost::numeric_cast<unsigned short>(day)); } return boost::gregorian::date(boost::gregorian::not_a_date_time); } diff --git a/Swift/QtUI/QtHistoryWindow.h b/Swift/QtUI/QtHistoryWindow.h index 49de098..fcbfd7e 100644 --- a/Swift/QtUI/QtHistoryWindow.h +++ b/Swift/QtUI/QtHistoryWindow.h @@ -4,17 +4,32 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2013 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once -#include <Swift/Controllers/UIInterfaces/HistoryWindow.h> -#include <Swift/QtUI/ui_QtHistoryWindow.h> -#include <QtChatView.h> -#include <QtTabbable.h> -#include <Swift/QtUI/Roster/QtTreeWidget.h> #include <set> + #include <QDate> +#include <Swift/Controllers/UIInterfaces/HistoryWindow.h> + +#include <Swift/QtUI/QtTabbable.h> + +#include <Swift/QtUI/ui_QtHistoryWindow.h> + namespace Swift { + class QtTabbable; + class QtTreeWidget; + class QtWebKitChatView; + class QtChatTheme; + class SettingsProvider; + class UIEventStream; + class QtHistoryWindow : public QtTabbable, public HistoryWindow { Q_OBJECT @@ -54,7 +69,7 @@ namespace Swift { Ui::QtHistoryWindow ui_; QtChatTheme* theme_; - QtChatView* conversation_; + QtWebKitChatView* conversation_; QtTreeWidget* conversationRoster_; std::set<QDate> dates_; int idCounter_; diff --git a/Swift/QtUI/QtInviteToChatWindow.cpp b/Swift/QtUI/QtInviteToChatWindow.cpp deleted file mode 100644 index ce6dea0..0000000 --- a/Swift/QtUI/QtInviteToChatWindow.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#include <Swift/QtUI/QtInviteToChatWindow.h> - -#include <QHBoxLayout> -#include <QCompleter> -#include <QLabel> -#include <QLineEdit> -#include <QPushButton> -#include <QDialogButtonBox> - -#include <Swift/QtUI/QtSwiftUtil.h> - -namespace Swift { - -QtInviteToChatWindow::QtInviteToChatWindow(QWidget* parent) : QDialog(parent) { - QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); - //layout->setContentsMargins(0,0,0,0); - //layout->setSpacing(2); - - QLabel* description = new QLabel(tr("Users to invite to this chat (one per line):")); - layout->addWidget(description); - - jidsLayout_ = new QBoxLayout(QBoxLayout::TopToBottom); - layout->addLayout(jidsLayout_); - - QLabel* reasonLabel = new QLabel(tr("If you want to provide a reason for the invitation, enter it here")); - layout->addWidget(reasonLabel); - reason_ = new QLineEdit(this); - layout->addWidget(reason_); - - connect(this, SIGNAL(accepted()), this, SLOT(handleAccepting())); - connect(this, SIGNAL(rejected()), this, SLOT(handleRejecting())); - - - buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - connect(buttonBox_, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox_, SIGNAL(rejected()), this, SLOT(reject())); - - layout->addWidget(buttonBox_); - addJIDLine(); - - jids_[0]->setFocus(); - - setModal(false); - show(); -} - -QtInviteToChatWindow::~QtInviteToChatWindow() { - -} - -void QtInviteToChatWindow::handleAccepting() { - onCompleted(); -} - -void QtInviteToChatWindow::handleRejecting() { - onDismissed(); -} - -std::string QtInviteToChatWindow::getReason() const { - return Q2PSTRING(reason_->text()); -} - -std::vector<JID> QtInviteToChatWindow::getJIDs() const { - std::vector<JID> results; - foreach (QLineEdit* jidEdit, jids_) { - QStringList parts = jidEdit->text().split(" "); - if (parts.size() > 0) { - JID jid(Q2PSTRING(parts.last())); - if (jid.isValid() && !jid.getNode().empty()) { - results.push_back(jid); - } - } - } - return results; -} - -void QtInviteToChatWindow::addJIDLine() { - QLineEdit* jid = new QLineEdit(this); - QCompleter* completer = new QCompleter(&completions_, this); - completer->setCaseSensitivity(Qt::CaseInsensitive); - jid->setCompleter(completer); - jidsLayout_->addWidget(jid); - connect(jid, SIGNAL(textChanged(const QString&)), this, SLOT(handleJIDTextChanged())); - if (!jids_.empty()) { - setTabOrder(jids_.back(), jid); - } - jids_.push_back(jid); - setTabOrder(jid, reason_); - setTabOrder(reason_, buttonBox_); - //setTabOrder(buttonBox_, jids_[0]); -} - -void QtInviteToChatWindow::handleJIDTextChanged() { - bool gotEmpty = false; - foreach(QLineEdit* edit, jids_) { - if (edit->text().isEmpty()) { - gotEmpty = true; - } - } - if (!gotEmpty) { - addJIDLine(); - } -} - -typedef std::pair<JID, std::string> JIDString; - -void QtInviteToChatWindow::setAutoCompletions(std::vector<std::pair<JID, std::string> > completions) { - QStringList list; - foreach (JIDString jidPair, completions) { - QString line = P2QSTRING(jidPair.first.toString()); - if (jidPair.second != jidPair.first.toString() && !jidPair.second.empty()) { - line = P2QSTRING(jidPair.second) + " - " + line; - } - list.append(line); - } - completions_.setStringList(list); -} - -} - - - diff --git a/Swift/QtUI/QtInviteToChatWindow.h b/Swift/QtUI/QtInviteToChatWindow.h deleted file mode 100644 index dd8743a..0000000 --- a/Swift/QtUI/QtInviteToChatWindow.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#pragma once - -#include <Swift/Controllers/UIInterfaces/InviteToChatWindow.h> - -#include <QDialog> -#include <QStringListModel> - -class QLineEdit; -class QBoxLayout; -class QDialogButtonBox; - -namespace Swift { - class QtInviteToChatWindow : public QDialog, public InviteToChatWindow { - Q_OBJECT - public: - QtInviteToChatWindow(QWidget* parent = NULL); - virtual ~QtInviteToChatWindow(); - - virtual std::string getReason() const; - - virtual std::vector<JID> getJIDs() const; - virtual void setAutoCompletions(std::vector<std::pair<JID, std::string> > completions); - private: - void addJIDLine(); - private slots: - void handleJIDTextChanged(); - void handleAccepting(); - void handleRejecting(); - private: - QStringListModel completions_; - QLineEdit* reason_; - QBoxLayout* jidsLayout_; - std::vector<QLineEdit*> jids_; - QDialogButtonBox* buttonBox_; - }; -} - - diff --git a/Swift/QtUI/QtJoinMUCWindow.ui b/Swift/QtUI/QtJoinMUCWindow.ui index 5a69292..9225f6f 100644 --- a/Swift/QtUI/QtJoinMUCWindow.ui +++ b/Swift/QtUI/QtJoinMUCWindow.ui @@ -43,9 +43,6 @@ </property> </widget> </item> - <item row="1" column="1" colspan="2"> - <widget class="QLineEdit" name="nickName"/> - </item> <item row="0" column="1"> <widget class="QLineEdit" name="room"> <property name="text"> @@ -63,6 +60,9 @@ <item row="2" column="1"> <widget class="QLineEdit" name="password"/> </item> + <item row="1" column="1" colspan="2"> + <widget class="QLineEdit" name="nickName"/> + </item> </layout> </item> <item> diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index c27edfb..188e55f 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -30,6 +30,7 @@ #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestXMLConsoleUIEvent.h> #include <Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> #include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/QtUI/QtUISettingConstants.h> @@ -54,7 +55,7 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set uiEventStream_ = uiEventStream; setWindowTitle("Swift"); -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); #endif QtUtilities::setX11Resource(this, "Main"); @@ -190,6 +191,10 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set generalMenu_->addAction(fileTransferOverviewAction_); #endif + highlightEditorAction_ = new QAction(tr("&Edit Highlight Rules"), this); + connect(highlightEditorAction_, SIGNAL(triggered()), SLOT(handleShowHighlightEditor())); + generalMenu_->addAction(highlightEditorAction_); + toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this); toggleSoundsAction_->setCheckable(true); toggleSoundsAction_->setChecked(settings_->getSetting(SettingConstants::PLAY_SOUNDS)); @@ -438,6 +443,10 @@ void QtLoginWindow::handleShowFileTransferOverview() { uiEventStream_->send(boost::make_shared<RequestFileTransferListUIEvent>()); } +void QtLoginWindow::handleShowHighlightEditor() { + uiEventStream_->send(boost::make_shared<RequestHighlightEditorUIEvent>()); +} + void QtLoginWindow::handleToggleSounds(bool enabled) { settings_->storeSetting(SettingConstants::PLAY_SOUNDS, enabled); } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index c1966d8..7415fbf 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -62,6 +62,7 @@ namespace Swift { void handleQuit(); void handleShowXMLConsole(); void handleShowFileTransferOverview(); + void handleShowHighlightEditor(); void handleToggleSounds(bool enabled); void handleToggleNotifications(bool enabled); void handleAbout(); @@ -103,6 +104,7 @@ namespace Swift { SettingsProvider* settings_; QAction* xmlConsoleAction_; QAction* fileTransferOverviewAction_; + QAction* highlightEditorAction_; TimerFactory* timerFactory_; ClientOptions currentOptions_; }; diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 5d50c1e..6f87a88 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -33,6 +33,7 @@ #include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h> #include <Swift/QtUI/QtUISettingConstants.h> #include <Swift/Controllers/SettingConstants.h> #include <Swiften/Base/Platform.h> @@ -47,14 +48,14 @@ namespace Swift { -QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, bool emoticonsExist) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { +QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { uiEventStream_ = uiEventStream; settings_ = settings; setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); mainLayout->setContentsMargins(0,0,0,0); mainLayout->setSpacing(0); - meView_ = new QtRosterHeader(settings, this); + meView_ = new QtRosterHeader(settings, statusCache, this); mainLayout->addWidget(meView_); connect(meView_, SIGNAL(onChangeStatusRequest(StatusShow::Type, const QString&)), this, SLOT(handleStatusChanged(StatusShow::Type, const QString&))); connect(meView_, SIGNAL(onEditProfileRequest()), this, SLOT(handleEditProfileRequest())); @@ -95,6 +96,14 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr QMenu* viewMenu = new QMenu(tr("&View"), this); menus_.push_back(viewMenu); + + compactRosterAction_ = new QAction(tr("&Compact Roster"), this); + compactRosterAction_->setCheckable(true); + compactRosterAction_->setChecked(false); + connect(compactRosterAction_, SIGNAL(toggled(bool)), SLOT(handleCompactRosterToggled(bool))); + viewMenu->addAction(compactRosterAction_); + handleCompactRosterToggled(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + showOfflineAction_ = new QAction(tr("&Show offline contacts"), this); showOfflineAction_->setCheckable(true); showOfflineAction_->setChecked(false); @@ -130,7 +139,12 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr connect(viewLogsAction, SIGNAL(triggered()), SLOT(handleViewLogsAction())); actionsMenu->addAction(viewLogsAction); #endif + openBlockingListEditor_ = new QAction(tr("Edit &Blocking List…"), this); + connect(openBlockingListEditor_, SIGNAL(triggered()), SLOT(handleEditBlockingList())); + actionsMenu->addAction(openBlockingListEditor_); + openBlockingListEditor_->setVisible(false); addUserAction_ = new QAction(tr("&Add Contact…"), this); + addUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D)); connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool))); actionsMenu->addAction(addUserAction_); editUserAction_ = new QAction(tr("&Edit Selected Contact…"), this); @@ -138,6 +152,7 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr actionsMenu->addAction(editUserAction_); editUserAction_->setEnabled(false); chatUserAction_ = new QAction(tr("Start &Chat…"), this); + chatUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N)); connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool))); actionsMenu->addAction(chatUserAction_); serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this); @@ -182,6 +197,10 @@ void QtMainWindow::handleShowCertificateInfo() { onShowCertificateRequest(); } +void QtMainWindow::handleEditBlockingList() { + uiEventStream_->send(boost::make_shared<RequestBlockListDialogUIEvent>()); +} + QtEventWindow* QtMainWindow::getEventWindow() { return eventWindow_; } @@ -261,6 +280,14 @@ void QtMainWindow::handleSettingChanged(const std::string& settingPath) { if (settingPath == SettingConstants::REQUEST_DELIVERYRECEIPTS.getKey()) { toggleRequestDeliveryReceipts_->setChecked(settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS)); } + if (settingPath == QtUISettingConstants::COMPACT_ROSTER.getKey()) { + handleCompactRosterToggled(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + } +} + +void QtMainWindow::handleCompactRosterToggled(bool state) { + settings_->storeSetting(QtUISettingConstants::COMPACT_ROSTER, state); + compactRosterAction_->setChecked(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); } void QtMainWindow::handleShowOfflineToggled(bool state) { @@ -343,5 +370,9 @@ void QtMainWindow::setAvailableAdHocCommands(const std::vector<DiscoItems::Item> } } +void QtMainWindow::setBlockingCommandAvailable(bool isAvailable) { + openBlockingListEditor_->setVisible(isAvailable); +} + } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 26d25e1..3e6e1d3 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -32,11 +32,12 @@ namespace Swift { class QtTabWidget; class SettingsProvider; class QtUIPreferences; + class StatusCache; class QtMainWindow : public QWidget, public MainWindow { Q_OBJECT public: - QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, bool emoticonsExist); + QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist); virtual ~QtMainWindow(); std::vector<QMenu*> getMenus() {return menus_;} void setMyNick(const std::string& name); @@ -52,9 +53,11 @@ namespace Swift { QtChatListWindow* getChatListWindow(); void setRosterModel(Roster* roster); void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands); + void setBlockingCommandAvailable(bool isAvailable); private slots: void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage); void handleSettingChanged(const std::string& settingPath); + void handleCompactRosterToggled(bool); void handleShowOfflineToggled(bool); void handleShowEmoticonsToggled(bool); void handleJoinMUCAction(); @@ -70,6 +73,7 @@ namespace Swift { void handleTabChanged(int index); void handleToggleRequestDeliveryReceipts(bool enabled); void handleShowCertificateInfo(); + void handleEditBlockingList(); private: SettingsProvider* settings_; @@ -81,7 +85,9 @@ namespace Swift { QAction* editUserAction_; QAction* chatUserAction_; QAction* showOfflineAction_; + QAction* compactRosterAction_; QAction* showEmoticonsAction_; + QAction* openBlockingListEditor_; QAction* toggleRequestDeliveryReceipts_; QMenu* serverAdHocMenu_; QtTabWidget* tabs_; diff --git a/Swift/QtUI/QtNameWidget.h b/Swift/QtUI/QtNameWidget.h index 0f00c41..3225879 100644 --- a/Swift/QtUI/QtNameWidget.h +++ b/Swift/QtUI/QtNameWidget.h @@ -31,7 +31,7 @@ namespace Swift { private: enum Mode { ShowNick, - ShowJID, + ShowJID }; SettingsProvider* settings; diff --git a/Swift/QtUI/QtProfileWindow.cpp b/Swift/QtUI/QtProfileWindow.cpp index 0faa78f..d0d1414 100644 --- a/Swift/QtUI/QtProfileWindow.cpp +++ b/Swift/QtUI/QtProfileWindow.cpp @@ -4,97 +4,79 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + #include "QtProfileWindow.h" +#include "ui_QtProfileWindow.h" -#include <QImage> -#include <QPixmap> -#include <QSizePolicy> -#include <QGridLayout> -#include <QLabel> -#include <QLineEdit> -#include <QPushButton> +#include <QCloseEvent> #include <QMovie> +#include <QShortcut> +#include <QTextDocument> -#include "QtSwiftUtil.h" -#include "QtAvatarWidget.h" +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> namespace Swift { -QtProfileWindow::QtProfileWindow() { - setWindowTitle(tr("Edit Profile")); - - QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(this->sizePolicy().hasHeightForWidth()); - setSizePolicy(sizePolicy); - - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins(10, 10, 10, 10); - - QHBoxLayout* topLayout = new QHBoxLayout(); - - avatar = new QtAvatarWidget(this); - topLayout->addWidget(avatar); - - QVBoxLayout* fieldsLayout = new QVBoxLayout(); - - QHBoxLayout* horizontalLayout_2 = new QHBoxLayout(); - nicknameLabel = new QLabel(tr("Nickname:"), this); - horizontalLayout_2->addWidget(nicknameLabel); - nickname = new QLineEdit(this); - horizontalLayout_2->addWidget(nickname); - - fieldsLayout->addLayout(horizontalLayout_2); - - errorLabel = new QLabel(this); - errorLabel->setAlignment(Qt::AlignHCenter); - fieldsLayout->addWidget(errorLabel); - - fieldsLayout->addItem(new QSpacerItem(198, 17, QSizePolicy::Minimum, QSizePolicy::Expanding)); - topLayout->addLayout(fieldsLayout); - - layout->addLayout(topLayout); - - QHBoxLayout* horizontalLayout = new QHBoxLayout(); - horizontalLayout->setContentsMargins(0, 0, 0, 0); - horizontalLayout->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); - - throbberLabel = new QLabel(this); - throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); - horizontalLayout->addWidget(throbberLabel); - - saveButton = new QPushButton(tr("Save"), this); - saveButton->setDefault( true ); - connect(saveButton, SIGNAL(clicked()), SLOT(handleSave())); - horizontalLayout->addWidget(saveButton); +QtProfileWindow::QtProfileWindow() : + QWidget(), + ui(new Ui::QtProfileWindow) { + ui->setupUi(this); + new QShortcut(QKeySequence::Close, this, SLOT(close())); + ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); + connect(ui->savePushButton, SIGNAL(clicked()), SLOT(handleSave())); + setEditable(false); + setAttribute(Qt::WA_DeleteOnClose); +} - fieldsLayout->addLayout(horizontalLayout); +QtProfileWindow::~QtProfileWindow() { + delete ui; +} - resize(360, 120); +void QtProfileWindow::setJID(const JID& jid) { + this->jid = jid; + updateTitle(); } -void QtProfileWindow::setVCard(Swift::VCard::ref vcard) { - this->vcard = vcard; - nickname->setText(P2QSTRING(vcard->getNickname())); - avatar->setAvatar(vcard->getPhoto(), vcard->getPhotoType()); +void QtProfileWindow::setVCard(VCard::ref vcard) { + ui->vcard->setVCard(vcard); } void QtProfileWindow::setEnabled(bool b) { - nickname->setEnabled(b); - nicknameLabel->setEnabled(b); - avatar->setEnabled(b); - saveButton->setEnabled(b); + ui->vcard->setEnabled(b); + ui->savePushButton->setEnabled(b); +} + +void QtProfileWindow::setEditable(bool b) { + ui->throbberLabel->setVisible(b); + ui->errorLabel->setVisible(b); + ui->savePushButton->setVisible(b); + ui->vcard->setEditable(b); + updateTitle(); } void QtProfileWindow::setProcessing(bool processing) { if (processing) { - throbberLabel->movie()->start(); - throbberLabel->show(); + ui->throbberLabel->movie()->start(); + ui->throbberLabel->show(); } else { - throbberLabel->hide(); - throbberLabel->movie()->stop(); + ui->throbberLabel->hide(); + ui->throbberLabel->movie()->stop(); + } +} + +void QtProfileWindow::setError(const std::string& error) { + if (!error.empty()) { + ui->errorLabel->setText("<font color='red'>" + QtUtilities::htmlEscape(P2QSTRING(error)) + "</font>"); + } + else { + ui->errorLabel->setText(""); } } @@ -103,31 +85,30 @@ void QtProfileWindow::show() { QWidget::activateWindow(); } -void QtProfileWindow::hideEvent(QHideEvent* event) { - QWidget::hideEvent(event); -} - void QtProfileWindow::hide() { QWidget::hide(); } -void QtProfileWindow::handleSave() { - assert(vcard); - vcard->setNickname(Q2PSTRING(nickname->text())); - vcard->setPhoto(avatar->getAvatarData()); - vcard->setPhotoType(avatar->getAvatarType()); - onVCardChangeRequest(vcard); -} - -void QtProfileWindow::setError(const std::string& error) { - if (!error.empty()) { - errorLabel->setText("<font color='red'>" + P2QSTRING(error) + "</font>"); +void QtProfileWindow::updateTitle() { + QString jidString; + if (jid.isValid()) { + jidString = QString(" ( %1 )").arg(P2QSTRING(jid.toString())); } - else { - errorLabel->setText(""); + + if (ui->vcard->isEditable()) { + setWindowTitle(tr("Edit Profile") + jidString); + } else { + setWindowTitle(tr("Show Profile") + jidString); } } +void QtProfileWindow::closeEvent(QCloseEvent* event) { + event->accept(); + onWindowAboutToBeClosed(jid); +} +void QtProfileWindow::handleSave() { + onVCardChangeRequest(ui->vcard->getVCard()); +} } diff --git a/Swift/QtUI/QtProfileWindow.h b/Swift/QtUI/QtProfileWindow.h index edb9cce..a2af63a 100644 --- a/Swift/QtUI/QtProfileWindow.h +++ b/Swift/QtUI/QtProfileWindow.h @@ -4,45 +4,54 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + #pragma once -#include <QWidget> +#include <Swiften/JID/JID.h> #include <Swift/Controllers/UIInterfaces/ProfileWindow.h> -class QLabel; -class QLineEdit; -class QHBoxLayout; -class QPushButton; +#include <QWidget> + +namespace Ui { + class QtProfileWindow; +} namespace Swift { - class QtAvatarWidget; - - class QtProfileWindow : public QWidget, public ProfileWindow { - Q_OBJECT - public: - QtProfileWindow(); - - void setVCard(Swift::VCard::ref); - void setEnabled(bool); - void setProcessing(bool); - virtual void setError(const std::string&); - void show(); - void hide(); - - void hideEvent (QHideEvent* event); - - private slots: - void handleSave(); - - private: - VCard::ref vcard; - QtAvatarWidget* avatar; - QLabel* nicknameLabel; - QLineEdit* nickname; - QLabel* throbberLabel; - QLabel* errorLabel; - QHBoxLayout* horizontalLayout; - QPushButton* saveButton; - }; + +class QtProfileWindow : public QWidget, public ProfileWindow { + Q_OBJECT + + public: + QtProfileWindow(); + virtual ~QtProfileWindow(); + + virtual void setJID(const JID& jid); + virtual void setVCard(VCard::ref vcard); + + virtual void setEnabled(bool b); + virtual void setProcessing(bool processing); + virtual void setError(const std::string& error); + virtual void setEditable(bool b); + + virtual void show(); + virtual void hide(); + + private: + void updateTitle(); + virtual void closeEvent(QCloseEvent* event); + + private slots: + void handleSave(); + + private: + Ui::QtProfileWindow* ui; + JID jid; +}; + } diff --git a/Swift/QtUI/QtProfileWindow.ui b/Swift/QtUI/QtProfileWindow.ui new file mode 100644 index 0000000..9394dc5 --- /dev/null +++ b/Swift/QtUI/QtProfileWindow.ui @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtProfileWindow</class> + <widget class="QWidget" name="QtProfileWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>334</width> + <height>197</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Profile</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="Swift::QtVCardWidget" name="vcard" native="true"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="errorLabel"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="throbberLabel"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="savePushButton"> + <property name="text"> + <string>Save</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtVCardWidget</class> + <extends>QWidget</extends> + <header>Swift/QtUI/QtVCardWidget/QtVCardWidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtRosterHeader.cpp b/Swift/QtUI/QtRosterHeader.cpp index d32a12e..44459d5 100644 --- a/Swift/QtUI/QtRosterHeader.cpp +++ b/Swift/QtUI/QtRosterHeader.cpp @@ -23,7 +23,7 @@ #include "QtScaledAvatarCache.h" namespace Swift { -QtRosterHeader::QtRosterHeader(SettingsProvider* settings, QWidget* parent) : QWidget(parent) { +QtRosterHeader::QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent) : QWidget(parent) { QHBoxLayout* topLayout = new QHBoxLayout(); topLayout->setSpacing(3); topLayout->setContentsMargins(4,4,4,4); @@ -62,7 +62,7 @@ QtRosterHeader::QtRosterHeader(SettingsProvider* settings, QWidget* parent) : QW rightLayout->addLayout(nameAndSecurityLayout); - statusWidget_ = new QtStatusWidget(this); + statusWidget_ = new QtStatusWidget(statusCache, this); connect(statusWidget_, SIGNAL(onChangeStatusRequest(StatusShow::Type, const QString&)), this, SLOT(handleChangeStatusRequest(StatusShow::Type, const QString&))); rightLayout->addWidget(statusWidget_); diff --git a/Swift/QtUI/QtRosterHeader.h b/Swift/QtUI/QtRosterHeader.h index 9527cf4..ad19178 100644 --- a/Swift/QtUI/QtRosterHeader.h +++ b/Swift/QtUI/QtRosterHeader.h @@ -24,11 +24,12 @@ namespace Swift { class QtStatusWidget; class QtNameWidget; class SettingsProvider; + class StatusCache; class QtRosterHeader : public QWidget { Q_OBJECT public: - QtRosterHeader(SettingsProvider* settings, QWidget* parent = NULL); + QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent = NULL); void setAvatar(const QString& path); void setJID(const QString& jid); diff --git a/Swift/QtUI/QtScaledAvatarCache.cpp b/Swift/QtUI/QtScaledAvatarCache.cpp index 6abff87..46ec2fc 100644 --- a/Swift/QtUI/QtScaledAvatarCache.cpp +++ b/Swift/QtUI/QtScaledAvatarCache.cpp @@ -14,6 +14,9 @@ #include <QPainter> #include <QByteArray> +#include <Swiften/Base/Log.h> +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { QtScaledAvatarCache::QtScaledAvatarCache(int size) : size(size) { @@ -31,18 +34,21 @@ QString QtScaledAvatarCache::getScaledAvatarPath(const QString& path) { QString targetFile = targetDir.absoluteFilePath(avatarFile.baseName()); if (!QFileInfo(targetFile).exists()) { QPixmap avatarPixmap; - avatarPixmap.load(path); - QPixmap maskedAvatar(avatarPixmap.size()); - maskedAvatar.fill(QColor(0, 0, 0, 0)); - QPainter maskPainter(&maskedAvatar); - maskPainter.setBrush(Qt::black); - maskPainter.drawRoundedRect(maskedAvatar.rect(), 25.0, 25.0, Qt::RelativeSize); - maskPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); - maskPainter.drawPixmap(0, 0, avatarPixmap); - maskPainter.end(); - - if (!maskedAvatar.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(targetFile, "PNG")) { - return path; + if (avatarPixmap.load(path)) { + QPixmap maskedAvatar(avatarPixmap.size()); + maskedAvatar.fill(QColor(0, 0, 0, 0)); + QPainter maskPainter(&maskedAvatar); + maskPainter.setBrush(Qt::black); + maskPainter.drawRoundedRect(maskedAvatar.rect(), 25.0, 25.0, Qt::RelativeSize); + maskPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + maskPainter.drawPixmap(0, 0, avatarPixmap); + maskPainter.end(); + + if (!maskedAvatar.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(targetFile, "PNG")) { + return path; + } + } else { + SWIFT_LOG(debug) << "Failed to load " << Q2PSTRING(path) << std::endl; } } return targetFile; diff --git a/Swift/QtUI/QtSettingsProvider.cpp b/Swift/QtUI/QtSettingsProvider.cpp index 64e88d4..a98cdf3 100644 --- a/Swift/QtUI/QtSettingsProvider.cpp +++ b/Swift/QtUI/QtSettingsProvider.cpp @@ -108,7 +108,7 @@ QSettings* QtSettingsProvider::getQSettings() { } void QtSettingsProvider::updatePermissions() { -#if !defined(Q_WS_WIN) && !defined(Q_WS_MAC) +#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) QFile file(settings_.fileName()); if (file.exists()) { file.setPermissions(QFile::ReadOwner|QFile::WriteOwner); diff --git a/Swift/QtUI/QtSingleWindow.cpp b/Swift/QtUI/QtSingleWindow.cpp new file mode 100644 index 0000000..c970296 --- /dev/null +++ b/Swift/QtUI/QtSingleWindow.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010-2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/QtSingleWindow.h> + + +#include <Swiften/Base/foreach.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/QtChatTabs.h> + +namespace Swift { + +static const QString SINGLE_WINDOW_GEOMETRY = QString("SINGLE_WINDOW_GEOMETRY"); +static const QString SINGLE_WINDOW_SPLITS = QString("SINGLE_WINDOW_SPLITS"); + +QtSingleWindow::QtSingleWindow(QtSettingsProvider* settings) : QSplitter() { + settings_ = settings; + QVariant geometryVariant = settings_->getQSettings()->value(SINGLE_WINDOW_GEOMETRY); + if (geometryVariant.isValid()) { + restoreGeometry(geometryVariant.toByteArray()); + } + connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int))); + restoreSplitters(); +} + +QtSingleWindow::~QtSingleWindow() { + +} + +void QtSingleWindow::addWidget(QWidget* widget) { + QtChatTabs* tabs = dynamic_cast<QtChatTabs*>(widget); + if (tabs) { + connect(tabs, SIGNAL(onTitleChanged(const QString&)), this, SLOT(handleTabsTitleChanged(const QString&))); + } + QSplitter::addWidget(widget); +} + +void QtSingleWindow::handleTabsTitleChanged(const QString& title) { + setWindowTitle(title); +} + +void QtSingleWindow::handleSplitterMoved(int, int) { + QList<QVariant> variantValues; + QList<int> intValues = sizes(); + foreach (int value, intValues) { + variantValues.append(QVariant(value)); + } + settings_->getQSettings()->setValue(SINGLE_WINDOW_SPLITS, QVariant(variantValues)); +} + +void QtSingleWindow::restoreSplitters() { + QList<QVariant> variantValues = settings_->getQSettings()->value(SINGLE_WINDOW_SPLITS).toList(); + QList<int> intValues; + foreach (QVariant value, variantValues) { + intValues.append(value.toInt()); + } + setSizes(intValues); +} + +void QtSingleWindow::insertAtFront(QWidget* widget) { + insertWidget(0, widget); + restoreSplitters(); +} + +void QtSingleWindow::handleGeometryChanged() { + settings_->getQSettings()->setValue(SINGLE_WINDOW_GEOMETRY, saveGeometry()); + +} + +void QtSingleWindow::resizeEvent(QResizeEvent*) { + handleGeometryChanged(); +} + +void QtSingleWindow::moveEvent(QMoveEvent*) { + handleGeometryChanged(); +} + +} diff --git a/Swift/QtUI/QtSingleWindow.h b/Swift/QtUI/QtSingleWindow.h new file mode 100644 index 0000000..b55b3c9 --- /dev/null +++ b/Swift/QtUI/QtSingleWindow.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010-2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QSplitter> + +namespace Swift { + class QtSettingsProvider; + + class QtSingleWindow : public QSplitter { + Q_OBJECT + public: + QtSingleWindow(QtSettingsProvider* settings); + virtual ~QtSingleWindow(); + void insertAtFront(QWidget* widget); + void addWidget(QWidget* widget); + protected: + void resizeEvent(QResizeEvent*); + void moveEvent(QMoveEvent*); + private slots: + void handleSplitterMoved(int, int); + void handleTabsTitleChanged(const QString& title); + private: + void handleGeometryChanged(); + void restoreSplitters(); + + private: + + QtSettingsProvider* settings_; + }; + +} + diff --git a/Swift/QtUI/QtSoundPlayer.cpp b/Swift/QtUI/QtSoundPlayer.cpp index 387c6f3..3f3782d 100644 --- a/Swift/QtUI/QtSoundPlayer.cpp +++ b/Swift/QtUI/QtSoundPlayer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -9,17 +9,20 @@ #include <QSound> #include <iostream> -#include "SwifTools/Application/ApplicationPathProvider.h" +#include <SwifTools/Application/ApplicationPathProvider.h> +#include <QtSwiftUtil.h> +#include <Swiften/Base/Path.h> namespace Swift { QtSoundPlayer::QtSoundPlayer(ApplicationPathProvider* applicationPathProvider) : applicationPathProvider(applicationPathProvider) { } -void QtSoundPlayer::playSound(SoundEffect sound) { +void QtSoundPlayer::playSound(SoundEffect sound, const std::string& soundResource) { + switch (sound) { case MessageReceived: - playSound("/sounds/message-received.wav"); + playSound(soundResource.empty() ? "/sounds/message-received.wav" : soundResource); break; } } @@ -27,7 +30,10 @@ void QtSoundPlayer::playSound(SoundEffect sound) { void QtSoundPlayer::playSound(const std::string& soundResource) { boost::filesystem::path resourcePath = applicationPathProvider->getResourcePath(soundResource); if (boost::filesystem::exists(resourcePath)) { - QSound::play(resourcePath.string().c_str()); + QSound::play(P2QSTRING(pathToString(resourcePath))); + } + else if (boost::filesystem::exists(soundResource)) { + QSound::play(P2QSTRING(soundResource)); } else { std::cerr << "Unable to find sound: " << soundResource << std::endl; diff --git a/Swift/QtUI/QtSoundPlayer.h b/Swift/QtUI/QtSoundPlayer.h index 6945f45..f8da392 100644 --- a/Swift/QtUI/QtSoundPlayer.h +++ b/Swift/QtUI/QtSoundPlayer.h @@ -19,7 +19,7 @@ namespace Swift { public: QtSoundPlayer(ApplicationPathProvider* applicationPathProvider); - void playSound(SoundEffect sound); + void playSound(SoundEffect sound, const std::string& soundResource); private: void playSound(const std::string& soundResource); diff --git a/Swift/QtUI/QtSpellCheckerWindow.cpp b/Swift/QtUI/QtSpellCheckerWindow.cpp new file mode 100644 index 0000000..db2b1e7 --- /dev/null +++ b/Swift/QtUI/QtSpellCheckerWindow.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swift/QtUI/QtSpellCheckerWindow.h" + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +#include <QCoreApplication> +#include <QFileDialog> +#include <QDir> +#include <QStringList> +#include <QTimer> + +namespace Swift { + +QtSpellCheckerWindow::QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent) : QDialog(parent) { + settings_ = settings; + ui_.setupUi(this); +#ifdef HAVE_HUNSPELL + ui_.hunspellOptions->show(); +#else + ui_.hunspellOptions->hide(); + QTimer::singleShot(0, this, SLOT(shrinkWindow())); +#endif + connect(ui_.spellChecker, SIGNAL(toggled(bool)), this, SLOT(handleChecker(bool))); + connect(ui_.cancel, SIGNAL(clicked()), this, SLOT(handleCancel())); + connect(ui_.apply, SIGNAL(clicked()), this, SLOT(handleApply())); + connect(ui_.pathButton, SIGNAL(clicked()), this, SLOT(handlePathButton())); + setFromSettings(); +} + +void QtSpellCheckerWindow::shrinkWindow() { + resize(0,0); +} + +void QtSpellCheckerWindow::setFromSettings() { + ui_.spellChecker->setChecked(settings_->getSetting(SettingConstants::SPELL_CHECKER)); + ui_.pathContent->setText(P2QSTRING(settings_->getSetting(SettingConstants::DICT_PATH))); + ui_.currentLanguageValue->setText(P2QSTRING(settings_->getSetting(SettingConstants::DICT_FILE))); + std::string currentPath = settings_->getSetting(SettingConstants::DICT_PATH); + QString filename = "*.dic"; + QDir dictDirectory = QDir(P2QSTRING(currentPath)); + QStringList files = dictDirectory.entryList(QStringList(filename), QDir::Files); + showFiles(files); + setEnabled(settings_->getSetting(SettingConstants::SPELL_CHECKER)); +} + +void QtSpellCheckerWindow::handleChecker(bool state) { + setEnabled(state); +} + +void QtSpellCheckerWindow::setEnabled(bool state) { + ui_.pathContent->setEnabled(state); + ui_.languageView->setEnabled(state); + ui_.pathButton->setEnabled(state); + ui_.pathLabel->setEnabled(state); + ui_.currentLanguage->setEnabled(state); + ui_.currentLanguageValue->setEnabled(state); + ui_.language->setEnabled(state); +} + +void QtSpellCheckerWindow::handleApply() { + settings_->storeSetting(SettingConstants::SPELL_CHECKER, ui_.spellChecker->isChecked()); + QList<QListWidgetItem* > selectedLanguage = ui_.languageView->selectedItems(); + if (!selectedLanguage.empty()) { + settings_->storeSetting(SettingConstants::DICT_FILE, Q2PSTRING((selectedLanguage.first())->text())); + } + this->done(0); +} + +void QtSpellCheckerWindow::handleCancel() { + this->done(0); +} + +void QtSpellCheckerWindow::handlePathButton() { + std::string currentPath = settings_->getSetting(SettingConstants::DICT_PATH); + QString dirpath = QFileDialog::getExistingDirectory(this, tr("Dictionary Path"), P2QSTRING(currentPath)); + if (dirpath != P2QSTRING(currentPath)) { + ui_.languageView->clear(); + settings_->storeSetting(SettingConstants::DICT_FILE, ""); + ui_.currentLanguageValue->setText(" "); + } + if (!dirpath.isEmpty()) { + if (!dirpath.endsWith("/")) { + dirpath.append("/"); + } + settings_->storeSetting(SettingConstants::DICT_PATH, Q2PSTRING(dirpath)); + QDir dictDirectory = QDir(dirpath); + ui_.pathContent->setText(dirpath); + QString filename = "*.dic"; + QStringList files = dictDirectory.entryList(QStringList(filename), QDir::Files); + showFiles(files); + } +} + +void QtSpellCheckerWindow::handlePersonalPathButton() { + std::string currentPath = settings_->getSetting(SettingConstants::PERSONAL_DICT_PATH); + QString filename = QFileDialog::getOpenFileName(this, tr("Select Personal Dictionary"), P2QSTRING(currentPath), tr("(*.dic")); + settings_->storeSetting(SettingConstants::PERSONAL_DICT_PATH, Q2PSTRING(filename)); +} + +void QtSpellCheckerWindow::showFiles(const QStringList& files) { + ui_.languageView->clear(); + for (int i = 0; i < files.size(); ++i) { + ui_.languageView->insertItem(i, files[i]); + } +} + +} diff --git a/Swift/QtUI/QtSpellCheckerWindow.h b/Swift/QtUI/QtSpellCheckerWindow.h new file mode 100644 index 0000000..7b63318 --- /dev/null +++ b/Swift/QtUI/QtSpellCheckerWindow.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "ui_QtSpellCheckerWindow.h" + +#include <QDialog> + +namespace Swift { + class SettingsProvider; + class QtSpellCheckerWindow : public QDialog, protected Ui::QtSpellCheckerWindow { + Q_OBJECT + public: + QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent = NULL); + public slots: + void handleChecker(bool state); + void handleCancel(); + void handlePathButton(); + void handlePersonalPathButton(); + void handleApply(); + private slots: + void shrinkWindow(); + private: + void setEnabled(bool state); + void setFromSettings(); + void showFiles(const QStringList& files); + SettingsProvider* settings_; + Ui::QtSpellCheckerWindow ui_; + }; +} diff --git a/Swift/QtUI/QtSpellCheckerWindow.ui b/Swift/QtUI/QtSpellCheckerWindow.ui new file mode 100644 index 0000000..b7f5161 --- /dev/null +++ b/Swift/QtUI/QtSpellCheckerWindow.ui @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtSpellCheckerWindow</class> + <widget class="QDialog" name="QtSpellCheckerWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>399</width> + <height>265</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="spellChecker"> + <property name="text"> + <string>Spell Checker Enabled</string> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="hunspellOptions" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="pathLabel"> + <property name="text"> + <string>Dictionary Path:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="pathContent"/> + </item> + <item> + <widget class="QPushButton" name="pathButton"> + <property name="text"> + <string>Change</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="currentLanguage"> + <property name="text"> + <string>Current Language:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="currentLanguageValue"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="language"> + <property name="text"> + <string>Language:</string> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="languageView"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="cancel"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="apply"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtStatusWidget.cpp b/Swift/QtUI/QtStatusWidget.cpp index 0e2731a..8cc366a 100644 --- a/Swift/QtUI/QtStatusWidget.cpp +++ b/Swift/QtUI/QtStatusWidget.cpp @@ -6,6 +6,10 @@ #include "QtStatusWidget.h" +#include <algorithm> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> + #include <QBoxLayout> #include <QComboBox> #include <QLabel> @@ -23,10 +27,20 @@ #include "Swift/QtUI/QtLineEdit.h" #include "Swift/QtUI/QtSwiftUtil.h" #include <Swift/Controllers/StatusUtil.h> +#include <Swift/Controllers/StatusCache.h> + +namespace lambda = boost::lambda; namespace Swift { -QtStatusWidget::QtStatusWidget(QWidget *parent) : QWidget(parent), editCursor_(Qt::IBeamCursor), viewCursor_(Qt::PointingHandCursor) { +QtStatusWidget::QtStatusWidget(StatusCache* statusCache, QWidget *parent) : QWidget(parent), statusCache_(statusCache), editCursor_(Qt::IBeamCursor), viewCursor_(Qt::PointingHandCursor) { + allTypes_.push_back(StatusShow::Online); + allTypes_.push_back(StatusShow::FFC); + allTypes_.push_back(StatusShow::Away); + allTypes_.push_back(StatusShow::XA); + allTypes_.push_back(StatusShow::DND); + allTypes_.push_back(StatusShow::None); + isClicking_ = false; connecting_ = false; setMaximumHeight(24); @@ -134,15 +148,42 @@ void QtStatusWidget::generateList() { item->setStatusTip(item->toolTip()); item->setData(Qt::UserRole, QVariant(type)); } + std::vector<StatusCache::PreviousStatus> previousStatuses = statusCache_->getMatches(Q2PSTRING(text), 8); + foreach (StatusCache::PreviousStatus savedStatus, previousStatuses) { + if (savedStatus.first.empty() || std::find_if(allTypes_.begin(), allTypes_.end(), + savedStatus.second == lambda::_1 && savedStatus.first == lambda::bind(&statusShowTypeToFriendlyName, lambda::_1)) != allTypes_.end()) { + continue; + } + QListWidgetItem* item = new QListWidgetItem(P2QSTRING(savedStatus.first), menu_); + item->setIcon(icons_[savedStatus.second]); + item->setToolTip(item->text()); + item->setStatusTip(item->toolTip()); + item->setData(Qt::UserRole, QVariant(savedStatus.second)); + } foreach (StatusShow::Type type, icons_.keys()) { + if (Q2PSTRING(text) == statusShowTypeToFriendlyName(type)) { + continue; + } QListWidgetItem* item = new QListWidgetItem(P2QSTRING(statusShowTypeToFriendlyName(type)), menu_); item->setIcon(icons_[type]); item->setToolTip(item->text()); item->setStatusTip(item->toolTip()); item->setData(Qt::UserRole, QVariant(type)); } + resizeMenu(); } +void QtStatusWidget::resizeMenu() { + int height = menu_->sizeHintForRow(0) * menu_->count(); + int marginLeft; + int marginTop; + int marginRight; + int marginBottom; + menu_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom); + height += marginTop + marginBottom; + + menu_->setGeometry(menu_->x(), menu_->y(), menu_->width(), height); +} void QtStatusWidget::handleClicked() { editing_ = true; @@ -159,18 +200,11 @@ void QtStatusWidget::handleClicked() { if (x + width > screenWidth) { x = screenWidth - width; } - std::vector<StatusShow::Type> types; - types.push_back(StatusShow::Online); - types.push_back(StatusShow::FFC); - types.push_back(StatusShow::Away); - types.push_back(StatusShow::XA); - types.push_back(StatusShow::DND); - types.push_back(StatusShow::None); - foreach (StatusShow::Type type, types) { - if (statusEdit_->text() == P2QSTRING(statusShowTypeToFriendlyName(type))) { + //foreach (StatusShow::Type type, allTypes_) { + // if (statusEdit_->text() == P2QSTRING(statusShowTypeToFriendlyName(type))) { statusEdit_->setText(""); - } - } + // } + //} generateList(); height = menu_->sizeHintForRow(0) * menu_->count(); @@ -197,7 +231,7 @@ void QtStatusWidget::viewMode() { disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*))); editing_ = false; menu_->hide(); - stack_->setCurrentIndex(0); + stack_->setCurrentIndex(0); } void QtStatusWidget::handleEditComplete() { @@ -205,6 +239,7 @@ void QtStatusWidget::handleEditComplete() { statusText_ = newStatusText_; viewMode(); emit onChangeStatusRequest(selectedStatusType_, statusText_); + statusCache_->addRecent(Q2PSTRING(statusText_), selectedStatusType_); } void QtStatusWidget::handleEditCancelled() { diff --git a/Swift/QtUI/QtStatusWidget.h b/Swift/QtUI/QtStatusWidget.h index 75bcf52..87e8d4a 100644 --- a/Swift/QtUI/QtStatusWidget.h +++ b/Swift/QtUI/QtStatusWidget.h @@ -22,10 +22,12 @@ class QMovie; namespace Swift { class QtLineEdit; class QtElidingLabel; + class StatusCache; + class QtStatusWidget : public QWidget { Q_OBJECT public: - QtStatusWidget(QWidget *parent); + QtStatusWidget(StatusCache* statusCache, QWidget *parent); ~QtStatusWidget(); StatusShow::Type getSelectedStatusShow(); void setStatusType(StatusShow::Type type); @@ -45,9 +47,11 @@ namespace Swift { void handleItemClicked(QListWidgetItem* item); static QString getNoMessage(); private: + void resizeMenu(); void viewMode(); void setNewToolTip(); //QComboBox *types_; + StatusCache* statusCache_; QStackedWidget* stack_; QLabel* statusIcon_; QtElidingLabel* statusTextLabel_; @@ -64,6 +68,7 @@ namespace Swift { QMovie* connectingMovie_; bool connecting_; static const QString NO_MESSAGE; + std::vector<StatusShow::Type> allTypes_; }; } diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 223f3ae..183f64d 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -1,74 +1,81 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtSwift.h" +#include <Swift/QtUI/QtSwift.h> #include <string> -#include <QSplitter> -#include <QFile> +#include <map> + #include <boost/bind.hpp> + +#include <QFile> #include <QMessageBox> #include <QApplication> #include <QMap> #include <qdebug.h> -#include <QtLoginWindow.h> -#include <QtChatTabs.h> -#include <QtSystemTray.h> -#include <QtSoundPlayer.h> -#include <QtSwiftUtil.h> -#include <QtUIFactory.h> -#include <QtChatWindowFactory.h> #include <Swiften/Base/Log.h> -#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> -#include <Swift/Controllers/Storages/FileStoragesFactory.h> -#include <SwifTools/Application/PlatformApplicationPathProvider.h> -#include <string> +#include <Swiften/Base/Path.h> #include <Swiften/Base/Platform.h> #include <Swiften/Elements/Presence.h> #include <Swiften/Client/Client.h> +#include <Swiften/Base/Paths.h> + +#include <SwifTools/Application/PlatformApplicationPathProvider.h> +#include <SwifTools/AutoUpdater/AutoUpdater.h> +#include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h> + +#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> +#include <Swift/Controllers/Storages/FileStoragesFactory.h> #include <Swift/Controllers/Settings/XMLSettingsProvider.h> #include <Swift/Controllers/Settings/SettingsProviderHierachy.h> #include <Swift/Controllers/MainController.h> #include <Swift/Controllers/ApplicationInfo.h> #include <Swift/Controllers/BuildVersion.h> -#include <SwifTools/AutoUpdater/AutoUpdater.h> -#include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h> -#include "Swiften/Base/Paths.h" +#include <Swift/Controllers/StatusCache.h> + +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtChatTabs.h> +#include <Swift/QtUI/QtSystemTray.h> +#include <Swift/QtUI/QtSoundPlayer.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUIFactory.h> +#include <Swift/QtUI/QtChatWindowFactory.h> +#include <Swift/QtUI/QtSingleWindow.h> #if defined(SWIFTEN_PLATFORM_WINDOWS) -#include "WindowsNotifier.h" +#include <Swift/QtUI/WindowsNotifier.h> #elif defined(HAVE_GROWL) -#include "SwifTools/Notifier/GrowlNotifier.h" +#include <SwifTools/Notifier/GrowlNotifier.h> #elif defined(SWIFTEN_PLATFORM_LINUX) -#include "FreeDesktopNotifier.h" +#include <Swift/QtUI/FreeDesktopNotifier.h> #else -#include "SwifTools/Notifier/NullNotifier.h" +#include <SwifTools/Notifier/NullNotifier.h> #endif #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "SwifTools/Dock/MacOSXDock.h" +#include <SwifTools/Dock/MacOSXDock.h> #else -#include "SwifTools/Dock/NullDock.h" +#include <SwifTools/Dock/NullDock.h> #endif #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "QtURIHandler.h" +#include <Swift/QtUI/QtURIHandler.h> #elif defined(SWIFTEN_PLATFORM_WIN32) #include <SwifTools/URIHandler/NullURIHandler.h> #else -#include "QtDBUSURIHandler.h" +#include <Swift/QtUI/QtDBUSURIHandler.h> #endif namespace Swift{ #if defined(SWIFTEN_PLATFORM_MACOSX) -#define SWIFT_APPCAST_URL "http://swift.im/appcast/swift-mac-dev.xml" +//#define SWIFT_APPCAST_URL "http://swift.im/appcast/swift-mac-dev.xml" #else -#define SWIFT_APPCAST_URL "" +//#define SWIFT_APPCAST_URL "" #endif po::options_description QtSwift::getOptionsDescription() { @@ -102,44 +109,46 @@ XMLSettingsProvider* QtSwift::loadSettingsFile(const QString& fileName) { return new XMLSettingsProvider(""); } -QMap<QString, QString> QtSwift::loadEmoticonsFile(const QString& fileName) { - QMap<QString, QString> emoticons; +void QtSwift::loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons) { QFile file(fileName); if (file.exists() && file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { QString line = file.readLine(); line.replace("\n", ""); line.replace("\r", ""); - qDebug() << "Parsing line : " << line; QStringList tokens = line.split(" "); if (tokens.size() == 2) { - emoticons[tokens[0]] = "file://" + tokens[1]; - qDebug() << "Adding mapping from " << tokens[0] << " to " << tokens[1]; + QString emoticonFile = tokens[1]; + if (!emoticonFile.startsWith(":/") && !emoticonFile.startsWith("qrc:/")) { + emoticonFile = "file://" + emoticonFile; + } + emoticons[Q2PSTRING(tokens[0])] = Q2PSTRING(emoticonFile); } } } - - return emoticons; } 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 { - splitter_ = NULL; - } QCoreApplication::setApplicationName(SWIFT_APPLICATION_NAME); QCoreApplication::setOrganizationName(SWIFT_ORGANIZATION_NAME); QCoreApplication::setOrganizationDomain(SWIFT_ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(buildVersion); qtSettings_ = new QtSettingsProvider(); - xmlSettings_ = loadSettingsFile(P2QSTRING((Paths::getExecutablePath() / "system-settings.xml").string())); + xmlSettings_ = loadSettingsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "system-settings.xml"))); settingsHierachy_ = new SettingsProviderHierachy(); settingsHierachy_->addProviderToTopOfStack(xmlSettings_); settingsHierachy_->addProviderToTopOfStack(qtSettings_); - QMap<QString, QString> emoticons = loadEmoticonsFile(P2QSTRING((Paths::getExecutablePath() / "emoticons.txt").string())); + std::map<std::string, std::string> emoticons; + loadEmoticonsFile(":/emoticons/emoticons.txt", emoticons); + loadEmoticonsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "emoticons.txt")), emoticons); + + if (options.count("netbook-mode")) { + splitter_ = new QtSingleWindow(qtSettings_); + } else { + splitter_ = NULL; + } int numberOfAccounts = 1; try { @@ -150,15 +159,15 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa } if (options.count("debug")) { - Swift::logging = true; + Log::setLogLevel(Swift::Log::debug); } - tabs_ = options.count("no-tabs") && !splitter_ ? NULL : new QtChatTabs(); + tabs_ = options.count("no-tabs") && !splitter_ ? NULL : new QtChatTabs(splitter_ != NULL); bool startMinimized = options.count("start-minimized") > 0; applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME); - storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir()); - certificateStorageFactory_ = new CertificateFileStorageFactory(applicationPathProvider_->getDataDir(), tlsFactories_.getCertificateFactory()); - chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, "", emoticons); + storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir(), networkFactories_.getCryptoProvider()); + certificateStorageFactory_ = new CertificateFileStorageFactory(applicationPathProvider_->getDataDir(), tlsFactories_.getCertificateFactory(), networkFactories_.getCryptoProvider()); + chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, ""); soundPlayer_ = new QtSoundPlayer(applicationPathProvider_); // Ugly, because the dock depends on the tray, but the temporary @@ -190,6 +199,8 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa uriHandler_ = new QtDBUSURIHandler(); #endif + statusCache_ = new StatusCache(applicationPathProvider_); + if (splitter_) { splitter_->show(); } @@ -199,7 +210,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa // Don't add the first tray (see note above) systemTrays_.push_back(new QtSystemTray()); } - QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), startMinimized, !emoticons.empty()); + QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), statusCache_, startMinimized, !emoticons.empty()); uiFactories_.push_back(uiFactory); MainController* mainController = new MainController( &clientMainThreadCaller_, @@ -214,6 +225,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa notifier_, uriHandler_, &idleDetector_, + emoticons, options.count("latency-debug") > 0); mainControllers_.push_back(mainController); } @@ -235,14 +247,15 @@ QtSwift::~QtSwift() { delete controller; } delete notifier_; - delete settingsHierachy_; - delete qtSettings_; - delete xmlSettings_; foreach (QtSystemTray* tray, systemTrays_) { delete tray; } delete tabs_; delete splitter_; + delete settingsHierachy_; + delete qtSettings_; + delete xmlSettings_; + delete statusCache_; delete uriHandler_; delete dock_; delete soundPlayer_; diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 42fb50f..1ea8886 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -50,6 +50,8 @@ namespace Swift { class URIHandler; class SettingsProviderHierachy; class XMLSettingsProvider; + class StatusCache; + class QtSingleWindow; class QtSwift : public QObject { Q_OBJECT @@ -59,7 +61,7 @@ namespace Swift { ~QtSwift(); private: XMLSettingsProvider* loadSettingsFile(const QString& fileName); - QMap<QString, QString> loadEmoticonsFile(const QString& fileName); + void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons); private: QtEventLoop clientMainThreadCaller_; PlatformTLSFactories tlsFactories_; @@ -71,7 +73,7 @@ namespace Swift { QtSettingsProvider* qtSettings_; XMLSettingsProvider* xmlSettings_; SettingsProviderHierachy* settingsHierachy_; - QSplitter* splitter_; + QtSingleWindow* splitter_; QtSoundPlayer* soundPlayer_; Dock* dock_; URIHandler* uriHandler_; @@ -81,6 +83,7 @@ namespace Swift { CertificateStorageFactory* certificateStorageFactory_; AutoUpdater* autoUpdater_; Notifier* notifier_; + StatusCache* statusCache_; PlatformIdleQuerier idleQuerier_; ActualIdleDetector idleDetector_; #if defined(SWIFTEN_PLATFORM_MACOSX) diff --git a/Swift/QtUI/QtSwiftUtil.h b/Swift/QtUI/QtSwiftUtil.h index 2d0f970..c903af1 100644 --- a/Swift/QtUI/QtSwiftUtil.h +++ b/Swift/QtUI/QtSwiftUtil.h @@ -9,4 +9,6 @@ #define P2QSTRING(a) QString::fromUtf8(a.c_str()) #define Q2PSTRING(a) std::string(a.toUtf8()) +#include <boost/date_time/posix_time/posix_time.hpp> + #define B2QDATE(a) QDateTime::fromTime_t((a - boost::posix_time::from_time_t(0)).total_seconds()) diff --git a/Swift/QtUI/QtSystemTray.cpp b/Swift/QtUI/QtSystemTray.cpp index 2f45709..456a56f 100644 --- a/Swift/QtUI/QtSystemTray.cpp +++ b/Swift/QtUI/QtSystemTray.cpp @@ -9,7 +9,7 @@ #include "Swift/QtUI/QtSystemTray.h" #include <QtDebug> -#ifdef Q_WS_X11 +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) #include <QDBusInterface> #endif #include <QIcon> @@ -24,7 +24,7 @@ QtSystemTray::QtSystemTray() : QObject(), trayMenu_(0), onlineIcon_(":icons/onli trayIcon_->setToolTip("Swift"); connect(trayIcon_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(handleIconActivated(QSystemTrayIcon::ActivationReason))); connect(&throbberMovie_, SIGNAL(frameChanged(int)), this, SLOT(handleThrobberFrameChanged(int))); -#ifdef Q_WS_X11 +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) bool isUnity = QDBusInterface("com.canonical.Unity.Launcher", "/com/canonical/Unity/Launcher").isValid(); if (isUnity) { // Add an activation menu, because this is the only way to get the application to the diff --git a/Swift/QtUI/QtTabbable.cpp b/Swift/QtUI/QtTabbable.cpp index 84a5100..5659157 100644 --- a/Swift/QtUI/QtTabbable.cpp +++ b/Swift/QtUI/QtTabbable.cpp @@ -4,15 +4,37 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtTabbable.h" +#include <Swift/QtUI/QtTabbable.h> #include <QApplication> +#include <QKeyEvent> -#include "QtChatTabs.h" +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Platform.h> + +#include <Swift/QtUI/QtChatTabs.h> namespace Swift { +namespace { +#ifdef SWIFTEN_PLATFORM_MACOSX +const Qt::KeyboardModifier ctrlHardwareKeyModifier = Qt::MetaModifier; +#else +const Qt::KeyboardModifier ctrlHardwareKeyModifier = Qt::ControlModifier; +#endif +} + +QtTabbable::QtTabbable() : QWidget() { + shortcuts << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), window(), SLOT(close())); + shortcuts << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageUp), window(), SIGNAL(requestPreviousTab())); + shortcuts << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageDown), window(), SIGNAL(requestNextTab())); + shortcuts << new QShortcut(QKeySequence(Qt::ALT + Qt::Key_A), window(), SIGNAL(requestActiveTab())); +} + QtTabbable::~QtTabbable() { + foreach (QShortcut* shortcut, shortcuts) { + delete shortcut; + } emit windowClosing(); } @@ -25,36 +47,20 @@ bool QtTabbable::isWidgetSelected() { return parent ? parent->getCurrentTab() == this : isAncestorOf(QApplication::focusWidget()); } -void QtTabbable::keyPressEvent(QKeyEvent *event) { - handleKeyPressEvent(event); -} - -void QtTabbable::handleKeyPressEvent(QKeyEvent *event) { - event->ignore(); - int key = event->key(); - Qt::KeyboardModifiers modifiers = event->modifiers(); - if (key == Qt::Key_W && modifiers == Qt::ControlModifier) { - close(); - event->accept(); - } else if ( - (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) -// || (key == Qt::Key_Left && modifiers == (Qt::ControlModifier & Qt::ShiftModifier)) - ) { - emit requestPreviousTab(); - event->accept(); - } else if ( - (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) -// || (key == Qt::Key_Right && modifiers == (Qt::ControlModifier & Qt::ShiftModifier) - || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) - ) { - emit requestNextTab(); - event->accept(); - } else if ( - (key == Qt::Key_A && modifiers == Qt::AltModifier) - ) { - emit requestActiveTab(); - event->accept(); +bool QtTabbable::event(QEvent* event) { + QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event); + if (keyEvent) { + // According to Qt's focus documentation, one can only override CTRL+TAB via reimplementing QWidget::event(). +#ifdef SWIFTEN_PLATFORM_LINUX + if (keyEvent->modifiers() == ctrlHardwareKeyModifier && keyEvent->key() == Qt::Key_Tab && event->type() != QEvent::KeyRelease) { +#else + if (keyEvent->modifiers() == ctrlHardwareKeyModifier && keyEvent->key() == Qt::Key_Tab) { +#endif + emit requestNextTab(); + return true; + } } + return QWidget::event(event); } } diff --git a/Swift/QtUI/QtTabbable.h b/Swift/QtUI/QtTabbable.h index baab15c..fc131ca 100644 --- a/Swift/QtUI/QtTabbable.h +++ b/Swift/QtUI/QtTabbable.h @@ -6,8 +6,9 @@ #pragma once -#include <QKeyEvent> #include <QWidget> +#include <QShortcut> +#include <QList> namespace Swift { @@ -15,16 +16,13 @@ namespace Swift { Q_OBJECT public: enum AlertType {NoActivity, WaitingActivity, ImpendingActivity}; - ~QtTabbable(); + virtual ~QtTabbable(); bool isWidgetSelected(); - virtual AlertType getWidgetAlertState() {return NoActivity;}; + virtual AlertType getWidgetAlertState() {return NoActivity;} virtual int getCount() {return 0;} protected: - QtTabbable() : QWidget() {}; - void keyPressEvent(QKeyEvent* event); - - protected slots: - void handleKeyPressEvent(QKeyEvent* event); + QtTabbable(); + bool event(QEvent* event); signals: void titleUpdated(); @@ -36,5 +34,8 @@ namespace Swift { void requestNextTab(); void requestActiveTab(); void requestFlash(); + + private: + QList<QShortcut*> shortcuts; }; } diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 3a62325..2c4677e 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -4,17 +4,41 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +#include <boost/tuple/tuple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> + +#include <SwifTools/SpellCheckerFactory.h> +#include <SwifTools/SpellChecker.h> + #include <Swift/QtUI/QtTextEdit.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSpellCheckerWindow.h> +#include <Swift/Controllers/SettingConstants.h> +#include <QApplication> #include <QFontMetrics> #include <QKeyEvent> +#include <QDebug> +#include <QMenu> namespace Swift { -QtTextEdit::QtTextEdit(QWidget* parent) : QTextEdit(parent){ +QtTextEdit::QtTextEdit(SettingsProvider* settings, QWidget* parent) : QTextEdit(parent) { connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); + checker_ = NULL; + settings_ = settings; +#ifdef HAVE_SPELLCHECKER + setUpSpellChecker(); +#endif handleTextChanged(); -}; +} + +QtTextEdit::~QtTextEdit() { + delete checker_; +} void QtTextEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); @@ -35,13 +59,41 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) { emit unhandledKeyPressEvent(event); } else if ((key == Qt::Key_Up) - || (key == Qt::Key_Down) - ){ + || (key == Qt::Key_Down)) { emit unhandledKeyPressEvent(event); QTextEdit::keyPressEvent(event); } else { QTextEdit::keyPressEvent(event); +#ifdef HAVE_SPELLCHECKER + if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { + underlineMisspells(); + } +#endif + } +} + +void QtTextEdit::underlineMisspells() { + QTextCursor cursor = textCursor(); + misspelledPositions_.clear(); + QTextCharFormat normalFormat; + cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor, 1); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 1); + cursor.setCharFormat(normalFormat); + if (checker_ == NULL) { + return; + } + QTextCharFormat spellingErrorFormat; + spellingErrorFormat.setUnderlineColor(QColor(Qt::red)); + spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + std::string fragment = Q2PSTRING(cursor.selectedText()); + checker_->checkFragment(fragment, misspelledPositions_); + foreach (PositionPair position, misspelledPositions_) { + cursor.setPosition(boost::get<0>(position), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(position), QTextCursor::KeepAnchor); + cursor.setCharFormat(spellingErrorFormat); + cursor.clearSelection(); + cursor.setCharFormat(normalFormat); } } @@ -53,6 +105,24 @@ void QtTextEdit::handleTextChanged() { } } +void QtTextEdit::replaceMisspelledWord(const QString& word, int cursorPosition) { + QTextCursor cursor = textCursor(); + PositionPair wordPosition = getWordFromCursor(cursorPosition); + cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor); + QTextCharFormat normalFormat; + cursor.insertText(word, normalFormat); +} + +PositionPair QtTextEdit::getWordFromCursor(int cursorPosition) { + for (PositionPairList::iterator it = misspelledPositions_.begin(); it != misspelledPositions_.end(); ++it) { + if (cursorPosition >= boost::get<0>(*it) && cursorPosition <= boost::get<1>(*it)) { + return *it; + } + } + return boost::make_tuple(-1,-1); +} + QSize QtTextEdit::sizeHint() const { QFontMetrics inputMetrics(currentFont()); QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999); @@ -66,7 +136,100 @@ QSize QtTextEdit::sizeHint() const { //return QSize(QTextEdit::sizeHint().width(), lineHeight * numberOfLines); } +void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) { + QMenu* menu = createStandardContextMenu(); + QTextCursor cursor = cursorForPosition(event->pos()); +#ifdef HAVE_SPELLCHECKER + QAction* insertPoint = menu->actions().first(); + QAction* settingsAction = new QAction(tr("Spell Checker Options"), menu); + menu->insertAction(insertPoint, settingsAction); + menu->insertAction(insertPoint, menu->addSeparator()); + addSuggestions(menu, event); + QAction* result = menu->exec(event->globalPos()); + if (result == settingsAction) { + spellCheckerSettingsWindow(); + } + for (std::vector<QAction*>::iterator it = replaceWordActions_.begin(); it != replaceWordActions_.end(); ++it) { + if (*it == result) { + replaceMisspelledWord((*it)->text(), cursor.position()); + } + } +#else + menu->exec(event->globalPos()); +#endif + delete menu; +} + +void QtTextEdit::addSuggestions(QMenu* menu, QContextMenuEvent* event) +{ + replaceWordActions_.clear(); + QAction* insertPoint = menu->actions().first(); + QTextCursor cursor = cursorForPosition(event->pos()); + PositionPair wordPosition = getWordFromCursor(cursor.position()); + if (boost::get<0>(wordPosition) < 0) { + // The click was executed outside a spellable word so no + // suggestions are necessary + return; + } + cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor); + std::vector<std::string> wordList; + checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList); + if (wordList.size() == 0) { + QAction* noSuggestions = new QAction(tr("No Suggestions"), menu); + noSuggestions->setDisabled(true); + menu->insertAction(insertPoint, noSuggestions); + } + else { + for (std::vector<std::string>::iterator it = wordList.begin(); it != wordList.end(); ++it) { + QAction* wordAction = new QAction(it->c_str(), menu); + menu->insertAction(insertPoint, wordAction); + replaceWordActions_.push_back(wordAction); + } + } + menu->insertAction(insertPoint, menu->addSeparator()); +} + + +#ifdef HAVE_SPELLCHECKER +void QtTextEdit::setUpSpellChecker() +{ + SpellCheckerFactory* checkerFactory = new SpellCheckerFactory(); + delete checker_; + if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { + std::string dictPath = settings_->getSetting(SettingConstants::DICT_PATH); + std::string dictFile = settings_->getSetting(SettingConstants::DICT_FILE); + checker_ = checkerFactory->createSpellChecker(dictPath + dictFile); + delete checkerFactory; + } + else { + checker_ = NULL; + } } +#endif +void QtTextEdit::spellCheckerSettingsWindow() { + if (!spellCheckerWindow_) { + spellCheckerWindow_ = new QtSpellCheckerWindow(settings_); + settings_->onSettingChanged.connect(boost::bind(&QtTextEdit::handleSettingChanged, this, _1)); + spellCheckerWindow_->show(); + } + else { + spellCheckerWindow_->show(); + spellCheckerWindow_->raise(); + spellCheckerWindow_->activateWindow(); + } +} +void QtTextEdit::handleSettingChanged(const std::string& settings) { + if (settings == SettingConstants::SPELL_CHECKER.getKey() + || settings == SettingConstants::DICT_PATH.getKey() + || settings == SettingConstants::DICT_FILE.getKey()) { +#ifdef HAVE_SPELLCHECKER + setUpSpellChecker(); + underlineMisspells(); +#endif + } +} +} diff --git a/Swift/QtUI/QtTextEdit.h b/Swift/QtUI/QtTextEdit.h index 075728b..a8df4d3 100644 --- a/Swift/QtUI/QtTextEdit.h +++ b/Swift/QtUI/QtTextEdit.h @@ -5,20 +5,46 @@ */ #pragma once + +#include <SwifTools/SpellParser.h> + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> + #include <QTextEdit> +#include <QPointer> namespace Swift { + class SpellChecker; + class QtSpellCheckerWindow; class QtTextEdit : public QTextEdit { Q_OBJECT public: - QtTextEdit(QWidget* parent = 0); + QtTextEdit(SettingsProvider* settings, QWidget* parent = 0); + virtual ~QtTextEdit(); virtual QSize sizeHint() const; signals: + void wordCorrected(QString& word); void returnPressed(); void unhandledKeyPressEvent(QKeyEvent* event); + public slots: + void handleSettingChanged(const std::string& settings); protected: virtual void keyPressEvent(QKeyEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); private slots: void handleTextChanged(); + private: + SpellChecker *checker_; + std::vector<QAction*> replaceWordActions_; + PositionPairList misspelledPositions_; + SettingsProvider *settings_; + QPointer<QtSpellCheckerWindow> spellCheckerWindow_; + void addSuggestions(QMenu* menu, QContextMenuEvent* event); + void replaceMisspelledWord(const QString& word, int cursorPosition); + void setUpSpellChecker(); + void underlineMisspells(); + void spellCheckerSettingsWindow(); + PositionPair getWordFromCursor(int cursorPosition); }; } diff --git a/Swift/QtUI/QtTranslator.h b/Swift/QtUI/QtTranslator.h index fdafaf0..a2129f0 100644 --- a/Swift/QtUI/QtTranslator.h +++ b/Swift/QtUI/QtTranslator.h @@ -16,6 +16,10 @@ class QtTranslator : public Swift::Translator { } virtual std::string translate(const std::string& text, const std::string& context) { +#if QT_VERSION >= 0x050000 + return std::string(QCoreApplication::translate(context.c_str(), text.c_str(), 0).toUtf8()); +#else return std::string(QCoreApplication::translate(context.c_str(), text.c_str(), 0, QCoreApplication::UnicodeUTF8).toUtf8()); +#endif } }; diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index a154fb0..e5db22d 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -4,36 +4,39 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtUIFactory.h" +#include <Swift/QtUI/QtUIFactory.h> #include <QSplitter> -#include "QtXMLConsoleWidget.h" -#include "QtChatTabs.h" -#include "QtMainWindow.h" -#include "QtLoginWindow.h" -#include "QtSystemTray.h" -#include "QtSettingsProvider.h" -#include "QtMainWindow.h" -#include "QtChatWindow.h" -#include "QtJoinMUCWindow.h" -#include "QtChatWindowFactory.h" -#include "QtSwiftUtil.h" -#include "MUCSearch/QtMUCSearchWindow.h" -#include "UserSearch/QtUserSearchWindow.h" -#include "QtProfileWindow.h" -#include "QtContactEditWindow.h" -#include "QtAdHocCommandWindow.h" -#include "QtFileTransferListWidget.h" -#include "Whiteboard/QtWhiteboardWindow.h" +#include <Swift/QtUI/QtXMLConsoleWidget.h> +#include <Swift/QtUI/QtChatTabs.h> +#include <Swift/QtUI/QtMainWindow.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtSystemTray.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/QtMainWindow.h> +#include <Swift/QtUI/QtChatWindow.h> +#include <Swift/QtUI/QtJoinMUCWindow.h> +#include <Swift/QtUI/QtChatWindowFactory.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/MUCSearch/QtMUCSearchWindow.h> +#include <Swift/QtUI/UserSearch/QtUserSearchWindow.h> +#include <Swift/QtUI/QtProfileWindow.h> +#include <Swift/QtUI/QtContactEditWindow.h> +#include <Swift/QtUI/QtAdHocCommandWindow.h> +#include <Swift/QtUI/QtFileTransferListWidget.h> +#include <Swift/QtUI/QtHighlightEditorWidget.h> +#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h> #include <Swift/Controllers/Settings/SettingsProviderHierachy.h> #include <Swift/QtUI/QtUISettingConstants.h> -#include <QtHistoryWindow.h> +#include <Swift/QtUI/QtHistoryWindow.h> #include <Swiften/Whiteboard/WhiteboardSession.h> +#include <Swift/QtUI/QtSingleWindow.h> +#include <Swift/QtUI/QtBlockListEditorWindow.h> namespace Swift { -QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) { +QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) { chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); } @@ -78,14 +81,14 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { } MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { - lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), emoticonsExist_); + lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_); return lastMainWindow; } LoginWindow* QtUIFactory::createLoginWindow(UIEventStream* eventStream) { loginWindow = new QtLoginWindow(eventStream, settings, timerFactory_); if (netbookSplitter) { - netbookSplitter->insertWidget(0, loginWindow); + netbookSplitter->insertAtFront(loginWindow); } connect(systemTray, SIGNAL(clicked()), loginWindow, SLOT(toggleBringToFront())); @@ -142,8 +145,8 @@ void QtUIFactory::handleChatWindowFontResized(int size) { } UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) { - return new QtUserSearchWindow(eventStream, type, groups); -}; + return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings); +} JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) { return new QtJoinMUCWindow(uiEventStream); @@ -161,6 +164,14 @@ WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<Whiteboa return new QtWhiteboardWindow(whiteboardSession); } +HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() { + return new QtHighlightEditorWidget(); +} + +BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { + return new QtBlockListEditorWindow(); +} + void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) { new QtAdHocCommandWindow(command); } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 30f0101..662c78e 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -26,11 +26,13 @@ namespace Swift { class TimerFactory; class historyWindow_; class WhiteboardSession; + class StatusCache; + class QtSingleWindow; class QtUIFactory : public QObject, public UIFactory { Q_OBJECT public: - QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist); + QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist); virtual XMLConsoleWidget* createXMLConsoleWidget(); virtual HistoryWindow* createHistoryWindow(UIEventStream*); @@ -46,6 +48,8 @@ namespace Swift { virtual ContactEditWindow* createContactEditWindow(); virtual FileTransferListWidget* createFileTransferListWidget(); virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession); + virtual HighlightEditorWidget* createHighlightEditorWidget(); + virtual BlockListEditorWidget* createBlockListEditorWidget(); virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); private slots: @@ -57,12 +61,13 @@ namespace Swift { SettingsProviderHierachy* settings; QtSettingsProvider* qtOnlySettings; QtChatTabs* tabs; - QSplitter* netbookSplitter; + QtSingleWindow* netbookSplitter; QtSystemTray* systemTray; QtChatWindowFactory* chatWindowFactory; TimerFactory* timerFactory_; QtMainWindow* lastMainWindow; QtLoginWindow* loginWindow; + StatusCache* statusCache; std::vector<QPointer<QtChatWindow> > chatWindows; bool startMinimized; int chatFontSize; diff --git a/Swift/QtUI/QtURLValidator.cpp b/Swift/QtUI/QtURLValidator.cpp index 2df59c4..4d56b98 100644 --- a/Swift/QtUI/QtURLValidator.cpp +++ b/Swift/QtUI/QtURLValidator.cpp @@ -10,11 +10,11 @@ #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtURLValidator::QtURLValidator(QObject* parent) { +QtURLValidator::QtURLValidator(QObject* parent) : QValidator(parent) { } -QValidator::State QtURLValidator::validate(QString& input, int& pos) const { +QValidator::State QtURLValidator::validate(QString& input, int&) const { URL url = URL::fromString(Q2PSTRING(input)); bool valid = !url.isEmpty(); valid &= (url.getScheme() == "http" || url.getScheme() == "https"); diff --git a/Swift/QtUI/QtUtilities.cpp b/Swift/QtUI/QtUtilities.cpp index be9d179..e9aa4a4 100644 --- a/Swift/QtUI/QtUtilities.cpp +++ b/Swift/QtUI/QtUtilities.cpp @@ -1,13 +1,14 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include "QtUtilities.h" +#include <QTextDocument> #include <QWidget> -#ifdef Q_WS_X11 +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) #include <QX11Info> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -18,7 +19,7 @@ namespace QtUtilities { void setX11Resource(QWidget* widget, const QString& c) { -#ifdef Q_WS_X11 +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) char res_class[] = SWIFT_APPLICATION_NAME; XClassHint hint; hint.res_name = (QString(SWIFT_APPLICATION_NAME) + "-" + c).toUtf8().data(); @@ -30,4 +31,12 @@ void setX11Resource(QWidget* widget, const QString& c) { #endif } +QString htmlEscape(const QString& s) { +#if QT_VERSION >= 0x050000 + return s.toHtmlEscaped(); +#else + return Qt::escape(s); +#endif +} + } diff --git a/Swift/QtUI/QtUtilities.h b/Swift/QtUI/QtUtilities.h index 6e64d6e..40c16bc 100644 --- a/Swift/QtUI/QtUtilities.h +++ b/Swift/QtUI/QtUtilities.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,4 +11,5 @@ class QString; namespace QtUtilities { void setX11Resource(QWidget* widget, const QString& c); -}; + QString htmlEscape(const QString& s); +} diff --git a/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp b/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp new file mode 100644 index 0000000..ebd62bc --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtCloseButton.h" + +#include <QMouseEvent> +#include <QPainter> +#include <QStyle> +#include <QStyleOption> + +namespace Swift { + +QtCloseButton::QtCloseButton(QWidget *parent) : QAbstractButton(parent) { + +} + +QSize QtCloseButton::sizeHint() const { + return QSize(style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0), style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, 0)); +} + +bool QtCloseButton::event(QEvent *e) { + if (e->type() == QEvent::Enter || e->type() == QEvent::Leave) { + update(); + } + return QAbstractButton::event(e); +} + +void QtCloseButton::paintEvent(QPaintEvent *) { + QPainter painter(this); + painter.setRenderHint(QPainter::HighQualityAntialiasing); + QStyleOption opt; + opt.init(this); + opt.state |= QStyle::State_AutoRaise; + if (underMouse() && !isDown()) { + opt.state |= QStyle::State_Raised; + } else if (isDown()) { + opt.state |= QStyle::State_Sunken; + } + style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &painter, this); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtCloseButton.h b/Swift/QtUI/QtVCardWidget/QtCloseButton.h new file mode 100644 index 0000000..cb92e12 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtCloseButton.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QAbstractButton> + +namespace Swift { + + class QtCloseButton : public QAbstractButton { + Q_OBJECT + public: + explicit QtCloseButton(QWidget *parent = 0); + virtual QSize sizeHint() const; + + protected: + virtual bool event(QEvent *e); + virtual void paintEvent(QPaintEvent* ); + }; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp new file mode 100644 index 0000000..1cae00a --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtRemovableItemDelegate.h" +#include <Swiften/Base/Platform.h> +#include <QEvent> +#include <QPainter> + +namespace Swift { + +QtRemovableItemDelegate::QtRemovableItemDelegate(const QStyle* style) : style(style) { + +} + +void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const { + QStyleOption opt; + opt.state = option.state; + opt.state |= QStyle::State_AutoRaise; + if (option.state.testFlag(QStyle::State_MouseOver)) { + opt.state |= QStyle::State_Raised; + } + opt.rect = option.rect; + painter->save(); + painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base()); +#ifdef SWIFTEN_PLATFORM_MACOSX + // workaround for Qt not painting relative to the cell we're in, on OS X + int height = style->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0); + painter->translate(option.rect.x(), option.rect.y() + (option.rect.height() - height)/2); +#endif + style->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, painter); + painter->restore(); +} + +QWidget* QtRemovableItemDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { + return NULL; +} + +bool QtRemovableItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { + if (event->type() == QEvent::MouseButtonRelease) { + model->removeRow(index.row()); + return true; + } else { + return QItemDelegate::editorEvent(event, model, option, index); + } +} + +QSize QtRemovableItemDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const { + QSize size(style->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0) + 2, style->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, 0) + 2); + return size; +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h new file mode 100644 index 0000000..75137e1 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QItemDelegate> + +namespace Swift { + +class QtRemovableItemDelegate : public QItemDelegate { + public: + QtRemovableItemDelegate(const QStyle* style); + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const; + virtual QWidget* createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const; + virtual QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const; + + protected: + virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); + + private: + const QStyle* style; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp new file mode 100644 index 0000000..4f1d3ab --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtResizableLineEdit.h" + +namespace Swift { + +QtResizableLineEdit::QtResizableLineEdit(QWidget* parent) : + QLineEdit(parent) { + connect(this, SIGNAL(textChanged(QString)), SLOT(textChanged(QString))); + setMinimumWidth(30); +} + +QtResizableLineEdit::~QtResizableLineEdit() { +} + +bool QtResizableLineEdit::isEditable() const { + return editable; +} +void QtResizableLineEdit::setEditable(const bool editable) { + this->editable = editable; + if (editable) { + setReadOnly(false); + } else { + setReadOnly(true); + } +} + + +QSize QtResizableLineEdit::sizeHint() const { + int horizontalMargin = 10; + int verticalMargin = 6; + QSize textDimensions; +#if QT_VERSION >= 0x040700 + textDimensions = fontMetrics().boundingRect(text().isEmpty() ? placeholderText() : text()).size(); +#else + textDimensions = fontMetrics().boundingRect(text().isEmpty() ? QString(" ") : text()).size(); +#endif + textDimensions.setWidth(textDimensions.width() + horizontalMargin); + textDimensions.setHeight(textDimensions.height() + verticalMargin); + return textDimensions; +} + +void QtResizableLineEdit::textChanged(QString) { + updateGeometry(); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h new file mode 100644 index 0000000..9022d38 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QLineEdit> + +namespace Swift { + + class QtResizableLineEdit : public QLineEdit { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + + public: + explicit QtResizableLineEdit(QWidget* parent = 0); + ~QtResizableLineEdit(); + + bool isEditable() const; + void setEditable(const bool); + + virtual QSize sizeHint() const; + + private slots: + void textChanged(QString); + + private: + bool editable; + }; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp b/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp new file mode 100644 index 0000000..bade009 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtTagComboBox.h" + +#include <QAbstractItemView> +#include <QtGui> + +namespace Swift { + +QtTagComboBox::QtTagComboBox(QWidget* parent) : QComboBox(parent) { + setEditable(false); + displayModel = new QStandardItemModel(); + displayItem = new QStandardItem(); + displayItem->setText(""); + displayModel->insertRow(0, displayItem); + editMenu = new QMenu(); + this->setModel(displayModel); + editable = true; +} + +QtTagComboBox::~QtTagComboBox() { + +} + +bool QtTagComboBox::isEditable() const { + return editable; +} + +void QtTagComboBox::setEditable(const bool editable) { + this->editable = editable; +} + +void QtTagComboBox::addTag(const QString &id, const QString &label) { + QAction* tagAction = new QAction(editMenu); + tagAction->setText(label); + tagAction->setCheckable(true); + tagAction->setData(QString(id)); + editMenu->addAction(tagAction); +} + +void QtTagComboBox::setTag(const QString &id, bool value) { + QList<QAction*> tagActions = editMenu->actions(); + foreach(QAction* action, tagActions) { + if (action->data() == id) { + action->setChecked(value); + updateDisplayItem(); + return; + } + } +} + +bool QtTagComboBox::isTagSet(const QString &id) const { + QList<QAction*> tagActions = editMenu->actions(); + foreach(QAction* action, tagActions) { + if (action->data() == id) { + return action->isChecked(); + } + } + return false; +} + +void QtTagComboBox::showPopup() { + +} + +void QtTagComboBox::hidePopup() { + +} + +bool QtTagComboBox::event(QEvent* event) { + if (event->type() == QEvent::MouseButtonPress || + event->type() == QEvent::KeyRelease) { + if (!editable) return true; + + QPoint p = mapToGlobal(QPoint(0,0)); + p += QPoint(0, height()); + editMenu->exec(p); + updateDisplayItem(); + return true; + } + return QComboBox::event(event); +} + +void QtTagComboBox::updateDisplayItem() { + QList<QAction*> tagActions = editMenu->actions(); + QString text = ""; + foreach(QAction* action, tagActions) { + if (action->isChecked()) { + if (text != "") { + text += ", "; + } + text += action->text(); + } + } + setItemText(0, text); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtTagComboBox.h b/Swift/QtUI/QtVCardWidget/QtTagComboBox.h new file mode 100644 index 0000000..37a60af --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtTagComboBox.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QComboBox> +#include <QMenu> +#include <QStandardItem> +#include <QStandardItemModel> + +namespace Swift { + +class QtTagComboBox : public QComboBox { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + + public: + explicit QtTagComboBox(QWidget* parent = 0); + ~QtTagComboBox(); + + bool isEditable() const; + void setEditable(const bool); + + void addTag(const QString& id, const QString& label); + void setTag(const QString& id, bool value); + bool isTagSet(const QString& id) const; + + virtual void showPopup(); + virtual void hidePopup(); + + virtual bool event(QEvent* event); + + private: + void updateDisplayItem(); + + private: + bool editable; + QStandardItemModel* displayModel; + QStandardItem* displayItem; + QMenu* editMenu; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp new file mode 100644 index 0000000..f394af0 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardAddressField.h" + +#include <QGridLayout> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardAddressField::QtVCardAddressField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Address")) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardAddressField::~QtVCardAddressField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardAddressField::setupContentWidgets() { + textFieldGridLayout = new QGridLayout(); + + streetLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(streetLineEdit, 0, 0, Qt::AlignVCenter); + + poboxLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(poboxLineEdit, 0, 1, Qt::AlignVCenter); + + addressextLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(addressextLineEdit, 1, 0, Qt::AlignVCenter); + + cityLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(cityLineEdit, 2, 0, Qt::AlignVCenter); + + pocodeLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(pocodeLineEdit, 2, 1, Qt::AlignVCenter); + + regionLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(regionLineEdit, 3, 0, Qt::AlignVCenter); + + countryLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(countryLineEdit, 4, 0, Qt::AlignVCenter); + textFieldGridLayout->setVerticalSpacing(2); + getGridLayout()->addLayout(textFieldGridLayout, getGridLayout()->rowCount()-1, 2, 5, 2, Qt::AlignVCenter); + textFieldGridLayoutItem = getGridLayout()->itemAtPosition(getGridLayout()->rowCount()-1, 2); + +#if QT_VERSION >= 0x040700 + streetLineEdit->setPlaceholderText(tr("Street")); + poboxLineEdit->setPlaceholderText(tr("PO Box")); + addressextLineEdit->setPlaceholderText(tr("Address Extension")); + cityLineEdit->setPlaceholderText(tr("City")); + pocodeLineEdit->setPlaceholderText(tr("Postal Code")); + regionLineEdit->setPlaceholderText(tr("Region")); + countryLineEdit->setPlaceholderText(tr("Country")); +#endif + + deliveryTypeLabel = new QLabel(this); + deliveryTypeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + getGridLayout()->addWidget(deliveryTypeLabel, getGridLayout()->rowCount()-3, 4, Qt::AlignVCenter); + + domesticRadioButton = new QRadioButton(tr("Domestic Delivery"), this); + getGridLayout()->addWidget(domesticRadioButton, getGridLayout()->rowCount()-2, 4, Qt::AlignVCenter); + + internationalRadioButton = new QRadioButton(tr("International Delivery"), this); + getGridLayout()->addWidget(internationalRadioButton, getGridLayout()->rowCount()-1, 4, Qt::AlignVCenter); + + buttonGroup = new QButtonGroup(this); + buttonGroup->addButton(domesticRadioButton); + buttonGroup->addButton(internationalRadioButton); + + setTabOrder(internationalRadioButton, getTagComboBox()); + getTagComboBox()->addTag("postal", tr("Postal")); + getTagComboBox()->addTag("parcel", tr("Parcel")); + + QtVCardHomeWork::setTagComboBox(getTagComboBox()); + + textFields << streetLineEdit << poboxLineEdit << addressextLineEdit << cityLineEdit << pocodeLineEdit << regionLineEdit << countryLineEdit; + childWidgets << deliveryTypeLabel << domesticRadioButton << internationalRadioButton; +} + +void QtVCardAddressField::customCleanup() { + foreach(QWidget* widget, textFields) { + widget->hide(); + textFieldGridLayout->removeWidget(widget); + } + getGridLayout()->removeItem(textFieldGridLayoutItem); +} + + + +bool QtVCardAddressField::isEmpty() const { + return streetLineEdit->text().isEmpty() && + poboxLineEdit->text().isEmpty() && + addressextLineEdit->text().isEmpty() && + cityLineEdit->text().isEmpty() && + pocodeLineEdit->text().isEmpty() && + regionLineEdit->text().isEmpty() && + countryLineEdit->text().isEmpty(); +} + +void QtVCardAddressField::setAddress(const VCard::Address& address) { + setPreferred(address.isPreferred); + setHome(address.isHome); + setWork(address.isWork); + getTagComboBox()->setTag("postal", address.isPostal); + getTagComboBox()->setTag("parcel", address.isParcel); + domesticRadioButton->setChecked(address.deliveryType == VCard::DomesticDelivery); + internationalRadioButton->setChecked(address.deliveryType == VCard::InternationalDelivery); + streetLineEdit->setText(P2QSTRING(address.street)); + poboxLineEdit->setText(P2QSTRING(address.poBox)); + addressextLineEdit->setText(P2QSTRING(address.addressExtension)); + cityLineEdit->setText(P2QSTRING(address.locality)); + pocodeLineEdit->setText(P2QSTRING(address.postalCode)); + regionLineEdit->setText(P2QSTRING(address.region)); + countryLineEdit->setText(P2QSTRING(address.country)); +} + +VCard::Address QtVCardAddressField::getAddress() const { + VCard::Address address; + address.isPreferred = getPreferred(); + address.isHome = getHome(); + address.isWork = getWork(); + address.deliveryType = domesticRadioButton->isChecked() ? VCard::DomesticDelivery : (internationalRadioButton->isChecked() ? VCard::InternationalDelivery : VCard::None); + address.isPostal = getTagComboBox()->isTagSet("postal"); + address.isParcel = getTagComboBox()->isTagSet("parcel"); + address.street = Q2PSTRING(streetLineEdit->text()); + address.poBox = Q2PSTRING(poboxLineEdit->text()); + address.addressExtension = Q2PSTRING(addressextLineEdit->text()); + address.locality = Q2PSTRING(cityLineEdit->text()); + address.postalCode = Q2PSTRING(pocodeLineEdit->text()); + address.region = Q2PSTRING(regionLineEdit->text()); + address.country = Q2PSTRING(countryLineEdit->text()); + return address; +} + +void QtVCardAddressField::handleEditibleChanged(bool isEditable) { + assert(streetLineEdit); + assert(poboxLineEdit); + assert(addressextLineEdit); + assert(cityLineEdit); + assert(pocodeLineEdit); + assert(regionLineEdit); + assert(countryLineEdit); + assert(deliveryTypeLabel); + assert(domesticRadioButton); + assert(internationalRadioButton); + + streetLineEdit->setEditable(isEditable); + poboxLineEdit->setEditable(isEditable); + addressextLineEdit->setEditable(isEditable); + cityLineEdit->setEditable(isEditable); + pocodeLineEdit->setEditable(isEditable); + regionLineEdit->setEditable(isEditable); + countryLineEdit->setEditable(isEditable); + + deliveryTypeLabel->setText(buttonGroup->checkedButton() == 0 ? "" : buttonGroup->checkedButton()->text()); + deliveryTypeLabel->setVisible(!isEditable); + + domesticRadioButton->setVisible(isEditable); + internationalRadioButton->setVisible(isEditable); + + foreach (QWidget* widget, textFields) { + QtResizableLineEdit* lineEdit; + if ((lineEdit = dynamic_cast<QtResizableLineEdit*>(widget))) { + lineEdit->setVisible(isEditable ? true : !lineEdit->text().isEmpty()); + lineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); + } + } +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardAddressField.h b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.h new file mode 100644 index 0000000..5a1256a --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include <QButtonGroup> +#include <QRadioButton> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" +#include "QtVCardHomeWork.h" + +namespace Swift { + +class QtVCardAddressField : public QtVCardGeneralField, public QtVCardHomeWork { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Address", UNLIMITED_INSTANCES, QtVCardAddressField) + + QtVCardAddressField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardAddressField(); + + virtual bool isEmpty() const; + + void setAddress(const VCard::Address& address); + VCard::Address getAddress() const; + + protected: + virtual void setupContentWidgets(); + virtual void customCleanup(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QList<QWidget*> textFields; + QtResizableLineEdit* streetLineEdit; + QtResizableLineEdit* poboxLineEdit; + QtResizableLineEdit* addressextLineEdit; + QtResizableLineEdit* cityLineEdit; + QtResizableLineEdit* pocodeLineEdit; + QtResizableLineEdit* regionLineEdit; + QtResizableLineEdit* countryLineEdit; + QGridLayout* textFieldGridLayout; + QLayoutItem* textFieldGridLayoutItem; + + QLabel* deliveryTypeLabel; + QRadioButton* domesticRadioButton; + QRadioButton* internationalRadioButton; + QButtonGroup* buttonGroup; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp new file mode 100644 index 0000000..98e313f --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardAddressLabelField.h" + +#include <QGridLayout> +#include <boost/algorithm/string.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardAddressLabelField::QtVCardAddressLabelField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Address Label")) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardAddressLabelField::~QtVCardAddressLabelField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardAddressLabelField::setupContentWidgets() { + addressLabelPlainTextEdit = new QPlainTextEdit(this); + addressLabelPlainTextEdit->setTabChangesFocus(true); + getGridLayout()->addWidget(addressLabelPlainTextEdit, getGridLayout()->rowCount()-1, 2, 3, 2, Qt::AlignVCenter); + + deliveryTypeLabel = new QLabel(this); + deliveryTypeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + getGridLayout()->addWidget(deliveryTypeLabel, getGridLayout()->rowCount()-2, 4, Qt::AlignVCenter); + + domesticRadioButton = new QRadioButton(tr("Domestic Delivery"), this); + getGridLayout()->addWidget(domesticRadioButton, getGridLayout()->rowCount()-2, 4, Qt::AlignVCenter); + + internationalRadioButton = new QRadioButton(tr("International Delivery"), this); + getGridLayout()->addWidget(internationalRadioButton, getGridLayout()->rowCount()-1, 4, Qt::AlignVCenter); + + buttonGroup = new QButtonGroup(this); + buttonGroup->addButton(domesticRadioButton); + buttonGroup->addButton(internationalRadioButton); + + setTabOrder(internationalRadioButton, getTagComboBox()); + getTagComboBox()->addTag("postal", tr("Postal")); + getTagComboBox()->addTag("parcel", tr("Parcel")); + + QtVCardHomeWork::setTagComboBox(getTagComboBox()); + deliveryTypeLabel->hide(); + childWidgets << addressLabelPlainTextEdit << deliveryTypeLabel << domesticRadioButton << internationalRadioButton; +} + +bool QtVCardAddressLabelField::isEmpty() const { + return addressLabelPlainTextEdit->toPlainText().isEmpty(); +} + +void QtVCardAddressLabelField::setAddressLabel(const VCard::AddressLabel& addressLabel) { + setPreferred(addressLabel.isPreferred); + setHome(addressLabel.isHome); + setWork(addressLabel.isWork); + getTagComboBox()->setTag("postal", addressLabel.isPostal); + getTagComboBox()->setTag("parcel", addressLabel.isParcel); + domesticRadioButton->setChecked(addressLabel.deliveryType == VCard::DomesticDelivery); + internationalRadioButton->setChecked(addressLabel.deliveryType == VCard::InternationalDelivery); + std::string joinedLines = boost::algorithm::join(addressLabel.lines, "\n"); + addressLabelPlainTextEdit->setPlainText(P2QSTRING(joinedLines)); +} + +VCard::AddressLabel QtVCardAddressLabelField::getAddressLabel() const { + VCard::AddressLabel addressLabel; + addressLabel.isPreferred = getPreferred(); + addressLabel.isHome = getHome(); + addressLabel.isWork = getWork(); + addressLabel.deliveryType = domesticRadioButton->isChecked() ? VCard::DomesticDelivery : (internationalRadioButton->isChecked() ? VCard::InternationalDelivery : VCard::None); + addressLabel.isPostal = getTagComboBox()->isTagSet("postal"); + addressLabel.isParcel = getTagComboBox()->isTagSet("parcel"); + + std::string lines = Q2PSTRING(addressLabelPlainTextEdit->toPlainText()); + boost::split(addressLabel.lines, lines, boost::is_any_of("\n")); + return addressLabel; +} + +void QtVCardAddressLabelField::handleEditibleChanged(bool isEditable) { + assert(addressLabelPlainTextEdit); + assert(deliveryTypeLabel); + assert(domesticRadioButton); + assert(internationalRadioButton); + + addressLabelPlainTextEdit->setReadOnly(!isEditable); + addressLabelPlainTextEdit->setStyleSheet(isEditable ? "" : "QPlainTextEdit { background: transparent; }"); + + deliveryTypeLabel->setText(buttonGroup->checkedButton() == 0 ? "" : buttonGroup->checkedButton()->text()); + deliveryTypeLabel->setVisible(!isEditable); + + domesticRadioButton->setVisible(isEditable); + internationalRadioButton->setVisible(isEditable); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h new file mode 100644 index 0000000..a665d31 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QButtonGroup> +#include <QPlainTextEdit> +#include <QRadioButton> + +#include <Swiften/Elements/VCard.h> + +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h> + +namespace Swift { + +class QtVCardAddressLabelField : public QtVCardGeneralField, public QtVCardHomeWork { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Address Label", UNLIMITED_INSTANCES, QtVCardAddressLabelField) + + QtVCardAddressLabelField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardAddressLabelField(); + + virtual bool isEmpty() const; + + void setAddressLabel(const VCard::AddressLabel& addressLabel); + VCard::AddressLabel getAddressLabel() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QPlainTextEdit* addressLabelPlainTextEdit; + + QLabel* deliveryTypeLabel; + QRadioButton* domesticRadioButton; + QRadioButton* internationalRadioButton; + QButtonGroup* buttonGroup; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp new file mode 100644 index 0000000..2afc2f6 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardBirthdayField.h" + +#include <QGridLayout> +#include <QHBoxLayout> +#include <boost/algorithm/string.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardBirthdayField::QtVCardBirthdayField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Birthday"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardBirthdayField::~QtVCardBirthdayField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardBirthdayField::setupContentWidgets() { + birthdayLabel = new QLabel(this); + birthdayLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + birthdayDateEdit = new QDateEdit(this); + birthdayDateEdit->setCalendarPopup(true); + + QHBoxLayout* birthdayLayout = new QHBoxLayout(); + birthdayLayout->addWidget(birthdayLabel); + birthdayLayout->addWidget(birthdayDateEdit); + + getGridLayout()->addLayout(birthdayLayout, getGridLayout()->rowCount()-1, 2, Qt::AlignVCenter); + + getTagComboBox()->hide(); + birthdayLabel->hide(); + childWidgets << birthdayLabel << birthdayDateEdit; +} + +bool QtVCardBirthdayField::isEmpty() const { + return false; +} + +void QtVCardBirthdayField::setBirthday(const boost::posix_time::ptime& birthday) { + birthdayDateEdit->setDate(B2QDATE(birthday).date()); +} + +boost::posix_time::ptime QtVCardBirthdayField::getBirthday() const { + return boost::posix_time::from_time_t(QDateTime(birthdayDateEdit->date()).toTime_t()); +} + +void QtVCardBirthdayField::handleEditibleChanged(bool isEditable) { + birthdayLabel->setText(birthdayDateEdit->date().toString(Qt::DefaultLocaleLongDate)); + birthdayDateEdit->setVisible(isEditable); + birthdayLabel->setVisible(!isEditable); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h new file mode 100644 index 0000000..4be6e27 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QDateEdit> +#include <Swiften/Elements/VCard.h> + +#include "QtCloseButton.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" + +namespace Swift { + +class QtVCardBirthdayField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Birthday", 1, QtVCardBirthdayField) + + QtVCardBirthdayField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardBirthdayField(); + + virtual bool isEmpty() const; + + void setBirthday(const boost::posix_time::ptime& addressLabel); + boost::posix_time::ptime getBirthday() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QLabel* birthdayLabel; + QDateEdit* birthdayDateEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp new file mode 100644 index 0000000..f16c351 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardDescriptionField.h" + +#include <boost/algorithm/string.hpp> +#include <QFontMetrics> +#include <QGridLayout> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardDescriptionField::QtVCardDescriptionField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Description"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardDescriptionField::~QtVCardDescriptionField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardDescriptionField::setupContentWidgets() { + descriptionPlainTextEdit = new QPlainTextEdit(this); + descriptionPlainTextEdit->setMinimumHeight(70); + getGridLayout()->addWidget(descriptionPlainTextEdit, getGridLayout()->rowCount()-1, 2, 2, 2, Qt::AlignVCenter); + getTagComboBox()->hide(); + childWidgets << descriptionPlainTextEdit; +} + +bool QtVCardDescriptionField::isEmpty() const { + return descriptionPlainTextEdit->toPlainText().isEmpty(); +} + +void QtVCardDescriptionField::setDescription(const std::string& description) { + descriptionPlainTextEdit->setPlainText(P2QSTRING(description)); +} + +std::string QtVCardDescriptionField::getDescription() const { + return Q2PSTRING(descriptionPlainTextEdit->toPlainText()); +} + +void QtVCardDescriptionField::handleEditibleChanged(bool isEditable) { + assert(descriptionPlainTextEdit); + + if (isEditable) { + descriptionPlainTextEdit->setMinimumHeight(70); + } else { + QFontMetrics inputMetrics(descriptionPlainTextEdit->document()->defaultFont()); + QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999); + QRect boundingRect = inputMetrics.boundingRect(horizontalBounds, Qt::TextWordWrap, descriptionPlainTextEdit->toPlainText() + "A"); + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + int height = boundingRect.height() + top + bottom + inputMetrics.height(); + descriptionPlainTextEdit->setMinimumHeight(height > 70 ? 70 : height); + } + descriptionPlainTextEdit->setReadOnly(!isEditable); + descriptionPlainTextEdit->setStyleSheet(isEditable ? "" : "QPlainTextEdit { background: transparent; }"); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h new file mode 100644 index 0000000..3b1b3d9 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include <QPlainTextEdit> + +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" + +namespace Swift { + +class QtVCardDescriptionField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Description", 1, QtVCardDescriptionField) + + QtVCardDescriptionField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardDescriptionField(); + + virtual bool isEmpty() const; + + void setDescription(const std::string& description); + std::string getDescription() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QPlainTextEdit* descriptionPlainTextEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h b/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h new file mode 100644 index 0000000..168c01b --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QGridLayout> +#include <QObject> +#include <QString> +#include <typeinfo> + +#define GENERIC_QT_VCARD_FIELD_INFO(MENU_NAME, ALLOWED_INSTANCES, FIELD_CLASS) \ + class FieldInfo : public QtVCardFieldInfo { \ + public: \ + virtual ~FieldInfo() { \ + } \ + \ + virtual QString getMenuName() const { \ + return QObject::tr(MENU_NAME); \ + } \ + \ + virtual int getAllowedInstances() const { \ + return ALLOWED_INSTANCES; \ + } \ + \ + virtual QWidget* createFieldInstance(QWidget* parent, QGridLayout* layout, bool editable) const { \ + return new FIELD_CLASS(parent, layout, editable); \ + } \ + \ + virtual bool testInstance(QWidget* widget) const { \ + return dynamic_cast<FIELD_CLASS*>(widget) != 0; \ + } \ + }; + +class QWidget; + +namespace Swift { + + class QtVCardFieldInfo { + public: + static const int UNLIMITED_INSTANCES = -1; + + virtual ~QtVCardFieldInfo() { + } + virtual QString getMenuName() const = 0; + virtual int getAllowedInstances() const = 0; + virtual QWidget* createFieldInstance(QWidget* parent, QGridLayout* layout, bool editable) const = 0; + virtual bool testInstance(QWidget*) const = 0; + }; +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.cpp new file mode 100644 index 0000000..f8b9247 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> + +#include <cassert> + +#include <QHBoxLayout> + +namespace Swift { + +QtVCardGeneralField::QtVCardGeneralField(QWidget* parent, QGridLayout* layout, bool editable, int row, QString label, bool preferrable, bool taggable) : + QWidget(parent), editable(editable), preferrable(preferrable), taggable(taggable), layout(layout), row(row), preferredCheckBox(0), label(0), labelText(label), + tagComboBox(0), closeButton(0) { +} + +QtVCardGeneralField::~QtVCardGeneralField() { + +} + +void QtVCardGeneralField::initialize() { + if (preferrable) { + preferredCheckBox = new QCheckBox(this); + preferredCheckBox->setStyleSheet( + "QCheckBox::indicator { width: 18px; height: 18px; }" + "QCheckBox::indicator:checked { image: url(:/icons/star-checked.png); }" + "QCheckBox::indicator:unchecked { image: url(:/icons/star-unchecked); }" + ); + layout->addWidget(preferredCheckBox, row, 0, Qt::AlignVCenter); + childWidgets << preferredCheckBox; + } + label = new QLabel(this); + label->setText(labelText); + layout->addWidget(label, row, 1, Qt::AlignVCenter | Qt::AlignRight); + + tagLabel = new QLabel(this); + tagLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + + tagComboBox = new QtTagComboBox(this); + closeButton = new QtCloseButton(this); + connect(closeButton, SIGNAL(clicked()), SLOT(handleCloseButtonClicked())); + + QHBoxLayout* tagLayout = new QHBoxLayout(); + tagLayout->addWidget(tagLabel); + tagLayout->addWidget(tagComboBox); + + setupContentWidgets(); + layout->addLayout(tagLayout, row, 4, Qt::AlignTop); + layout->addWidget(closeButton, row, 5, Qt::AlignCenter); + closeButton->resize(12, 12); + tagLabel->hide(); + + childWidgets << label << tagComboBox << tagLabel << closeButton; + setEditable(editable); +} + +bool QtVCardGeneralField::isEditable() const { + return editable; +} + +void QtVCardGeneralField::setEditable(bool editable) { + assert(tagComboBox); + assert(closeButton); + + this->editable = editable; + if (taggable) { + tagLabel->setText(tagComboBox->itemText(0)); + tagComboBox->setVisible(editable); + tagLabel->setVisible(!editable); + } else { + tagLabel->hide(); + tagComboBox->hide(); + } + closeButton->setVisible(editable); + if (preferrable) { + assert(preferredCheckBox); + preferredCheckBox->setVisible(editable ? true : preferredCheckBox->isChecked()); + preferredCheckBox->setEnabled(editable); + } + editableChanged(this->editable); +} + +void QtVCardGeneralField::setPreferred(const bool preferred) { + if (preferredCheckBox) preferredCheckBox->setChecked(preferred); +} + +bool QtVCardGeneralField::getPreferred() const { + return preferredCheckBox ? preferredCheckBox->isChecked() : false; +} + +void QtVCardGeneralField::customCleanup() { +} + +QtTagComboBox* QtVCardGeneralField::getTagComboBox() const { + return tagComboBox; +} + +QGridLayout* QtVCardGeneralField::getGridLayout() const { + return layout; +} + +void QtVCardGeneralField::handleCloseButtonClicked() { + customCleanup(); + foreach(QWidget* widget, childWidgets) { + widget->hide(); + layout->removeWidget(widget); + } + deleteField(this); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h new file mode 100644 index 0000000..4afe692 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QCheckBox> +#include <QGridLayout> +#include <QLabel> +#include <QWidget> + +#include "QtCloseButton.h" +#include "QtTagComboBox.h" + +namespace Swift { + +/* + * covers features like: + * - preffered (star ceckbox) + * - combo check boxh + * - label + * - remove button + */ +class QtVCardGeneralField : public QWidget { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged) + Q_PROPERTY(bool empty READ isEmpty) + + public: + explicit QtVCardGeneralField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false, int row = 0, QString label = QString(), + bool preferrable = true, bool taggable = true); + virtual ~QtVCardGeneralField(); + + void initialize(); + + virtual bool isEditable() const; + virtual void setEditable(bool); + + virtual bool isEmpty() const = 0; + + void setPreferred(const bool preferred); + bool getPreferred() const; + + protected: + virtual void setupContentWidgets() = 0; + virtual void customCleanup(); + + QtTagComboBox* getTagComboBox() const; + QGridLayout* getGridLayout() const; + + signals: + void editableChanged(bool); + void deleteField(QtVCardGeneralField*); + + public slots: + void handleCloseButtonClicked(); + + protected: + QList<QWidget*> childWidgets; + + private: + bool editable; + bool preferrable; + bool taggable; + QGridLayout* layout; + int row; + QCheckBox* preferredCheckBox; + QLabel* label; + QString labelText; + QtTagComboBox* tagComboBox; + QLabel* tagLabel; + QtCloseButton* closeButton; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp new file mode 100644 index 0000000..3119a80 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardHomeWork.h" + +namespace Swift { + +QtVCardHomeWork::QtVCardHomeWork() : tagComboBox(0) { +} + +QtVCardHomeWork::~QtVCardHomeWork() { +} + +void QtVCardHomeWork::setTagComboBox(QtTagComboBox* tagBox) { + tagComboBox = tagBox; + tagComboBox->addTag("home", QObject::tr("Home")); + tagComboBox->addTag("work", QObject::tr("Work")); +} + +void QtVCardHomeWork::setHome(const bool home) { + tagComboBox->setTag("home", home); +} + +bool QtVCardHomeWork::getHome() const { + return tagComboBox->isTagSet("home"); +} + +void QtVCardHomeWork::setWork(const bool work) { + tagComboBox->setTag("work", work); +} + +bool QtVCardHomeWork::getWork() const { + return tagComboBox->isTagSet("work"); +} + +} + diff --git a/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h new file mode 100644 index 0000000..768d984 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QObject> + +#include "QtTagComboBox.h" + +namespace Swift { + +class QtVCardHomeWork { + public: + QtVCardHomeWork(); + virtual ~QtVCardHomeWork(); + + void setTagComboBox(QtTagComboBox* tagBox); + + void setHome(const bool home); + bool getHome() const; + void setWork(const bool work); + bool getWork() const; + + private: + QtTagComboBox* tagComboBox; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp new file mode 100644 index 0000000..e6f8298 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardInternetEMailField.h" + +#include <QGridLayout> +#include <QHBoxLayout> +#include <QTextDocument> +#include <Swiften/Base/Log.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> + +namespace Swift { + +QtVCardInternetEMailField::QtVCardInternetEMailField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("E-Mail")) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardInternetEMailField::~QtVCardInternetEMailField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardInternetEMailField::setupContentWidgets() { + emailLabel = new QLabel(this); + emailLabel->setOpenExternalLinks(true); + emailLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); + emailLineEdit = new QtResizableLineEdit(this); +#if QT_VERSION >= 0x040700 + emailLineEdit->setPlaceholderText(tr("alice@wonderland.lit")); +#endif + QHBoxLayout* emailLayout = new QHBoxLayout(); + emailLayout->addWidget(emailLabel); + emailLayout->addWidget(emailLineEdit); + getGridLayout()->addLayout(emailLayout, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + setTabOrder(emailLineEdit, getTagComboBox()); + QtVCardHomeWork::setTagComboBox(getTagComboBox()); + emailLabel->hide(); + childWidgets << emailLabel << emailLineEdit; +} + +bool QtVCardInternetEMailField::isEmpty() const { + return emailLineEdit->text().isEmpty(); +} + +void QtVCardInternetEMailField::setInternetEMailAddress(const VCard::EMailAddress& address) { + assert(address.isInternet); + setPreferred(address.isPreferred); + setHome(address.isHome); + setWork(address.isWork); + emailLineEdit->setText(P2QSTRING(address.address)); +} + +VCard::EMailAddress QtVCardInternetEMailField::getInternetEMailAddress() const { + VCard::EMailAddress address; + address.isInternet = true; + address.isPreferred = getPreferred(); + address.isHome = getHome(); + address.isWork = getWork(); + address.address = Q2PSTRING(emailLineEdit->text()); + return address; +} + +void QtVCardInternetEMailField::handleEditibleChanged(bool isEditable) { + assert(emailLineEdit); + assert(emailLabel); + + if (isEditable) { + emailLineEdit->show(); + emailLabel->hide(); + } else { + emailLineEdit->hide(); + emailLabel->setText(QString("<a href=\"mailto:%1\">%1</a>").arg(QtUtilities::htmlEscape(emailLineEdit->text()))); + emailLabel->show(); + } +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h new file mode 100644 index 0000000..3f8a27f --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" +#include "QtVCardHomeWork.h" + +namespace Swift { + +class QtVCardInternetEMailField : public QtVCardGeneralField, public QtVCardHomeWork { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("E-Mail", UNLIMITED_INSTANCES, QtVCardInternetEMailField) + + QtVCardInternetEMailField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardInternetEMailField(); + + virtual bool isEmpty() const; + + void setInternetEMailAddress(const VCard::EMailAddress& address); + VCard::EMailAddress getInternetEMailAddress() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QtResizableLineEdit* emailLineEdit; + QLabel* emailLabel; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp new file mode 100644 index 0000000..23a2b5d --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardJIDField.h" + +#include <QGridLayout> +#include <QTextDocument> +#include <boost/algorithm/string.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> + +namespace Swift { + +QtVCardJIDField::QtVCardJIDField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("JID"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardJIDField::~QtVCardJIDField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardJIDField::setupContentWidgets() { + jidLabel = new QLabel(this); + jidLabel->setOpenExternalLinks(true); + jidLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); + jidLineEdit = new QtResizableLineEdit(this); +#if QT_VERSION >= 0x040700 + jidLineEdit->setPlaceholderText(tr("alice@wonderland.lit")); +#endif + QHBoxLayout* jidLayout = new QHBoxLayout(); + jidLayout->addWidget(jidLabel); + jidLayout->addWidget(jidLineEdit); + getGridLayout()->addLayout(jidLayout, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + + jidLabel->hide(); + getTagComboBox()->hide(); + + childWidgets << jidLabel << jidLineEdit; +} + +bool QtVCardJIDField::isEmpty() const { + return jidLineEdit->text().isEmpty(); +} + +void QtVCardJIDField::setJID(const JID& jid) { + std::string jidStr = jid.toBare().toString(); + jidLineEdit->setText(P2QSTRING(jidStr)); +} + +JID QtVCardJIDField::getJID() const { + return JID(Q2PSTRING(jidLineEdit->text())); +} + +void QtVCardJIDField::handleEditibleChanged(bool isEditable) { + assert(jidLineEdit); + assert(jidLabel); + + if (isEditable) { + jidLineEdit->show(); + jidLabel->hide(); + } else { + jidLineEdit->hide(); + jidLabel->setText(QString("<a href=\"xmpp:%1\">%1</a>").arg(QtUtilities::htmlEscape(jidLineEdit->text()))); + jidLabel->show(); + } +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardJIDField.h b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.h new file mode 100644 index 0000000..016bcf8 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" + +namespace Swift { + +class QtVCardJIDField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("JID", UNLIMITED_INSTANCES, QtVCardJIDField) + + QtVCardJIDField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardJIDField(); + + virtual bool isEmpty() const; + + void setJID(const JID& jid); + JID getJID() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QLabel* jidLabel; + QtResizableLineEdit* jidLineEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp new file mode 100644 index 0000000..98c8e8e --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h> + +#include <boost/algorithm/string.hpp> + +#include <QGridLayout> +#include <QHBoxLayout> +#include <QHeaderView> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardOrganizationField::QtVCardOrganizationField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Organisation"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardOrganizationField::~QtVCardOrganizationField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardOrganizationField::setupContentWidgets() { + organizationLabel = new QLabel(this); + organizationLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + organizationLineEdit = new QtResizableLineEdit(this); + QHBoxLayout* organizationLayout = new QHBoxLayout(); + organizationLayout->addWidget(organizationLabel); + organizationLayout->addWidget(organizationLineEdit); + + getGridLayout()->addLayout(organizationLayout, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + + itemDelegate = new QtRemovableItemDelegate(style()); + + unitsTreeWidget = new QTreeWidget(this); + connect(unitsTreeWidget->model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), SLOT(handleRowsRemoved(QModelIndex,int,int))); + unitsTreeWidget->setColumnCount(2); + unitsTreeWidget->header()->setStretchLastSection(false); + unitsTreeWidget->header()->resizeSection(1, itemDelegate->sizeHint(QStyleOptionViewItem(), QModelIndex()).width()); + +#if QT_VERSION >= 0x050000 + unitsTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); +#else + unitsTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch); +#endif + + unitsTreeWidget->setHeaderHidden(true); + unitsTreeWidget->setRootIsDecorated(false); + unitsTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); + unitsTreeWidget->setItemDelegateForColumn(1, itemDelegate); + connect(unitsTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int))); + getGridLayout()->addWidget(unitsTreeWidget, getGridLayout()->rowCount()-1, 4, 2, 1); + + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + unitsTreeWidget->addTopLevelItem(item); + + getTagComboBox()->hide(); + organizationLabel->hide(); + childWidgets << organizationLabel << organizationLineEdit << unitsTreeWidget; +} + +bool QtVCardOrganizationField::isEmpty() const { + return organizationLineEdit->text().isEmpty() && unitsTreeWidget->model()->rowCount() != 0; +} + +void QtVCardOrganizationField::setOrganization(const VCard::Organization& organization) { + organizationLineEdit->setText(P2QSTRING(organization.name)); + unitsTreeWidget->clear(); + foreach(std::string unit, organization.units) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(P2QSTRING(unit)) << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + unitsTreeWidget->addTopLevelItem(item); + } + + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + unitsTreeWidget->addTopLevelItem(item); +} + +VCard::Organization QtVCardOrganizationField::getOrganization() const { + VCard::Organization organization; + organization.name = Q2PSTRING(organizationLineEdit->text()); + for(int i=0; i < unitsTreeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem* row = unitsTreeWidget->topLevelItem(i); + if (!row->text(0).isEmpty()) { + organization.units.push_back(Q2PSTRING(row->text(0))); + } + } + + return organization; +} + +void QtVCardOrganizationField::handleEditibleChanged(bool isEditable) { + assert(organizationLineEdit); + assert(unitsTreeWidget); + + organizationLineEdit->setVisible(isEditable); + organizationLabel->setVisible(!isEditable); + + if (!isEditable) { + QString label; + for(int i=0; i < unitsTreeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem* row = unitsTreeWidget->topLevelItem(i); + if (!row->text(0).isEmpty()) { + label += row->text(0) + ", "; + } + } + label += organizationLineEdit->text(); + organizationLabel->setText(label); + } + unitsTreeWidget->setVisible(isEditable); +} + +void QtVCardOrganizationField::handleItemChanged(QTreeWidgetItem *, int) { + guaranteeEmptyRow(); +} + +void QtVCardOrganizationField::handleRowsRemoved(const QModelIndex&, int, int) { + guaranteeEmptyRow(); +} + +void QtVCardOrganizationField::guaranteeEmptyRow() { + bool hasEmptyRow = false; + QList<QTreeWidgetItem*> rows = unitsTreeWidget->findItems("", Qt::MatchFixedString); + foreach(QTreeWidgetItem* row, rows) { + if (row->text(0).isEmpty()) { + hasEmptyRow = true; + } + } + + if (!hasEmptyRow) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + unitsTreeWidget->addTopLevelItem(item); + unitsTreeWidget->setCurrentItem(item); + } +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h new file mode 100644 index 0000000..47868a7 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QTreeWidget> + +#include <Swiften/Elements/VCard.h> + +#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> + +namespace Swift { + +class QtVCardOrganizationField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Organization", UNLIMITED_INSTANCES, QtVCardOrganizationField) + + QtVCardOrganizationField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardOrganizationField(); + + virtual bool isEmpty() const; + + void setOrganization(const VCard::Organization& organization); + VCard::Organization getOrganization() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private slots: + void handleItemChanged(QTreeWidgetItem*, int); + void handleRowsRemoved(const QModelIndex&, int, int); + + private: + void guaranteeEmptyRow(); + + private: + QLabel* organizationLabel; + QtResizableLineEdit* organizationLineEdit; + QTreeWidget* unitsTreeWidget; + QtRemovableItemDelegate* itemDelegate; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp new file mode 100644 index 0000000..aaea194 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h> + +#include <Swift/QtUI/QtVCardWidget/ui_QtVCardPhotoAndNameFields.h> + +#include <QMenu> + +namespace Swift { + +QtVCardPhotoAndNameFields::QtVCardPhotoAndNameFields(QWidget* parent) : + QWidget(parent), + ui(new Ui::QtVCardPhotoAndNameFields) { + ui->setupUi(this); + ui->lineEditPREFIX->hide(); + ui->lineEditMIDDLE->hide(); + ui->lineEditSUFFIX->hide(); + ui->lineEditFN->hide(); + ui->lineEditNICKNAME->hide(); + ui->labelFULLNAME->hide(); + +#if QT_VERSION >= 0x040700 + ui->lineEditFN->setPlaceholderText(tr("Formatted Name")); + ui->lineEditNICKNAME->setPlaceholderText(tr("Nickname")); + ui->lineEditPREFIX->setPlaceholderText(tr("Prefix")); + ui->lineEditGIVEN->setPlaceholderText(tr("Given Name")); + ui->lineEditMIDDLE->setPlaceholderText(tr("Middle Name")); + ui->lineEditFAMILY->setPlaceholderText(tr("Last Name")); + ui->lineEditSUFFIX->setPlaceholderText(tr("Suffix")); +#endif + +} + +QtVCardPhotoAndNameFields::~QtVCardPhotoAndNameFields() { + delete ui; +} + +bool QtVCardPhotoAndNameFields::isEditable() const { + return editable; +} + +void QtVCardPhotoAndNameFields::setEditable(bool editable) { + this->editable = editable; + + ui->avatarWidget->setEditable(editable); + ui->lineEditFN->setVisible(editable ? true : !ui->lineEditFN->text().isEmpty()); + ui->lineEditFN->setEditable(editable); + ui->lineEditFN->setStyleSheet(editable ? "" : "QLineEdit {border: none; background-color: transparent;}"); + + ui->lineEditNICKNAME->setVisible(editable ? true : !ui->lineEditNICKNAME->text().isEmpty()); + ui->lineEditNICKNAME->setEditable(editable); + ui->lineEditNICKNAME->setStyleSheet(editable ? "" : "QLineEdit {border: none; background-color: transparent;}"); + + // prefix given middle last suffix + ui->lineEditPREFIX->setVisible(editable); + ui->lineEditGIVEN->setVisible(editable); + ui->lineEditMIDDLE->setVisible(editable); + ui->lineEditFAMILY->setVisible(editable); + ui->lineEditSUFFIX->setVisible(editable); + ui->labelFULLNAME->setVisible(!editable); + + QStringList fullname; + fullname << ui->lineEditPREFIX->text() << ui->lineEditGIVEN->text() << ui->lineEditMIDDLE->text(); + fullname << ui->lineEditFAMILY->text() << ui->lineEditSUFFIX->text(); + fullname = fullname.filter(".*\\S.*"); + ui->labelFULLNAME->setText(fullname.join(" ")); +} + +void QtVCardPhotoAndNameFields::setAvatar(const ByteArray &data, const std::string &type) { + ui->avatarWidget->setAvatar(data, type); +} + +ByteArray QtVCardPhotoAndNameFields::getAvatarData() const { + return ui->avatarWidget->getAvatarData(); +} + +std::string QtVCardPhotoAndNameFields::getAvatarType() const { + return ui->avatarWidget->getAvatarType(); +} + +void QtVCardPhotoAndNameFields::setFormattedName(const QString formattedName) { + ui->lineEditFN->setText(formattedName); +} + +QString QtVCardPhotoAndNameFields::getFormattedName() const { + return ui->lineEditFN->text(); +} + +void QtVCardPhotoAndNameFields::setNickname(const QString nickname) { + ui->lineEditNICKNAME->setText(nickname); +} + +QString QtVCardPhotoAndNameFields::getNickname() const { + return ui->lineEditNICKNAME->text(); +} + +void QtVCardPhotoAndNameFields::setPrefix(const QString prefix) { + ui->lineEditPREFIX->setText(prefix); +} + +QString QtVCardPhotoAndNameFields::getPrefix() const { + return ui->lineEditPREFIX->text(); +} + +void QtVCardPhotoAndNameFields::setGivenName(const QString givenName) { + ui->lineEditGIVEN->setText(givenName); +} + +QString QtVCardPhotoAndNameFields::getGivenName() const { + return ui->lineEditGIVEN->text(); +} + +void QtVCardPhotoAndNameFields::setMiddleName(const QString middleName) { + ui->lineEditMIDDLE->setText(middleName); +} + +QString QtVCardPhotoAndNameFields::getMiddleName() const { + return ui->lineEditMIDDLE->text(); +} + +void QtVCardPhotoAndNameFields::setFamilyName(const QString familyName) { + ui->lineEditFAMILY->setText(familyName); +} + +QString QtVCardPhotoAndNameFields::getFamilyName() const { + return ui->lineEditFAMILY->text(); +} + +void QtVCardPhotoAndNameFields::setSuffix(const QString suffix) { + ui->lineEditSUFFIX->setText(suffix); +} + +QString QtVCardPhotoAndNameFields::getSuffix() const { + return ui->lineEditSUFFIX->text(); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h new file mode 100644 index 0000000..6a5ae46 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QMenu> +#include <QWidget> + +#include <Swiften/Base/ByteArray.h> + +namespace Ui { + class QtVCardPhotoAndNameFields; +} + + +namespace Swift { + + class QtVCardPhotoAndNameFields : public QWidget { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + + public: + explicit QtVCardPhotoAndNameFields(QWidget* parent = 0); + ~QtVCardPhotoAndNameFields(); + + bool isEditable() const; + void setEditable(bool); + + void setAvatar(const ByteArray& data, const std::string& type); + ByteArray getAvatarData() const; + std::string getAvatarType() const; + + void setFormattedName(const QString formattedName); + QString getFormattedName() const; + + void setNickname(const QString nickname); + QString getNickname() const; + + void setPrefix(const QString prefix); + QString getPrefix() const; + + void setGivenName(const QString givenName); + QString getGivenName() const; + + void setMiddleName(const QString middleName); + QString getMiddleName() const; + + void setFamilyName(const QString familyName); + QString getFamilyName() const; + + void setSuffix(const QString suffix); + QString getSuffix() const; + + private: + Ui::QtVCardPhotoAndNameFields* ui; + bool editable; + }; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui new file mode 100644 index 0000000..04da2bc --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui @@ -0,0 +1,251 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtVCardPhotoAndNameFields</class> + <widget class="QWidget" name="QtVCardPhotoAndNameFields"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>522</width> + <height>81</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0" rowminimumheight="0,0,0,0,0"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <property name="horizontalSpacing"> + <number>5</number> + </property> + <property name="verticalSpacing"> + <number>1</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item row="0" column="0" rowspan="5"> + <widget class="Swift::QtAvatarWidget" name="avatarWidget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text" stdset="0"> + <string/> + </property> + <property name="flat" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditFN"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>18</pointsize> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + <property name="toolTip"> + <string>Formatted Name</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditNICKNAME"> + <property name="toolTip"> + <string>Nickname</string> + </property> + <property name="frame"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="2" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>2</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item> + <widget class="QLabel" name="labelFULLNAME"> + <property name="text"> + <string/> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditPREFIX"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Prefix</string> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditGIVEN"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Given Name</string> + </property> + <property name="text"> + <string/> + </property> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditMIDDLE"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Middle Name</string> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="frame"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditFAMILY"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Last Name</string> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditSUFFIX"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Suffix</string> + </property> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtResizableLineEdit</class> + <extends>QLineEdit</extends> + <header>QtResizableLineEdit.h</header> + </customwidget> + <customwidget> + <class>Swift::QtAvatarWidget</class> + <extends>QWidget</extends> + <header>Swift/QtUI/QtAvatarWidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp new file mode 100644 index 0000000..b9da767 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardRoleField.h" + +#include <QGridLayout> +#include <boost/algorithm/string.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardRoleField::QtVCardRoleField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Role"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardRoleField::~QtVCardRoleField() { +} + +void QtVCardRoleField::setupContentWidgets() { + roleLineEdit = new QtResizableLineEdit(this); + getGridLayout()->addWidget(roleLineEdit, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + getTagComboBox()->hide(); + childWidgets << roleLineEdit; +} + +bool QtVCardRoleField::isEmpty() const { + return roleLineEdit->text().isEmpty(); +} + +void QtVCardRoleField::setRole(const std::string& role) { + roleLineEdit->setText(P2QSTRING(role)); +} + +std::string QtVCardRoleField::getRole() const { + return Q2PSTRING(roleLineEdit->text()); +} + +void QtVCardRoleField::handleEditibleChanged(bool isEditable) { + assert(roleLineEdit); + + roleLineEdit->setEditable(isEditable); + roleLineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardRoleField.h b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.h new file mode 100644 index 0000000..3c819ed --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" + +namespace Swift { + +class QtVCardRoleField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Role", UNLIMITED_INSTANCES, QtVCardRoleField) + + QtVCardRoleField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardRoleField(); + + virtual bool isEmpty() const; + + void setRole(const std::string& role); + std::string getRole() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QtResizableLineEdit* roleLineEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp new file mode 100644 index 0000000..063319e --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardTelephoneField.h" + +#include <QGridLayout> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardTelephoneField::QtVCardTelephoneField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Telephone")) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardTelephoneField::~QtVCardTelephoneField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardTelephoneField::setupContentWidgets() { + telephoneLineEdit = new QtResizableLineEdit(this); +#if QT_VERSION >= 0x040700 + telephoneLineEdit->setPlaceholderText(tr("0118 999 881 999 119 7253")); +#endif + getGridLayout()->addWidget(telephoneLineEdit, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + setTabOrder(telephoneLineEdit, getTagComboBox()); + QtVCardHomeWork::setTagComboBox(getTagComboBox()); + + getTagComboBox()->addTag("voice", QObject::tr("Voice")); + getTagComboBox()->addTag("fax", QObject::tr("Fax")); + getTagComboBox()->addTag("pager", QObject::tr("Pager")); + getTagComboBox()->addTag("msg", QObject::tr("Voice Messaging")); + getTagComboBox()->addTag("cell", QObject::tr("Cell")); + getTagComboBox()->addTag("video", QObject::tr("Video")); + getTagComboBox()->addTag("bbs", QObject::tr("Bulletin Board System")); + getTagComboBox()->addTag("modem", QObject::tr("Modem")); + getTagComboBox()->addTag("isdn", QObject::tr("ISDN")); + getTagComboBox()->addTag("pcs", QObject::tr("Personal Communication Services")); + + childWidgets << telephoneLineEdit; +} + +bool QtVCardTelephoneField::isEmpty() const { + return telephoneLineEdit->text().isEmpty(); +} + +void QtVCardTelephoneField::setTelephone(const VCard::Telephone& telephone) { + setPreferred(telephone.isPreferred); + setHome(telephone.isHome); + setWork(telephone.isWork); + + telephoneLineEdit->setText(P2QSTRING(telephone.number)); + + getTagComboBox()->setTag("voice", telephone.isVoice); + getTagComboBox()->setTag("fax", telephone.isFax); + getTagComboBox()->setTag("pager", telephone.isPager); + getTagComboBox()->setTag("msg", telephone.isMSG); + getTagComboBox()->setTag("cell", telephone.isCell); + getTagComboBox()->setTag("video", telephone.isVideo); + getTagComboBox()->setTag("bbs", telephone.isBBS); + getTagComboBox()->setTag("modem", telephone.isModem); + getTagComboBox()->setTag("isdn", telephone.isISDN); + getTagComboBox()->setTag("pcs", telephone.isPCS); +} + +VCard::Telephone QtVCardTelephoneField::getTelephone() const { + VCard::Telephone telephone; + + telephone.number = Q2PSTRING(telephoneLineEdit->text()); + + telephone.isPreferred = getPreferred(); + telephone.isHome = getHome(); + telephone.isWork = getWork(); + + telephone.isVoice = getTagComboBox()->isTagSet("voice"); + telephone.isFax = getTagComboBox()->isTagSet("fax"); + telephone.isPager = getTagComboBox()->isTagSet("pager"); + telephone.isMSG = getTagComboBox()->isTagSet("msg"); + telephone.isCell = getTagComboBox()->isTagSet("cell"); + telephone.isVideo = getTagComboBox()->isTagSet("video"); + telephone.isBBS = getTagComboBox()->isTagSet("bbs"); + telephone.isModem = getTagComboBox()->isTagSet("modem"); + telephone.isISDN = getTagComboBox()->isTagSet("isdn"); + telephone.isPCS = getTagComboBox()->isTagSet("pcs"); + + return telephone; +} + +void QtVCardTelephoneField::handleEditibleChanged(bool isEditable) { + assert(telephoneLineEdit); + + telephoneLineEdit->setEditable(isEditable); + telephoneLineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h new file mode 100644 index 0000000..b433e3c --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" +#include "QtVCardHomeWork.h" + +namespace Swift { + +class QtVCardTelephoneField : public QtVCardGeneralField, public QtVCardHomeWork { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Telephone", UNLIMITED_INSTANCES, QtVCardTelephoneField) + + QtVCardTelephoneField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardTelephoneField(); + + virtual bool isEmpty() const; + + void setTelephone(const VCard::Telephone& telephone); + VCard::Telephone getTelephone() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QtResizableLineEdit* telephoneLineEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp new file mode 100644 index 0000000..43972d0 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardTitleField.h" + +#include <QGridLayout> +#include <boost/algorithm/string.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardTitleField::QtVCardTitleField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Title"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardTitleField::~QtVCardTitleField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardTitleField::setupContentWidgets() { + titleLineEdit = new QtResizableLineEdit(this); + getGridLayout()->addWidget(titleLineEdit, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + getTagComboBox()->hide(); + childWidgets << titleLineEdit; +} + +bool QtVCardTitleField::isEmpty() const { + return titleLineEdit->text().isEmpty(); +} + +void QtVCardTitleField::setTitle(const std::string& title) { + titleLineEdit->setText(P2QSTRING(title)); +} + +std::string QtVCardTitleField::getTitle() const { + return Q2PSTRING(titleLineEdit->text()); +} + +void QtVCardTitleField::handleEditibleChanged(bool isEditable) { + assert(titleLineEdit); + + titleLineEdit->setEditable(isEditable); + titleLineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardTitleField.h b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.h new file mode 100644 index 0000000..28dc603 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" + +namespace Swift { + +class QtVCardTitleField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("Title", UNLIMITED_INSTANCES, QtVCardTitleField) + + QtVCardTitleField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardTitleField(); + + virtual bool isEmpty() const; + + void setTitle(const std::string& title); + std::string getTitle() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QtResizableLineEdit* titleLineEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp new file mode 100644 index 0000000..b39eeaa --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtVCardURLField.h" + +#include <QGridLayout> +#include <QHBoxLayout> +#include <QTextDocument> +#include <boost/algorithm/string.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> + + +namespace Swift { + +QtVCardURLField::QtVCardURLField(QWidget* parent, QGridLayout *layout, bool editable) : + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("URL"), false, false) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); +} + +QtVCardURLField::~QtVCardURLField() { + disconnect(this, SLOT(handleEditibleChanged(bool))); +} + +void QtVCardURLField::setupContentWidgets() { + urlLabel = new QLabel(this); + urlLabel->setOpenExternalLinks(true); + urlLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); + urlLineEdit = new QtResizableLineEdit(this); + + QHBoxLayout* urlLayout = new QHBoxLayout(); + urlLayout->addWidget(urlLabel); + urlLayout->addWidget(urlLineEdit); + + getGridLayout()->addLayout(urlLayout, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); + getTagComboBox()->hide(); + urlLabel->hide(); + childWidgets << urlLabel << urlLineEdit; +} + +bool QtVCardURLField::isEmpty() const { + return urlLineEdit->text().isEmpty(); +} + +void QtVCardURLField::setURL(const std::string& url) { + urlLineEdit->setText(P2QSTRING(url)); +} + +std::string QtVCardURLField::getURL() const { + return Q2PSTRING(urlLineEdit->text()); +} + +void QtVCardURLField::handleEditibleChanged(bool isEditable) { + assert(urlLineEdit); + assert(urlLabel); + + if (isEditable) { + urlLineEdit->show(); + urlLabel->hide(); + } else { + urlLineEdit->hide(); + urlLabel->setText(QString("<a href=\"%1\">%1</a>").arg(QtUtilities::htmlEscape(urlLineEdit->text()))); + urlLabel->show(); + } +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardURLField.h b/Swift/QtUI/QtVCardWidget/QtVCardURLField.h new file mode 100644 index 0000000..2c011d8 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardURLField.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/VCard.h> + +#include "QtResizableLineEdit.h" +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" + +namespace Swift { + +class QtVCardURLField : public QtVCardGeneralField { + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO("URL", UNLIMITED_INSTANCES, QtVCardURLField) + + QtVCardURLField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); + virtual ~QtVCardURLField(); + + virtual bool isEmpty() const; + + void setURL(const std::string& url); + std::string getURL() const; + + protected: + virtual void setupContentWidgets(); + + public slots: + void handleEditibleChanged(bool isEditable); + + private: + QLabel* urlLabel; + QtResizableLineEdit* urlLineEdit; +}; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp new file mode 100644 index 0000000..d681fe9 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardWidget.h> + +#include <QDebug> +#include <QLineEdit> +#include <QMenu> + +#include <Swift/QtUI/QtVCardWidget/ui_QtVCardWidget.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardAddressField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardJIDField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardRoleField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardTitleField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardURLField.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtVCardWidget::QtVCardWidget(QWidget* parent) : + QWidget(parent), + ui(new ::Ui::QtVCardWidget) { + ui->setupUi(this); + + ui->cardFields->setColumnStretch(0,0); + ui->cardFields->setColumnStretch(1,0); + ui->cardFields->setColumnStretch(2,2); + ui->cardFields->setColumnStretch(3,1); + ui->cardFields->setColumnStretch(4,2); + menu = new QMenu(this); + + toolButton = new QToolButton(this); + toolButton->setText(tr("Add Field")); + toolButton->setArrowType(Qt::NoArrow); + toolButton->setAutoRaise(false); + toolButton->setPopupMode(QToolButton::InstantPopup); + toolButton->hide(); + toolButton->setMenu(menu); + + addFieldType(menu, boost::make_shared<QtVCardInternetEMailField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardTelephoneField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardAddressField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardAddressLabelField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardBirthdayField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardJIDField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardDescriptionField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardRoleField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardTitleField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardOrganizationField::FieldInfo>()); + addFieldType(menu, boost::make_shared<QtVCardURLField::FieldInfo>()); + + setEditable(false); +} + +QtVCardWidget::~QtVCardWidget() { + delete ui; +} + +bool QtVCardWidget::isEditable() const { + return editable; +} + +void QtVCardWidget::setEditable(bool editable) { + this->editable = editable; + + ui->photoAndName->setProperty("editable", QVariant(editable)); + + foreach(QtVCardGeneralField* field, fields) { + field->setEditable(editable); + } + toolButton->setVisible(editable); + + editableChanged(editable); +} + +void QtVCardWidget::setVCard(VCard::ref vcard) { + clearFields(); + this->vcard = vcard; + ui->photoAndName->setFormattedName(P2QSTRING(vcard->getFullName())); + ui->photoAndName->setNickname(P2QSTRING(vcard->getNickname())); + ui->photoAndName->setPrefix(P2QSTRING(vcard->getPrefix())); + ui->photoAndName->setGivenName(P2QSTRING(vcard->getGivenName())); + ui->photoAndName->setMiddleName(P2QSTRING(vcard->getMiddleName())); + ui->photoAndName->setFamilyName(P2QSTRING(vcard->getFamilyName())); + ui->photoAndName->setSuffix(P2QSTRING(vcard->getSuffix())); + ui->photoAndName->setAvatar(vcard->getPhoto(), vcard->getPhotoType()); + + foreach (const VCard::EMailAddress& address, vcard->getEMailAddresses()) { + if (address.isInternet) { + QtVCardInternetEMailField* internetEmailField = new QtVCardInternetEMailField(this, ui->cardFields); + internetEmailField->initialize(); + internetEmailField->setInternetEMailAddress(address); + appendField(internetEmailField); + } + } + + foreach (const VCard::Telephone& telephone, vcard->getTelephones()) { + QtVCardTelephoneField* telField = new QtVCardTelephoneField(this, ui->cardFields); + telField->initialize(); + telField->setTelephone(telephone); + appendField(telField); + } + + foreach (const VCard::Address& address, vcard->getAddresses()) { + QtVCardAddressField* addressField = new QtVCardAddressField(this, ui->cardFields); + addressField->initialize(); + addressField->setAddress(address); + appendField(addressField); + } + + foreach (const VCard::AddressLabel& label, vcard->getAddressLabels()) { + QtVCardAddressLabelField* addressLabelField = new QtVCardAddressLabelField(this, ui->cardFields); + addressLabelField->initialize(); + addressLabelField->setAddressLabel(label); + appendField(addressLabelField); + } + + if (!vcard->getBirthday().is_not_a_date_time()) { + QtVCardBirthdayField* bdayField = new QtVCardBirthdayField(this, ui->cardFields); + bdayField->initialize(); + bdayField->setBirthday(vcard->getBirthday()); + appendField(bdayField); + } + + foreach (const JID& jid, vcard->getJIDs()) { + QtVCardJIDField* jidField = new QtVCardJIDField(this, ui->cardFields); + jidField->initialize(); + jidField->setJID(jid); + appendField(jidField); + } + + if (!vcard->getDescription().empty()) { + QtVCardDescriptionField* descField = new QtVCardDescriptionField(this, ui->cardFields); + descField->initialize(); + descField->setDescription(vcard->getDescription()); + appendField(descField); + } + + foreach (const VCard::Organization& org, vcard->getOrganizations()) { + QtVCardOrganizationField* orgField = new QtVCardOrganizationField(this, ui->cardFields); + orgField->initialize(); + orgField->setOrganization(org); + appendField(orgField); + } + + foreach (const std::string& role, vcard->getRoles()) { + QtVCardRoleField* roleField = new QtVCardRoleField(this, ui->cardFields); + roleField->initialize(); + roleField->setRole(role); + appendField(roleField); + } + + foreach (const std::string& title, vcard->getTitles()) { + QtVCardTitleField* titleField = new QtVCardTitleField(this, ui->cardFields); + titleField->initialize(); + titleField->setTitle(title); + appendField(titleField); + } + + foreach (const std::string& url, vcard->getURLs()) { + QtVCardURLField* urlField = new QtVCardURLField(this, ui->cardFields); + urlField->initialize(); + urlField->setURL(url); + appendField(urlField); + } + + relayoutToolButton(); + setEditable(editable); + window()->resize(sizeHint().width(), size().height() < 200 ? 200 : size().height()); +} + +VCard::ref QtVCardWidget::getVCard() { + clearEmptyFields(); + vcard->setFullName(Q2PSTRING(ui->photoAndName->getFormattedName())); + vcard->setNickname(Q2PSTRING(ui->photoAndName->getNickname())); + vcard->setPrefix(Q2PSTRING(ui->photoAndName->getPrefix())); + vcard->setGivenName(Q2PSTRING(ui->photoAndName->getGivenName())); + vcard->setMiddleName(Q2PSTRING(ui->photoAndName->getMiddleName())); + vcard->setFamilyName(Q2PSTRING(ui->photoAndName->getFamilyName())); + vcard->setSuffix(Q2PSTRING(ui->photoAndName->getSuffix())); + vcard->setPhoto(ui->photoAndName->getAvatarData()); + vcard->setPhotoType(ui->photoAndName->getAvatarType()); + + vcard->clearEMailAddresses(); + vcard->clearJIDs(); + vcard->clearURLs(); + vcard->clearTelephones(); + vcard->clearRoles(); + vcard->clearTitles(); + vcard->clearOrganizations(); + vcard->clearAddresses(); + vcard->clearAddressLabels(); + + + QtVCardBirthdayField* bdayField = NULL; + QtVCardDescriptionField* descriptionField = NULL; + + foreach(QtVCardGeneralField* field, fields) { + QtVCardInternetEMailField* emailField; + if ((emailField = dynamic_cast<QtVCardInternetEMailField*>(field))) { + vcard->addEMailAddress(emailField->getInternetEMailAddress()); + continue; + } + + QtVCardTelephoneField* telephoneField; + if ((telephoneField = dynamic_cast<QtVCardTelephoneField*>(field))) { + vcard->addTelephone(telephoneField->getTelephone()); + continue; + } + + QtVCardAddressField* addressField; + if ((addressField = dynamic_cast<QtVCardAddressField*>(field))) { + vcard->addAddress(addressField->getAddress()); + continue; + } + + QtVCardAddressLabelField* addressLabelField; + if ((addressLabelField = dynamic_cast<QtVCardAddressLabelField*>(field))) { + vcard->addAddressLabel(addressLabelField->getAddressLabel()); + continue; + } + + if ((bdayField = dynamic_cast<QtVCardBirthdayField*>(field))) { + continue; + } + + QtVCardJIDField* jidField; + if ((jidField = dynamic_cast<QtVCardJIDField*>(field))) { + vcard->addJID(jidField->getJID()); + continue; + } + + if ((descriptionField = dynamic_cast<QtVCardDescriptionField*>(field))) { + continue; + } + + QtVCardOrganizationField* orgField; + if ((orgField = dynamic_cast<QtVCardOrganizationField*>(field))) { + vcard->addOrganization(orgField->getOrganization()); + continue; + } + + QtVCardRoleField* roleField; + if ((roleField = dynamic_cast<QtVCardRoleField*>(field))) { + vcard->addRole(roleField->getRole()); + continue; + } + + QtVCardTitleField* titleField; + if ((titleField = dynamic_cast<QtVCardTitleField*>(field))) { + vcard->addTitle(titleField->getTitle()); + continue; + } + + QtVCardURLField* urlField; + if ((urlField = dynamic_cast<QtVCardURLField*>(field))) { + vcard->addURL(urlField->getURL()); + continue; + } + } + + if (bdayField) { + vcard->setBirthday(bdayField->getBirthday()); + } else { + vcard->setBirthday(boost::posix_time::ptime()); + } + + if (descriptionField) { + vcard->setDescription(descriptionField->getDescription()); + } else { + vcard->setDescription(""); + } + + return vcard; +} + +void QtVCardWidget::addField() { + QAction* action = NULL; + if ((action = dynamic_cast<QAction*>(sender()))) { + boost::shared_ptr<QtVCardFieldInfo> fieldInfo = actionFieldInfo[action]; + QWidget* newField = fieldInfo->createFieldInstance(this, ui->cardFields, true); + QtVCardGeneralField* newGeneralField = dynamic_cast<QtVCardGeneralField*>(newField); + if (newGeneralField) { + newGeneralField->initialize(); + } + appendField(newGeneralField); + relayoutToolButton(); + } +} + +void QtVCardWidget::removeField(QtVCardGeneralField *field) { + fields.remove(field); + delete field; +} + +void QtVCardWidget::addFieldType(QMenu* menu, boost::shared_ptr<QtVCardFieldInfo> fieldType) { + if (!fieldType->getMenuName().isEmpty()) { + QAction* action = new QAction(tr("Add ") + fieldType->getMenuName(), this); + actionFieldInfo[action] = fieldType; + connect(action, SIGNAL(triggered()), this, SLOT(addField())); + menu->addAction(action); + } +} + +int QtVCardWidget::fieldTypeInstances(boost::shared_ptr<QtVCardFieldInfo> fieldType) { + int instances = 0; + for (int n = 0; n < ui->cardFields->count(); n++) { + if (fieldType->testInstance(ui->cardFields->itemAt(n)->widget())) instances++; + } + return instances; +} + +void layoutDeleteChildren(QLayout *layout) { + while(layout->count() > 0) { + QLayoutItem* child; + if ((child = layout->takeAt(0)) != 0) { + if (child->layout()) { + layoutDeleteChildren(child->layout()); + } + if (dynamic_cast<QToolButton*>(child->widget())) { + delete child; + break; + } + delete child->widget(); + delete child; + } + } +} + +void QtVCardWidget::clearFields() { + foreach(QtVCardGeneralField* field, fields) { + delete field; + } + fields.clear(); + + assert(ui->cardFields->count() >= 0); + layoutDeleteChildren(ui->cardFields); +} + +void QtVCardWidget::clearEmptyFields() { + std::vector<QtVCardGeneralField*> items_to_remove; + foreach (QtVCardGeneralField* field, fields) { + if (field->property("empty").isValid() && field->property("empty").toBool()) { + ui->cardFields->removeWidget(field); + items_to_remove.push_back(field); + delete field; + } + } + + foreach(QtVCardGeneralField* field, items_to_remove) { + fields.remove(field); + } +} + +void QtVCardWidget::appendField(QtVCardGeneralField *field) { + connect(field, SIGNAL(deleteField(QtVCardGeneralField*)), SLOT(removeField(QtVCardGeneralField*))); + fields.push_back(field); +} + +void QtVCardWidget::relayoutToolButton() { + ui->cardFields->addWidget(toolButton, ui->cardFields->rowCount(), ui->cardFields->columnCount()-2, 1, 1, Qt::AlignRight); +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardWidget.h b/Swift/QtUI/QtVCardWidget/QtVCardWidget.h new file mode 100644 index 0000000..29ed499 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QWidget> +#include <QToolButton> +#include <Swiften/Elements/VCard.h> +#include <boost/smart_ptr/make_shared.hpp> + +#include "QtVCardFieldInfo.h" +#include "QtVCardGeneralField.h" +#include "QtVCardPhotoAndNameFields.h" + +namespace Ui { + class QtVCardWidget; +} + +namespace Swift { + + class QtVCardWidget : public QWidget { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + + public : + explicit QtVCardWidget(QWidget* parent = 0); + ~QtVCardWidget(); + + bool isEditable() const; + void setEditable(bool); + + void setVCard(VCard::ref vcard); + VCard::ref getVCard(); + + signals: + void editableChanged(bool editable); + + private slots: + void addField(); + void removeField(QtVCardGeneralField* field); + + private: + void addFieldType(QMenu*, boost::shared_ptr<QtVCardFieldInfo>); + int fieldTypeInstances(boost::shared_ptr<QtVCardFieldInfo>); + void clearFields(); + void clearEmptyFields(); + void appendField(QtVCardGeneralField* field); + void relayoutToolButton(); + + private: + VCard::ref vcard; + Ui::QtVCardWidget* ui; + QToolButton* toolButton; + bool editable; + QMenu* menu; + std::list<QtVCardGeneralField*> fields; + std::map<QAction*, boost::shared_ptr<QtVCardFieldInfo> > actionFieldInfo; + }; + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui b/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui new file mode 100644 index 0000000..4fc8605 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtVCardWidget</class> + <widget class="QWidget" name="QtVCardWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>535</width> + <height>126</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout" rowstretch="1" columnstretch="1,0"> + <property name="leftMargin"> + <number>5</number> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <property name="bottomMargin"> + <number>5</number> + </property> + <item row="0" column="0" colspan="2"> + <layout class="QVBoxLayout" name="card" stretch="0,0,1"> + <property name="spacing"> + <number>2</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="Swift::QtVCardPhotoAndNameFields" name="photoAndName" native="true"/> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>523</width> + <height>110</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinAndMaxSize</enum> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QGridLayout" name="cardFields"/> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>1000</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtVCardPhotoAndNameFields</class> + <extends>QWidget</extends> + <header>QtVCardPhotoAndNameFields.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp new file mode 100644 index 0000000..bc57de4 --- /dev/null +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -0,0 +1,948 @@ +/* + * Copyright (c) 2010-2013 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtWebKitChatView.h" + +#include <boost/format.hpp> + +#include <QtDebug> +#include <QEventLoop> +#include <QFile> +#include <QDesktopServices> +#include <QVBoxLayout> +#include <QWebFrame> +#include <QKeyEvent> +#include <QStackedWidget> +#include <QTimer> +#include <QMessageBox> +#include <QApplication> +#include <QInputDialog> +#include <QFileDialog> + +#include <Swiften/Base/Log.h> +#include <Swiften/StringCodecs/Base64.h> + +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> + +#include <Swift/QtUI/QtWebView.h> +#include <Swift/QtUI/QtChatWindow.h> +#include <Swift/QtUI/QtChatWindowJSBridge.h> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/MessageSnippet.h> +#include <Swift/QtUI/SystemMessageSnippet.h> + +namespace Swift { + +const QString QtWebKitChatView::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel"); +const QString QtWebKitChatView::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest"); +const QString QtWebKitChatView::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow"); +const QString QtWebKitChatView::ButtonFileTransferCancel = QString("filetransfer-cancel"); +const QString QtWebKitChatView::ButtonFileTransferSetDescription = QString("filetransfer-setdescription"); +const QString QtWebKitChatView::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest"); +const QString QtWebKitChatView::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest"); +const QString QtWebKitChatView::ButtonMUCInvite = QString("mucinvite"); + +QtWebKitChatView::QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QtChatView(parent), window_(window), eventStream_(eventStream), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll), previousMessageKind_(PreviosuMessageWasNone), previousMessageWasSelf_(false), showEmoticons_(false), insertingLastLine_(false), idCounter_(0) { + theme_ = theme; + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + webView_ = new QtWebView(this); + connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&))); + 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())); +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) + /* To give a border on Linux, where it looks bad without */ + QStackedWidget* stack = new QStackedWidget(this); + stack->addWidget(webView_); + stack->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + stack->setLineWidth(2); + mainLayout->addWidget(stack); +#else + mainLayout->addWidget(webView_); +#endif + +#ifdef SWIFT_EXPERIMENTAL_FT + setAcceptDrops(true); +#endif + + webPage_ = new QWebPage(this); + webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); + if (Log::getLogLevel() == Log::debug) { + webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + } + webView_->setPage(webPage_); + connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); + connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&))); + + viewReady_ = false; + isAtBottom_ = true; + resetView(); + + jsBridge = new QtChatWindowJSBridge(); + addToJSEnvironment("chatwindow", jsBridge); + connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString))); + +} + +QtWebKitChatView::~QtWebKitChatView() { + delete jsBridge; +} + +void QtWebKitChatView::handleClearRequested() { + QMessageBox messageBox(this); + messageBox.setWindowTitle(tr("Clear log")); + messageBox.setText(tr("You are about to clear the contents of your chat log.")); + messageBox.setInformativeText(tr("Are you sure?")); + messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + messageBox.setDefaultButton(QMessageBox::Yes); + int button = messageBox.exec(); + if (button == QMessageBox::Yes) { + logCleared(); + resetView(); + } +} + +void QtWebKitChatView::handleKeyPressEvent(QKeyEvent* event) { + webView_->keyPressEvent(event); +} + +void QtWebKitChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) { + if (viewReady_) { + addToDOM(snippet); + } else { + /* If this asserts, the previous queuing code was necessary and should be reinstated */ + assert(false); + } +} + +void QtWebKitChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) { + // save scrollbar maximum value + if (!topMessageAdded_) { + scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); + } + topMessageAdded_ = true; + + QWebElement continuationElement = firstElement_.findFirst("#insert"); + + bool insert = snippet->getAppendToPrevious(); + bool fallback = continuationElement.isNull(); + + boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet; + QWebElement newElement = snippetToDOM(newSnippet); + + if (insert && !fallback) { + Q_ASSERT(!continuationElement.isNull()); + continuationElement.replace(newElement); + } else { + continuationElement.removeFromDocument(); + topInsertPoint_.prependOutside(newElement); + } + + firstElement_ = newElement; + + if (lastElement_.isNull()) { + lastElement_ = firstElement_; + } + + if (fontSizeSteps_ != 0) { + double size = 1.0 + 0.2 * fontSizeSteps_; + QString sizeString(QString().setNum(size, 'g', 3) + "em"); + const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable"); + Q_FOREACH (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + } +} + +QWebElement QtWebKitChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) { + QWebElement newElement = newInsertPoint_.clone(); + newElement.setInnerXml(snippet->getContent()); + Q_ASSERT(!newElement.isNull()); + return newElement; +} + +void QtWebKitChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) { + //qDebug() << snippet->getContent(); + rememberScrolledToBottom(); + bool insert = snippet->getAppendToPrevious(); + QWebElement continuationElement = lastElement_.findFirst("#insert"); + bool fallback = insert && continuationElement.isNull(); + boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet; + QWebElement newElement = snippetToDOM(newSnippet); + if (insert && !fallback) { + Q_ASSERT(!continuationElement.isNull()); + continuationElement.replace(newElement); + } else { + continuationElement.removeFromDocument(); + newInsertPoint_.prependOutside(newElement); + } + lastElement_ = newElement; + if (fontSizeSteps_ != 0) { + double size = 1.0 + 0.2 * fontSizeSteps_; + QString sizeString(QString().setNum(size, 'g', 3) + "em"); + const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable"); + Q_FOREACH (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + } + //qDebug() << "-----------------"; + //qDebug() << webPage_->mainFrame()->toHtml(); +} + +void QtWebKitChatView::addLastSeenLine() { + /* if the line is added we should break the snippet */ + insertingLastLine_ = true; + if (lineSeparator_.isNull()) { + lineSeparator_ = newInsertPoint_.clone(); + lineSeparator_.setInnerXml(QString("<hr/>")); + newInsertPoint_.prependOutside(lineSeparator_); + } + else { + QWebElement lineSeparatorC = lineSeparator_.clone(); + lineSeparatorC.removeFromDocument(); + } + newInsertPoint_.prependOutside(lineSeparator_); +} + +void QtWebKitChatView::replaceLastMessage(const QString& newMessage) { + assert(viewReady_); + rememberScrolledToBottom(); + assert(!lastElement_.isNull()); + QWebElement replace = lastElement_.findFirst("span.swift_message"); + assert(!replace.isNull()); + QString old = lastElement_.toOuterXml(); + replace.setInnerXml(ChatSnippet::escape(newMessage)); +} + +void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) { + rememberScrolledToBottom(); + replaceLastMessage(newMessage); + QWebElement replace = lastElement_.findFirst("span.swift_time"); + assert(!replace.isNull()); + replace.setInnerXml(ChatSnippet::escape(note)); +} + +QString QtWebKitChatView::getLastSentMessage() { + return lastElement_.toPlainText(); +} + +void QtWebKitChatView::addToJSEnvironment(const QString& name, QObject* obj) { + webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj); +} + +void QtWebKitChatView::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_inner_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)))); + } + else { + qWarning() << "Trying to replace element with id " << id << " but it's not there."; + } +} + +void QtWebKitChatView::showEmoticons(bool show) { + showEmoticons_ = show; + { + const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image"); + Q_FOREACH (QWebElement span, spans) { + span.setStyleProperty("display", show ? "inline" : "none"); + } + } + { + const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text"); + Q_FOREACH (QWebElement span, spans) { + span.setStyleProperty("display", show ? "none" : "inline"); + } + } +} + +void QtWebKitChatView::copySelectionToClipboard() { + if (!webPage_->selectedText().isEmpty()) { + webPage_->triggerAction(QWebPage::Copy); + } +} + +void QtWebKitChatView::setAckXML(const QString& id, const QString& xml) { + QWebElement message = document_.findFirst("#" + id); + /* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */ + if (message.isNull()) return; + QWebElement ackElement = message.findFirst("span.swift_ack"); + assert(!ackElement.isNull()); + ackElement.setInnerXml(xml); +} + +void QtWebKitChatView::setReceiptXML(const QString& id, const QString& xml) { + QWebElement message = document_.findFirst("#" + id); + if (message.isNull()) return; + QWebElement receiptElement = message.findFirst("span.swift_receipt"); + assert(!receiptElement.isNull()); + receiptElement.setInnerXml(xml); +} + +void QtWebKitChatView::displayReceiptInfo(const QString& id, bool showIt) { + QWebElement message = document_.findFirst("#" + id); + if (message.isNull()) return; + QWebElement receiptElement = message.findFirst("span.swift_receipt"); + assert(!receiptElement.isNull()); + receiptElement.setStyleProperty("display", showIt ? "inline" : "none"); +} + +void QtWebKitChatView::rememberScrolledToBottom() { + isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1); +} + +void QtWebKitChatView::scrollToBottom() { + isAtBottom_ = true; + webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)); + webView_->update(); /* Work around redraw bug in some versions of Qt. */ +} + +void QtWebKitChatView::handleFrameSizeChanged() { + if (topMessageAdded_) { + // adjust new scrollbar position + int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); + webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_); + topMessageAdded_ = false; + } + + if (isAtBottom_ && !disableAutoScroll_) { + scrollToBottom(); + } +} + +void QtWebKitChatView::handleLinkClicked(const QUrl& url) { + QDesktopServices::openUrl(url); +} + +void QtWebKitChatView::handleViewLoadFinished(bool ok) { + Q_ASSERT(ok); + viewReady_ = true; +} + +void QtWebKitChatView::increaseFontSize(int numSteps) { + //qDebug() << "Increasing"; + fontSizeSteps_ += numSteps; + emit fontResized(fontSizeSteps_); +} + +void QtWebKitChatView::decreaseFontSize() { + fontSizeSteps_--; + if (fontSizeSteps_ < 0) { + fontSizeSteps_ = 0; + } + emit fontResized(fontSizeSteps_); +} + +void QtWebKitChatView::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.swift_resizable"); + Q_FOREACH (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + webView_->setFontSizeIsMinimal(size == 1.0); +} + +void QtWebKitChatView::resetView() { + lastElement_ = QWebElement(); + firstElement_ = lastElement_; + topMessageAdded_ = false; + scrollBarMaximum_ = 0; + QString pageHTML = theme_->getTemplate(); + pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3"); + pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase()); + if (pageHTML.count("%@") > 3) { + pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getMainCSS()); + } + pageHTML.replace(pageHTML.indexOf("%@"), 2, "Variants/Blue on Green.css"); + pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*headerSnippet.getContent()*/); + pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/); + QEventLoop syncLoop; + connect(webView_, SIGNAL(loadFinished(bool)), &syncLoop, SLOT(quit())); + webPage_->mainFrame()->setHtml(pageHTML); + while (!viewReady_) { + QTimer t; + t.setSingleShot(true); + connect(&t, SIGNAL(timeout()), &syncLoop, SLOT(quit())); + t.start(50); + syncLoop.exec(); + } + document_ = webPage_->mainFrame()->documentElement(); + + resetTopInsertPoint(); + QWebElement chatElement = document_.findFirst("#Chat"); + newInsertPoint_ = chatElement.clone(); + newInsertPoint_.setOuterXml("<div id='swift_insert'/>"); + chatElement.appendInside(newInsertPoint_); + Q_ASSERT(!newInsertPoint_.isNull()); + + scrollToBottom(); + + connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); +} + +static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) { + QWebElementCollection elements = document.findAll(elementName); + Q_FOREACH(QWebElement element, elements) { + if (element.attribute("id") == id) { + return element; + } + } + return QWebElement(); +} + +void QtWebKitChatView::setFileTransferProgress(QString id, const int percentageDone) { + QWebElement ftElement = findElementWithID(document_, "div", 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 QtWebKitChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) { + QWebElement ftElement = findElementWithID(document_, "div", 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 = tr("Waiting for other side to accept the transfer.") + "<br/>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id); + } + if (state == ChatWindow::Negotiating) { + // replace with text "Negotiaging" + Cancel button + newInnerHTML = tr("Negotiating...") + "<br/>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, 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>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id); + } + else if (state == ChatWindow::Canceled) { + newInnerHTML = tr("Transfer has been canceled!"); + } + else if (state == ChatWindow::Finished) { + // text "Successful transfer" + newInnerHTML = tr("Transfer completed successfully."); + } + else if (state == ChatWindow::FTFailed) { + newInnerHTML = tr("Transfer failed."); + } + + ftElement.setInnerXml(newInnerHTML); +} + +void QtWebKitChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) { + QWebElement divElement = findElementWithID(document_, "div", id); + QString newInnerHTML; + if (state == ChatWindow::WhiteboardAccepted) { + newInnerHTML = tr("Started whiteboard chat") + "<br/>" + buildChatWindowButton(tr("Show whiteboard"), ButtonWhiteboardShowWindow, id); + } else if (state == ChatWindow::WhiteboardTerminated) { + newInnerHTML = tr("Whiteboard chat has been canceled"); + } else if (state == ChatWindow::WhiteboardRejected) { + newInnerHTML = tr("Whiteboard chat request has been rejected"); + } + divElement.setInnerXml(newInnerHTML); +} + +void QtWebKitChatView::setMUCInvitationJoined(QString id) { + QWebElement divElement = findElementWithID(document_, "div", id); + QWebElement buttonElement = findElementWithID(divElement, "input", "mucinvite"); + if (!buttonElement.isNull()) { + buttonElement.setAttribute("value", tr("Return to room")); + } +} + +void QtWebKitChatView::handleScrollRequested(int, int dy, const QRect&) { + rememberScrolledToBottom(); + + int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy; + emit scrollRequested(pos); + + if (pos == 0) { + emit scrollReachedTop(); + } + else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) { + emit scrollReachedBottom(); + } +} + +int QtWebKitChatView::getSnippetPositionByDate(const QDate& date) { + QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate)); + + return message.geometry().top(); +} + +void QtWebKitChatView::resetTopInsertPoint() { + QWebElement continuationElement = firstElement_.findFirst("#insert"); + continuationElement.removeFromDocument(); + firstElement_ = QWebElement(); + + topInsertPoint_.removeFromDocument(); + QWebElement chatElement = document_.findFirst("#Chat"); + topInsertPoint_ = chatElement.clone(); + topInsertPoint_.setOuterXml("<div id='swift_insert'/>"); + chatElement.prependInside(topInsertPoint_); +} + + +std::string QtWebKitChatView::addMessage( + const ChatWindow::ChatMessage& message, + const std::string& senderName, + bool senderIsSelf, + boost::shared_ptr<SecurityLabel> label, + const std::string& avatarPath, + const boost::posix_time::ptime& time, + const HighlightAction& highlight) { + return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message)); +} + +QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) { + QString result; + foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) { + boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart; + boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart; + boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart; + + if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); + text.replace("\n","<br/>"); + result += text; + continue; + } + if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) { + QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); + result += "<a href='" + uri + "' >" + uri + "</a>"; + continue; + } + if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(part))) { + QString textStyle = showEmoticons_ ? "style='display:none'" : ""; + QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; + result += "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + P2QSTRING(emoticonPart->imagePath) + "'/></span><span class='swift_emoticon_text' " + textStyle + ">" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + "</span>"; + continue; + } + if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) { + //FIXME: Maybe do something here. Anything, really. + continue; + } + + } + return result; +} + + +QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { + QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor())); + QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground())); + if (color.isEmpty()) { + color = "black"; + } + if (background.isEmpty()) { + background = "yellow"; + } + + return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background); +} + +std::string QtWebKitChatView::addMessage( + const QString& 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, + const HighlightAction& highlight, + ChatSnippet::Direction direction) { + + QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); + + QString htmlString; + if (label) { + htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor()))); + htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking()))); + } + + QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; + QString styleSpanEnd = style == "" ? "" : "</span>"; + QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : ""; + QString highlightSpanEnd = highlight.highlightText() ? "</span>" : ""; + htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ; + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf); + + QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); + std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction)); + + previousMessageWasSelf_ = senderIsSelf; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasMessage; + return id; +} + +std::string QtWebKitChatView::addAction(const ChatWindow::ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message)); +} + +// FIXME: Move this to a different file +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] : "") ); +} + +static QString encodeButtonArgument(const QString& str) { + return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str))))); +} + +static QString decodeButtonArgument(const QString& str) { + return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str)))); +} + +QString QtWebKitChatView::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) { + QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+"); + Q_ASSERT(regex.exactMatch(id)); + QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\", \"%6\", \"%7\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5)); + return html; +} + +std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { + SWIFT_LOG(debug) << "addFileTransfer" << std::endl; + QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); + + QString actionText; + QString htmlString; + QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes)); + if (senderIsSelf) { + // outgoing + actionText = tr("Send file"); + htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" + + "<div id='" + ft_id + "'>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) + + buildChatWindowButton(tr("Set Description"), ButtonFileTransferSetDescription, ft_id) + + buildChatWindowButton(tr("Send"), ButtonFileTransferSendRequest, ft_id) + + "</div>"; + } else { + // incoming + actionText = tr("Receiving file"); + htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" + + "<div id='" + ft_id + "'>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) + + buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) + + "</div>"; + } + + //addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time()); + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf); + + QString qAvatarPath = "qrc:/icons/avatar.png"; + std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++); + addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText))); + + previousMessageWasSelf_ = senderIsSelf; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasFileTransfer; + return Q2PSTRING(ft_id); +} + +void QtWebKitChatView::setFileTransferProgress(std::string id, const int percentageDone) { + setFileTransferProgress(P2QSTRING(id), percentageDone); +} + +void QtWebKitChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) { + setFileTransferStatus(P2QSTRING(id), state, P2QSTRING(msg)); +} + +std::string QtWebKitChatView::addWhiteboardRequest(const QString& contact, bool senderIsSelf) { + QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); + QString htmlString; + QString actionText; + if (senderIsSelf) { + actionText = tr("Starting whiteboard chat"); + htmlString = "<div id='" + wb_id + "'>" + actionText + "<br />"+ + buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) + + "</div>"; + } else { + actionText = tr("%1 would like to start a whiteboard chat"); + htmlString = "<div id='" + wb_id + "'>" + actionText.arg(QtUtilities::htmlEscape(contact)) + ": <br/>" + + buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) + + buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) + + "</div>"; + } + QString qAvatarPath = "qrc:/icons/avatar.png"; + std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++); + addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(contact), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText))); + previousMessageWasSelf_ = false; + previousSenderName_ = contact; + return Q2PSTRING(wb_id); +} + +void QtWebKitChatView::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) { + setWhiteboardSessionStatus(P2QSTRING(id), state); +} + + + + +void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) { + QString arg1 = decodeButtonArgument(encodedArgument1); + QString arg2 = decodeButtonArgument(encodedArgument2); + QString arg3 = decodeButtonArgument(encodedArgument3); + QString arg4 = decodeButtonArgument(encodedArgument4); + QString arg5 = decodeButtonArgument(encodedArgument5); + + if (id.startsWith(ButtonFileTransferCancel)) { + QString ft_id = arg1; + window_->onFileTransferCancel(Q2PSTRING(ft_id)); + } + else if (id.startsWith(ButtonFileTransferSetDescription)) { + QString ft_id = arg1; + bool ok = false; + QString text = QInputDialog::getText(this, tr("File transfer description"), + tr("Description:"), QLineEdit::Normal, "", &ok); + if (ok) { + descriptions_[ft_id] = text; + } + } + else if (id.startsWith(ButtonFileTransferSendRequest)) { + QString ft_id = arg1; + QString text = descriptions_.find(ft_id) == descriptions_.end() ? QString() : descriptions_[ft_id]; + window_->onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text)); + } + else if (id.startsWith(ButtonFileTransferAcceptRequest)) { + QString ft_id = arg1; + QString filename = arg2; + + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename); + if (!path.isEmpty()) { + window_->onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path)); + } + } + else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) { + QString id = arg1; + setWhiteboardSessionStatus(id, ChatWindow::WhiteboardAccepted); + window_->onWhiteboardSessionAccept(); + } + else if (id.startsWith(ButtonWhiteboardSessionCancel)) { + QString id = arg1; + setWhiteboardSessionStatus(id, ChatWindow::WhiteboardTerminated); + window_->onWhiteboardSessionCancel(); + } + else if (id.startsWith(ButtonWhiteboardShowWindow)) { + QString id = arg1; + window_->onWhiteboardWindowShow(); + } + else if (id.startsWith(ButtonMUCInvite)) { + QString roomJID = arg1; + QString password = arg2; + QString elementID = arg3; + QString isImpromptu = arg4; + QString isContinuation = arg5; + eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true"))); + setMUCInvitationJoined(elementID); + } + else { + SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl; + } +} + +void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessage) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } + + QString errorMessageHTML(chatMessageToHTML(errorMessage)); + + addMessageBottom(boost::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, ChatSnippet::getDirection(errorMessage))); + + previousMessageWasSelf_ = false; + previousMessageKind_ = PreviousMessageWasSystem; +} + +void QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } + + QString messageHTML = chatMessageToHTML(message); + addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction))); + + previousMessageKind_ = PreviousMessageWasSystem; +} + +void QtWebKitChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", highlight); +} + +void QtWebKitChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) { + replaceMessage(chatMessageToHTML(message), id, time, "", highlight); +} + +void QtWebKitChatView::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) { + if (!id.empty()) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } + + QString messageHTML(message); + + QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; + QString styleSpanEnd = style == "" ? "" : "</span>"; + QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : ""; + QString highlightSpanEnd = highlight.highlightText() ? "</span>" : ""; + messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd; + + replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time)); + } + else { + std::cerr << "Trying to replace a message with no id"; + } +} + +void QtWebKitChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } + + QString messageHTML = chatMessageToHTML(message); + addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction))); + + previousMessageKind_ = PreviousMessageWasPresence; +} + +void QtWebKitChatView::replaceLastMessage(const ChatWindow::ChatMessage& message) { + replaceLastMessage(chatMessageToHTML(message)); +} + +void QtWebKitChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } + + QString message; + if (isImpromptu) { + message = QObject::tr("You've been invited to join a chat.") + "\n"; + } else { + message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n"; + } + QString htmlString = message; + if (!reason.empty()) { + htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "\n"; + } + if (!direct) { + htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n"; + } + htmlString = chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING(htmlString))); + + + QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); + htmlString += "<div id='" + id + "'>" + + buildChatWindowButton(chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id, QtUtilities::htmlEscape(isImpromptu ? "true" : "false"), QtUtilities::htmlEscape(isContinuation ? "true" : "false")) + + "</div>"; + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false); + + QString qAvatarPath = "qrc:/icons/avatar.png"; + + addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id, ChatSnippet::getDirection(message))); + previousMessageWasSelf_ = false; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasMUCInvite; +} + +void QtWebKitChatView::setAckState(std::string const& id, ChatWindow::AckState state) { + QString xml; + switch (state) { + case ChatWindow::Pending: + xml = "<img src='qrc:/icons/throbber.gif' title='" + tr("This message has not been received by your server yet.") + "'/>"; + displayReceiptInfo(P2QSTRING(id), false); + break; + case ChatWindow::Received: + xml = ""; + displayReceiptInfo(P2QSTRING(id), true); + break; + case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' title='" + tr("This message may not have been transmitted.") + "'/>"; break; + } + setAckXML(P2QSTRING(id), xml); +} + +void QtWebKitChatView::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { + QString xml; + switch (state) { + case ChatWindow::ReceiptReceived: + xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>"; + break; + case ChatWindow::ReceiptRequested: + xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>"; + break; + } + setReceiptXML(P2QSTRING(id), xml); +} + +bool QtWebKitChatView::appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) { + bool result = previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName))); + if (insertingLastLine_) { + insertingLastLine_ = false; + return false; + } + return result; +} + +ChatSnippet::Direction QtWebKitChatView::getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) { + if (direction == ChatWindow::DefaultDirection) { + return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR; + } + else { + return ChatSnippet::getDirection(message); + } +} + +// void QtWebKitChatView::setShowEmoticons(bool value) { +// showEmoticons_ = value; +// } + + +} diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h new file mode 100644 index 0000000..6bdaf96 --- /dev/null +++ b/Swift/QtUI/QtWebKitChatView.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010-2013 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QString> +#include <QWidget> +#include <QList> +#include <QWebElement> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/Override.h> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + +#include <Swift/QtUI/ChatSnippet.h> +#include <Swift/QtUI/QtChatView.h> + +class QWebPage; +class QUrl; +class QDate; + +namespace Swift { + class QtWebView; + class QtChatTheme; + class QtChatWindowJSBridge; + class UIEventStream; + class QtChatWindow; + class QtWebKitChatView : public QtChatView { + Q_OBJECT + + public: + static const QString ButtonWhiteboardSessionCancel; + static const QString ButtonWhiteboardSessionAcceptRequest; + static const QString ButtonWhiteboardShowWindow; + static const QString ButtonFileTransferCancel; + static const QString ButtonFileTransferSetDescription; + static const QString ButtonFileTransferSendRequest; + static const QString ButtonFileTransferAcceptRequest; + static const QString ButtonMUCInvite; + public: + QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false); + ~QtWebKitChatView(); + + /** Add message to window. + * @return id of added message (for acks). + */ + virtual std::string addMessage(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; + /** Adds action to window. + * @return id of added message (for acks); + */ + virtual std::string addAction(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; + + virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE; + virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE; + + virtual void addErrorMessage(const ChatWindow::ChatMessage& message) SWIFTEN_OVERRIDE; + virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; + virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; + void replaceLastMessage(const ChatWindow::ChatMessage& message); + void setAckState(const std::string& id, ChatWindow::AckState state); + + virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) SWIFTEN_OVERRIDE; + virtual void setFileTransferProgress(std::string, const int percentageDone) SWIFTEN_OVERRIDE; + virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") SWIFTEN_OVERRIDE; + virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) SWIFTEN_OVERRIDE; + virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) SWIFTEN_OVERRIDE; + virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) SWIFTEN_OVERRIDE; + virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) SWIFTEN_OVERRIDE; + + virtual void showEmoticons(bool show) SWIFTEN_OVERRIDE; + void addMessageTop(boost::shared_ptr<ChatSnippet> snippet); + void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet); + + int getSnippetPositionByDate(const QDate& date); // FIXME : This probably shouldn't have been public + void addLastSeenLine(); + + private: // previously public, now private + 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); + void setReceiptXML(const QString& id, const QString& xml); + void displayReceiptInfo(const QString& id, bool showIt); + + 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); + void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state); + void setMUCInvitationJoined(QString id); + + signals: + void gotFocus(); + void fontResized(int); + void logCleared(); + void scrollRequested(int pos); + void scrollReachedTop(); + void scrollReachedBottom(); + + public slots: + void copySelectionToClipboard(); + void handleLinkClicked(const QUrl&); + void resetView(); + void resetTopInsertPoint(); + void increaseFontSize(int numSteps = 1); + void decreaseFontSize(); + void resizeFont(int fontSizeSteps); + void scrollToBottom(); + void handleKeyPressEvent(QKeyEvent* event); + + private slots: + void handleViewLoadFinished(bool); + void handleFrameSizeChanged(); + void handleClearRequested(); + void handleScrollRequested(int dx, int dy, const QRect& rectToScroll); + void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); + + private: + enum PreviousMessageKind { + PreviosuMessageWasNone, + PreviousMessageWasMessage, + PreviousMessageWasSystem, + PreviousMessageWasPresence, + PreviousMessageWasFileTransfer, + PreviousMessageWasMUCInvite + }; + std::string addMessage( + const QString& 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, + const HighlightAction& highlight, + ChatSnippet::Direction direction); + void replaceMessage( + const QString& message, + const std::string& id, + const boost::posix_time::ptime& time, + const QString& style, + const HighlightAction& highlight); + bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf); + static ChatSnippet::Direction getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction); + QString chatMessageToHTML(const ChatWindow::ChatMessage& message); + QString getHighlightSpanStart(const HighlightAction& highlight); + static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString()); + + private: + void headerEncode(); + void messageEncode(); + void addToDOM(boost::shared_ptr<ChatSnippet> snippet); + QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); + + QtChatWindow* window_; + UIEventStream* eventStream_; + bool viewReady_; + bool isAtBottom_; + bool topMessageAdded_; + int scrollBarMaximum_; + QtWebView* webView_; + QWebPage* webPage_; + int fontSizeSteps_; + QtChatTheme* theme_; + QWebElement newInsertPoint_; + QWebElement topInsertPoint_; + QWebElement lineSeparator_; + QWebElement lastElement_; + QWebElement firstElement_; + QWebElement document_; + bool disableAutoScroll_; + QtChatWindowJSBridge* jsBridge; + PreviousMessageKind previousMessageKind_; + bool previousMessageWasSelf_; + bool showEmoticons_; + bool insertingLastLine_; + int idCounter_; + QString previousSenderName_; + std::map<QString, QString> descriptions_; + }; +} diff --git a/Swift/QtUI/QtWebView.cpp b/Swift/QtUI/QtWebView.cpp index 388f06a..33fa817 100644 --- a/Swift/QtUI/QtWebView.cpp +++ b/Swift/QtUI/QtWebView.cpp @@ -9,7 +9,9 @@ #include <QKeyEvent> #include <QFocusEvent> +#include <boost/numeric/conversion/cast.hpp> #include <QMenu> +#include <Swiften/Base/Log.h> namespace Swift { QtWebView::QtWebView(QWidget* parent) : QWebView(parent), fontSizeIsMinimal(false) { @@ -17,6 +19,9 @@ QtWebView::QtWebView(QWidget* parent) : QWebView(parent), fontSizeIsMinimal(fals filteredActions.push_back(QWebPage::CopyLinkToClipboard); filteredActions.push_back(QWebPage::CopyImageToClipboard); filteredActions.push_back(QWebPage::Copy); + if (Log::getLogLevel() == Log::debug) { + filteredActions.push_back(QWebPage::InspectElement); + } } void QtWebView::keyPressEvent(QKeyEvent* event) { @@ -30,7 +35,7 @@ void QtWebView::keyPressEvent(QKeyEvent* event) { modifiers, event->text(), event->isAutoRepeat(), - event->count()); + boost::numeric_cast<unsigned short>(event->count())); QWebView::keyPressEvent(translatedEvent); delete translatedEvent; } diff --git a/Swift/QtUI/QtWin32NotifierWindow.h b/Swift/QtUI/QtWin32NotifierWindow.h index b8d9c77..cd43cf2 100644 --- a/Swift/QtUI/QtWin32NotifierWindow.h +++ b/Swift/QtUI/QtWin32NotifierWindow.h @@ -23,7 +23,7 @@ namespace Swift { } virtual HWND getID() const { - return winId(); + return (HWND) winId(); } }; } diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp index e674df8..284f3c3 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.cpp +++ b/Swift/QtUI/QtXMLConsoleWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2013 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -96,7 +96,11 @@ void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QCol cursor.insertText(P2QSTRING(data)); cursor.endEditBlock(); - if (scrollToBottom) { + // Checking for the scrollbar again, because it could have appeared after inserting text. + // In practice, I suspect that the scrollbar is always there, but hidden, but since we were + // explicitly testing for this already above, I'm leaving the code in. + scrollBar = textEdit->verticalScrollBar(); + if (scrollToBottom && scrollBar) { scrollBar->setValue(scrollBar->maximum()); } } diff --git a/Swift/QtUI/Roster/DelegateCommons.cpp b/Swift/QtUI/Roster/DelegateCommons.cpp index a575cb0..776d90c 100644 --- a/Swift/QtUI/Roster/DelegateCommons.cpp +++ b/Swift/QtUI/Roster/DelegateCommons.cpp @@ -14,11 +14,13 @@ namespace Swift { 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->setClipRect(region); painter->drawText(region, flags, adjustedText.simplified()); + painter->setClipping(false); } -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, bool compact) const { - painter->save(); +void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const { + painter->save(); QRect fullRegion(option.rect); if ( option.state & QStyle::State_Selected ) { painter->fillRect(fullRegion, option.palette.highlight()); @@ -29,6 +31,7 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin)); + QRect idleIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth*2, 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)); @@ -51,6 +54,10 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem //Paint the presence icon over the top of the avatar presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + if (isIdle) { + idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + } + QFontMetrics nameMetrics(nameFont); painter->setFont(nameFont); int extraFontWidth = nameMetrics.width("H"); diff --git a/Swift/QtUI/Roster/DelegateCommons.h b/Swift/QtUI/Roster/DelegateCommons.h index 8732598..0684410 100644 --- a/Swift/QtUI/Roster/DelegateCommons.h +++ b/Swift/QtUI/Roster/DelegateCommons.h @@ -17,7 +17,7 @@ namespace Swift { class DelegateCommons { public: - DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()) { + DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()), idleIcon(QIcon(":/icons/zzz.png")) { detailFontSizeDrop = nameFont.pointSize() >= 10 ? 2 : 0; detailFont.setStyle(QFont::StyleItalic); detailFont.setPointSize(nameFont.pointSize() - detailFontSizeDrop); @@ -26,7 +26,7 @@ namespace Swift { static void drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags = Qt::AlignTop); QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index, bool compact) 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, bool compact) const; + void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const; int detailFontSizeDrop; QFont nameFont; @@ -38,5 +38,6 @@ namespace Swift { static const int presenceIconHeight; static const int presenceIconWidth; static const int unreadCountSize; + QIcon idleIcon; }; } diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.cpp b/Swift/QtUI/Roster/QtOccupantListWidget.cpp index 5d26c46..12dc1e4 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.cpp +++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp @@ -58,6 +58,7 @@ void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) { case ChatWindow::MakeParticipant: text = tr("Make participant"); break; case ChatWindow::MakeVisitor: text = tr("Remove voice"); break; case ChatWindow::AddContact: text = tr("Add to contacts"); break; + case ChatWindow::ShowProfile: text = tr("Show profile"); break; } QAction* action = contextMenu.addAction(text); actions[action] = availableAction; diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index 1cf073b..6bf3989 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -4,23 +4,25 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Roster/QtRosterWidget.h" +#include <Swift/QtUI/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 "Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.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" +#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 <Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/QtUI/QtContactEditWindow.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -57,6 +59,18 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { QAction* editContact = contextMenu.addAction(tr("Edit…")); QAction* removeContact = contextMenu.addAction(tr("Remove")); + QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile")); + + QAction* unblockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsBlocked) { + unblockContact = contextMenu.addAction(tr("Unblock")); + } + + QAction* blockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsUnblocked) { + blockContact = contextMenu.addAction(tr("Block")); + } + #ifdef SWIFT_EXPERIMENTAL_FT QAction* sendFile = NULL; if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { @@ -69,6 +83,7 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat")); } #endif + QAction* result = contextMenu.exec(event->globalPos()); if (result == editContact) { eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID())); @@ -78,6 +93,15 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID())); } } + else if (result == showProfileForContact) { + eventStream_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(contact->getJID())); + } + else if (unblockContact && result == unblockContact) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID())); + } + else if (blockContact && result == blockContact) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, contact->getJID())); + } #ifdef SWIFT_EXPERIMENTAL_FT else if (sendFile && result == sendFile) { QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;")); diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 5fdf138..99f1f34 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -10,6 +10,7 @@ #include <boost/bind.hpp> #include <QUrl> +#include <QMimeData> #include <Swiften/Base/Platform.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> @@ -41,6 +42,7 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* setting #ifdef SWIFT_EXPERIMENTAL_FT setAcceptDrops(true); #endif + setDragEnabled(true); setRootIsDecorated(true); connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); connect(model_, SIGNAL(itemExpanded(const QModelIndex&, bool)), this, SLOT(handleModelItemExpanded(const QModelIndex&, bool))); @@ -156,7 +158,7 @@ void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) { } } } - event->ignore(); + QTreeView::dragMoveEvent(event); } void QtTreeWidget::handleExpanded(const QModelIndex& index) { diff --git a/Swift/QtUI/Roster/RosterDelegate.cpp b/Swift/QtUI/Roster/RosterDelegate.cpp index 7e6428b..aef588c 100644 --- a/Swift/QtUI/Roster/RosterDelegate.cpp +++ b/Swift/QtUI/Roster/RosterDelegate.cpp @@ -35,6 +35,7 @@ RosterDelegate::~RosterDelegate() { void RosterDelegate::setCompact(bool compact) { compact_ = compact; + emit sizeHintChanged(QModelIndex()); } QSize RosterDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { @@ -73,9 +74,10 @@ void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& QIcon presenceIcon = index.data(PresenceIconRole).isValid() && !index.data(PresenceIconRole).value<QIcon>().isNull() ? index.data(PresenceIconRole).value<QIcon>() : QIcon(":/icons/offline.png"); + bool isIdle = index.data(IdleRole).isValid() ? index.data(IdleRole).toBool() : false; QString name = index.data(Qt::DisplayRole).toString(); QString statusText = index.data(StatusTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, 0, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, isIdle, 0, compact_); } } diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp index 1fc20dd..3791ffa 100644 --- a/Swift/QtUI/Roster/RosterModel.cpp +++ b/Swift/QtUI/Roster/RosterModel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,12 +10,14 @@ #include <QColor> #include <QIcon> +#include <QMimeData> #include <qdebug.h> #include "Swiften/Elements/StatusShow.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" #include <Swift/Controllers/StatusUtil.h> +#include <Swiften/Base/Path.h> #include "QtSwiftUtil.h" #include "Swift/QtUI/Roster/QtTreeWidget.h" @@ -40,7 +42,8 @@ void RosterModel::setRoster(Roster* roster) { void RosterModel::reLayout() { //emit layoutChanged(); - reset(); + beginResetModel(); + endResetModel(); // TODO: Not sure if this isn't too early? if (!roster_) { return; } @@ -53,7 +56,7 @@ void RosterModel::reLayout() { void RosterModel::handleChildrenChanged(GroupRosterItem* /*group*/) { reLayout(); -} +} void RosterModel::handleDataChanged(RosterItem* item) { Q_ASSERT(item); @@ -63,6 +66,14 @@ void RosterModel::handleDataChanged(RosterItem* item) { } } +Qt::ItemFlags RosterModel::flags(const QModelIndex& index) const { + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (dynamic_cast<GroupRosterItem*>(getItem(index)) == NULL) { + flags |= Qt::ItemIsDragEnabled; + } + return flags; +} + int RosterModel::columnCount(const QModelIndex& /*parent*/) const { return 1; } @@ -84,7 +95,8 @@ QVariant RosterModel::data(const QModelIndex& index, int role) const { case AvatarRole: return getAvatar(item); case PresenceIconRole: return getPresenceIcon(item); case ChildCountRole: return getChildCount(item); - default: return QVariant(); + case IdleRole: return getIsIdle(item); + default: return QVariant(); } } @@ -93,6 +105,11 @@ int RosterModel::getChildCount(RosterItem* item) const { return group ? group->getDisplayedChildren().size() : 0; } +bool RosterModel::getIsIdle(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + return contact ? !contact->getIdleText().empty() : false; +} + QColor RosterModel::intToColor(int color) const { return QColor( ((color & 0xFF0000)>>16), @@ -131,6 +148,9 @@ QString RosterModel::getToolTip(RosterItem* item) const { if (!getStatusText(item).isEmpty()) { tip += ": " + getStatusText(item); } + if (!contact->getIdleText().empty()) { + tip += "\n " + tr("Idle since ") + P2QSTRING(contact->getIdleText()); + } } return tip; } @@ -140,7 +160,7 @@ QString RosterModel::getAvatar(RosterItem* item) const { if (!contact) { return ""; } - return QString(contact->getAvatarPath().c_str()); + return P2QSTRING(pathToString(contact->getAvatarPath())); } QString RosterModel::getStatusText(RosterItem* item) const { @@ -152,14 +172,18 @@ QString RosterModel::getStatusText(RosterItem* item) const { QIcon RosterModel::getPresenceIcon(RosterItem* item) const { ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); if (!contact) return QIcon(); + if (contact->blockState() == ContactRosterItem::IsBlocked) { + return QIcon(":/icons/stop.png"); + } + QString iconString; switch (contact->getStatusShow()) { - 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; + 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"); } @@ -215,4 +239,25 @@ int RosterModel::rowCount(const QModelIndex& parent) const { return count; } +QMimeData* RosterModel::mimeData(const QModelIndexList& indexes) const { + QMimeData* data = QAbstractItemModel::mimeData(indexes); + + ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(getItem(indexes.first())); + if (item == NULL) { + return data; + } + + QByteArray itemData; + QDataStream dataStream(&itemData, QIODevice::WriteOnly); + + // jid, chatName, activity, statusType, avatarPath + dataStream << P2QSTRING(item->getJID().toString()); + dataStream << P2QSTRING(item->getDisplayName()); + dataStream << P2QSTRING(item->getStatusText()); + dataStream << item->getSimplifiedStatusShow(); + dataStream << P2QSTRING(item->getAvatarPath().string()); + data->setData("application/vnd.swift.contact-jid", itemData); + return data; +} + } diff --git a/Swift/QtUI/Roster/RosterModel.h b/Swift/QtUI/Roster/RosterModel.h index bd34e9c..cae80c4 100644 --- a/Swift/QtUI/Roster/RosterModel.h +++ b/Swift/QtUI/Roster/RosterModel.h @@ -18,6 +18,7 @@ namespace Swift { PresenceIconRole = Qt::UserRole + 2, StatusShowTypeRole = Qt::UserRole + 3, ChildCountRole = Qt::UserRole + 4, + IdleRole = Qt::UserRole + 5 }; class QtTreeWidget; @@ -28,12 +29,15 @@ namespace Swift { RosterModel(QtTreeWidget* view); ~RosterModel(); void setRoster(Roster* swiftRoster); + Qt::ItemFlags flags(const QModelIndex& index) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; QModelIndex index(RosterItem* item) const; QModelIndex parent(const QModelIndex& index) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + signals: void itemExpanded(const QModelIndex& item, bool expanded); private: @@ -48,6 +52,7 @@ namespace Swift { QString getStatusText(RosterItem* item) const; QIcon getPresenceIcon(RosterItem* item) const; int getChildCount(RosterItem* item) const; + bool getIsIdle(RosterItem* item) const; void reLayout(); Roster* roster_; QtTreeWidget* view_; diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 64c3b15..6835872 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -20,7 +20,12 @@ def generateDefaultTheme(dir) : Import("env") myenv = env.Clone() + +# Disable warnings that affect Qt myenv["CXXFLAGS"] = filter(lambda x : x != "-Wfloat-equal", myenv["CXXFLAGS"]) +if "clang" in env["CC"] : + myenv.Append(CXXFLAGS = ["-Wno-float-equal", "-Wno-shorten-64-to-32", "-Wno-missing-prototypes", "-Wno-unreachable-code", "-Wno-disabled-macro-expansion", "-Wno-unused-private-field", "-Wno-extra-semi", "-Wno-duplicate-enum", "-Wno-missing-variable-declarations", "-Wno-conversion", "-Wno-undefined-reinterpret-cast"]) + myenv.UseFlags(env["SWIFT_CONTROLLERS_FLAGS"]) myenv.UseFlags(env["SWIFTOOLS_FLAGS"]) if myenv["HAVE_XSS"] : @@ -31,7 +36,8 @@ if myenv["HAVE_SPARKLE"] : myenv.UseFlags(env["SPARKLE_FLAGS"]) myenv.UseFlags(env["SWIFTEN_FLAGS"]) myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"]) -myenv.UseFlags(env["BREAKPAD_FLAGS"]) +if myenv.get("HAVE_BREAKPAD") : + myenv.UseFlags(env["BREAKPAD_FLAGS"]) if myenv.get("HAVE_GROWL", False) : myenv.UseFlags(myenv["GROWL_FLAGS"]) myenv.Append(CPPDEFINES = ["HAVE_GROWL"]) @@ -40,6 +46,9 @@ if myenv["swift_mobile"] : if myenv.get("HAVE_SNARL", False) : myenv.UseFlags(myenv["SNARL_FLAGS"]) myenv.Append(CPPDEFINES = ["HAVE_SNARL"]) +if myenv.get("HAVE_HUNSPELL", True): + myenv.Append(CPPDEFINES = ["HAVE_HUNSPELL"]) + myenv.UseFlags(myenv["HUNSPELL_FLAGS"]) if env["PLATFORM"] == "win32" : myenv.Append(LIBS = ["cryptui"]) myenv.UseFlags(myenv["PLATFORM_FLAGS"]) @@ -48,18 +57,23 @@ myenv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"]) myenv.Tool("nsis", toolpath = ["#/BuildTools/SCons/Tools"]) myenv.Tool("wix", toolpath = ["#/BuildTools/SCons/Tools"]) myenv.Tool("textfile", toolpath = ["#/BuildTools/SCons/Tools"]) -qt4modules = ['QtCore', 'QtGui', 'QtWebKit'] +qt4modules = ['QtCore', 'QtWebKit', 'QtGui'] +if myenv["qt5"] : + qt_version = '5' + qt4modules += ['QtWidgets', 'QtWebKitWidgets', 'QtMultimedia'] +else : + qt_version = '4' if env["PLATFORM"] == "posix" : qt4modules += ["QtDBus"] if env["PLATFORM"] != "win32" and env["PLATFORM"] != "darwin" : qt4modules += ["QtNetwork"] -myenv.EnableQt4Modules(qt4modules, debug = False) +myenv.EnableQt4Modules(qt4modules, debug = False, version = qt_version) myenv.Append(CPPPATH = ["."]) if env["PLATFORM"] == "win32" : - #myenv["LINKFLAGS"] = ["/SUBSYSTEM:CONSOLE"] + #myenv.Append(LINKFLAGS = ["/SUBSYSTEM:CONSOLE"]) myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"]) myenv.Append(LIBS = "qtmain") if myenv.get("HAVE_SCHANNEL", 0) : @@ -73,21 +87,24 @@ myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("# sources = [ "main.cpp", "QtAboutWidget.cpp", + "QtSpellCheckerWindow.cpp", "QtAvatarWidget.cpp", "QtUIFactory.cpp", "QtChatWindowFactory.cpp", - "QtChatWindow.cpp", "QtClickableLabel.cpp", "QtLoginWindow.cpp", "QtMainWindow.cpp", "QtProfileWindow.cpp", + "QtBlockListEditorWindow.cpp", "QtNameWidget.cpp", "QtSettingsProvider.cpp", "QtStatusWidget.cpp", "QtScaledAvatarCache.cpp", "QtSwift.cpp", "QtURIHandler.cpp", + "QtChatWindow.cpp", "QtChatView.cpp", + "QtWebKitChatView.cpp", "QtChatTheme.cpp", "QtChatTabs.cpp", "QtSoundPlayer.cpp", @@ -107,6 +124,11 @@ sources = [ "QtEditBookmarkWindow.cpp", "QtContactEditWindow.cpp", "QtContactEditWidget.cpp", + "QtSingleWindow.cpp", + "QtHighlightEditorWidget.cpp", + "QtHighlightRulesItemModel.cpp", + "QtHighlightRuleWidget.cpp", + "QtColorToolButton.cpp", "ChatSnippet.cpp", "MessageSnippet.cpp", "SystemMessageSnippet.cpp", @@ -115,7 +137,6 @@ sources = [ "QtFormResultItemModel.cpp", "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", - "QtInviteToChatWindow.cpp", "QtConnectionSettingsWindow.cpp", "Roster/RosterModel.cpp", "Roster/QtTreeWidget.cpp", @@ -141,7 +162,12 @@ sources = [ "MUCSearch/MUCSearchRoomItem.cpp", "MUCSearch/MUCSearchEmptyItem.cpp", "MUCSearch/MUCSearchDelegate.cpp", + "UserSearch/ContactListDelegate.cpp", + "UserSearch/ContactListModel.cpp", + "UserSearch/QtContactListWidget.cpp", + "UserSearch/QtSuggestingJIDInput.cpp", "UserSearch/QtUserSearchFirstPage.cpp", + "UserSearch/QtUserSearchFirstMultiJIDPage.cpp", "UserSearch/QtUserSearchFieldsPage.cpp", "UserSearch/QtUserSearchResultsPage.cpp", "UserSearch/QtUserSearchDetailsPage.cpp", @@ -165,6 +191,34 @@ sources = [ "QtURLValidator.cpp" ] +# QtVCardWidget +sources.extend([ + "QtVCardWidget/QtCloseButton.cpp", + "QtVCardWidget/QtRemovableItemDelegate.cpp", + "QtVCardWidget/QtResizableLineEdit.cpp", + "QtVCardWidget/QtTagComboBox.cpp", + "QtVCardWidget/QtVCardHomeWork.cpp", + "QtVCardWidget/QtVCardAddressField.cpp", + "QtVCardWidget/QtVCardAddressLabelField.cpp", + "QtVCardWidget/QtVCardBirthdayField.cpp", + "QtVCardWidget/QtVCardDescriptionField.cpp", + "QtVCardWidget/QtVCardInternetEMailField.cpp", + "QtVCardWidget/QtVCardJIDField.cpp", + "QtVCardWidget/QtVCardOrganizationField.cpp", + "QtVCardWidget/QtVCardPhotoAndNameFields.cpp", + "QtVCardWidget/QtVCardRoleField.cpp", + "QtVCardWidget/QtVCardTelephoneField.cpp", + "QtVCardWidget/QtVCardTitleField.cpp", + "QtVCardWidget/QtVCardURLField.cpp", + "QtVCardWidget/QtVCardGeneralField.cpp", + "QtVCardWidget/QtVCardWidget.cpp" +]) + +myenv.Uic4("QtVCardWidget/QtVCardPhotoAndNameFields.ui") +myenv.Uic4("QtVCardWidget/QtVCardWidget.ui") +myenv.Uic4("QtProfileWindow.ui") + + # Determine the version myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") if env["PLATFORM"] == "win32" : @@ -218,6 +272,7 @@ else : myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui") myenv.Uic4("UserSearch/QtUserSearchWizard.ui") myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui") +myenv.Uic4("UserSearch/QtUserSearchFirstMultiJIDPage.ui") myenv.Uic4("UserSearch/QtUserSearchFieldsPage.ui") myenv.Uic4("UserSearch/QtUserSearchResultsPage.ui") myenv.Uic4("QtBookmarkDetailWindow.ui") @@ -225,6 +280,10 @@ myenv.Uic4("QtAffiliationEditor.ui") myenv.Uic4("QtJoinMUCWindow.ui") myenv.Uic4("QtHistoryWindow.ui") myenv.Uic4("QtConnectionSettings.ui") +myenv.Uic4("QtHighlightRuleWidget.ui") +myenv.Uic4("QtHighlightEditorWidget.ui") +myenv.Uic4("QtBlockListEditorWindow.ui") +myenv.Uic4("QtSpellCheckerWindow.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") @@ -306,7 +365,7 @@ if env.get("SWIFT_INSTALLDIR", "") : env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "swift", dir), resource) if env["PLATFORM"] == "win32" : - if env["DIST"] : + if env["DIST"] or ARGUMENTS.get("dump_trace") : commonResources[""] = commonResources.get("", []) + [ #os.path.join(env["OPENSSL_DIR"], "bin", "ssleay32.dll"), #os.path.join(env["OPENSSL_DIR"], "bin", "libeay32.dll"), @@ -314,13 +373,25 @@ if env["PLATFORM"] == "win32" : ] if env["SWIFTEN_DLL"] : commonResources[""] = commonResources.get("", []) + ["#/Swiften/${SWIFTEN_LIBRARY_FILE}"] - qtimageformats = ["gif", "ico", "jpeg", "mng", "svg", "tiff"] - qtlibs = ["QtCore4", "QtGui4", "QtNetwork4", "QtWebKit4", "QtXMLPatterns4", "phonon4"] + qtplugins = {} + qtplugins["imageformats"] = ["gif", "ico", "jpeg", "mng", "svg", "tiff"] + qtlibs = ["QtCore", "QtGui", "QtNetwork", "QtWebKit", "QtXMLPatterns"] + if qt_version == '4' : + qtlibs.append("phonon") + qtlibs = [lib + '4' for lib in qtlibs] + else : + qtlibs += ['QtQuick', 'QtQml', 'QtV8', 'QtMultimedia', 'QtSql', 'QtSensors', 'QtWidgets', 'QtWebKitWidgets', 'QtMultimediaWidgets', 'QtOpenGL', 'QtPrintSupport'] + qtlibs = [lib.replace('Qt', 'Qt5') for lib in qtlibs] + qtlibs += ['icuin51', 'icuuc51', 'icudt51', 'libGLESv2', 'libEGL'] + qtplugins["platforms"] = ['windows'] + qtplugins["accessible"] = ["taccessiblewidgets"] windowsBundleFiles = myenv.WindowsBundle("Swift", resources = commonResources, - qtimageformats = qtimageformats, - qtlibs = qtlibs) + qtplugins = qtplugins, + qtlibs = qtlibs, + qtversion = qt_version) + if env["DIST"] : #myenv.Append(NSIS_OPTIONS = [ # "/DmsvccRedistributableDir=\"" + env["vcredist"] + "\"", # "/DbuildVersion=" + myenv["SWIFT_VERSION"] @@ -344,7 +415,7 @@ if env["PLATFORM"] == "win32" : outfile.write('}') outfile.close() infile.close() - env.Command(["Swift/COPYING.rtf"], ["COPYING"], convertToRTF) + copying = env.Command(["Swift/COPYING.rtf"], ["COPYING"], convertToRTF) wixvariables = { 'VCCRTFile': env["vcredist"], @@ -355,7 +426,7 @@ if env["PLATFORM"] == "win32" : wixincludecontent += "<?define %s = \"%s\" ?>" % (key, wixvariables[key]) wixincludecontent += "</Include>" myenv.WriteVal("..\\Packaging\\Wix\\variables.wxs", env.Value(wixincludecontent)) - myenv.WiX_Heat('..\\Packaging\\WiX\\gen_files.wxs', windowsBundleFiles) + myenv.WiX_Heat('..\\Packaging\\WiX\\gen_files.wxs', windowsBundleFiles + copying) myenv.WiX_Candle('..\\Packaging\\WiX\\Swift.wixobj', '..\\Packaging\\WiX\\Swift.wxs') myenv.WiX_Candle('..\\Packaging\\WiX\\gen_files.wixobj', '..\\Packaging\\WiX\\gen_files.wxs') myenv.WiX_Light('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.msi', ['..\\Packaging\\WiX\\gen_files.wixobj','..\\Packaging\\WiX\\Swift.wixobj']) diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index eb4f7ee..eeef80d 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -29,5 +29,18 @@ <file alias="icons/polygon.png">../resources/icons/polygon.png</file> <file alias="icons/cursor.png">../resources/icons/cursor.png</file> <file alias="icons/eraser.png">../resources/icons/eraser.png</file> + <file alias="emoticons/emoticons.txt">../resources/emoticons/emoticons.txt</file> + <file alias="emoticons/evilgrin.png">../resources/emoticons/evilgrin.png</file> + <file alias="emoticons/grin.png">../resources/emoticons/grin.png</file> + <file alias="emoticons/happy.png">../resources/emoticons/happy.png</file> + <file alias="emoticons/smile.png">../resources/emoticons/smile.png</file> + <file alias="emoticons/surprised.png">../resources/emoticons/surprised.png</file> + <file alias="emoticons/tongue.png">../resources/emoticons/tongue.png</file> + <file alias="emoticons/unhappy.png">../resources/emoticons/unhappy.png</file> + <file alias="emoticons/wink.png">../resources/emoticons/wink.png</file> + <file alias="icons/star-checked.png">../resources/icons/star-checked2.png</file> + <file alias="icons/star-unchecked.png">../resources/icons/star-unchecked2.png</file> + <file alias="icons/zzz.png">../resources/icons/zzz.png</file> + <file alias="icons/stop.png">../resources/icons/stop.png</file> </qresource> </RCC> diff --git a/Swift/QtUI/SystemMessageSnippet.cpp b/Swift/QtUI/SystemMessageSnippet.cpp index c78fe36..39349bc 100644 --- a/Swift/QtUI/SystemMessageSnippet.cpp +++ b/Swift/QtUI/SystemMessageSnippet.cpp @@ -10,12 +10,13 @@ namespace Swift { -SystemMessageSnippet::SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme) : ChatSnippet(appendToPrevious) { +SystemMessageSnippet::SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme, Direction direction) : ChatSnippet(appendToPrevious) { if (appendToPrevious) { - setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(message, time, false, theme))); + setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(message, time, false, theme, direction))); } content_ = theme->getStatus(); + content_.replace("%direction%", directionToCSS(direction)); content_.replace("%message%", wrapResizable("<span class='swift_message'>" + escape(message) + "</span>")); content_.replace("%shortTime%", wrapResizable(escape(time.toString("h:mm")))); content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); diff --git a/Swift/QtUI/SystemMessageSnippet.h b/Swift/QtUI/SystemMessageSnippet.h index 69d231f..34b0d40 100644 --- a/Swift/QtUI/SystemMessageSnippet.h +++ b/Swift/QtUI/SystemMessageSnippet.h @@ -15,7 +15,7 @@ class QDateTime; namespace Swift { class SystemMessageSnippet : public ChatSnippet { public: - SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme); + SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme, Direction direction); virtual ~SystemMessageSnippet(); const QString& getContent() const {return content_;} diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.cpp b/Swift/QtUI/UserSearch/ContactListDelegate.cpp new file mode 100644 index 0000000..29cab83 --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListDelegate.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/UserSearch/ContactListDelegate.h> +#include <Swift/QtUI/UserSearch/ContactListModel.h> +#include <Swift/Controllers/Contact.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +ContactListDelegate::ContactListDelegate(bool compact) : compact_(compact) { +} + +ContactListDelegate::~ContactListDelegate() { +} + +void ContactListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + if (!index.isValid()) { + return; + } + const Contact* contact = static_cast<Contact*>(index.internalPointer()); + QColor nameColor = index.data(Qt::TextColorRole).value<QColor>(); + QString avatarPath = index.data(ContactListModel::AvatarRole).value<QString>(); + QIcon presenceIcon =index.data(ChatListRecentItem::PresenceIconRole).isValid() && !index.data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull() + ? index.data(ChatListRecentItem::PresenceIconRole).value<QIcon>() + : QIcon(":/icons/offline.png"); + QString name = P2QSTRING(contact->name); + QString statusText = P2QSTRING(contact->jid.toString()); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, 0, compact_); +} + +QSize ContactListDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const { + QFontMetrics nameMetrics(common_.nameFont); + QFontMetrics statusMetrics(common_.detailFont); + int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); + return QSize(150, sizeByText); +} + +void ContactListDelegate::setCompact(bool compact) { + compact_ = compact; +} + +} diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.h b/Swift/QtUI/UserSearch/ContactListDelegate.h new file mode 100644 index 0000000..7680aba --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListDelegate.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QStyledItemDelegate> + +#include <Swift/QtUI/Roster/DelegateCommons.h> + +namespace Swift { + +class ContactListDelegate : public QStyledItemDelegate { + public: + ContactListDelegate(bool compact); + virtual ~ContactListDelegate(); + + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + public slots: + void setCompact(bool compact); + + private: + bool compact_; + DelegateCommons common_; +}; +} diff --git a/Swift/QtUI/UserSearch/ContactListModel.cpp b/Swift/QtUI/UserSearch/ContactListModel.cpp new file mode 100644 index 0000000..6523a4d --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListModel.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/UserSearch/ContactListModel.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swiften/Base/Path.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Elements/StatusShow.h> + +#include <QMimeData> + +namespace Swift { + +QDataStream& operator >>(QDataStream& in, StatusShow::Type& e){ + quint32 buffer; + in >> buffer; + switch(buffer) { + case StatusShow::Online: + e = StatusShow::Online; + break; + case StatusShow::Away: + e = StatusShow::Away; + break; + case StatusShow::FFC: + e = StatusShow::FFC; + break; + case StatusShow::XA: + e = StatusShow::XA; + break; + case StatusShow::DND: + e = StatusShow::DND; + break; + default: + e = StatusShow::None; + break; + } + return in; +} + +ContactListModel::ContactListModel(bool editable) : QAbstractItemModel(), editable_(editable) { +} + +void ContactListModel::setList(const std::vector<Contact>& list) { + emit layoutAboutToBeChanged(); + contacts_ = list; + emit layoutChanged(); +} + +const std::vector<Contact>& ContactListModel::getList() const { + return contacts_; +} + +Qt::ItemFlags ContactListModel::flags(const QModelIndex& index) const { + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (index.isValid()) { + flags = flags & ~Qt::ItemIsDropEnabled; + } else { + flags = Qt::ItemIsDropEnabled | flags; + } + return flags; +} + +int ContactListModel::columnCount(const QModelIndex&) const { + return editable_ ? 2 : 1; +} + +QVariant ContactListModel::data(const QModelIndex& index, int role) const { + if (boost::numeric_cast<size_t>(index.row()) < contacts_.size()) { + const Contact& contact = contacts_[index.row()]; + if (role == Qt::EditRole) { + return P2QSTRING(contact.jid.toString()); + } + return dataForContact(contact, role); + } else { + return QVariant(); + } +} + +bool ContactListModel::dropMimeData(const QMimeData* data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/) { + if (!data->hasFormat("application/vnd.swift.contact-jid")) { + return false; + } + + QByteArray dataBytes = data->data("application/vnd.swift.contact-jid"); + QDataStream dataStream(&dataBytes, QIODevice::ReadOnly); + QString jidString; + QString displayName; + QString statusText; + StatusShow::Type statusType; + QString avatarPath; + + dataStream >> jidString; + dataStream >> displayName; + dataStream >> statusText; + dataStream >> statusType; + dataStream >> avatarPath; + + JID jid = JID(Q2PSTRING(jidString)); + + foreach(const Contact& contact, contacts_) { + if (contact.jid == jid) { + return false; + } + } + + emit layoutAboutToBeChanged(); + contacts_.push_back(Contact(Q2PSTRING(displayName), jid, statusType, Q2PSTRING(avatarPath))); + emit layoutChanged(); + + onJIDsDropped(std::vector<JID>(1, jid)); + onListChanged(getList()); + + return true; +} + +QModelIndex ContactListModel::index(int row, int column, const QModelIndex& parent) const { + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + return boost::numeric_cast<size_t>(row) < contacts_.size() ? createIndex(row, column, (void*)&(contacts_[row])) : QModelIndex(); +} + +QModelIndex ContactListModel::parent(const QModelIndex& index) const { + if (!index.isValid()) { + return QModelIndex(); + } + return QModelIndex(); +} + +int ContactListModel::rowCount(const QModelIndex& /*parent*/) const { + return contacts_.size(); +} + +bool ContactListModel::removeRows(int row, int /*count*/, const QModelIndex& /*parent*/) { + if (boost::numeric_cast<size_t>(row) < contacts_.size()) { + emit layoutAboutToBeChanged(); + contacts_.erase(contacts_.begin() + row); + emit layoutChanged(); + onListChanged(getList()); + return true; + } + return false; +} + +QVariant ContactListModel::dataForContact(const Contact& contact, int role) const { + switch (role) { + case Qt::DisplayRole: return P2QSTRING(contact.name); + case DetailTextRole: return P2QSTRING(contact.jid.toString()); + case AvatarRole: return QVariant(P2QSTRING(pathToString(contact.avatarPath))); + case PresenceIconRole: return getPresenceIconForContact(contact); + default: return QVariant(); + } +} + +QIcon ContactListModel::getPresenceIconForContact(const Contact& contact) const { + QString iconString; + switch (contact.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/UserSearch/ContactListModel.h b/Swift/QtUI/UserSearch/ContactListModel.h new file mode 100644 index 0000000..e7f4a0b --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListModel.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> +#include <boost/bind.hpp> +#include <Swiften/Base/boost_bsignals.h> + +#include <QAbstractItemModel> + +#include <Swift/Controllers/Contact.h> +#include <Swift/QtUI/ChatList/ChatListItem.h> +#include <Swift/QtUI/ChatList/ChatListGroupItem.h> +#include <Swift/QtUI/ChatList/ChatListRecentItem.h> + +namespace Swift { + class ContactListModel : public QAbstractItemModel { + Q_OBJECT + public: + enum ContactRoles { + DetailTextRole = Qt::UserRole, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2 + }; + + public: + ContactListModel(bool editable); + + void setList(const std::vector<Contact>& list); + const std::vector<Contact>& getList() const; + + Qt::ItemFlags flags(const QModelIndex& index) const; + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); + + private: + QVariant dataForContact(const Contact& contact, int role) const; + QIcon getPresenceIconForContact(const Contact& contact) const; + + signals: + void onListChanged(std::vector<Contact> list); + void onJIDsDropped(const std::vector<JID>& contact); + + private: + bool editable_; + std::vector<Contact> contacts_; + }; + +} diff --git a/Swift/QtUI/UserSearch/QtContactListWidget.cpp b/Swift/QtUI/UserSearch/QtContactListWidget.cpp new file mode 100644 index 0000000..90adc11 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtContactListWidget.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/UserSearch/QtContactListWidget.h> + +#include <Swift/QtUI/UserSearch/ContactListModel.h> +#include <Swift/QtUI/UserSearch/ContactListDelegate.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> + +#include <QHeaderView> + +namespace Swift { + +QtContactListWidget::QtContactListWidget(QWidget* parent, SettingsProvider* settings) : QTreeView(parent), settings_(settings), limited_(false) { + contactListModel_ = new ContactListModel(true); + setModel(contactListModel_); + + connect(contactListModel_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(handleListChanged(std::vector<Contact>))); + connect(contactListModel_, SIGNAL(onListChanged(std::vector<Contact>)), this, SIGNAL(onListChanged(std::vector<Contact>))); + connect(contactListModel_, SIGNAL(onJIDsDropped(std::vector<JID>)), this, SIGNAL(onJIDsAdded(std::vector<JID>))); + + setSelectionMode(QAbstractItemView::SingleSelection); + setSelectionBehavior(QAbstractItemView::SelectRows); + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setUniformRowHeights(true); + + setAlternatingRowColors(true); + setIndentation(0); + setHeaderHidden(true); + setExpandsOnDoubleClick(false); + setItemsExpandable(false); + setEditTriggers(QAbstractItemView::DoubleClicked); + + contactListDelegate_ = new ContactListDelegate(settings->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + removableItemDelegate_ = new QtRemovableItemDelegate(style()); + + setItemDelegateForColumn(0, contactListDelegate_); + setItemDelegateForColumn(1, removableItemDelegate_); + + header()->resizeSection(1, removableItemDelegate_->sizeHint(QStyleOptionViewItem(), QModelIndex()).width()); + + header()->setStretchLastSection(false); +#if QT_VERSION >= 0x050000 + header()->setSectionResizeMode(0, QHeaderView::Stretch); +#else + header()->setResizeMode(0, QHeaderView::Stretch); +#endif +} + +QtContactListWidget::~QtContactListWidget() { + delete contactListDelegate_; + delete removableItemDelegate_; +} + +void QtContactListWidget::setList(const std::vector<Contact>& list) { + contactListModel_->setList(list); +} + +std::vector<Contact> QtContactListWidget::getList() const { + return contactListModel_->getList(); +} + +void QtContactListWidget::setMaximumNoOfContactsToOne(bool limited) { + limited_ = limited; + if (limited) { + handleListChanged(getList()); + } else { + setAcceptDrops(true); + setDropIndicatorShown(true); + } +} + +void QtContactListWidget::updateContacts(const std::vector<Contact>& contactUpdates) { + std::vector<Contact> contacts = contactListModel_->getList(); + foreach(const Contact& contactUpdate, contactUpdates) { + for(size_t n = 0; n < contacts.size(); n++) { + if (contactUpdate.jid == contacts[n].jid) { + contacts[n] = contactUpdate; + break; + } + } + } + contactListModel_->setList(contacts); +} + +void QtContactListWidget::handleListChanged(std::vector<Contact> list) { + if (limited_) { + setAcceptDrops(list.size() <= 1); + setDropIndicatorShown(list.size() <= 1); + } +} + +void QtContactListWidget::handleSettingsChanged(const std::string&) { + contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); +} + +} diff --git a/Swift/QtUI/UserSearch/QtContactListWidget.h b/Swift/QtUI/UserSearch/QtContactListWidget.h new file mode 100644 index 0000000..f360a91 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtContactListWidget.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <QTreeView> + +#include <Swift/Controllers/Contact.h> +#include <Swiften/Base/Log.h> + +#include <QDragEnterEvent> +#include <QDragMoveEvent> +#include <QDropEvent> + +namespace Swift { + +class ContactListDelegate; +class ContactListModel; +class SettingsProvider; +class QtRemovableItemDelegate; + +class QtContactListWidget : public QTreeView { + Q_OBJECT +public: + QtContactListWidget(QWidget* parent, SettingsProvider* settings); + virtual ~QtContactListWidget(); + + void setList(const std::vector<Contact>& list); + std::vector<Contact> getList() const; + void setMaximumNoOfContactsToOne(bool limited); + +public slots: + void updateContacts(const std::vector<Contact>& contactUpdates); + +signals: + void onListChanged(std::vector<Contact> list); + void onJIDsAdded(const std::vector<JID>& jids); + +private slots: + void handleListChanged(std::vector<Contact> list); + +private: + void handleSettingsChanged(const std::string&); + +private: + SettingsProvider* settings_; + ContactListModel* contactListModel_; + ContactListDelegate* contactListDelegate_; + QtRemovableItemDelegate* removableItemDelegate_; + bool limited_; +}; + +} diff --git a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp new file mode 100644 index 0000000..ca65dca --- /dev/null +++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> +#include <Swift/QtUI/UserSearch/ContactListDelegate.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/UserSearch/ContactListModel.h> + +#include <Swiften/Base/boost_bsignals.h> +#include <boost/bind.hpp> + +#include <Swift/QtUI/QtSwiftUtil.h> + +#include <QAbstractItemView> +#include <QApplication> +#include <QDesktopWidget> +#include <QKeyEvent> + + +namespace Swift { + +QtSuggestingJIDInput::QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings) : QLineEdit(parent), settings_(settings), currentContact_(NULL) { + treeViewPopup_ = new QTreeView(); + treeViewPopup_->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + //treeViewPopup_->setAttribute(Qt::WA_ShowWithoutActivating); + treeViewPopup_->setAlternatingRowColors(true); + treeViewPopup_->setIndentation(0); + treeViewPopup_->setHeaderHidden(true); + treeViewPopup_->setExpandsOnDoubleClick(false); + treeViewPopup_->setItemsExpandable(false); + treeViewPopup_->setSelectionMode(QAbstractItemView::SingleSelection); + treeViewPopup_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + treeViewPopup_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QSizePolicy policy(treeViewPopup_->sizePolicy()); + policy.setVerticalPolicy(QSizePolicy::Expanding); + treeViewPopup_->setSizePolicy(policy); + treeViewPopup_->hide(); + treeViewPopup_->setFocusProxy(this); + connect(treeViewPopup_, SIGNAL(clicked(QModelIndex)), this, SLOT(handleClicked(QModelIndex))); + connect(treeViewPopup_, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(handleClicked(QModelIndex))); + + contactListModel_ = new ContactListModel(false); + treeViewPopup_->setModel(contactListModel_); + + contactListDelegate_ = new ContactListDelegate(settings->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + treeViewPopup_->setItemDelegate(contactListDelegate_); + + settings_->onSettingChanged.connect(boost::bind(&QtSuggestingJIDInput::handleSettingsChanged, this, _1)); +} + +QtSuggestingJIDInput::~QtSuggestingJIDInput() { + settings_->onSettingChanged.disconnect(boost::bind(&QtSuggestingJIDInput::handleSettingsChanged, this, _1)); + delete treeViewPopup_; +} + +const Contact* QtSuggestingJIDInput::getContact() { + if (currentContact_ != NULL) { + return currentContact_; + } else { + if (!text().isEmpty()) { + JID jid(Q2PSTRING(text())); + if (jid.isValid()) { + manualContact_.name = jid.toString(); + manualContact_.jid = jid; + return &manualContact_; + } + } + return NULL; + } +} + +void QtSuggestingJIDInput::setSuggestions(const std::vector<Contact>& suggestions) { + contactListModel_->setList(suggestions); + positionPopup(); + if (!suggestions.empty()) { + treeViewPopup_->setCurrentIndex(contactListModel_->index(0, 0)); + showPopup(); + } else { + currentContact_ = NULL; + } +} + +void QtSuggestingJIDInput::keyPressEvent(QKeyEvent* event) { + if (event->key() == Qt::Key_Up) { + if (contactListModel_->rowCount() > 0) { + int row = treeViewPopup_->currentIndex().row(); + row = (row + contactListModel_->rowCount() - 1) % contactListModel_->rowCount(); + treeViewPopup_->setCurrentIndex(contactListModel_->index(row, 0)); + } + } else if (event->key() == Qt::Key_Down) { + if (contactListModel_->rowCount() > 0) { + int row = treeViewPopup_->currentIndex().row(); + row = (row + contactListModel_->rowCount() + 1) % contactListModel_->rowCount(); + treeViewPopup_->setCurrentIndex(contactListModel_->index(row, 0)); + } + } else if (event->key() == Qt::Key_Return && treeViewPopup_->isVisible()) { + QModelIndex index = treeViewPopup_->currentIndex(); + if (!contactListModel_->getList().empty() && index.isValid()) { + currentContact_ = &contactListModel_->getList()[index.row()]; + setText(P2QSTRING(currentContact_->jid.toString())); + hidePopup(); + clearFocus(); + } else { + currentContact_ = NULL; + } + editingDone(); + } else { + QLineEdit::keyPressEvent(event); + } +} + +void QtSuggestingJIDInput::handleApplicationFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { + /* Using the now argument gives use the wrong widget. This is part of the code needed + to prevent stealing of focus when opening a the suggestion window. */ + QWidget* now = qApp->focusWidget(); + if (!now || (now != treeViewPopup_ && now != this && !now->isAncestorOf(this) && !now->isAncestorOf(treeViewPopup_) && !this->isAncestorOf(now) && !treeViewPopup_->isAncestorOf(now))) { + hidePopup(); + } +} + +void QtSuggestingJIDInput::handleSettingsChanged(const std::string& setting) { + if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) { + contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + } +} + +void QtSuggestingJIDInput::handleClicked(const QModelIndex& index) { + if (index.isValid()) { + currentContact_ = &contactListModel_->getList()[index.row()]; + setText(P2QSTRING(currentContact_->jid.toString())); + hidePopup(); + } +} + +void QtSuggestingJIDInput::positionPopup() { + QDesktopWidget* desktop = QApplication::desktop(); + int screen = desktop->screenNumber(this); + QPoint point = mapToGlobal(QPoint(0, height())); + QRect geometry = desktop->availableGeometry(screen); + int x = point.x(); + int y = point.y(); + int width = this->width(); + int height = 80; + + int screenWidth = geometry.x() + geometry.width(); + if (x + width > screenWidth) { + x = screenWidth - width; + } + + height = treeViewPopup_->sizeHintForRow(0) * contactListModel_->rowCount(); + height = height > 200 ? 200 : height; + + int marginLeft; + int marginTop; + int marginRight; + int marginBottom; + treeViewPopup_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom); + height += marginTop + marginBottom; + width += marginLeft + marginRight; + + treeViewPopup_->setGeometry(x, y, width, height); + treeViewPopup_->move(x, y); + treeViewPopup_->setMaximumWidth(width); +} + +void QtSuggestingJIDInput::showPopup() { + treeViewPopup_->show(); + activateWindow(); + connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*)), Qt::QueuedConnection); + setFocus(); +} + +void QtSuggestingJIDInput::hidePopup() { + disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*))); + treeViewPopup_->hide(); +} + +} diff --git a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h new file mode 100644 index 0000000..673621c --- /dev/null +++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QLineEdit> +#include <QTreeView> + +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class ContactListDelegate; +class SettingsProvider; +class ContactListModel; + +class QtSuggestingJIDInput : public QLineEdit { + Q_OBJECT + public: + QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings); + virtual ~QtSuggestingJIDInput(); + + const Contact* getContact(); + + void setSuggestions(const std::vector<Contact>& suggestions); + + signals: + void editingDone(); + + protected: + virtual void keyPressEvent(QKeyEvent* event); + + private: + void handleSettingsChanged(const std::string& setting); + + private slots: + void handleClicked(const QModelIndex& index); + void handleApplicationFocusChanged(QWidget* old, QWidget* now); + + private: + void positionPopup(); + void showPopup(); + void hidePopup(); + + private: + SettingsProvider* settings_; + ContactListModel* contactListModel_; + QTreeView* treeViewPopup_; + ContactListDelegate* contactListDelegate_; + Contact manualContact_; + const Contact* currentContact_; +}; + +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp new file mode 100644 index 0000000..b1e9a12 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h" + +#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/QtUI/UserSearch/QtContactListWidget.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> + +namespace Swift { + +QtUserSearchFirstMultiJIDPage::QtUserSearchFirstMultiJIDPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings) { + setupUi(this); + setTitle(title); + QString introText = ""; + switch (type) { + case UserSearchWindow::AddContact: + introText = tr("Add another user to your contact list"); + break; + case UserSearchWindow::ChatToContact: + introText = tr("Chat to another user"); + break; + case UserSearchWindow::InviteToChat: + introText = tr("Invite contact to chat"); + break; + } + + setSubTitle(QString(tr("%1. If you know their address you can enter it directly, or you can search for them.")).arg(introText)); + + contactList_ = new QtContactListWidget(this, settings); + horizontalLayout_5->addWidget(contactList_); + + jid_ = new QtSuggestingJIDInput(this, settings); + horizontalLayout_6->insertWidget(0, jid_); + + connect(contactList_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(emitCompletenessCheck())); + connect(jid_, SIGNAL(editingDone()), this, SLOT(handleEditingDone())); +} + +bool QtUserSearchFirstMultiJIDPage::isComplete() const { + return !contactList_->getList().empty(); +} + +void QtUserSearchFirstMultiJIDPage::emitCompletenessCheck() { + emit completeChanged(); +} + +void QtUserSearchFirstMultiJIDPage::handleEditingDone() { + addContactButton_->setFocus(); +} + +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h new file mode 100644 index 0000000..427995e --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QWizardPage> + +#include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstMultiJIDPage.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> + +namespace Swift { + class UserSearchModel; + class UserSearchDelegate; + class UserSearchResult; + class UIEventStream; + class QtContactListWidget; + class ContactSuggester; + class AvatarManager; + class VCardManager; + class SettingsProvider; + class QtSuggestingJIDInput; + + class QtUserSearchFirstMultiJIDPage : public QWizardPage, public Ui::QtUserSearchFirstMultiJIDPage { + Q_OBJECT + public: + QtUserSearchFirstMultiJIDPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings); + virtual bool isComplete() const; + + public slots: + void emitCompletenessCheck(); + void handleEditingDone(); + + public: + QtContactListWidget* contactList_; + QtSuggestingJIDInput* jid_; + }; +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui new file mode 100644 index 0000000..4a87f41 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtUserSearchFirstMultiJIDPage</class> + <widget class="QWizardPage" name="QtUserSearchFirstMultiJIDPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>477</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string/> + </property> + <property name="title"> + <string>Add a user</string> + </property> + <property name="subTitle"> + <string>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="howLabel_"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"/> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Choose another contact</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>-1</number> + </property> + <property name="margin"> + <number>6</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0"> + <property name="spacing"> + <number>-1</number> + </property> + <item> + <widget class="QPushButton" name="addContactButton_"> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="text"> + <string>Add Contact</string> + </property> + <property name="default"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QRadioButton" name="byLocalSearch_"> + <property name="text"> + <string>I'd like to search my server</string> + </property> + <property name="autoExclusive"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="byRemoteSearch_"> + <property name="text"> + <string>I'd like to search another server:</string> + </property> + <property name="autoExclusive"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="service_"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="addViaSearchButton_"> + <property name="text"> + <string>Add via Search</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Reason:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="reason_"/> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>77</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="errorLabel_"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp index 7a91a98..af53a26 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp @@ -8,12 +8,19 @@ #include "Swift/QtUI/QtSwiftUtil.h" +#include <Swiften/Base/Log.h> + namespace Swift { -QtUserSearchFirstPage::QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title) { +QtUserSearchFirstPage::QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings) { setupUi(this); setTitle(title); setSubTitle(QString(tr("%1. If you know their address you can enter it directly, or you can search for them.")).arg(type == UserSearchWindow::AddContact ? tr("Add another user to your contact list") : tr("Chat to another user"))); + jid_ = new QtSuggestingJIDInput(this, settings); + horizontalLayout_2->addWidget(jid_); + setTabOrder(byJID_, jid_); + setTabOrder(jid_, byLocalSearch_); + setTabOrder(byLocalSearch_, byRemoteSearch_); connect(jid_, SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck())); connect(service_->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck())); } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h index d23b87d..d7487b0 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h @@ -11,6 +11,8 @@ #include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstPage.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> + namespace Swift { class UserSearchModel; class UserSearchDelegate; @@ -20,9 +22,11 @@ namespace Swift { class QtUserSearchFirstPage : public QWizardPage, public Ui::QtUserSearchFirstPage { Q_OBJECT public: - QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title); + QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings); virtual bool isComplete() const; public slots: void emitCompletenessCheck(); + public: + QtSuggestingJIDInput* jid_; }; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui index bb0a625..24d401e 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui @@ -34,11 +34,11 @@ <property name="text"> <string>I know their address:</string> </property> + <property name="autoExclusive"> + <bool>true</bool> + </property> </widget> </item> - <item> - <widget class="QLineEdit" name="jid_"/> - </item> </layout> </item> <item> @@ -48,6 +48,9 @@ <property name="text"> <string>I'd like to search my server</string> </property> + <property name="autoExclusive"> + <bool>true</bool> + </property> </widget> </item> <item> @@ -72,6 +75,9 @@ <property name="text"> <string>I'd like to search another server:</string> </property> + <property name="autoExclusive"> + <bool>true</bool> + </property> </widget> </item> <item> diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp index d69c626..d06fa19 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -13,57 +13,50 @@ #include <boost/smart_ptr/make_shared.hpp> #include <Swiften/Base/foreach.h> -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/AddContactUIEvent.h" -#include "Swift/QtUI/UserSearch/UserSearchModel.h" -#include "Swift/QtUI/UserSearch/UserSearchDelegate.h" -#include "Swift/QtUI/QtSwiftUtil.h" -#include "Swift/QtUI/QtFormResultItemModel.h" -#include "QtUserSearchFirstPage.h" -#include "QtUserSearchFieldsPage.h" -#include "QtUserSearchResultsPage.h" -#include "QtUserSearchDetailsPage.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/AddContactUIEvent.h> +#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> +#include <Swift/QtUI/UserSearch/UserSearchModel.h> +#include <Swift/QtUI/UserSearch/UserSearchDelegate.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtFormResultItemModel.h> +#include <Swift/QtUI/UserSearch/QtUserSearchFirstPage.h> +#include <Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h> +#include <Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h> +#include <Swift/QtUI/UserSearch/QtUserSearchResultsPage.h> +#include <Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h> +#include <Swift/QtUI/UserSearch/QtContactListWidget.h> + +#include <Swiften/Base/Log.h> namespace Swift { -QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups) : eventStream_(eventStream), type_(type), model_(NULL) { +QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider) : eventStream_(eventStream), type_(type), model_(NULL), settings_(settingsProvider), searchNext_(false), supportsImpromptu_(false) { setupUi(this); -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); #endif - QString title(type == UserSearchWindow::AddContact ? tr("Add Contact") : tr("Chat to User")); + QString title; + switch(type) { + case AddContact: + title = tr("Add Contact"); + break; + case ChatToContact: + title = tr("Chat to Users"); + break; + case InviteToChat: + title = tr("Add Users to Chat"); + break; + } setWindowTitle(title); delegate_ = new UserSearchDelegate(); - firstPage_ = new QtUserSearchFirstPage(type, title); - connect(firstPage_->byJID_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); - connect(firstPage_->byLocalSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); - connect(firstPage_->byRemoteSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); -#if QT_VERSION >= 0x040700 - firstPage_->jid_->setPlaceholderText(tr("alice@wonderland.lit")); -#endif - firstPage_->service_->setEnabled(false); - setPage(1, firstPage_); - - fieldsPage_ = new QtUserSearchFieldsPage(); - fieldsPage_->fetchingThrobber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); - fieldsPage_->fetchingThrobber_->movie()->stop(); - setPage(2, fieldsPage_); - - resultsPage_ = new QtUserSearchResultsPage(); - -#ifdef SWIFT_PLATFORM_MACOSX - resultsPage_->results_->setAlternatingRowColors(true); -#endif - if (type == AddContact) { - connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next())); - } - else { - connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(accept())); - } - setPage(3, resultsPage_); + setFirstPage(title); + setSecondPage(); + setThirdPage(); detailsPage_ = new QtUserSearchDetailsPage(groups); setPage(4, detailsPage_); @@ -78,16 +71,34 @@ QtUserSearchWindow::~QtUserSearchWindow() { } void QtUserSearchWindow::handleCurrentChanged(int page) { + searchNext_ = false; resultsPage_->emitCompletenessCheck(); - if (page == 2 && lastPage_ == 1) { + if (page == 1 && lastPage_ == 3) { + addSearchedJIDToList(getContactJID()); + setSecondPage(); + } + else if (page == 2 && lastPage_ == 1) { setError(""); /* next won't be called if JID is selected */ JID server = getServerToSearch(); clearForm(); onFormRequested(server); + setThirdPage(); } else if (page == 3 && lastPage_ == 2) { + JID server = getServerToSearch(); handleSearch(); + + if (type_ == AddContact) { + bool remote = firstPage_->byRemoteSearch_->isChecked(); + firstPage_->byRemoteSearch_->setChecked(remote); + firstPage_->service_->setEditText(P2QSTRING(server.toString())); + } else { + bool remote = firstMultiJIDPage_->byRemoteSearch_->isChecked(); + setFirstPage(); + firstMultiJIDPage_->byRemoteSearch_->setChecked(remote); + firstMultiJIDPage_->service_->setEditText(P2QSTRING(server.toString())); + } } else if (page == 4) { detailsPage_->clear(); @@ -98,28 +109,77 @@ void QtUserSearchWindow::handleCurrentChanged(int page) { } JID QtUserSearchWindow::getServerToSearch() { - return firstPage_->byRemoteSearch_->isChecked() ? JID(Q2PSTRING(firstPage_->service_->currentText().trimmed())) : myServer_; + if (type_ == AddContact) { + return firstPage_->byRemoteSearch_->isChecked() ? JID(Q2PSTRING(firstPage_->service_->currentText().trimmed())) : myServer_; + } else { + return firstMultiJIDPage_->byRemoteSearch_->isChecked() ? JID(Q2PSTRING(firstMultiJIDPage_->service_->currentText().trimmed())) : myServer_; + } } void QtUserSearchWindow::handleAccepted() { - JID jid = getContactJID(); + JID jid; + std::vector<JID> jids; + switch(type_) { + case AddContact: + jid = getContactJID(); + eventStream_->send(boost::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups())); + break; + case ChatToContact: + if (contactVector_.size() == 1) { + boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(contactVector_[0].jid)); + eventStream_->send(event); + break; + } - if (type_ == AddContact) { - eventStream_->send(boost::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups())); + foreach(const Contact& contact, contactVector_) { + jids.push_back(contact.jid); + } + + eventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(jids, JID(), Q2PSTRING(firstMultiJIDPage_->reason_->text()))); + break; + case InviteToChat: + foreach(const Contact& contact, contactVector_) { + jids.push_back(contact.jid); + } + eventStream_->send(boost::make_shared<InviteToMUCUIEvent>(roomJID_, jids, Q2PSTRING(firstMultiJIDPage_->reason_->text()))); + break; } - else { - boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(jid)); - eventStream_->send(event); +} + +void QtUserSearchWindow::handleContactSuggestionRequested(const QString& text) { + std::string stdText = Q2PSTRING(text); + onContactSuggestionsRequested(stdText); +} + +void QtUserSearchWindow::addContact() { + if (firstMultiJIDPage_->jid_->getContact() != 0) { + Contact contact = *(firstMultiJIDPage_->jid_->getContact()); + contactVector_.push_back(contact); + } + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->emitCompletenessCheck(); + if (type_ == ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); } } int QtUserSearchWindow::nextId() const { - switch (currentId()) { - case 1: return firstPage_->byJID_->isChecked() ? (type_ == AddContact ? 4 : -1) : 2; - case 2: return 3; - case 3: return type_ == AddContact ? 4 : -1; - case 4: return -1; - default: return -1; + if (type_ == AddContact) { + switch (currentId()) { + case 1: return firstPage_->byJID_->isChecked() ? (type_ == AddContact ? 4 : -1) : 2; + case 2: return 3; + case 3: return type_ == AddContact ? 4 : -1; + case 4: return -1; + default: return -1; + } + } else { + switch (currentId()) { + case 1: return searchNext_ ? 2 : -1; + case 2: return 3; + case 3: return 1; + case 4: return -1; + default: return -1; + } } } @@ -167,7 +227,15 @@ void QtUserSearchWindow::handleSearch() { JID QtUserSearchWindow::getContactJID() const { JID jid; - if (!firstPage_->byJID_->isChecked()) { + + bool useSearchResult; + if (type_ == AddContact) { + useSearchResult = !firstPage_->byJID_->isChecked(); + } else { + useSearchResult = true; + } + + if (useSearchResult) { if (dynamic_cast<UserSearchModel*>(model_)) { UserSearchResult* userItem = static_cast<UserSearchResult*>(resultsPage_->results_->currentIndex().internalPointer()); if (userItem) { /* Remember to leave this if we change to dynamic cast */ @@ -179,12 +247,12 @@ JID QtUserSearchWindow::getContactJID() const { Form::FormItem item = dynamic_cast<QtFormResultItemModel*>(model_)->getForm()->getItems().at(row); JID fallbackJid; foreach(FormField::ref field, item) { - if (boost::dynamic_pointer_cast<JIDSingleFormField>(field)) { - jid = JID(field->getRawValues().at(0)); + if (field->getType() == FormField::JIDSingleType) { + jid = JID(field->getJIDSingleValue()); break; } if (field->getName() == "jid") { - fallbackJid = field->getRawValues().at(0); + fallbackJid = field->getValues()[0]; } } if (!jid.isValid()) { @@ -198,17 +266,35 @@ JID QtUserSearchWindow::getContactJID() const { return jid; } +void QtUserSearchWindow::addSearchedJIDToList(const JID& jid) { + Contact contact(jid, jid.toString(), StatusShow::None, ""); + contactVector_.push_back(contact); + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->emitCompletenessCheck(); + if (type_ == ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); + } +} + void QtUserSearchWindow::show() { clear(); QWidget::show(); } void QtUserSearchWindow::addSavedServices(const std::vector<JID>& services) { - firstPage_->service_->clear(); - foreach (JID jid, services) { - firstPage_->service_->addItem(P2QSTRING(jid.toString())); + if (type_ == AddContact) { + firstPage_->service_->clear(); + foreach (JID jid, services) { + firstPage_->service_->addItem(P2QSTRING(jid.toString())); + } + firstPage_->service_->clearEditText(); + } else { + firstMultiJIDPage_->service_->clear(); + foreach (JID jid, services) { + firstMultiJIDPage_->service_->addItem(P2QSTRING(jid.toString())); + } + firstMultiJIDPage_->service_->clearEditText(); } - firstPage_->service_->clearEditText(); } void QtUserSearchWindow::setSearchFields(boost::shared_ptr<SearchPayload> fields) { @@ -246,6 +332,66 @@ void QtUserSearchWindow::prepopulateJIDAndName(const JID& jid, const std::string detailsPage_->setName(name); } +void QtUserSearchWindow::setContactSuggestions(const std::vector<Contact>& suggestions) { + if (type_ == AddContact) { + firstPage_->jid_->setSuggestions(suggestions); + } else { + firstMultiJIDPage_->jid_->setSuggestions(suggestions); + } +} + +void QtUserSearchWindow::setJIDs(const std::vector<JID> &jids) { + foreach(JID jid, jids) { + addSearchedJIDToList(jid); + } + onJIDUpdateRequested(jids); +} + +void QtUserSearchWindow::setRoomJID(const JID& roomJID) { + roomJID_ = roomJID; +} + +std::string QtUserSearchWindow::getReason() const { + return Q2PSTRING(firstMultiJIDPage_->reason_->text()); +} + +std::vector<JID> QtUserSearchWindow::getJIDs() const { + std::vector<JID> jids; + foreach (const Contact& contact, contactVector_) { + jids.push_back(contact.jid); + } + return jids; +} + +void QtUserSearchWindow::setCanStartImpromptuChats(bool supportsImpromptu) { + supportsImpromptu_ = supportsImpromptu; + if (type_ == ChatToContact) { + firstMultiJIDPage_->contactList_->setMaximumNoOfContactsToOne(!supportsImpromptu_); + } +} + +void QtUserSearchWindow::updateContacts(const std::vector<Contact>& contacts) { + if (type_ != AddContact) { + firstMultiJIDPage_->contactList_->updateContacts(contacts); + } +} + +void QtUserSearchWindow::handleAddViaSearch() { + searchNext_ = true; + next(); +} + +void QtUserSearchWindow::handleListChanged(std::vector<Contact> list) { + contactVector_ = list; + if (type_ == ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); + } +} + +void QtUserSearchWindow::handleJIDsAdded(std::vector<JID> jids) { + onJIDUpdateRequested(jids); +} + void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results) { UserSearchModel *newModel = new UserSearchModel(); newModel->setResults(results); @@ -264,7 +410,11 @@ void QtUserSearchWindow::setResultsForm(Form::ref results) { resultsPage_->results_->setModel(newModel); resultsPage_->results_->setItemDelegate(new QItemDelegate()); resultsPage_->results_->setHeaderHidden(false); +#if QT_VERSION >= 0x050000 + resultsPage_->results_->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#else resultsPage_->results_->header()->setResizeMode(QHeaderView::ResizeToContents); +#endif delete model_; model_ = newModel; resultsPage_->setNoResults(model_->rowCount() == 0); @@ -275,6 +425,60 @@ void QtUserSearchWindow::setSelectedService(const JID& jid) { myServer_ = jid; } +void QtUserSearchWindow::setFirstPage(QString title) { + if (page(1) != 0) { + removePage(1); + } + if (type_ == AddContact) { + firstPage_ = new QtUserSearchFirstPage(type_, title.isEmpty() ? firstPage_->title() : title, settings_); + connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString))); + connect(firstPage_->byJID_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); + connect(firstPage_->byLocalSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); + connect(firstPage_->byRemoteSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); +#if QT_VERSION >= 0x040700 + firstPage_->jid_->setPlaceholderText(tr("alice@wonderland.lit")); +#endif + firstPage_->service_->setEnabled(false); + setPage(1, firstPage_); + } else { + firstMultiJIDPage_ = new QtUserSearchFirstMultiJIDPage(type_, title.isEmpty() ? firstMultiJIDPage_->title() : title, settings_); + connect(firstMultiJIDPage_->addContactButton_, SIGNAL(clicked()), this, SLOT(addContact())); + connect(firstMultiJIDPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString))); + connect(firstMultiJIDPage_->addViaSearchButton_, SIGNAL(clicked()), this, SLOT(handleAddViaSearch())); + connect(firstMultiJIDPage_->contactList_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(handleListChanged(std::vector<Contact>))); + connect(firstMultiJIDPage_->contactList_, SIGNAL(onJIDsAdded(std::vector<JID>)), this, SLOT(handleJIDsAdded(std::vector<JID>))); + setPage(1, firstMultiJIDPage_); + } +} + +void QtUserSearchWindow::setSecondPage() { + if (page(2) != 0) { + removePage(2); + } + fieldsPage_ = new QtUserSearchFieldsPage(); + fieldsPage_->fetchingThrobber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); + fieldsPage_->fetchingThrobber_->movie()->stop(); + setPage(2, fieldsPage_); +} + +void QtUserSearchWindow::setThirdPage() { + if (page(3) != 0) { + removePage(3); + } + resultsPage_ = new QtUserSearchResultsPage(); + +#ifdef SWIFT_PLATFORM_MACOSX + resultsPage_->results_->setAlternatingRowColors(true); +#endif + if (type_ == AddContact) { + connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next())); + } + else { + connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next())); + } + setPage(3, resultsPage_); +} + void QtUserSearchWindow::clearForm() { fieldsPage_->fetchingThrobber_->show(); fieldsPage_->fetchingThrobber_->movie()->start(); @@ -290,32 +494,48 @@ void QtUserSearchWindow::clearForm() { } void QtUserSearchWindow::clear() { - firstPage_->errorLabel_->setVisible(false); QString howText; if (type_ == AddContact) { + firstPage_->errorLabel_->setVisible(false); howText = QString(tr("How would you like to find the user to add?")); + firstPage_->howLabel_->setText(howText); + firstPage_->byJID_->setChecked(true); + handleFirstPageRadioChange(); + } else { + contactVector_.clear(); + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->errorLabel_->setVisible(false); + if (type_ == ChatToContact) { + howText = QString(tr("Who would you like to chat to?")); + } else if (type_ == InviteToChat) { + howText = QString(tr("Who do you want to invite to the chat?")); + } + firstMultiJIDPage_->howLabel_->setText(howText); } - else { - howText = QString(tr("How would you like to find the user to chat to?")); - } - firstPage_->howLabel_->setText(howText); - firstPage_->byJID_->setChecked(true); clearForm(); resultsPage_->results_->setModel(NULL); delete model_; model_ = NULL; - handleFirstPageRadioChange(); restart(); lastPage_ = 1; } void QtUserSearchWindow::setError(const QString& error) { if (error.isEmpty()) { - firstPage_->errorLabel_->hide(); + if (type_ == AddContact) { + firstPage_->errorLabel_->hide(); + } else { + firstMultiJIDPage_->errorLabel_->hide(); + } } else { - firstPage_->errorLabel_->setText(QString("<font color='red'>%1</font>").arg(error)); - firstPage_->errorLabel_->show(); + if (type_ == AddContact) { + firstPage_->errorLabel_->setText(QString("<font color='red'>%1</font>").arg(error)); + firstPage_->errorLabel_->show(); + } else { + firstMultiJIDPage_->errorLabel_->setText(QString("<font color='red'>%1</font>").arg(error)); + firstMultiJIDPage_->errorLabel_->show(); + } restart(); lastPage_ = 1; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h index 32e851a..e5a9f80 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h @@ -18,15 +18,17 @@ namespace Swift { class UserSearchResult; class UIEventStream; class QtUserSearchFirstPage; + class QtUserSearchFirstMultiJIDPage; class QtUserSearchFieldsPage; class QtUserSearchResultsPage; class QtUserSearchDetailsPage; class QtFormResultItemModel; + class SettingsProvider; class QtUserSearchWindow : public QWizard, public UserSearchWindow, private Ui::QtUserSearchWizard { Q_OBJECT public: - QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups); + QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider); virtual ~QtUserSearchWindow(); virtual void addSavedServices(const std::vector<JID>& services); @@ -41,19 +43,39 @@ namespace Swift { virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields); virtual void setNameSuggestions(const std::vector<std::string>& suggestions); virtual void prepopulateJIDAndName(const JID& jid, const std::string& name); + virtual void setContactSuggestions(const std::vector<Contact>& suggestions); + virtual void setJIDs(const std::vector<JID> &jids); + virtual void setRoomJID(const JID &roomJID); + virtual std::string getReason() const; + virtual std::vector<JID> getJIDs() const; + virtual void setCanStartImpromptuChats(bool supportsImpromptu); + virtual void updateContacts(const std::vector<Contact> &contacts); protected: virtual int nextId() const; + private slots: void handleFirstPageRadioChange(); virtual void handleCurrentChanged(int); virtual void handleAccepted(); + void handleContactSuggestionRequested(const QString& text); + void addContact(); + void handleAddViaSearch(); + void handleListChanged(std::vector<Contact> list); + void handleJIDsAdded(std::vector<JID> jids); + + private: + void setFirstPage(QString title = ""); + void setSecondPage(); + void setThirdPage(); + private: void clearForm(); void setError(const QString& error); JID getServerToSearch(); void handleSearch(); JID getContactJID() const; + void addSearchedJIDToList(const JID& jid); private: UIEventStream* eventStream_; @@ -61,10 +83,16 @@ namespace Swift { QAbstractItemModel* model_; UserSearchDelegate* delegate_; QtUserSearchFirstPage* firstPage_; + QtUserSearchFirstMultiJIDPage* firstMultiJIDPage_; QtUserSearchFieldsPage* fieldsPage_; QtUserSearchResultsPage* resultsPage_; QtUserSearchDetailsPage* detailsPage_; JID myServer_; + JID roomJID_; int lastPage_; + std::vector<Contact> contactVector_; + SettingsProvider* settings_; + bool searchNext_; + bool supportsImpromptu_; }; } diff --git a/Swift/QtUI/Whiteboard/ColorWidget.h b/Swift/QtUI/Whiteboard/ColorWidget.h index 6abdf00..ae1af0f 100644 --- a/Swift/QtUI/Whiteboard/ColorWidget.h +++ b/Swift/QtUI/Whiteboard/ColorWidget.h @@ -10,7 +10,7 @@ namespace Swift { class ColorWidget : public QWidget { - Q_OBJECT; + Q_OBJECT public: ColorWidget(QWidget* parent = 0); QSize sizeHint() const; diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.h b/Swift/QtUI/Whiteboard/FreehandLineItem.h index f3c6607..b1af3d1 100644 --- a/Swift/QtUI/Whiteboard/FreehandLineItem.h +++ b/Swift/QtUI/Whiteboard/FreehandLineItem.h @@ -10,8 +10,6 @@ #include <QPainter> #include <iostream> -using namespace std; - namespace Swift { class FreehandLineItem : public QGraphicsItem { public: diff --git a/Swift/QtUI/Whiteboard/GView.h b/Swift/QtUI/Whiteboard/GView.h index 88ea326..6a4fd2f 100644 --- a/Swift/QtUI/Whiteboard/GView.h +++ b/Swift/QtUI/Whiteboard/GView.h @@ -18,7 +18,7 @@ namespace Swift { class GView : public QGraphicsView { - Q_OBJECT; + Q_OBJECT public: enum Mode { Rubber, Line, Rect, Circle, HandLine, Text, Polygon, Select }; enum Type { New, Update, MoveUp, MoveDown }; diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp index 50d7f54..89de95e 100644 --- a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp +++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp @@ -10,6 +10,7 @@ #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <Swiften/Whiteboard/WhiteboardSession.h> #include <Swiften/Elements/WhiteboardPayload.h> @@ -25,7 +26,7 @@ namespace Swift { QtWhiteboardWindow::QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession) : QWidget() { -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); #endif layout = new QVBoxLayout(this); @@ -264,7 +265,9 @@ namespace Swift { std::vector<std::pair<int, int> > points; QVector<QPointF>::const_iterator it = freehandLineItem->points().constBegin(); for ( ; it != freehandLineItem->points().constEnd(); ++it) { - points.push_back(std::pair<int, int>(it->x()+item->pos().x(), it->y()+item->pos().y())); + points.push_back(std::pair<int, int>( + boost::numeric_cast<int>(it->x()+item->pos().x()), + boost::numeric_cast<int>(it->y()+item->pos().y()))); } element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha())); @@ -310,7 +313,9 @@ namespace Swift { std::vector<std::pair<int, int> > points; QVector<QPointF>::const_iterator it = polygon.begin(); for (; it != polygon.end(); ++it) { - points.push_back(std::pair<int, int>(it->x()+item->pos().x(), it->y()+item->pos().y())); + points.push_back(std::pair<int, int>( + boost::numeric_cast<int>(it->x()+item->pos().x()), + boost::numeric_cast<int>(it->y()+item->pos().y()))); } element->setPoints(points); @@ -328,10 +333,10 @@ namespace Swift { QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item); if (ellipseItem) { QRectF rect = ellipseItem->rect(); - int cx = rect.x()+rect.width()/2 + item->pos().x(); - int cy = rect.y()+rect.height()/2 + item->pos().y(); - int rx = rect.width()/2; - int ry = rect.height()/2; + int cx = boost::numeric_cast<int>(rect.x()+rect.width()/2 + item->pos().x()); + int cy = boost::numeric_cast<int>(rect.y()+rect.height()/2 + item->pos().y()); + int rx = boost::numeric_cast<int>(rect.width()/2); + int ry = boost::numeric_cast<int>(rect.height()/2); WhiteboardEllipseElement::ref element = boost::make_shared<WhiteboardEllipseElement>(cx, cy, rx, ry); QColor penColor = ellipseItem->pen().color(); diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h index 4665ef0..3957bb7 100644 --- a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h +++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h @@ -30,7 +30,7 @@ namespace Swift { class QtWhiteboardWindow : public QWidget, public WhiteboardWindow { - Q_OBJECT; + Q_OBJECT public: QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession); void show(); diff --git a/Swift/QtUI/Whiteboard/TextDialog.h b/Swift/QtUI/Whiteboard/TextDialog.h index f4d9a13..64a0fe2 100644 --- a/Swift/QtUI/Whiteboard/TextDialog.h +++ b/Swift/QtUI/Whiteboard/TextDialog.h @@ -16,12 +16,10 @@ #include <iostream> -using namespace std; - namespace Swift { class TextDialog : public QDialog { - Q_OBJECT; + Q_OBJECT public: TextDialog(QGraphicsTextItem* item, QWidget* parent = 0); diff --git a/Swift/QtUI/WinUIHelpers.cpp b/Swift/QtUI/WinUIHelpers.cpp index 3942ac1..161ff1d 100644 --- a/Swift/QtUI/WinUIHelpers.cpp +++ b/Swift/QtUI/WinUIHelpers.cpp @@ -47,7 +47,7 @@ void WinUIHelpers::displayCertificateChainAsSheet(QWidget* parent, const std::ve CRYPTUI_VIEWCERTIFICATE_STRUCT viewDialogProperties = { 0 }; viewDialogProperties.dwSize = sizeof(viewDialogProperties); - viewDialogProperties.hwndParent = parent->winId(); + viewDialogProperties.hwndParent = (HWND) parent->winId(); viewDialogProperties.dwFlags = CRYPTUI_DISABLE_EDITPROPERTIES | CRYPTUI_DISABLE_ADDTOSTORE | CRYPTUI_ENABLE_REVOCATION_CHECKING; viewDialogProperties.pCertContext = certificate_chain.get(); viewDialogProperties.cStores = 1; diff --git a/Swift/QtUI/main.cpp b/Swift/QtUI/main.cpp index d02cce6..d734713 100644 --- a/Swift/QtUI/main.cpp +++ b/Swift/QtUI/main.cpp @@ -21,6 +21,7 @@ #include <SwifTools/Application/PlatformApplicationPathProvider.h> #include <SwifTools/CrashReporter.h> #include <stdlib.h> +#include <Swiften/Base/Path.h> #include "QtSwift.h" #include "QtTranslator.h" @@ -33,7 +34,9 @@ int main(int argc, char* argv[]) { Swift::CrashReporter crashReporter(applicationPathProvider.getDataDir() / "crashes"); +#if QT_VERSION < 0x050000 QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); +#endif // Parse program options boost::program_options::options_description desc = Swift::QtSwift::getOptionsDescription(); @@ -62,21 +65,23 @@ int main(int argc, char* argv[]) { } // Translation +#if QT_VERSION < 0x050000 QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); +#endif boost::filesystem::path someTranslationPath = applicationPathProvider.getResourcePath("/translations/swift_en.qm"); QTranslator qtTranslator; if (!someTranslationPath.empty()) { #if QT_VERSION >= 0x040800 if (vm.count("language") > 0) { - qtTranslator.load(QString(SWIFT_APPLICATION_NAME).toLower() + "_" + P2QSTRING(vm["language"].as<std::string>()), someTranslationPath.parent_path().string().c_str()); + qtTranslator.load(QString(SWIFT_APPLICATION_NAME).toLower() + "_" + P2QSTRING(vm["language"].as<std::string>()), P2QSTRING(Swift::pathToString(someTranslationPath.parent_path()))); } else { - qtTranslator.load(QLocale::system(), QString(SWIFT_APPLICATION_NAME).toLower(), "_", someTranslationPath.parent_path().string().c_str()); + qtTranslator.load(QLocale::system(), QString(SWIFT_APPLICATION_NAME).toLower(), "_", P2QSTRING(Swift::pathToString(someTranslationPath))); } #else //std::cout << "Loading " << std::string(QLocale::system().name().toUtf8()) << std::endl; - qtTranslator.load(QString(SWIFT_APPLICATION_NAME).toLower() + "_" + QLocale::system().name(), someTranslationPath.parent_path().string().c_str()); + qtTranslator.load(QString(SWIFT_APPLICATION_NAME).toLower() + "_" + QLocale::system().name(), P2QSTRING(Swift::pathToString(someTranslationPath))); #endif } app.installTranslator(&qtTranslator); diff --git a/Swift/resources/emoticons/emoticons.txt b/Swift/resources/emoticons/emoticons.txt new file mode 100644 index 0000000..fdfa899 --- /dev/null +++ b/Swift/resources/emoticons/emoticons.txt @@ -0,0 +1,18 @@ +ignore >:) qrc:/emoticons/evilgrin.png +ignore >:-) qrc:/emoticons/evilgrin.png +:) qrc:/emoticons/smile.png +:-) qrc:/emoticons/smile.png +(: qrc:/emoticons/smile.png +(-: qrc:/emoticons/smile.png +:D qrc:/emoticons/happy.png +:-D qrc:/emoticons/happy.png +:o qrc:/emoticons/surprised.png +:-o qrc:/emoticons/surprised.png +:-O qrc:/emoticons/surprised.png +:O qrc:/emoticons/surprised.png +:p qrc:/emoticons/tongue.png +:-p qrc:/emoticons/tongue.png +:( qrc:/emoticons/unhappy.png +:-( qrc:/emoticons/unhappy.png +;) qrc:/emoticons/wink.png +;-) qrc:/emoticons/wink.png diff --git a/Swift/resources/emoticons/evilgrin.png b/Swift/resources/emoticons/evilgrin.png Binary files differnew file mode 100644 index 0000000..817bd50 --- /dev/null +++ b/Swift/resources/emoticons/evilgrin.png diff --git a/Swift/resources/emoticons/grin.png b/Swift/resources/emoticons/grin.png Binary files differnew file mode 100644 index 0000000..fc60c5e --- /dev/null +++ b/Swift/resources/emoticons/grin.png diff --git a/Swift/resources/emoticons/happy.png b/Swift/resources/emoticons/happy.png Binary files differnew file mode 100644 index 0000000..6b7336e --- /dev/null +++ b/Swift/resources/emoticons/happy.png diff --git a/Swift/resources/emoticons/smile.png b/Swift/resources/emoticons/smile.png Binary files differnew file mode 100644 index 0000000..ade4318 --- /dev/null +++ b/Swift/resources/emoticons/smile.png diff --git a/Swift/resources/emoticons/surprised.png b/Swift/resources/emoticons/surprised.png Binary files differnew file mode 100644 index 0000000..4520cfc --- /dev/null +++ b/Swift/resources/emoticons/surprised.png diff --git a/Swift/resources/emoticons/tongue.png b/Swift/resources/emoticons/tongue.png Binary files differnew file mode 100644 index 0000000..ecafd2f --- /dev/null +++ b/Swift/resources/emoticons/tongue.png diff --git a/Swift/resources/emoticons/unhappy.png b/Swift/resources/emoticons/unhappy.png Binary files differnew file mode 100644 index 0000000..fd5d030 --- /dev/null +++ b/Swift/resources/emoticons/unhappy.png diff --git a/Swift/resources/emoticons/wink.png b/Swift/resources/emoticons/wink.png Binary files differnew file mode 100644 index 0000000..a631949 --- /dev/null +++ b/Swift/resources/emoticons/wink.png diff --git a/Swift/resources/icons/star-checked2.png b/Swift/resources/icons/star-checked2.png Binary files differnew file mode 100644 index 0000000..2908534 --- /dev/null +++ b/Swift/resources/icons/star-checked2.png diff --git a/Swift/resources/icons/star-checked2.svg b/Swift/resources/icons/star-checked2.svg new file mode 100644 index 0000000..ea4d1da --- /dev/null +++ b/Swift/resources/icons/star-checked2.svg @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="89.89167" + height="85.751091" + id="svg2" + version="1.1" + inkscape:version="0.48.2 r9819" + sodipodi:docname="star-checked2.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7.9195959" + inkscape:cx="27.108144" + inkscape:cy="28.243526" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + showguides="false" + inkscape:snap-global="false" + fit-margin-top="0.6" + fit-margin-left="0.6" + fit-margin-right="0.6" + fit-margin-bottom="0.6" + inkscape:window-width="1440" + inkscape:window-height="852" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid2987" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-114.63435,-344.09596)"> + <path + sodipodi:type="star" + style="fill:#ffd700;fill-opacity:1;stroke:#444444;stroke-width:4;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="path2989" + sodipodi:sides="5" + sodipodi:cx="150" + sodipodi:cy="393.07648" + sodipodi:r1="44.498337" + sodipodi:r2="24.474085" + sodipodi:arg1="-0.32682665" + sodipodi:arg2="0.30149188" + inkscape:flatsided="false" + inkscape:rounded="0.05" + inkscape:randomized="0" + d="m 192.14286,378.79076 c 0.4588,1.35347 -18.34832,20.18852 -18.77269,21.55318 -0.42437,1.36466 4.38466,27.54365 3.23921,28.39825 -1.14545,0.85459 -24.87036,-11.21169 -26.29937,-11.19359 -1.429,0.0181 -24.84064,12.68151 -26.00737,11.85621 -1.16672,-0.8253 2.97759,-27.11772 2.51879,-28.47119 -0.4588,-1.35347 -19.73702,-19.70605 -19.31265,-21.07071 0.42437,-1.36466 26.71061,-5.54798 27.85606,-6.40257 1.14545,-0.8546 12.64249,-24.86053 14.0715,-24.87863 1.429,-0.0181 13.53048,23.68888 14.69721,24.51418 1.16672,0.82531 27.5505,4.3414 28.00931,5.69487 z" + inkscape:transform-center-x="0.015620988" + inkscape:transform-center-y="-4.2343566" + transform="matrix(0.99993351,0.01153134,-0.01153134,0.99993351,14.139118,-3.5976011)" /> + </g> +</svg> diff --git a/Swift/resources/icons/star-unchecked2.png b/Swift/resources/icons/star-unchecked2.png Binary files differnew file mode 100644 index 0000000..ee85d69 --- /dev/null +++ b/Swift/resources/icons/star-unchecked2.png diff --git a/Swift/resources/icons/star-unchecked2.svg b/Swift/resources/icons/star-unchecked2.svg new file mode 100644 index 0000000..4186d0e --- /dev/null +++ b/Swift/resources/icons/star-unchecked2.svg @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="89.89167" + height="85.751091" + id="svg2" + version="1.1" + inkscape:version="0.48.2 r9819" + sodipodi:docname="star-unchecked2.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7.9195959" + inkscape:cx="27.108141" + inkscape:cy="28.243523" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + showguides="false" + inkscape:snap-global="false" + fit-margin-top="0.6" + fit-margin-left="0.6" + fit-margin-right="0.6" + fit-margin-bottom="0.6" + inkscape:window-width="1440" + inkscape:window-height="852" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid2987" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-114.63435,-344.09596)"> + <path + sodipodi:type="star" + style="fill:#ffffff;fill-opacity:1;stroke:#444444;stroke-width:4;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="path2989" + sodipodi:sides="5" + sodipodi:cx="150" + sodipodi:cy="393.07648" + sodipodi:r1="44.498337" + sodipodi:r2="24.474085" + sodipodi:arg1="-0.32682665" + sodipodi:arg2="0.30149188" + inkscape:flatsided="false" + inkscape:rounded="0.05" + inkscape:randomized="0" + d="m 192.14286,378.79076 c 0.4588,1.35347 -18.34832,20.18852 -18.77269,21.55318 -0.42437,1.36466 4.38466,27.54365 3.23921,28.39825 -1.14545,0.85459 -24.87036,-11.21169 -26.29937,-11.19359 -1.429,0.0181 -24.84064,12.68151 -26.00737,11.85621 -1.16672,-0.8253 2.97759,-27.11772 2.51879,-28.47119 -0.4588,-1.35347 -19.73702,-19.70605 -19.31265,-21.07071 0.42437,-1.36466 26.71061,-5.54798 27.85606,-6.40257 1.14545,-0.8546 12.64249,-24.86053 14.0715,-24.87863 1.429,-0.0181 13.53048,23.68888 14.69721,24.51418 1.16672,0.82531 27.5505,4.3414 28.00931,5.69487 z" + inkscape:transform-center-x="0.015620988" + inkscape:transform-center-y="-4.2343566" + transform="matrix(0.99993351,0.01153134,-0.01153134,0.99993351,14.139118,-3.5976011)" /> + </g> +</svg> diff --git a/Swift/resources/icons/stop.png b/Swift/resources/icons/stop.png Binary files differnew file mode 100644 index 0000000..1574342 --- /dev/null +++ b/Swift/resources/icons/stop.png diff --git a/Swift/resources/icons/stop.svg b/Swift/resources/icons/stop.svg new file mode 100644 index 0000000..b6171ef --- /dev/null +++ b/Swift/resources/icons/stop.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="601" + id="svg2" + version="1.1" + inkscape:version="0.48.2 r9819" + height="601" + sodipodi:docname="Blank_stop_sign_octagon.svg" + inkscape:export-filename="/Users/tobias/Downloads/Blank_stop_sign_octagon.png" + inkscape:export-xdpi="3.29" + inkscape:export-ydpi="3.29"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1440" + inkscape:window-height="852" + id="namedview8" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="0.63429569" + inkscape:cx="102.64275" + inkscape:cy="390.12926" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /> + <path + d="m 0.5,424.5 v -248 l 176,-176 h 248 l 176,176 v 248 l -176,176 h -248 z" + id="path4" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000" /> + <path + d="M 17,417 V 182 L 183,16 H 418 L 584,182 V 417 L 418,583 H 183 z" + id="path6" + inkscape:connector-curvature="0" + style="fill:#ff0000" /> + <rect + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + id="rect2991" + width="368.91312" + height="96.169655" + x="116.04344" + y="249.42896" /> +</svg> diff --git a/Swift/resources/icons/stop.txt b/Swift/resources/icons/stop.txt new file mode 100644 index 0000000..9d4a1b1 --- /dev/null +++ b/Swift/resources/icons/stop.txt @@ -0,0 +1,2 @@ +License: Public Domain +Source: https://commons.wikimedia.org/wiki/File:Blank_stop_sign_octagon.svg diff --git a/Swift/resources/icons/zzz.png b/Swift/resources/icons/zzz.png Binary files differnew file mode 100644 index 0000000..706c2f4 --- /dev/null +++ b/Swift/resources/icons/zzz.png diff --git a/Swift/resources/icons/zzz.svg b/Swift/resources/icons/zzz.svg new file mode 100644 index 0000000..adbd0be --- /dev/null +++ b/Swift/resources/icons/zzz.svg @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1024" + height="1024" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.48.2 r9819" + version="1.0" + inkscape:export-filename="/Users/tobias/dev/rep/swift-lastseen/Swift/resources/icons/zzz.png" + inkscape:export-xdpi="2.8125" + inkscape:export-ydpi="2.8125" + sodipodi:docname="Swift_resources_icons_zzz_Before_b244d1f210fa994df40297664f111de31b6bf676.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs4"> + <linearGradient + id="linearGradient3155"> + <stop + style="stop-color:#007600;stop-opacity:1;" + offset="0" + id="stop3157" /> + <stop + style="stop-color:#97f597;stop-opacity:1;" + offset="1" + id="stop3159" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective10" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3155" + id="linearGradient3185" + gradientUnits="userSpaceOnUse" + x1="146.36209" + y1="457.4635" + x2="376.76218" + y2="76.855392" + gradientTransform="matrix(0.5616546,0,0,0.5616546,113.6799,171.43482)" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.45254834" + inkscape:cx="461.16373" + inkscape:cy="517.26047" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + inkscape:window-width="1440" + inkscape:window-height="852" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:snap-global="false" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid2988" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,512)"> + <path + style="fill:url(#linearGradient3185);fill-opacity:1;stroke:#004900;stroke-width:40;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:0" + d="m 400.8403,315.01501 c 0,79.25627 -64.77302,144.32061 -143.37683,143.5802 -78.9121,-0.74331 -143.37683,-64.32393 -143.37683,-143.5802 0,-80.79036 64.23282,-234.091516 143.37683,-234.091516 79.14401,0 143.37683,154.835246 143.37683,234.091516 z" + id="path2383" + sodipodi:nodetypes="csssc" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-size:145.84281920999998761px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#ffffff;font-family:Sans;stroke-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" + x="278.41916" + y="192.9375" + id="text2990" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan2992" + x="278.41916" + y="192.9375" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans;-inkscape-font-specification:Helvetica Bold"><tspan + style="font-size:290px;font-weight:bold;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;-inkscape-font-specification:Helvetica Bold;font-family:Sans;font-style:normal;font-stretch:normal;font-variant:normal" + id="tspan2998" + dy="0" + dx="0">Z</tspan><tspan + style="font-size:350px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans;-inkscape-font-specification:Helvetica Bold" + id="tspan2994" + dy="-60">Z</tspan><tspan + style="font-size:450px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;letter-spacing:-60px;writing-mode:lr-tb;fill:#000000;stroke:#ffffff;stroke-width:20;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;font-family:Sans;-inkscape-font-specification:Helvetica Bold" + id="tspan2996" + dy="-80">Z</tspan></tspan></text> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="470.72134" + y="-22.449778" + id="text3000" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3002" + x="470.72134" + y="-22.449778" /></text> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="1020.8854" + y="840.59418" + id="text3791" + sodipodi:linespacing="125%" + transform="translate(0,-512)"><tspan + sodipodi:role="line" + id="tspan3793" + x="1020.8854" + y="840.59418" /></text> + <text + xml:space="preserve" + style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans" + x="279.52814" + y="289.27185" + id="text3766" + sodipodi:linespacing="125%" + transform="translate(0,-512)"><tspan + sodipodi:role="line" + id="tspan3768" + x="279.52814" + y="289.27185" /></text> + </g> +</svg> diff --git a/Swift/resources/themes/Default/Incoming/Content.html b/Swift/resources/themes/Default/Incoming/Content.html index eb5bdea..fb4795f 100755 --- a/Swift/resources/themes/Default/Incoming/Content.html +++ b/Swift/resources/themes/Default/Incoming/Content.html @@ -11,7 +11,7 @@ <td class="tr"></td> </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% <div class="timeStamp"><span class="name">%wrapped_sender% @</span> %time%</div> <span id="insert"></span> diff --git a/Swift/resources/themes/Default/Incoming/Context.html b/Swift/resources/themes/Default/Incoming/Context.html index b1aca27..c6fa61c 100755 --- a/Swift/resources/themes/Default/Incoming/Context.html +++ b/Swift/resources/themes/Default/Incoming/Context.html @@ -11,7 +11,7 @@ <td class="tr"></td> </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% <div class="timeStamp"><span class="name">%sender% @</span> %time%</div> <span id="insert"></span> diff --git a/Swift/resources/themes/Default/Incoming/NextContent.html b/Swift/resources/themes/Default/Incoming/NextContent.html index 4aec8ab..aff669b 100755 --- a/Swift/resources/themes/Default/Incoming/NextContent.html +++ b/Swift/resources/themes/Default/Incoming/NextContent.html @@ -1,6 +1,6 @@ <div> <div class="followUp"></div> - <div> + <div style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> diff --git a/Swift/resources/themes/Default/Incoming/NextContext.html b/Swift/resources/themes/Default/Incoming/NextContext.html index 18b8dc4..c0a8846 100755 --- a/Swift/resources/themes/Default/Incoming/NextContext.html +++ b/Swift/resources/themes/Default/Incoming/NextContext.html @@ -1,5 +1,5 @@ <div class="followUp"></div> - <div> + <div style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> diff --git a/Swift/resources/themes/Default/Outgoing/Content.html b/Swift/resources/themes/Default/Outgoing/Content.html index f855f56..87ca272 100755 --- a/Swift/resources/themes/Default/Outgoing/Content.html +++ b/Swift/resources/themes/Default/Outgoing/Content.html @@ -11,7 +11,7 @@ <td class="tr"></td> </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% <div class="timeStamp"><span class="name">%wrapped_sender% @</span> %time%</div> <span id="insert"></span> diff --git a/Swift/resources/themes/Default/Outgoing/Context.html b/Swift/resources/themes/Default/Outgoing/Context.html index 7822cac..229c592 100755 --- a/Swift/resources/themes/Default/Outgoing/Context.html +++ b/Swift/resources/themes/Default/Outgoing/Context.html @@ -11,7 +11,7 @@ <td class="tr"></td> </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% <div class="timeStamp"><span class="name">%sender% @</span> %time%</div> diff --git a/Swift/resources/themes/Default/Outgoing/NextContent.html b/Swift/resources/themes/Default/Outgoing/NextContent.html index 4367197..fe6490e 100755 --- a/Swift/resources/themes/Default/Outgoing/NextContent.html +++ b/Swift/resources/themes/Default/Outgoing/NextContent.html @@ -1,6 +1,6 @@ <div> <div class="followUp"></div> - <div> + <div style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> </div> diff --git a/Swift/resources/themes/Default/Outgoing/NextContext.html b/Swift/resources/themes/Default/Outgoing/NextContext.html index 1f84771..f66aeeb 100755 --- a/Swift/resources/themes/Default/Outgoing/NextContext.html +++ b/Swift/resources/themes/Default/Outgoing/NextContext.html @@ -1,5 +1,5 @@ <div class="followUp"></div> -<div> +<div style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> </div><span id="insert"></span> diff --git a/Swift/resources/themes/Default/Status.html b/Swift/resources/themes/Default/Status.html index b8168e8..e7add89 100755 --- a/Swift/resources/themes/Default/Status.html +++ b/Swift/resources/themes/Default/Status.html @@ -9,7 +9,7 @@ <td class="tr"></td> </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> </td> |