diff options
Diffstat (limited to 'Swift')
483 files changed, 49951 insertions, 3926 deletions
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md new file mode 100644 index 0000000..097de4d --- /dev/null +++ b/Swift/ChangeLog.md @@ -0,0 +1,60 @@ +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. + +2.0-beta2 +--------- +- Enable auto-completion of nicknames that don't start with a letter. +- Generate crash dumps on Windows. +- Connection timeouts are now on each connection separately, instead of on the complete connection process. +- Don't allow pressing `<Enter>` in the roster to trigger logins. +- Don't lose security labels when correcting a message. +- Don't crash when completing user search without selection. +- Always auto join MUC with a consistent nickname. +- Renamed `swift` binary to `swift-im` on Linux. +- Avoid woosh down and woosh up on Mac when opening chat windows. +- Improved MUC invitation dialog. +- Fixed problem with displaying group names containing '&' in the 'Edit' dialog. +- Show stream encryption status in header. +- Show extended certificate dialog when encryption errors occur. +- Don't allow server command results to get interpreted as HTML. +- Additional connection settings (such as manual host setting, proxy configuration and BOSH) can now be specified through a new dialog on the login window. + +Thanks to Tobias Markmann. + + +2.0-beta1 +--------- +- Windows packages are now built in Microsoft Installer's ".msi" format. Please + uninstall any older NSIS-based Swift packages before upgrading to this release. +- Suitable names will now be suggested from the contact's vcard when adding/editing their roster entry. +- It is now possible for sysadmins to deploy files with policies for configuration options, such as + making it impossible for users to save passwords or to force sound notifications off, or to set defaults. +- Swift will now use appropriate algorithms when run on a Windows platform locked down in FIPS-140-2 mode. +- Our TLS provider has been changed to the platform-provided one (Schannel) on Windows, + allowing us to use certificates (both file and card-based) from the system store (CAPI). +- Added support for message receipts in one-to-one conversations (XEP-0184). +- Added support for several MUC operations (kicking, banning, invites, topic changes, room destruction, + changing participant affiliations, ...). +- It is now possible to resize the font in the chat window conversations. +- Added support for message correction. Use 'up' to edit the previous message. +- A list of recent chats is now kept in the 'Chats' tab of the main window. +- Added support for server commands. +- Chat tabs with unread messages from several chats will now be a little more descriptive. +- Use a bar for showing where the last read messages in a chat are. +- Added support for SOCKS5 and HTTPConnect proxies. These settings are taken from the platform preferences. +- Retrieve incremental contact list on login on servers that support it (XEP-0237: Roster Versioning). +- Lots of bugfixes, performance fixes, and other changes. + +Thanks to Tobias Markmann, Jan Kaluza, Thilo Cestonaro, Arnt Gulbrandsen, Vlad Voicu, Vitaly Takmazov, +Yoann Blein, Catalin Badea, Pavol Babincak, Mateusz Piekos, Alexey Melnikov and Soren Dreijer. + + +1.0 +--- +- Initial release. diff --git a/Swift/Controllers/AdHocController.cpp b/Swift/Controllers/AdHocController.cpp new file mode 100644 index 0000000..c017120 --- /dev/null +++ b/Swift/Controllers/AdHocController.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/bind.hpp> +#include <Swift/Controllers/AdHocController.h> +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> + +namespace Swift { + +AdHocController::AdHocController(AdHocCommandWindowFactory* factory, boost::shared_ptr<OutgoingAdHocCommandSession> command) { + window_ = factory->createAdHocCommandWindow(command); + window_->onClosing.connect(boost::bind(&AdHocController::handleWindowClosed, this)); +} + +AdHocController::~AdHocController() { + window_->onClosing.disconnect(boost::bind(&AdHocController::handleWindowClosed, this)); + delete window_; +} + +void AdHocController::setOnline(bool online) { + window_->setOnline(online); +} + +void AdHocController::handleWindowClosed() { + onDeleting(); +} + +} diff --git a/Swift/Controllers/AdHocController.h b/Swift/Controllers/AdHocController.h new file mode 100644 index 0000000..910756f --- /dev/null +++ b/Swift/Controllers/AdHocController.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> + +namespace Swift { + +class AdHocCommandWindowFactory; +class AdHocCommandWindow; + +class AdHocController { +public: + AdHocController(AdHocCommandWindowFactory* factory, boost::shared_ptr<OutgoingAdHocCommandSession> command); + ~AdHocController(); + boost::signal<void ()> onDeleting; + void setOnline(bool online); +private: + void handleWindowClosed(); + AdHocCommandWindow* window_; +}; + +} diff --git a/Swift/Controllers/AdHocManager.cpp b/Swift/Controllers/AdHocManager.cpp index e926138..b179ec6 100644 --- a/Swift/Controllers/AdHocManager.cpp +++ b/Swift/Controllers/AdHocManager.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -16,4 +16,5 @@ #include <Swift/Controllers/UIInterfaces/MainWindow.h> #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> +#include <Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> @@ -32,4 +33,12 @@ AdHocManager::AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, I AdHocManager::~AdHocManager() { uiEventStream_->onUIEvent.disconnect(boost::bind(&AdHocManager::handleUIEvent, this, _1)); + for (size_t i = 0; i < controllers_.size(); ++i) { + controllers_[i]->onDeleting.disconnect(boost::bind(&AdHocManager::removeController, this, controllers_[i])); + } +} + +void AdHocManager::removeController(boost::shared_ptr<AdHocController> controller) { + controller->onDeleting.disconnect(boost::bind(&AdHocManager::removeController, this, controller)); + controllers_.erase(std::find(controllers_.begin(), controllers_.end(), controller)); } @@ -46,5 +55,10 @@ void AdHocManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) { mainWindow_->setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); } +} +void AdHocManager::setOnline(bool online) { + foreach (boost::shared_ptr<AdHocController> controller, controllers_) { + controller->setOnline(online); + } } @@ -64,5 +78,15 @@ void AdHocManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { boost::shared_ptr<RequestAdHocUIEvent> adHocEvent = boost::dynamic_pointer_cast<RequestAdHocUIEvent>(event); if (adHocEvent) { - factory_->createAdHocCommandWindow(boost::make_shared<OutgoingAdHocCommandSession>(adHocEvent->getCommand().getJID(), adHocEvent->getCommand().getNode(), iqRouter_)); + boost::shared_ptr<OutgoingAdHocCommandSession> command = boost::make_shared<OutgoingAdHocCommandSession>(adHocEvent->getCommand().getJID(), adHocEvent->getCommand().getNode(), iqRouter_); + boost::shared_ptr<AdHocController> controller = boost::make_shared<AdHocController>(factory_, command); + controller->onDeleting.connect(boost::bind(&AdHocManager::removeController, this, controller)); + controllers_.push_back(controller); + } + boost::shared_ptr<RequestAdHocWithJIDUIEvent> adHocJIDEvent = boost::dynamic_pointer_cast<RequestAdHocWithJIDUIEvent>(event); + if (!!adHocJIDEvent) { + boost::shared_ptr<OutgoingAdHocCommandSession> command = boost::make_shared<OutgoingAdHocCommandSession>(adHocJIDEvent->getJID(), adHocJIDEvent->getNode(), iqRouter_); + boost::shared_ptr<AdHocController> controller = boost::make_shared<AdHocController>(factory_, command); + controller->onDeleting.connect(boost::bind(&AdHocManager::removeController, this, controller)); + controllers_.push_back(controller); } } diff --git a/Swift/Controllers/AdHocManager.h b/Swift/Controllers/AdHocManager.h index 47b03cd..b2c34c5 100644 --- a/Swift/Controllers/AdHocManager.h +++ b/Swift/Controllers/AdHocManager.h @@ -1,11 +1,8 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#pragma once - -#include <boost/shared_ptr.hpp> #include <vector> @@ -16,5 +13,7 @@ #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Disco/GetDiscoItemsRequest.h> +#include <Swiften/Client/Client.h> #include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swift/Controllers/AdHocController.h> namespace Swift { @@ -27,8 +26,11 @@ namespace Swift { AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow); ~AdHocManager(); + void removeController(boost::shared_ptr<AdHocController> contoller); void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); + void setOnline(bool online); private: - void handleUIEvent(boost::shared_ptr<UIEvent> event); void handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems>, ErrorPayload::ref error); + void handleUIEvent(boost::shared_ptr<UIEvent> event); + boost::signal<void (const AdHocController&)> onControllerComplete; JID jid_; IQRouter* iqRouter_; @@ -37,4 +39,6 @@ namespace Swift { AdHocCommandWindowFactory* factory_; GetDiscoItemsRequest::ref discoItemsRequest_; + std::vector<boost::shared_ptr<AdHocController> > controllers_; }; + } diff --git a/Swift/Controllers/BlockListController.cpp b/Swift/Controllers/BlockListController.cpp new file mode 100644 index 0000000..9cd42f0 --- /dev/null +++ b/Swift/Controllers/BlockListController.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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_(uiEventStream) { + 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() { + uiEventStream_->onUIEvent.disconnect(boost::bind(&BlockListController::handleUIEvent, this, _1)); + 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_->setError(""); + 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()); + } + if (blockListEditorWidget_ && originEditor) { + blockListEditorWidget_->setError(errorMessage); + blockListEditorWidget_->setBusy(false); + } + else { + eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage)); + } + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) { + blockListEditorWidget_->setBusy(false); + blockListEditorWidget_->hide(); + } + } +} + +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()); + } + if (blockListEditorWidget_ && originEditor) { + blockListEditorWidget_->setError(errorMessage); + blockListEditorWidget_->setBusy(false); + } + else { + eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage)); + } + } + if (originEditor) { + remainingRequests_--; + if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) { + blockListEditorWidget_->setBusy(false); + blockListEditorWidget_->hide(); + } + } +} + +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); + blockListEditorWidget_->setError(""); + } else { + blockListEditorWidget_->hide(); + } +} + +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) { + if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) { + 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..99c143c --- /dev/null +++ b/Swift/Controllers/BlockListController.h @@ -0,0 +1,49 @@ +/* + * 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_; + UIEventStream* uiEventStream_; +}; + +} diff --git a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h new file mode 100644 index 0000000..2265c3b --- /dev/null +++ b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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()) { + return false; /* always ask the user for normal MUC invites */ + } + + if (invite->getIsContinuation()) { + return true; + } + + 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); + return false; + } + } + + private: + JID domain_; + XMPPRoster* roster_; + SettingsProvider* settings_; + }; +} diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 5a18a98..0b34681 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 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> @@ -11,25 +11,37 @@ #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/Base/Algorithm.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/format.h> +#include <Swiften/Base/Log.h> #include <Swiften/Chat/ChatStateNotifier.h> #include <Swiften/Chat/ChatStateTracker.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Idle.h> +#include <Swiften/FileTransfer/FileTransferManager.h> + +#include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> -#include <Swiften/Client/NickResolver.h> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> -#include <Swiften/Disco/EntityCapsProvider.h> -#include <Swiften/Base/foreach.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> -#include <Swiften/Elements/DeliveryReceipt.h> -#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> +#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.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 { @@ -38,6 +50,6 @@ 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) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), 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, boost::shared_ptr<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; @@ -60,4 +72,8 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ 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()) { @@ -67,5 +83,5 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ 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_)); @@ -74,7 +90,15 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); + 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)); + chatWindow_->onClosed.connect(boost::bind(&ChatController::handleWindowClosed, this)); handleBareJIDCapsChanged(toJID_); settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1)); + eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1)); } @@ -86,4 +110,5 @@ 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)); @@ -92,4 +117,8 @@ ChatController::~ChatController() { } +JID ChatController::getBaseJID() { + return isInMUC_ ? toJID_ : ChatControllerBase::getBaseJID(); +} + void ChatController::cancelReplaces() { lastWasPresence_ = false; @@ -109,4 +138,9 @@ void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) { contactSupportsReceipts_ = ChatWindow::No; } + if (FileTransferManager::isSupportedBy(disco)) { + chatWindow_->setFileTransferEnabled(ChatWindow::Yes); + } else { + chatWindow_->setFileTransferEnabled(ChatWindow::No); + } } else { SWIFT_LOG(debug) << "No disco info :(" << std::endl; @@ -130,4 +164,17 @@ void ChatController::setToJID(const JID& jid) { } +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::handleBlockingStateChanged, this)); + blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&ChatController::handleBlockingStateChanged, this)); + + handleBlockingStateChanged(); + } +} + bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { return false; @@ -147,6 +194,8 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me } chatStateTracker_->handleMessageReceived(message); - chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>()); + chatStateNotifier_->receivedMessageFromContact(!!message->getPayload<ChatState>()); + // handle XEP-0184 Message Receipts + // incomming receipts if (boost::shared_ptr<DeliveryReceipt> receipt = message->getPayload<DeliveryReceipt>()) { SWIFT_LOG(debug) << "received receipt for id: " << receipt->getReceivedID() << std::endl; @@ -155,4 +204,11 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me requestedReceipts_.erase(receipt->getReceivedID()); } + // incomming errors in response to send out receipts + } else if (message->getPayload<DeliveryReceiptRequest>() && (message->getType() == Message::Error)) { + if (requestedReceipts_.find(message->getID()) != requestedReceipts_.end()) { + chatWindow_->setMessageReceiptState(requestedReceipts_[message->getID()], ChatWindow::ReceiptFailed); + requestedReceipts_.erase(message->getID()); + } + // incoming receipt requests } else if (message->getPayload<DeliveryReceiptRequest>()) { if (receivingPresenceFromUs_) { @@ -165,6 +221,9 @@ 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); + } } @@ -189,20 +248,90 @@ void ChatController::handleSettingChanged(const std::string& settingPath) { void ChatController::checkForDisplayingDisplayReceiptsAlert() { + boost::optional<ChatWindow::AlertID> newDeliverReceiptAlert; if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::No)) { - chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); + newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat doesn't support delivery receipts.")); } else if (userWantsReceipts_ && (contactSupportsReceipts_ == ChatWindow::Maybe)) { - chatWindow_->setAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.")); + newDeliverReceiptAlert = chatWindow_->addAlert(QT_TRANSLATE_NOOP("", "This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.")); + } else { + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + deliveryReceiptAlert_.reset(); + } + } + if (newDeliverReceiptAlert) { + if (deliveryReceiptAlert_) { + chatWindow_->removeAlert(*deliveryReceiptAlert_); + } + deliveryReceiptAlert_ = newDeliverReceiptAlert; + } +} + +void ChatController::handleBlockingStateChanged() { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + if (blockList->getState() == BlockList::Available) { + if (isInMUC_ ? blockList->isBlocked(toJID_) : blockList->isBlocked(toJID_.toBare())) { + if (!blockedContactAlert_) { + blockedContactAlert_ = chatWindow_->addAlert(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); + + // disconnect typing events to prevent chat state notifciations to blocked contacts + chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + } else { + if (blockedContactAlert_) { + chatWindow_->removeAlert(*blockedContactAlert_); + blockedContactAlert_.reset(); + } + chatWindow_->setInputEnabled(true); + chatWindow_->setBlockingState(ChatWindow::IsUnblocked); + + chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); + chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + } + } +} + +void ChatController::handleBlockUserRequest() { + if (isInMUC_) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, toJID_)); } else { - chatWindow_->cancelAlert(); + 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, RequestInviteToMUCUIEvent::Impromptu)); + eventStream_->send(event); +} + +void ChatController::handleWindowClosed() { + onWindowClosed(); +} + +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_, true, 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()); } @@ -252,4 +381,12 @@ void ChatController::handleNewFileTransferController(FileTransferController* ftc } +void ChatController::handleWhiteboardSessionRequest(bool senderIsSelf) { + lastWbID_ = chatWindow_->addWhiteboardRequest(senderIsSelf); +} + +void ChatController::handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state) { + chatWindow_->setWhiteboardSessionStatus(lastWbID_, state); +} + void ChatController::handleFileTransferCancel(std::string id) { SWIFT_LOG(debug) << "handleFileTransferCancel(" << id << ")" << std::endl; @@ -271,5 +408,5 @@ 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); @@ -284,4 +421,16 @@ void ChatController::handleSendFileRequest(std::string filename) { } +void ChatController::handleWhiteboardSessionAccept() { + eventStream_->send(boost::make_shared<AcceptWhiteboardSessionUIEvent>(toJID_)); +} + +void ChatController::handleWhiteboardSessionCancel() { + eventStream_->send(boost::make_shared<CancelWhiteboardSessionUIEvent>(toJID_)); +} + +void ChatController::handleWhiteboardWindowShow() { + eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(toJID_)); +} + std::string ChatController::senderDisplayNameFromMessage(const JID& from) { return nickResolver_->jidToNick(from); @@ -303,4 +452,9 @@ std::string ChatController::getStatusChangeString(boost::shared_ptr<Presence> pr } } + 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); @@ -337,7 +491,7 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc if (newStatusChangeString != lastStatusChangeString_) { if (lastWasPresence_) { - chatWindow_->replaceLastMessage(newStatusChangeString); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::UpdateTimestamp); } else { - chatWindow_->addPresenceMessage(newStatusChangeString); + chatWindow_->addPresenceMessage(chatMessageParser_->parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection); } lastStatusChangeString_ = newStatusChangeString; @@ -350,3 +504,23 @@ boost::optional<boost::posix_time::ptime> ChatController::getMessageTimestamp(bo } +void ChatController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool /* isIncoming */) { + HistoryMessage::Type type; + if (mucRegistry_->isMUC(fromJID.toBare()) || mucRegistry_->isMUC(toJID.toBare())) { + type = HistoryMessage::PrivateMessage; + } + else { + type = HistoryMessage::Chat; + } + + if (historyController_) { + historyController_->addMessage(message, fromJID, toJID, type, timeStamp); + } +} + +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 00d167e..998b437 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -22,16 +22,26 @@ 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); + 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, boost::shared_ptr<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(); + JID getBaseJID(); + void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming); private: @@ -41,5 +51,5 @@ namespace Swift { 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); @@ -55,7 +65,21 @@ namespace Swift { void handleSendFileRequest(std::string filename); + void handleWhiteboardSessionAccept(); + void handleWhiteboardSessionCancel(); + void handleWhiteboardWindowShow(); + void handleSettingChanged(const std::string& settingPath); void checkForDisplayingDisplayReceiptsAlert(); + void handleBlockingStateChanged(); + void handleBlockUserRequest(); + void handleUnblockUserRequest(); + + void handleInviteToChat(const std::vector<JID>& droppedJIDs); + + void handleWindowClosed(); + + void handleUIEvent(boost::shared_ptr<UIEvent> event); + private: NickResolver* nickResolver_; @@ -76,4 +100,13 @@ 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_; + + boost::optional<ChatWindow::AlertID> deliveryReceiptAlert_; + boost::optional<ChatWindow::AlertID> blockedContactAlert_; }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 3ff52a6..2c2540c 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 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> @@ -14,8 +14,9 @@ #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> @@ -24,15 +25,23 @@ #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) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) { +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, boost::shared_ptr<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)); @@ -40,4 +49,5 @@ ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaCha 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(); @@ -45,4 +55,5 @@ ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaCha ChatControllerBase::~ChatControllerBase() { + delete highlighter_; delete chatWindow_; } @@ -52,4 +63,10 @@ void ChatControllerBase::handleLogCleared() { } +ChatWindow* ChatControllerBase::detachChatWindow() { + ChatWindow* chatWindow = chatWindow_; + chatWindow_ = NULL; + return chatWindow; +} + void ChatControllerBase::handleCapsChanged(const JID& jid) { if (jid.compare(toJID_, JID::WithoutResource) == 0) { @@ -58,9 +75,15 @@ void ChatControllerBase::handleCapsChanged(const JID& 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)); @@ -72,5 +95,5 @@ 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(); @@ -85,7 +108,11 @@ void ChatControllerBase::setOnline(bool online) { } +JID ChatControllerBase::getBaseJID() { + return JID(toJID_.toBare()); +} + void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) { if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabelsCatalogFeature)) { - GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(JID(toJID_.toBare()), iqRouter_); + GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(getBaseJID(), iqRouter_); request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); request->send(); @@ -109,5 +136,5 @@ void ChatControllerBase::handleAllMessagesRead() { int ChatControllerBase::getUnreadCount() { - return targetedUnreadMessages_.size(); + return boost::numeric_cast<int>(targetedUnreadMessages_.size()); } @@ -121,5 +148,8 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool message->setBody(body); if (labelsEnabled_) { - SecurityLabelsCatalog::Item labelItem = chatWindow_->getSelectedSecurityLabel(); + if (!isCorrectionMessage) { + lastLabel_ = chatWindow_->getSelectedSecurityLabel(); + } + SecurityLabelsCatalog::Item labelItem = lastLabel_; if (labelItem.getLabel()) { message->addPayload(labelItem.getLabel()); @@ -127,6 +157,7 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool } preSendMessageRequest(message); - if (useDelayForLatency_) { + boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + if (useDelayForLatency_) { message->addPayload(boost::make_shared<Delay>(now, selfJID_)); } @@ -138,8 +169,12 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message)); onActivity(message->getBody()); + +#ifdef SWIFT_EXPERIMENTAL_HISTORY + logMessage(body, selfJID_, toJID_, now, false); +#endif } void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { - if (!error) { + if (catalog && !error) { if (catalog->getItems().size() == 0) { chatWindow_->setSecurityLabelsEnabled(false); @@ -164,17 +199,21 @@ void ChatControllerBase::activateChatWindow() { } -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) { +bool ChatControllerBase::hasOpenWindow() const { + return chatWindow_ && chatWindow_->isVisible(); +} + +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,highlighter_->getNick(),senderIsSelf), 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, bool senderIsSelf, 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,highlighter_->getNick(),senderIsSelf), id, time, highlight); } } @@ -194,7 +233,10 @@ 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()) { + if (!message->getTo().getResource().empty()) { std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); - chatWindow_->addErrorMessage(errorMessage); + chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage)); + } } else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) { @@ -220,5 +262,5 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m 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>(); @@ -232,4 +274,9 @@ 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) { @@ -239,15 +286,17 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m lastMessage = lastMessagesUIID_.find(from); if (lastMessage != lastMessagesUIID_.end()) { - replaceMessage(body, lastMessagesUIID_[from], timeStamp); + replaceMessage(body, lastMessagesUIID_[from], isIncomingMessageFromMe(message), 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); } @@ -259,28 +308,29 @@ std::string ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> else { switch (error->getCondition()) { - case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); break; - case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); break; - case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); break; - case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); break; - case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); break; - case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); break; - case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); break; - case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); break; - case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); break; - case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); break; - case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); break; - case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); break; - case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); break; - case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); break; - case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); break; - case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); break; - case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); break; - case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); break; - case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); break; - case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); break; - case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); break; - case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); break; - } - } + case ErrorPayload::BadRequest: return QT_TRANSLATE_NOOP("", "Bad request"); + case ErrorPayload::Conflict: return QT_TRANSLATE_NOOP("", "Conflict"); + case ErrorPayload::FeatureNotImplemented: return QT_TRANSLATE_NOOP("", "This feature is not implemented"); + case ErrorPayload::Forbidden: return QT_TRANSLATE_NOOP("", "Forbidden"); + case ErrorPayload::Gone: return QT_TRANSLATE_NOOP("", "Recipient can no longer be contacted"); + case ErrorPayload::InternalServerError: return QT_TRANSLATE_NOOP("", "Internal server error"); + case ErrorPayload::ItemNotFound: return QT_TRANSLATE_NOOP("", "Item not found"); + case ErrorPayload::JIDMalformed: return QT_TRANSLATE_NOOP("", "JID Malformed"); + case ErrorPayload::NotAcceptable: return QT_TRANSLATE_NOOP("", "Message was rejected"); + case ErrorPayload::NotAllowed: return QT_TRANSLATE_NOOP("", "Not allowed"); + case ErrorPayload::NotAuthorized: return QT_TRANSLATE_NOOP("", "Not authorized"); + case ErrorPayload::PaymentRequired: return QT_TRANSLATE_NOOP("", "Payment is required"); + case ErrorPayload::RecipientUnavailable: return QT_TRANSLATE_NOOP("", "Recipient is unavailable"); + case ErrorPayload::Redirect: return QT_TRANSLATE_NOOP("", "Redirect"); + case ErrorPayload::RegistrationRequired: return QT_TRANSLATE_NOOP("", "Registration required"); + case ErrorPayload::RemoteServerNotFound: return QT_TRANSLATE_NOOP("", "Recipient's server not found"); + case ErrorPayload::RemoteServerTimeout: return QT_TRANSLATE_NOOP("", "Remote server timeout"); + case ErrorPayload::ResourceConstraint: return QT_TRANSLATE_NOOP("", "The server is low on resources"); + case ErrorPayload::ServiceUnavailable: return QT_TRANSLATE_NOOP("", "The service is unavailable"); + case ErrorPayload::SubscriptionRequired: return QT_TRANSLATE_NOOP("", "A subscription is required"); + case ErrorPayload::UndefinedCondition: return QT_TRANSLATE_NOOP("", "Undefined condition"); + case ErrorPayload::UnexpectedRequest: return QT_TRANSLATE_NOOP("", "Unexpected request"); + } + } + assert(false); return defaultMessage; } @@ -289,7 +339,7 @@ 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); } @@ -298,7 +348,11 @@ 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); + 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) { @@ -314,9 +368,7 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) { } - 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 8654311..a0b848b 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,28 +9,33 @@ #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 <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class IQRouter; class StanzaChannel; - class ChatWindow; class ChatWindowFactory; class AvatarManager; @@ -38,4 +43,8 @@ namespace Swift { class EventController; class EntityCapsProvider; + class HighlightManager; + class Highlighter; + class ChatMessageParser; + class AutoAcceptMUCInviteDecider; class ChatControllerBase : public boost::bsignals::trackable { @@ -44,37 +53,45 @@ namespace Swift { void showChatWindow(); void activateChatWindow(); - void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info); + bool hasOpenWindow() const; + 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, bool senderIsSelf, 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; + boost::signal<void ()> onWindowClosed; 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); + 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, boost::shared_ptr<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 */) {} virtual void cancelReplaces() = 0; + /** JID any iq for account should go to - bare except for PMs */ + virtual JID getBaseJID(); + virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0; private: @@ -109,4 +126,11 @@ namespace Swift { TimerFactory* timerFactory_; EntityCapsProvider* entityCapsProvider_; + SecurityLabelsCatalog::Item lastLabel_; + HistoryController* historyController_; + MUCRegistry* mucRegistry_; + Highlighter* highlighter_; + boost::shared_ptr<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..5a608db --- /dev/null +++ b/Swift/Controllers/Chat/ChatMessageParser.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2013-2014 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, HighlightRulesListPtr highlightRules, bool mucMode) + : emoticons_(emoticons), highlightRules_(highlightRules), mucMode_(mucMode) { + } + + typedef std::pair<std::string, std::string> StringPair; + + ChatWindow::ChatMessage ChatMessageParser::parseMessageBody(const std::string& body, const std::string& nick, bool senderIsSelf) { + 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)); + } + } + } + } + + /* do emoticon substitution */ + parsedMessage = emoticonHighlight(parsedMessage); + + if (!senderIsSelf) { /* do not highlight our own messsages */ + /* do word-based color highlighting */ + parsedMessage = splitHighlight(parsedMessage, nick); + } + + return parsedMessage; + } + + ChatWindow::ChatMessage ChatMessageParser::emoticonHighlight(const ChatWindow::ChatMessage& message) + { + ChatWindow::ChatMessage parsedMessage = message; + + 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 + * at the start or end of the line, or beside whitespace. + */ + regexString += regexString.empty() ? "" : "|"; + std::string escaped = "(" + Regex::escape(emoticon.first) + ")"; + regexString += "^" + escaped + "|"; + regexString += escaped + "$|"; + regexString += "\\s" + escaped + "|"; + regexString += escaped + "\\s"; + + } + 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)) { + int matchIndex = 0; + for (matchIndex = 1; matchIndex < static_cast<int>(match.size()); matchIndex++) { + if (match[matchIndex].length() > 0) { + //This is the matching subgroup + break; + } + } + std::string::const_iterator matchStart = match[matchIndex].first; + std::string::const_iterator matchEnd = match[matchIndex].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::string matchString = match[matchIndex].str(); + std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(matchString); + 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; + } + + ChatWindow::ChatMessage ChatMessageParser::splitHighlight(const ChatWindow::ChatMessage& message, const std::string& nick) + { + ChatWindow::ChatMessage parsedMessage = message; + + for (size_t i = 0; i < highlightRules_->getSize(); ++i) { + const HighlightRule& rule = highlightRules_->getRule(i); + if (rule.getMatchMUC() && !mucMode_) { + continue; /* this rule only applies to MUC's, and this is a CHAT */ + } else if (rule.getMatchChat() && mucMode_) { + continue; /* this rule only applies to CHAT's, and this is a MUC */ + } + const std::vector<boost::regex> keywordRegex = rule.getKeywordRegex(nick); + foreach(const boost::regex& regex, keywordRegex) { + 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, regex)) { + 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::ChatHighlightingMessagePart> highlightPart = boost::make_shared<ChatWindow::ChatHighlightingMessagePart>(); + highlightPart->text = match.str(); + highlightPart->foregroundColor = rule.getAction().getTextColor(); + highlightPart->backgroundColor = rule.getAction().getTextBackground(); + newMessage.append(highlightPart); + 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..2f5c171 --- /dev/null +++ b/Swift/Controllers/Chat/ChatMessageParser.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2013-2014 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, HighlightRulesListPtr highlightRules, bool mucMode = false); + ChatWindow::ChatMessage parseMessageBody(const std::string& body, const std::string& nick = "", bool senderIsSelf = false); + private: + ChatWindow::ChatMessage emoticonHighlight(const ChatWindow::ChatMessage& parsedMessage); + ChatWindow::ChatMessage splitHighlight(const ChatWindow::ChatMessage& parsedMessage, const std::string& nick); + std::map<std::string, std::string> emoticons_; + HighlightRulesListPtr highlightRules_; + bool mucMode_; + }; +} diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index b0aef95..979f87a 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,21 +1,45 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 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/optional.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 <Swiften/VCards/VCardManager.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> @@ -23,24 +47,53 @@ #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> + +BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 1) + +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; + if (version > 0) { + ar & chat.password; + } + } +} +} namespace Swift { @@ -72,5 +125,12 @@ ChatsManager::ChatsManager( XMPPRoster* roster, bool eagleMode, - SettingsProvider* settings) : + SettingsProvider* settings, + HistoryController* historyController, + WhiteboardManager* whiteboardManager, + HighlightManager* highlightManager, + ClientBlockListManager* clientBlockListManager, + const std::map<std::string, std::string>& emoticons, + UserSearchController* inviteUserSearchController, + VCardManager* vcardManager) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), @@ -82,5 +142,12 @@ ChatsManager::ChatsManager( roster_(roster), eagleMode_(eagleMode), - settings_(settings) { + settings_(settings), + historyController_(historyController), + whiteboardManager_(whiteboardManager), + highlightManager_(highlightManager), + emoticons_(emoticons), + clientBlockListManager_(clientBlockListManager), + inviteUserSearchController_(inviteUserSearchController), + vcardManager_(vcardManager) { timerFactory_ = timerFactory; eventController_ = eventController; @@ -108,4 +175,8 @@ ChatsManager::ChatsManager( mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); + whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2)); + whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted)); + whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated)); + whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected)); roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); @@ -119,4 +190,6 @@ ChatsManager::ChatsManager( setupBookmarks(); loadRecents(); + + autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); } @@ -136,23 +209,32 @@ ChatsManager::~ChatsManager() { delete mucBookmarkManager_; delete mucSearchController_; + 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); + } + + class RemoveRecent { + public: + static bool ifPrivateMessage(const ChatListWindow::Chat& chat) { + return chat.isPrivateMessage; + } + }; + + recentsLimited.erase(std::remove_if(recentsLimited.begin(), recentsLimited.end(), RemoveRecent::ifPrivateMessage), recentsLimited.end()); + + oa << recentsLimited; + std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); + profileSettings_->storeString(RECENT_CHATS, serializedStr); } @@ -196,6 +278,24 @@ void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid) } +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; + } + } else { + if (avatarManager_) { + fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid); + } + 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")); @@ -219,19 +319,28 @@ void ChatsManager::loadRecents() { StatusShow::Type type = StatusShow::None; boost::filesystem::path path; - if (isMUC) { - if (mucControllers_.find(jid.toBare()) != mucControllers_.end()) { - type = StatusShow::Online; - } - } else { - if (avatarManager_) { - path = avatarManager_->getAvatarPath(jid); + + ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, false, nick); + chat = updateChatStatusAndAvatarHelper(chat); + prependRecent(chat); } - Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare()); - type = presence ? presence->getShow() : StatusShow::None; + } else if (!recentsString.empty()){ + // boost searilaize based format + ByteArray debase64 = Base64::decode(recentsString); + std::vector<ChatListWindow::Chat> recentChats; + std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), 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); + foreach(ChatListWindow::Chat chat, recentChats) { + chat.statusType = StatusShow::None; + chat = updateChatStatusAndAvatarHelper(chat); prependRecent(chat); } + } handleUnreadCountChanged(NULL); } @@ -260,5 +369,5 @@ 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); @@ -269,5 +378,5 @@ void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) { } -ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) { +ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity, bool privateMessage) { int unreadCount = 0; if (mucRegistry_->isMUC(jid)) { @@ -275,4 +384,5 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const StatusShow::Type type = StatusShow::None; std::string nick = ""; + std::string password = ""; if (controller) { unreadCount = controller->getUnreadCount(); @@ -281,7 +391,17 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const } nick = controller->getNick(); + + if (controller->getPassword()) { + password = *controller->getPassword(); } - return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick); + if (controller->isImpromptu()) { + ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, privateMessage, nick, password); + 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, privateMessage, nick, password); } else { ChatController* controller = getChatControllerIfExists(jid, false); @@ -293,14 +413,11 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const StatusShow::Type type = presence ? presence->getShow() : StatusShow::None; boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path(); - return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false); + return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false, privateMessage); } } void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) { - if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) { - /* Don't include PMs in MUC rooms.*/ - return; - } - ChatListWindow::Chat chat = createChatListChatItem(jid, activity); + const bool privateMessage = mucRegistry_->isMUC(jid.toBare()) && !isMUC; + ChatListWindow::Chat chat = createChatListChatItem(jid, activity, privateMessage); /* FIXME: handle nick changes */ appendRecent(chat); @@ -309,4 +426,9 @@ void ChatsManager::handleChatActivity(const JID& jid, const std::string& activit } +void ChatsManager::handleChatClosed(const JID& /*jid*/) { + cleanupPrivateMessageRecents(); + chatListWindow_->setRecents(recentChats_); +} + void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { int unreadTotal = 0; @@ -332,12 +454,49 @@ void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { } -void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) { +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()); - recentChats_.push_front(chat); + return boost::optional<ChatListWindow::Chat>(existingChat); + } else { + return boost::optional<ChatListWindow::Chat>(); + } +} + +void ChatsManager::cleanupPrivateMessageRecents() { + /* if we leave a MUC and close a PM, remove it's recent chat entry */ + const std::list<ChatListWindow::Chat> chats = recentChats_; + foreach (const ChatListWindow::Chat& chat, chats) { + if (chat.isPrivateMessage) { + typedef std::map<JID, MUCController*> ControllerMap; + ControllerMap::iterator muc = mucControllers_.find(chat.jid.toBare()); + if (muc == mucControllers_.end() || !muc->second->isJoined()) { + ChatController* chatController = getChatControllerIfExists(chat.jid); + if (!chatController || !chatController->hasOpenWindow()) { + removeExistingChat(chat); + break; + } + } + } + } +} + +void ChatsManager::appendRecent(const ChatListWindow::Chat& 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); } @@ -349,13 +508,13 @@ void ChatsManager::handleUserLeftMUC(MUCController* mucController) { if (chat.isMUC && chat.jid == (*it).first) { chat.statusType = StatusShow::None; - chatListWindow_->setRecents(recentChats_); - break; } } mucControllers_.erase(it); delete mucController; - return; + break; } } + cleanupPrivateMessageRecents(); + chatListWindow_->setRecents(recentChats_); } @@ -367,4 +526,25 @@ 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); @@ -384,4 +564,15 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { } + 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); @@ -390,5 +581,5 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { } 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(); } @@ -412,4 +603,20 @@ void ChatsManager::markAllRecentsOffline() { } +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. @@ -489,4 +696,9 @@ void ChatsManager::setOnline(bool enabled) { } 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(); } @@ -513,10 +725,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_); + boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), false); /* a message parser that knows this is a chat (not a room/MUC) */ + 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->onWindowClosed.connect(boost::bind(&ChatsManager::handleChatClosed, this, contact)); 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; } @@ -545,5 +761,4 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool return pair.second; } - } } @@ -560,8 +775,9 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& 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) { @@ -582,13 +798,31 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional } 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_); + if (isImpromptu) { + muc->setCreateAsReservedIfNew(); + } + + MUCController* controller = NULL; + SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL; + if (reuseChatwindow) { + chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow); + } + boost::shared_ptr<ChatMessageParser> chatMessageParser = boost::make_shared<ChatMessageParser>(emoticons_, highlightManager_->getRules(), true); /* a message parser that knows this is a room/MUC (not a chat) */ + 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_, vcardManager_); + 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)); controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); + controller->onUserNicknameChanged.connect(boost::bind(&ChatsManager::handleUserNicknameChanged, this, controller, _1, _2)); controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); @@ -597,4 +831,5 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional mucControllers_[mucJID]->showChatWindow(); + return muc; } @@ -603,8 +838,31 @@ void ChatsManager::handleSearchMUCRequest() { } +void ChatsManager::handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname) { + JID oldMUCChatJID = mucController->getToJID().withResource(oldNickname); + JID newMUCChatJID = mucController->getToJID().withResource(newNickname); + + SWIFT_LOG(debug) << "nickname change in " << mucController->getToJID().toString() << " from " << oldNickname << " to " << newNickname << std::endl; + + // get current chat controller + ChatController *chatController = getChatControllerIfExists(oldMUCChatJID); + if (chatController) { + // adjust chat controller + chatController->setToJID(newMUCChatJID); + nickResolver_->onNickChanged(newMUCChatJID, oldNickname); + chatControllers_.erase(oldMUCChatJID); + chatControllers_[newMUCChatJID] = chatController; + + chatController->onActivity.disconnect(boost::bind(&ChatsManager::handleChatActivity, this, oldMUCChatJID, _1, false)); + chatController->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, newMUCChatJID, _1, false)); + /*for (std::list<ChatListWindow::Chat>::iterator i = recentChats_.begin(); i != recentChats_.end(); i++) { + if (i->jid == + }*/ + } +} + void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { JID jid = message->getFrom(); boost::shared_ptr<MessageEvent> event(new MessageEvent(message)); - bool isInvite = message->getPayload<MUCInvitationPayload>(); + bool isInvite = !!message->getPayload<MUCInvitationPayload>(); bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite()); if (isMediatedInvite) { @@ -628,7 +886,34 @@ void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { } + // 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); + return; + } + } 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.*/ + ChatController* controller = getChatControllerIfExists(jid); + if (controller) { + controller->handleIncomingMessage(event); + } + } else { getChatControllerOrCreate(jid)->handleIncomingMessage(event); } +} void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) { @@ -648,6 +933,37 @@ void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) } +void ChatsManager::handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf) { + ChatController* chatController = getChatControllerOrCreate(contact); + chatController->handleWhiteboardSessionRequest(senderIsSelf); + chatController->activateChatWindow(); +} + +void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state) { + ChatController* chatController = getChatControllerOrCreate(contact); + chatController->handleWhiteboardStateChange(state); + chatController->activateChatWindow(); + if (state == ChatWindow::WhiteboardAccepted) { + boost::filesystem::path path; + JID bareJID = contact.toBare(); + if (avatarManager_) { + path = avatarManager_->getAvatarPath(bareJID); + } + ChatListWindow::Chat chat(bareJID, nickResolver_->jidToNick(bareJID), "", 0, StatusShow::None, path, false); + chatListWindow_->addWhiteboardSession(chat); + } else { + chatListWindow_->removeWhiteboardSession(contact.toBare()); + } +} + 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)); @@ -658,3 +974,56 @@ 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::ref> Swift::ChatsManager::getContacts(bool withMUCNicks) { + std::vector<Contact::ref> result; + foreach (ChatListWindow::Chat chat, recentChats_) { + if (!chat.isMUC) { + result.push_back(boost::make_shared<Contact>(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath)); + } + } + if (withMUCNicks) { + /* collect MUC nicks */ + typedef std::map<JID, MUCController*>::value_type Item; + foreach (const Item& item, mucControllers_) { + JID mucJID = item.second->getToJID(); + std::map<std::string, JID> participants = item.second->getParticipantJIDs(); + typedef std::map<std::string, JID>::value_type ParticipantType; + foreach (const ParticipantType& participant, participants) { + const JID nickJID = JID(mucJID.getNode(), mucJID.getDomain(), participant.first); + Presence::ref presence = presenceOracle_->getLastPresence(nickJID); + const boost::filesystem::path avatar = avatarManager_->getAvatarPath(nickJID); + result.push_back(boost::make_shared<Contact>(participant.first, JID(), presence->getShow(), avatar)); + } + } + } + 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 a8c69c4..82daf67 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,8 +8,9 @@ #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> @@ -17,7 +18,14 @@ #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 <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> + namespace Swift { @@ -27,5 +35,4 @@ namespace Swift { class MUCController; class MUCManager; - class ChatWindowFactory; class JoinMUCWindow; class JoinMUCWindowFactory; @@ -48,8 +55,17 @@ namespace Swift { class XMPPRoster; class SettingsProvider; + class WhiteboardManager; + class HistoryController; + class HighlightManager; + class ClientBlockListManager; + class ChatMessageParser; + class DiscoServiceWalker; + class AutoAcceptMUCInviteDecider; + class UserSearchController; + class VCardManager; - class ChatsManager { + 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); + 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, VCardManager* vcardManager); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); @@ -57,9 +73,25 @@ namespace Swift { void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<Message> message); + std::vector<ChatListWindow::Chat> getRecentChats() const; + virtual std::vector<Contact::ref> getContacts(bool withMUCNicks); + + boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered; private: - ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity); + 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, bool privateMessage); 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&); @@ -70,7 +102,13 @@ namespace Swift { void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); void handleUserLeftMUC(MUCController* mucController); + void handleUserNicknameChanged(MUCController* mucController, const std::string& oldNickname, const std::string& newNickname); void handleBookmarksReady(); void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); + void handleChatClosed(const JID& jid); 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 cleanupPrivateMessageRecents(); void appendRecent(const ChatListWindow::Chat& chat); void prependRecent(const ChatListWindow::Chat& chat); @@ -90,6 +128,12 @@ namespace Swift { 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); @@ -101,4 +145,5 @@ namespace Swift { std::map<JID, MUCController*> mucControllers_; std::map<JID, ChatController*> chatControllers_; + std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_; EventController* eventController_; JID jid_; @@ -130,4 +175,15 @@ namespace Swift { bool userWantsReceipts_; SettingsProvider* settings_; + HistoryController* historyController_; + WhiteboardManager* whiteboardManager_; + HighlightManager* highlightManager_; + std::map<std::string, std::string> emoticons_; + ClientBlockListManager* clientBlockListManager_; + JID localMUCServiceJID_; + boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_; + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_; + UserSearchController* inviteUserSearchController_; + IDGenerator idGenerator_; + VCardManager* vcardManager_; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index e4209f4..fe90c60 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -11,28 +11,38 @@ #include <boost/algorithm/string.hpp> -#include <Swift/Controllers/Intl.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Elements/Delay.h> +#include <Swiften/MUC/MUC.h> #include <Swiften/Network/Timer.h> #include <Swiften/Network/TimerFactory.h> -#include <Swiften/Base/foreach.h> +#include <Swiften/Roster/XMPPRoster.h> + #include <SwifTools/TabComplete.h> -#include <Swiften/Base/foreach.h> -#include <Swift/Controllers/XMPPEvents/EventController.h> -#include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> -#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> -#include <Swift/Controllers/Roster/GroupRosterItem.h> + +#include <Swift/Controllers/Chat/ChatMessageParser.h> +#include <Swift/Controllers/Highlighter.h> +#include <Swift/Controllers/Intl.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> -#include <Swiften/Avatars/AvatarManager.h> -#include <Swiften/Elements/Delay.h> -#include <Swiften/MUC/MUC.h> -#include <Swiften/Client/StanzaChannel.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h> #include <Swift/Controllers/Roster/Roster.h> -#include <Swift/Controllers/Roster/SetAvatar.h> -#include <Swift/Controllers/Roster/SetPresence.h> -#include <Swiften/Disco/EntityCapsProvider.h> - +#include <Swift/Controllers/Roster/RosterVCardProvider.h> +#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 @@ -57,6 +67,14 @@ MUCController::MUCController ( TimerFactory* timerFactory, EventController* eventController, - EntityCapsProvider* entityCapsProvider) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password) { + EntityCapsProvider* entityCapsProvider, + XMPPRoster* roster, + HistoryController* historyController, + MUCRegistry* mucRegistry, + HighlightManager* highlightManager, + boost::shared_ptr<ChatMessageParser> chatMessageParser, + bool isImpromptu, + AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, + VCardManager* vcardManager) : + 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; @@ -65,18 +83,20 @@ MUCController::MUCController ( doneGettingHistory_ = false; events_ = uiEventStream; + xmppRoster_ = roster; roster_ = new Roster(false, true); + rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithResource); 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)); chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); + chatWindow_->onBookmarkRequest.connect(boost::bind(&MUCController::handleBookmarkRequest, this)); 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, _1, _2)); + 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)); @@ -84,12 +104,13 @@ MUCController::MUCController ( muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); + muc_->onOccupantNicknameChanged.connect(boost::bind(&MUCController::handleOccupantNicknameChanged, this, _1, _2)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); - muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); - muc_->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(isImpromptu_ ? Highlighter::ChatMode : Highlighter::MUCMode); + highlighter_->setNick(nick_); if (timerFactory) { loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); @@ -97,5 +118,13 @@ MUCController::MUCController ( loginCheckTimer_->start(); } - chatWindow_->convertToMUC(); + if (isImpromptu) { + muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this)); + chatWindow_->convertToMUC(ChatWindow::ImpromptuMUC); + } else { + muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); + muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3)); + chatWindow_->convertToMUC(ChatWindow::StandardMUC); + chatWindow_->setName(muc->getJID().getNode()); + } setOnline(true); if (avatarManager_ != NULL) { @@ -103,8 +132,11 @@ MUCController::MUCController ( } 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 rosterVCardProvider_; delete roster_; if (loginCheckTimer_) { @@ -125,5 +157,5 @@ void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item MUCOccupant::Affiliation affiliation = muc_->getOccupant(getNick()).getAffiliation(); MUCOccupant::Role role = muc_->getOccupant(getNick()).getRole(); - if (role == MUCOccupant::Moderator) + if (role == MUCOccupant::Moderator && !isImpromptu_) { if (affiliation == MUCOccupant::Admin || affiliation == MUCOccupant::Owner) { @@ -140,4 +172,5 @@ void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item actions.push_back(ChatWindow::AddContact); } + actions.push_back(ChatWindow::ShowProfile); } chatWindow_->setAvailableOccupantActions(actions); @@ -158,4 +191,5 @@ void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction a 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; } } @@ -189,7 +223,13 @@ void MUCController::rejoin() { } //FIXME: check for received activity +#ifdef SWIFT_EXPERIMENTAL_HISTORY + if (lastActivity_ == boost::posix_time::not_a_date_time && historyController_) { + lastActivity_ = historyController_->getLastTimeStampFromMUC(selfJID_, toJID_); + } +#endif if (lastActivity_ == boost::posix_time::not_a_date_time) { muc_->joinAs(nick_); - } else { + } + else { muc_->joinWithContextSince(nick_, lastActivity_); } @@ -205,7 +245,33 @@ const std::string& MUCController::getNick() { } +const boost::optional<std::string> MUCController::getPassword() const { + return password_; +} + +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); } @@ -216,4 +282,7 @@ void MUCController::receivedActivity() { } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wswitch-enum" + void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { receivedActivity(); @@ -255,24 +324,46 @@ 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 joined the chat as %1%.")) % nick); + } else { + joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick); + } + setNick(nick); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::UpdateTimestamp); + +#ifdef SWIFT_EXPERIMENTAL_HISTORY + addRecentLogs(); +#endif + clearPresenceQueue(); shouldJoinOnReconnect_ = true; setEnabled(true); + if (isImpromptu_) { + setAvailableRoomActions(MUCOccupant::NoAffiliation, MUCOccupant::Participant); + } else { MUCOccupant occupant = muc_->getOccupant(nick); setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole()); + } onUserJoined(); + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } } @@ -281,6 +372,5 @@ void MUCController::handleAvatarChanged(const JID& jid) { return; } - std::string path = avatarManager_->getAvatarPath(jid).string(); - roster_->applyOnItems(SetAvatar(jid, path, JID::WithResource)); + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid), JID::WithResource)); } @@ -305,15 +395,21 @@ 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_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole())); + MUCOccupant::Role role = MUCOccupant::Participant; + MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; + if (!isImpromptu_) { + role = occupant.getRole(); + affiliation = occupant.getAffiliation(); + } + std::string groupName(roleToGroupName(role)); + roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid)); + roster_->applyOnItems(SetMUC(jid, role, affiliation)); + roster_->getGroup(groupName)->setManualSort(roleToSortName(role)); 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()) { @@ -321,5 +417,9 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { } else { addPresenceMessage(joinString); + } + if (isImpromptu_) { + setImpromptuWindowTitle(); + onActivity(""); } } @@ -331,5 +431,5 @@ 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); } @@ -369,4 +469,5 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) { case MUCOccupant::NoRole: return ""; } + assert(false); return ""; } @@ -379,9 +480,10 @@ std::string MUCController::roleToSortName(MUCOccupant::Role role) { case MUCOccupant::NoRole: return "4"; } + assert(false); return "5"; } JID MUCController::nickToJID(const std::string& nick) { - return JID(toJID_.getNode(), toJID_.getDomain(), nick); + return muc_->getJID().withResource(nick); } @@ -398,6 +500,5 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes clearPresenceQueue(); boost::shared_ptr<Message> message = messageEvent->getStanza(); - if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable() -) { + if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && messageTargetsMe(message) && !message->getPayload<Delay>() && messageEvent->isReadable()) { chatWindow_->flash(); } @@ -405,4 +506,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes messageEvent->setTargetsMe(false); } + if (messageEvent->isReadable() && isImpromptu_) { + chatWindow_->flash(); /* behave like a regular char*/ + } if (joined_) { std::string nick = message->getFrom().getResource(); @@ -416,5 +520,5 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes 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; @@ -426,12 +530,18 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes if (!doneGettingHistory_) { + checkDuplicates(message); messageEvent->conclude(); } } -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>()) { + if (joined_ && messageEvent->getStanza()->getFrom().getResource() != nick_ && !message->getPayload<Delay>()) { + if (messageTargetsMe(message) || isImpromptu_) { eventController_->handleIncomingEvent(messageEvent); + if (!messageEvent->getConcluded()) { + highlighter_->handleHighlightAction(highlight); + } + } } } @@ -447,7 +557,8 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC } 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()))); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation())); + 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()); @@ -460,4 +571,7 @@ void MUCController::handleOccupantAffiliationChanged(const std::string& nick, co setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole()); } + JID jid(nickToJID(nick)); + MUCOccupant occupant = muc_->getOccupant(nick); + roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation)); } @@ -469,5 +583,4 @@ std::string MUCController::roleToGroupName(MUCOccupant::Role role) { case MUCOccupant::Visitor: result = QT_TRANSLATE_NOOP("", "Visitors"); break; case MUCOccupant::NoRole: result = QT_TRANSLATE_NOOP("", "Occupants"); break; - default: assert(false); } return result; @@ -482,9 +595,14 @@ void MUCController::setOnline(bool online) { } 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 join 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(); } @@ -523,6 +641,20 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving case MUC::LeavePart: break; } + if (isImpromptu_) { + partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the chat%2%")) % occupant.getNick() % partType); + } else { partMessage = str(format(QT_TRANSLATE_NOOP("", "%1% has left the room%2%")) % occupant.getNick() % partType); } + } + else if (isImpromptu_) { + switch (type) { + case MUC::LeaveKick: + case MUC::LeaveBan: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; + case MUC::LeaveNotMember: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "You have been removed from this chat"); break; + case MUC::LeaveDestroy: clearPresenceQueue(); clearAfter = true; partMessage = QT_TRANSLATE_NOOP("", "This chat has ended"); break; + case MUC::Disconnect: + case MUC::LeavePart: partMessage = QT_TRANSLATE_NOOP("", "You have left the chat"); + } + } else { switch (type) { @@ -555,4 +687,46 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving clearPresenceQueue(); } + + if (isImpromptu_) { + setImpromptuWindowTitle(); + } +} + +void MUCController::handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname) { + addPresenceMessage(generateNicknameChangeString(oldNickname, newNickname)); + JID oldJID = muc_->getJID().withResource(oldNickname); + JID newJID = muc_->getJID().withResource(newNickname); + + // adjust occupants + currentOccupants_.erase(oldNickname); + currentOccupants_.insert(newNickname); + + // adjust completer + completer_->removeWord(oldNickname); + completer_->addWord(newNickname); + + // update contact + roster_->removeContact(oldJID); + MUCOccupant occupant = muc_->getOccupant(newNickname); + + JID realJID; + if (occupant.getRealJID()) { + realJID = occupant.getRealJID().get(); + } + MUCOccupant::Role role = MUCOccupant::Participant; + MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation; + if (!isImpromptu_) { + role = occupant.getRole(); + affiliation = occupant.getAffiliation(); + } + std::string groupName(roleToGroupName(role)); + roster_->addContact(newJID, realJID, newNickname, groupName, avatarManager_->getAvatarPath(newJID)); + roster_->applyOnItems(SetMUC(newJID, role, affiliation)); + if (avatarManager_ != NULL) { + handleAvatarChanged(newJID); + } + + clearPresenceQueue(); + onUserNicknameChanged(oldNickname, newNickname); } @@ -580,5 +754,5 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo void MUCController::updateJoinParts() { - chatWindow_->replaceLastMessage(generateJoinPartString(joinParts_)); + chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())), ChatWindow::UpdateTimestamp); } @@ -593,5 +767,6 @@ void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, cons 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; @@ -620,5 +795,5 @@ std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart } -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]; @@ -635,32 +810,32 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart 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; @@ -683,8 +858,20 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart } +std::string MUCController::generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname) { + return str(boost::format(QT_TRANSLATE_NOOP("", "%1% is now known as %2%.")) % oldNickname % newNickname); +} + void MUCController::handleChangeSubjectRequest(const std::string& subject) { muc_->changeSubject(subject); } +void MUCController::handleBookmarkRequest() { + const JID jid = muc_->getJID(); + MUCBookmark bookmark(jid, jid.toBare().toString()); + bookmark.setPassword(password_); + bookmark.setNick(nick_); + chatWindow_->showBookmarkWindow(bookmark); +} + void MUCController::handleConfigureRequest(Form::ref form) { if (form) { @@ -699,5 +886,5 @@ 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)); } @@ -705,10 +892,22 @@ void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, cons 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) { + if (isImpromptu_) { + if (!isImpromptuAlreadyConfigured_) { + configureAsImpromptuRoom(form); + } + } else { chatWindow_->showRoomConfigurationForm(form); } +} void MUCController::handleConfigurationCancelled() { @@ -720,6 +919,17 @@ void MUCController::handleDestroyRoomRequest() { } -void MUCController::handleInvitePersonToThisMUCRequest(const JID& jid, const std::string& reason) { - muc_->invitePerson(jid, reason); +void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) { + RequestInviteToMUCUIEvent::ImpromptuMode mode = isImpromptu_ ? RequestInviteToMUCUIEvent::Impromptu : RequestInviteToMUCUIEvent::NotImpromptu; + boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite, mode)); + eventStream_->send(event); +} + +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_); + } + } } @@ -751,3 +961,124 @@ void MUCController::handleAffiliationListReceived(MUCOccupant::Affiliation affil } +void MUCController::logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) { + // log only incoming messages + if (isIncoming && historyController_) { + historyController_->addMessage(message, fromJID, toJID, HistoryMessage::Groupchat, timeStamp); + } +} + +void MUCController::addRecentLogs() { + if (!historyController_) { + return; + } + + joinContext_ = historyController_->getMUCContext(selfJID_, toJID_, lastActivity_); + + foreach (const HistoryMessage& message, joinContext_) { + bool senderIsSelf = nick_ == message.getFromJID().getResource(); + + // the chatWindow uses utc timestamps + 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()); + } +} + +void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) { + std::string body = newMessage->getBody(); + JID jid = newMessage->getFrom(); + boost::optional<boost::posix_time::ptime> time = newMessage->getTimestamp(); + + reverse_foreach (const HistoryMessage& message, joinContext_) { + boost::posix_time::ptime messageTime = message.getTime() - boost::posix_time::hours(message.getOffset()); + if (time && time < messageTime) { + break; + } + if (time && time != messageTime) { + continue; + } + if (message.getFromJID() != jid) { + continue; + } + if (message.getMessage() != body) { + continue; + } + + // Mark the message as unreadable + newMessage->setBody(""); + } +} + +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 9550ca9..2d732a1 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,12 +7,13 @@ #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> @@ -20,4 +21,6 @@ #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> @@ -32,9 +35,14 @@ namespace Swift { class TimerFactory; class TabComplete; + class XMPPRoster; + class HighlightManager; + class UIEvent; + class VCardManager; + class RosterVCardProvider; 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; @@ -43,15 +51,22 @@ namespace Swift { 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); - ~MUCController(); + 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, boost::shared_ptr<ChatMessageParser> chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider, VCardManager* vcardManager); + virtual ~MUCController(); boost::signal<void ()> onUserLeft; boost::signal<void ()> onUserJoined; + boost::signal<void ()> onImpromptuConfigCompleted; + boost::signal<void (const std::string&, const std::string& )> onUserNicknameChanged; 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); + static std::string generateNicknameChangeString(const std::string& oldNickname, const std::string& newNickname); bool isJoined(); const std::string& getNick(); + const boost::optional<std::string> getPassword() const; + bool isImpromptu() const; + std::map<std::string, JID> getParticipantJIDs() const; + void sendInvites(const std::vector<JID>& jids, const std::string& reason) const; protected: @@ -61,6 +76,7 @@ namespace Swift { 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); private: @@ -73,4 +89,5 @@ namespace Swift { void handleAvatarChanged(const JID& jid); void handleOccupantJoined(const MUCOccupant& occupant); + void handleOccupantNicknameChanged(const std::string& oldNickname, const std::string& newNickname); void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const std::string& reason); void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence); @@ -81,4 +98,5 @@ namespace Swift { void handleJoinTimeoutTick(); void handleChangeSubjectRequest(const std::string&); + void handleBookmarkRequest(); std::string roleToGroupName(MUCOccupant::Role role); std::string roleToSortName(MUCOccupant::Role role); @@ -96,5 +114,5 @@ namespace Swift { void handleConfigurationFormReceived(Form::ref); void handleDestroyRoomRequest(); - void handleInvitePersonToThisMUCRequest(const JID& jid, const std::string& reason); + void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite); void handleConfigurationCancelled(); void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role); @@ -102,4 +120,14 @@ namespace Swift { void handleAffiliationListReceived(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids); 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: @@ -121,4 +149,10 @@ namespace Swift { boost::posix_time::ptime lastActivity_; boost::optional<std::string> password_; + XMPPRoster* xmppRoster_; + std::vector<HistoryMessage> joinContext_; + size_t renameCounter_; + bool isImpromptu_; + bool isImpromptuAlreadyConfigured_; + RosterVCardProvider* rosterVCardProvider_; }; } diff --git a/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp new file mode 100644 index 0000000..2a07654 --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChatMessageParserTest.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2013-2014 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(testOneEmoticon); + CPPUNIT_TEST(testBareEmoticon); + CPPUNIT_TEST(testHiddenEmoticon); + CPPUNIT_TEST(testEndlineEmoticon); + CPPUNIT_TEST(testBoundedEmoticons); + 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(!!part); + CPPUNIT_ASSERT_EQUAL(text, part->alternativeText); + CPPUNIT_ASSERT_EQUAL(path, part->imagePath); + } + + void assertHighlight(const ChatWindow::ChatMessage& result, size_t index, const std::string& text) { + boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> part = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(result.getParts()[index]); + CPPUNIT_ASSERT_EQUAL(text, part->text); + } + + 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); + } + + static HighlightRule ruleFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + HighlightRule rule; + std::vector<std::string> keywords; + keywords.push_back(keyword); + rule.setKeywords(keywords); + rule.setMatchCase(matchCase); + rule.setMatchWholeWords(matchWholeWord); + rule.setMatchChat(true); + return rule; + } + + static const HighlightRulesListPtr ruleListFromKeyword(const std::string& keyword, bool matchCase, bool matchWholeWord) + { + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(ruleFromKeyword(keyword, matchCase, matchWholeWord)); + return list; + } + + static const HighlightRulesListPtr ruleListFromKeywords(const HighlightRule &rule1, const HighlightRule &rule2) + { + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule1); + list->addRule(rule2); + return list; + } + + static HighlightRulesListPtr ruleListWithNickHighlight() + { + HighlightRule rule; + rule.setMatchChat(true); + rule.setNickIsKeyword(true); + rule.setMatchCase(true); + rule.setMatchWholeWords(true); + boost::shared_ptr<HighlightManager::HighlightRulesList> list = boost::make_shared<HighlightManager::HighlightRulesList>(); + list->addRule(rule); + return list; + } + + void testFullBody() { + const std::string no_special_message = "a message with no special content"; + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(no_special_message); + assertText(result, 0, no_special_message); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody(":) shiny :( trigger :) 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, " "); + assertHighlight(result, 4, "trigger"); + assertText(result, 5, " "); + assertEmoticon(result, 6, smile1_, smile1Path_); + assertText(result, 7, " "); + assertURL(result, 8, "http://wonderland.lit/blah"); + assertText(result, 9, " "); + assertURL(result, 10, "http://denmark.lit"); + assertText(result, 11, " boom boom"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "test"); + assertHighlight(result, 1, "trigger"); + assertText(result, 2, "message"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, true)); + result = testling.parseMessageBody("testtriggermessage"); + assertText(result, 0, "testtriggermessage"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", true, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertText(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("TrIgGeR"); + assertHighlight(result, 0, "TrIgGeR"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeyword("trigger", false, false)); + result = testling.parseMessageBody("partialTrIgGeRmatch"); + assertText(result, 0, "partial"); + assertHighlight(result, 1, "TrIgGeR"); + assertText(result, 2, "match"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero one two three"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "one"); + assertText(result, 2, " two "); + assertHighlight(result, 3, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe"); + assertText(result, 2, " two "); + assertHighlight(result, 3, "tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero "); + assertHighlight(result, 1, "oNe"); + assertText(result, 2, " two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", true, false))); + result = testling.parseMessageBody("zero oNe two tHrEe"); + assertText(result, 0, "zero oNe two tHrEe"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zero"); + assertHighlight(result, 1, "one"); + assertText(result, 2, "two"); + assertHighlight(result, 3, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", true, false), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroOnEtwoThReE"); + assertText(result, 0, "zeroOnEtwo"); + assertHighlight(result, 1, "ThReE"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, false))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwo"); + assertHighlight(result, 1, "three"); + + testling = ChatMessageParser(emoticons_, ruleListFromKeywords(ruleFromKeyword("one", false, true), ruleFromKeyword("three", false, true))); + result = testling.parseMessageBody("zeroonetwothree"); + assertText(result, 0, "zeroonetwothree"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice", "Alice"); + assertHighlight(result, 0, "Alice"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("TextAliceText", "Alice"); + assertText(result, 0, "TextAliceText"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice Text", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice"); + assertText(result, 2, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Alice Text", "Alice"); + assertHighlight(result, 0, "Alice"); + assertText(result, 1, " Text"); + + testling = ChatMessageParser(emoticons_, ruleListWithNickHighlight()); + result = testling.parseMessageBody("Text Alice", "Alice"); + assertText(result, 0, "Text "); + assertHighlight(result, 1, "Alice"); + } + + void testOneEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(" :) "); + assertText(result, 0, " "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, " "); + } + + + void testBareEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(":)"); + assertEmoticon(result, 0, smile1_, smile1Path_); + } + + void testHiddenEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("b:)a"); + assertText(result, 0, "b:)a"); + } + + void testEndlineEmoticon() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("Lazy:)"); + assertText(result, 0, "Lazy"); + assertEmoticon(result, 1, smile1_, smile1Path_); + } + + void testBoundedEmoticons() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody(":)Lazy:("); + assertEmoticon(result, 0, smile1_, smile1Path_); + assertText(result, 1, "Lazy"); + assertEmoticon(result, 2, smile2_, smile2Path_); + } + + void testEmoticonParenthesis() { + ChatMessageParser testling(emoticons_, boost::make_shared<HighlightManager::HighlightRulesList>()); + ChatWindow::ChatMessage result = testling.parseMessageBody("(Like this :))"); + assertText(result, 0, "(Like this "); + assertEmoticon(result, 1, smile1_, smile1Path_); + assertText(result, 2, ")"); + } + +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 bbfb22f..4c604ac 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -7,47 +7,56 @@ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> -#include "3rdParty/hippomocks.h" + +#include <hippomocks.h> #include <boost/bind.hpp> -#include "Swift/Controllers/Chat/ChatsManager.h" - -#include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" -#include "Swift/Controllers/Settings/DummySettingsProvider.h" -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.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 <Swiften/Avatars/AvatarMemoryStorage.h> +#include <Swiften/Avatars/NullAvatarManager.h> #include <Swiften/Base/Algorithm.h> +#include <Swiften/Client/Client.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Crypto/CryptoProvider.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Elements/DeliveryReceipt.h> +#include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swiften/Presence/DirectedPresenceSender.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/StanzaChannelPresenceSender.h> +#include <Swiften/Queries/DummyIQChannel.h> +#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/VCards/VCardManager.h> +#include <Swiften/VCards/VCardMemoryStorage.h> +#include <Swiften/Whiteboard/WhiteboardSessionManager.h> + +#include <Swift/Controllers/Chat/ChatsManager.h> +#include <Swift/Controllers/Chat/ChatController.h> +#include <Swift/Controllers/Chat/MUCController.h> +#include <Swift/Controllers/Chat/UnitTest/MockChatListWindow.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UnitTest/MockChatWindow.h> +#include <Swift/Controllers/WhiteboardManager.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> + using namespace Swift; @@ -101,19 +110,33 @@ public: ftManager_ = new DummyFileTransferManager(); ftOverview_ = new FileTransferOverview(ftManager_); + avatarManager_ = new NullAvatarManager(); + wbSessionManager_ = new WhiteboardSessionManager(iqRouter_, stanzaChannel_, presenceOracle_, entityCapsManager_); + wbManager_ = new WhiteboardManager(whiteboardWindowFactory_, uiEventStream_, nickResolver_, wbSessionManager_); + highlightManager_ = new HighlightManager(settings_); + crypto_ = PlatformCryptoProvider::create(); + vcardStorage_ = new VCardMemoryStorage(crypto_); + vcardManager_ = new VCardManager(jid_, iqRouter_, vcardStorage_); 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_); + 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, vcardManager_); - avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); - }; + } void tearDown() { + delete highlightManager_; //delete chatListWindowFactory delete profileSettings_; delete avatarManager_; delete manager_; + delete clientBlockListManager_; + delete vcardManager_; + delete vcardStorage_; + delete crypto_; delete ftOverview_; delete ftManager_; + delete wbSessionManager_; + delete wbManager_; delete directedPresenceSender_; delete presenceSender_; @@ -434,4 +457,5 @@ private: message->setFrom(from); message->setID(id); + message->setBody("This will cause the window to open"); message->addPayload(boost::make_shared<DeliveryReceiptRequest>()); return message; @@ -460,4 +484,5 @@ private: UIEventStream* uiEventStream_; ChatListWindowFactory* chatListWindowFactory_; + WhiteboardWindowFactory* whiteboardWindowFactory_; MUCSearchWindowFactory* mucSearchWindowFactory_; MUCRegistry* mucRegistry_; @@ -471,4 +496,12 @@ private: FileTransferOverview* ftOverview_; FileTransferManager* ftManager_; + WhiteboardSessionManager* wbSessionManager_; + WhiteboardManager* wbManager_; + HighlightManager* highlightManager_; + ClientBlockListManager* clientBlockListManager_; + VCardManager* vcardManager_; + CryptoProvider* crypto_; + VCardStorage* vcardStorage_; + std::map<std::string, std::string> emoticons_; }; diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index d4fbcfd..bb22e43 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,6 +8,7 @@ #include <cppunit/extensions/TestFactoryRegistry.h> #include <boost/algorithm/string.hpp> -#include "3rdParty/hippomocks.h" +#include <hippomocks.h> +#include "Swiften/Base/foreach.h" #include "Swift/Controllers/XMPPEvents/EventController.h" #include "Swiften/Presence/DirectedPresenceSender.h" @@ -21,4 +22,5 @@ #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/UnitTest/MockChatWindow.h" +#include "Swiften/MUC/UnitTest/MockMUC.h" #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" @@ -27,4 +29,14 @@ #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 <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swiften/Crypto/CryptoProvider.h> using namespace Swift; @@ -40,8 +52,11 @@ class MUCControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testMessageWithEmptyLabelItem); CPPUNIT_TEST(testMessageWithLabelItem); + CPPUNIT_TEST(testCorrectMessageWithLabelItem); + CPPUNIT_TEST(testRoleAffiliationStates); CPPUNIT_TEST_SUITE_END(); public: void setUp() { + crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); self_ = JID("girl@wonderland.lit/rabbithole"); nick_ = "aLiCe"; @@ -53,4 +68,5 @@ public: eventController_ = new EventController(); chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>(); + userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>(); presenceOracle_ = new PresenceOracle(stanzaChannel_); presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_); @@ -62,12 +78,21 @@ public: mucRegistry_ = new MUCRegistry(); entityCapsProvider_ = new DummyEntityCapsProvider(); - muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_); + settings_ = new DummySettingsProvider(); + highlightManager_ = new HighlightManager(settings_); + muc_ = boost::make_shared<MockMUC>(mucJID_); 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_); - }; + chatMessageParser_ = boost::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true); + 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, vcardManager_); + } void tearDown() { - delete entityCapsProvider_; delete controller_; + delete vcardManager_; + delete vcardStorage_; + delete highlightManager_; + delete settings_; + delete entityCapsProvider_; delete eventController_; delete presenceOracle_; @@ -185,5 +210,5 @@ public: void testMessageWithLabelItem() { - SecurityLabel::ref label = boost::make_shared<SecurityLabel>(); + boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>(); label->setLabel("a"); SecurityLabelsCatalog::Item labelItem; @@ -211,4 +236,42 @@ public: } + void testCorrectMessageWithLabelItem() { + boost::shared_ptr<SecurityLabel> label = boost::make_shared<SecurityLabel>(); + label->setLabel("a"); + SecurityLabelsCatalog::Item labelItem; + labelItem.setSelector("Bob"); + labelItem.setLabel(label); + boost::shared_ptr<SecurityLabel> label2 = boost::make_shared<SecurityLabel>(); + label->setLabel("b"); + SecurityLabelsCatalog::Item labelItem2; + labelItem2.setSelector("Charlie"); + labelItem2.setLabel(label2); + window_->label_ = labelItem; + boost::shared_ptr<DiscoInfo> features = boost::make_shared<DiscoInfo>(); + features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature); + controller_->setAvailableServerFeatures(features); + IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1]; + SecurityLabelsCatalog::ref labelPayload = boost::make_shared<SecurityLabelsCatalog>(); + labelPayload->addItem(labelItem); + IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload); + iqChannel_->onIQReceived(result); + std::string messageBody("agamemnon"); + window_->onSendMessageRequest(messageBody, false); + boost::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + Message::ref message = boost::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom()); + CPPUNIT_ASSERT(window_->labelsEnabled_); + CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */ + CPPUNIT_ASSERT(message); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + window_->label_ = labelItem2; + window_->onSendMessageRequest(messageBody, true); + rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1]; + message = boost::dynamic_pointer_cast<Message>(rawStanza); + CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody()); + CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>()); + } + void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) { CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size()); @@ -258,11 +321,11 @@ public: 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)); } @@ -270,11 +333,74 @@ public: 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)); + } + + JID jidFromOccupant(const MUCOccupant& occupant) { + return JID(mucJID_.toString()+"/"+occupant.getNick()); + } + + void testRoleAffiliationStates() { + + typedef std::map<std::string, MUCOccupant> occupant_map; + occupant_map occupants; + occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner))); + occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner))); + + /* populate the MUC with fake users */ + typedef const std::pair<std::string,MUCOccupant> occupantIterator; + foreach(occupantIterator &occupant, occupants) { + muc_->insertOccupant(occupant.second); + } + + std::vector<MUCOccupant> alterations; + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner)); + alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast)); + alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation)); + alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast)); + + foreach(const MUCOccupant& alteration, alterations) { + /* perform an alteration to a user's role and affiliation */ + occupant_map::iterator occupant = occupants.find(alteration.getNick()); + CPPUNIT_ASSERT(occupant != occupants.end()); + const JID jid = jidFromOccupant(occupant->second); + /* change the affiliation, leave the role in place */ + muc_->changeAffiliation(jid, alteration.getAffiliation()); + occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + /* change the role, leave the affiliation in place */ + muc_->changeOccupantRole(jid, alteration.getRole()); + occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation()); + testRoleAffiliationStatesVerify(occupants); + } + } + + void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) { + /* verify that the roster is in sync */ + GroupRosterItem* group = window_->getRosterModel()->getRoot(); + foreach(RosterItem* rosterItem, group->getChildren()) { + GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem); + CPPUNIT_ASSERT(child); + foreach(RosterItem* childItem, child->getChildren()) { + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem); + CPPUNIT_ASSERT(item); + std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource()); + CPPUNIT_ASSERT(occupant != occupants.end()); + CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole()); + CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation()); + } + } } @@ -282,5 +408,5 @@ private: JID self_; JID mucJID_; - MUC::ref muc_; + MockMUC::ref muc_; std::string nick_; DummyStanzaChannel* stanzaChannel_; @@ -289,4 +415,5 @@ private: EventController* eventController_; ChatWindowFactory* chatWindowFactory_; + UserSearchWindowFactory* userSearchWindowFactory_; MUCController* controller_; // NickResolver* nickResolver_; @@ -300,4 +427,10 @@ private: MUCRegistry* mucRegistry_; DummyEntityCapsProvider* entityCapsProvider_; + DummySettingsProvider* settings_; + HighlightManager* highlightManager_; + boost::shared_ptr<ChatMessageParser> chatMessageParser_; + boost::shared_ptr<CryptoProvider> crypto_; + VCardManager* vcardManager_; + VCardMemoryStorage* vcardStorage_; }; diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h index 6ac8d4a..287c4b9 100644 --- a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -13,12 +13,15 @@ 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 setBookmarksEnabled(bool /*enabled*/) {} void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} void setUnreadCount(int /*unread*/) {} void clearBookmarks() {} + void setOnline(bool /*isOnline*/) {} }; diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp index 839f4fa..f1849c9 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -16,16 +16,22 @@ #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; @@ -39,38 +45,61 @@ UserSearchController::~UserSearchController() { 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)); + window_->onJIDAddRequested.disconnect(boost::bind(&UserSearchController::handleJIDAddRequested, 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))) { + 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; } - } else { + 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; } - 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(); + break; } + if (handle) { + 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_->setCanSupplyDescription(!inviteToMUCRequest->isImpromptu()); + window_->setJIDs(inviteToMUCRequest->getInvites()); + window_->setRoomJID(inviteToMUCRequest->getRoom()); } return; @@ -99,5 +128,4 @@ void UserSearchController::endDiscoWalker() { } - void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { //bool isUserDirectory = false; @@ -167,4 +195,26 @@ void UserSearchController::handleNameSuggestionRequest(const JID &jid) { } +void UserSearchController::handleContactSuggestionsRequested(std::string text) { + const std::vector<JID> existingJIDs = window_->getJIDs(); + std::vector<Contact::ref> suggestions = contactSuggester_->getSuggestions(text, false); + /* do not suggest contacts that have already been added to the chat list */ + std::vector<Contact::ref>::iterator i = suggestions.begin(); + while (i != suggestions.end()) { + bool found = false; + foreach (const JID& jid, existingJIDs) { + if ((*i)->jid == jid) { + found = true; + break; + } + } + if (found) { + i = suggestions.erase(i); + } else { + i++; + } + } + window_->setContactSuggestions(suggestions); +} + void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) { if (jid == suggestionsJID_) { @@ -172,4 +222,61 @@ void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref 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::ref> updates; + foreach(const JID& jid, jids) { + updates.push_back(convertJIDtoContact(jid)); + } + window_->updateContacts(updates); + } +} + +void UserSearchController::handleJIDAddRequested(const std::vector<JID>& jids) { + std::vector<Contact::ref> contacts; + foreach(const JID& jid, jids) { + contacts.push_back(convertJIDtoContact(jid)); + } + window_->addContacts(contacts); +} + +Contact::ref UserSearchController::convertJIDtoContact(const JID& jid) { + Contact::ref contact = boost::make_shared<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; } @@ -179,3 +286,30 @@ void UserSearchController::handleDiscoWalkFinished() { } +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_->onJIDAddRequested.connect(boost::bind(&UserSearchController::handleJIDAddRequested, 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..d630580 100644 --- a/Swift/Controllers/Chat/UserSearchController.h +++ b/Swift/Controllers/Chat/UserSearchController.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,15 +8,18 @@ #include <boost/shared_ptr.hpp> + #include <map> #include <vector> -#include <Swiften/Base/boost_bsignals.h> - -#include <Swiften/Elements/SearchPayload.h> #include <string> -#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Contact.h> +#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/DiscoItems.h> #include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/SearchPayload.h> #include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> namespace Swift { @@ -29,4 +32,7 @@ namespace Swift { class RosterController; class VCardManager; + class ContactSuggester; + class AvatarManager; + class PresenceOracle; class UserSearchResult { @@ -42,8 +48,11 @@ 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); @@ -55,6 +64,13 @@ namespace Swift { 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); + void handleJIDAddRequested(const std::vector<JID>& jids); + Contact::ref convertJIDtoContact(const JID& jid); void endDiscoWalker(); + void initializeUserWindow(); private: @@ -69,4 +85,7 @@ namespace Swift { 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,4 +1,4 @@ /* - * 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. @@ -39,5 +39,5 @@ string ChatMessageSummarizer::getSummary(const string& current, const vector<Unr 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); } diff --git a/Swift/Controllers/ConnectionSettings.h b/Swift/Controllers/ConnectionSettings.h new file mode 100644 index 0000000..c02c5d4 --- /dev/null +++ b/Swift/Controllers/ConnectionSettings.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +struct ConnectionSettings { + enum Method { + Automatic, + Manual, + BOSH + }; + enum ProxyType { + None, + System, + SOCKS5, + HTTPConnect + }; + + Method method; + struct { + bool useManualServer; + std::string manualServerHostname; + int manualServerPort; + ProxyType proxyType; + bool useManualProxy; + std::string manualProxyHostname; + int manualProxyPort; + } manualSettings; + struct { + std::string boshURI; + bool useManualProxy; + std::string manualProxyHostname; + int manualProxyPort; + } boshSettings; +}; diff --git a/Swift/Controllers/Contact.cpp b/Swift/Controllers/Contact.cpp new file mode 100644 index 0000000..be2b83a --- /dev/null +++ b/Swift/Controllers/Contact.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/find.hpp> +#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) { +} + +bool Contact::lexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b) { + if (a->jid.isValid() && b->jid.isValid()) { + return a->jid < b->jid; + } else { + return a->name < b->name; + } +} + +bool Contact::equalityPredicate(const Contact::ref& a, const Contact::ref& b) { + if (a->jid.isValid() && b->jid.isValid()) { + return a->jid == b->jid; + } else { + return a->name == b->name; + } +} + +bool Contact::sortPredicate(const Contact::ref& a, const Contact::ref& b, const std::string& search) { + /* perform case insensitive comparisons */ + std::string aLower = a->name; + boost::to_lower(aLower); + std::string bLower = b->name; + boost::to_lower(bLower); + std::string searchLower = search; + boost::to_lower(searchLower); + + /* name starts with the search term */ + if (aLower.find(searchLower) == 0 && bLower.find(searchLower) != 0) { + return true; + } else if (bLower.find(searchLower) == 0 && aLower.find(searchLower) != 0) { + return false; + } + + /* name contains search term */ + if (aLower.find(searchLower) != std::string::npos && bLower.find(searchLower) == std::string::npos) { + return true; + } else if (bLower.find(searchLower) != std::string::npos && aLower.find(searchLower) == std::string::npos) { + return false; + } + + /* Levenshtein should be done here */ + /* if edit distances are equal, fall through to the tests below */ + + /* lexicographical sort */ + if (a->statusType == b->statusType) { + return aLower.compare(bLower) < 0; + } + + /* online status */ + return a->statusType < b->statusType; +} + +} diff --git a/Swift/Controllers/Contact.h b/Swift/Controllers/Contact.h new file mode 100644 index 0000000..f83230f --- /dev/null +++ b/Swift/Controllers/Contact.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/enable_shared_from_this.hpp> +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class Contact : public boost::enable_shared_from_this<Contact> { + public: + typedef boost::shared_ptr<Contact> ref; + + Contact(); + Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path); + + static bool lexicographicalSortPredicate(const Contact::ref& a, const Contact::ref& b); + static bool equalityPredicate(const Contact::ref& a, const Contact::ref& b); + static bool sortPredicate(const Contact::ref& a, const Contact::ref& b, const std::string& search); + + 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..acc2bdc --- /dev/null +++ b/Swift/Controllers/ContactProvider.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class ContactProvider { + public: + virtual ~ContactProvider(); + virtual std::vector<Contact::ref> getContacts(bool withMUCNicks) = 0; +}; + +} diff --git a/Swift/Controllers/ContactSuggester.cpp b/Swift/Controllers/ContactSuggester.cpp new file mode 100644 index 0000000..8627aeb --- /dev/null +++ b/Swift/Controllers/ContactSuggester.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/ContactSuggester.h> + +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/find.hpp> +#include <boost/bind.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::ref& c) { + if (fuzzyMatch(c->name, search)) { + return true; + } + else if (c->jid.isValid()) { + return fuzzyMatch(c->jid.toString(), search); + } + return false; +} + +std::vector<Contact::ref> ContactSuggester::getSuggestions(const std::string& search, bool withMUCNicks) const { + std::vector<Contact::ref> results; + + foreach(ContactProvider* provider, contactProviders_) { + append(results, provider->getContacts(withMUCNicks)); + } + + std::sort(results.begin(), results.end(), Contact::lexicographicalSortPredicate); + results.erase(std::unique(results.begin(), results.end(), Contact::equalityPredicate), results.end()); + results.erase(std::remove_if(results.begin(), results.end(), !lambda::bind(&matchContact, search, lambda::_1)), + results.end()); + std::sort(results.begin(), results.end(), boost::bind(&Contact::sortPredicate, _1, _2, search)); + + return results; +} + +bool ContactSuggester::fuzzyMatch(std::string text, std::string match) { + std::string lowerText = text; + boost::algorithm::to_lower(lowerText); + std::string lowerMatch = match; + boost::algorithm::to_lower(lowerMatch); + size_t lastMatch = 0; + for (size_t i = 0; i < lowerMatch.length(); ++i) { + size_t where = lowerText.find_first_of(lowerMatch[i], lastMatch); + if (where == std::string::npos) { + return false; + } + lastMatch = where + 1; + } + return true; +} + +} diff --git a/Swift/Controllers/ContactSuggester.h b/Swift/Controllers/ContactSuggester.h new file mode 100644 index 0000000..ae47766 --- /dev/null +++ b/Swift/Controllers/ContactSuggester.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> + +#include <Swift/Controllers/Contact.h> + +class ContactSuggesterTest; + +namespace Swift { + class ContactProvider; + + class ContactSuggester { + public: + ContactSuggester(); + ~ContactSuggester(); + + void addContactProvider(ContactProvider* provider); + + std::vector<Contact::ref> getSuggestions(const std::string& search, bool withMUCNicks) const; + public: + static bool matchContact(const std::string& search, const Contact::ref& 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); + + private: + std::vector<ContactProvider*> contactProviders_; + }; +} diff --git a/Swift/Controllers/ContactsFromXMPPRoster.cpp b/Swift/Controllers/ContactsFromXMPPRoster.cpp new file mode 100644 index 0000000..abd62bd --- /dev/null +++ b/Swift/Controllers/ContactsFromXMPPRoster.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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::ref> ContactsFromXMPPRoster::getContacts(bool /*withMUCNicks*/) { + std::vector<Contact::ref> results; + std::vector<XMPPRosterItem> rosterItems = roster_->getItems(); + foreach(const XMPPRosterItem& rosterItem, rosterItems) { + Contact::ref contact = boost::make_shared<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..b76adc4 --- /dev/null +++ b/Swift/Controllers/ContactsFromXMPPRoster.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. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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::ref> getContacts(bool withMUCNicks); + private: + XMPPRoster* roster_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; +}; + +} diff --git a/Swift/Controllers/DummySoundPlayer.h b/Swift/Controllers/DummySoundPlayer.h index 36dcb28..d0601c5 100644 --- a/Swift/Controllers/DummySoundPlayer.h +++ b/Swift/Controllers/DummySoundPlayer.h @@ -1,4 +1,4 @@ /* - * 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. @@ -7,10 +7,10 @@ #pragma once -#include "Swift/Controllers/SoundPlayer.h" +#include <Swift/Controllers/SoundPlayer.h> namespace Swift { class DummySoundPlayer : public SoundPlayer { public: - void playSound(SoundEffect sound) {}; + void playSound(SoundEffect sound, const std::string& soundResource) {} }; } 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 @@ -12,6 +12,6 @@ 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 @@ -11,4 +11,5 @@ #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> @@ -25,5 +26,5 @@ 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) { } @@ -42,5 +43,5 @@ std::string FileTransferController::setChatWindow(ChatWindow* wnd, std::string n 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; @@ -65,5 +66,5 @@ int FileTransferController::getProgress() const { boost::uintmax_t FileTransferController::getSize() const { if (transfer) { - return transfer->fileSizeInBytes; + return transfer->getFileSizeInBytes(); } else { return 0; @@ -76,7 +77,7 @@ void FileTransferController::start(std::string& description) { 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(); @@ -93,7 +94,7 @@ void FileTransferController::accept(std::string& file) { 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); @@ -114,5 +115,8 @@ void FileTransferController::handleFileTransferStateChange(FileTransfer::State s 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); @@ -139,5 +143,5 @@ void FileTransferController::handleFileTransferStateChange(FileTransfer::State s return; } - std::cerr << "Unhandled FileTransfer::State!" << std::endl; + assert(false); } 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 @@ -7,4 +7,6 @@ #include "FileTransferProgressInfo.h" +#include <boost/numeric/conversion/cast.hpp> + #include <Swiften/Base/Log.h> @@ -17,5 +19,5 @@ 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) { diff --git a/Swift/Controllers/HighlightAction.cpp b/Swift/Controllers/HighlightAction.cpp new file mode 100644 index 0000000..492d4d2 --- /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::setHighlightAllText(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..90768a7 --- /dev/null +++ b/Swift/Controllers/HighlightAction.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> + +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.hpp> + +namespace Swift { + + class HighlightRule; + + class HighlightAction { + public: + HighlightAction() : highlightText_(false), playSound_(false) {} + + /** + * Gets the flag that indicates the entire message should be highlighted. + */ + bool highlightAllText() const { return highlightText_; } + void setHighlightAllText(bool highlightText); + + /** + * Gets the foreground highlight color. + */ + const std::string& getTextColor() const { return textColor_; } + void setTextColor(const std::string& textColor) { textColor_ = textColor; } + + /** + * Gets the background highlight color. + */ + const std::string& getTextBackground() const { return textBackground_; } + void setTextBackground(const std::string& textBackground) { textBackground_ = textBackground; } + + bool playSound() const { return playSound_; } + void setPlaySound(bool playSound); + + /** + * Gets the sound filename. If the string is empty, assume a default sound file. + */ + const std::string& getSoundFile() const { return soundFile_; } + void setSoundFile(const std::string& soundFile) { soundFile_ = soundFile; } + + bool isEmpty() const { return !highlightText_ && !playSound_; } + + private: + friend class boost::serialization::access; + template<class Archive> void serialize(Archive & ar, const unsigned int version); + + bool highlightText_; + std::string textColor_; + std::string textBackground_; + + bool playSound_; + std::string soundFile_; + }; + + template<class Archive> + void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) + { + ar & highlightText_; + ar & textColor_; + ar & textBackground_; + ar & playSound_; + ar & soundFile_; + } + +} diff --git a/Swift/Controllers/HighlightEditorController.cpp b/Swift/Controllers/HighlightEditorController.cpp new file mode 100644 index 0000000..efa3ba2 --- /dev/null +++ b/Swift/Controllers/HighlightEditorController.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/bind.hpp> + +#include <Swift/Controllers/HighlightEditorController.h> +#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> +#include <Swift/Controllers/ContactSuggester.h> + +namespace Swift { + +HighlightEditorController::HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager) +: highlightEditorWindowFactory_(highlightEditorWindowFactory), highlightEditorWindow_(NULL), highlightManager_(highlightManager), contactSuggester_(0) +{ + uiEventStream->onUIEvent.connect(boost::bind(&HighlightEditorController::handleUIEvent, this, _1)); +} + +HighlightEditorController::~HighlightEditorController() +{ + delete highlightEditorWindow_; + highlightEditorWindow_ = NULL; +} + +void HighlightEditorController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) +{ + boost::shared_ptr<RequestHighlightEditorUIEvent> event = boost::dynamic_pointer_cast<RequestHighlightEditorUIEvent>(rawEvent); + if (event) { + if (!highlightEditorWindow_) { + highlightEditorWindow_ = highlightEditorWindowFactory_->createHighlightEditorWindow(); + highlightEditorWindow_->setHighlightManager(highlightManager_); + highlightEditorWindow_->onContactSuggestionsRequested.connect(boost::bind(&HighlightEditorController::handleContactSuggestionsRequested, this, _1)); + } + highlightEditorWindow_->show(); + } +} + +void HighlightEditorController::handleContactSuggestionsRequested(const std::string& text) +{ + if (contactSuggester_) { + highlightEditorWindow_->setContactSuggestions(contactSuggester_->getSuggestions(text, true)); + } +} + +} diff --git a/Swift/Controllers/HighlightEditorController.h b/Swift/Controllers/HighlightEditorController.h new file mode 100644 index 0000000..54322e2 --- /dev/null +++ b/Swift/Controllers/HighlightEditorController.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + + class UIEventStream; + + class HighlightEditorWindowFactory; + class HighlightEditorWindow; + + class HighlightManager; + class ContactSuggester; + + class HighlightEditorController { + public: + HighlightEditorController(UIEventStream* uiEventStream, HighlightEditorWindowFactory* highlightEditorWindowFactory, HighlightManager* highlightManager); + ~HighlightEditorController(); + + HighlightManager* getHighlightManager() const { return highlightManager_; } + void setContactSuggester(ContactSuggester *suggester) { contactSuggester_ = suggester; } + + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleContactSuggestionsRequested(const std::string& text); + + private: + HighlightEditorWindowFactory* highlightEditorWindowFactory_; + HighlightEditorWindow* highlightEditorWindow_; + HighlightManager* highlightManager_; + ContactSuggester* contactSuggester_; + }; + +} diff --git a/Swift/Controllers/HighlightManager.cpp b/Swift/Controllers/HighlightManager.cpp new file mode 100644 index 0000000..ca0567e --- /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. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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 <boost/serialization/vector.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.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) +{ + rules_ = boost::make_shared<HighlightRulesList>(); + 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(); + } +} + +std::string HighlightManager::rulesToString() const +{ + std::stringstream stream; + boost::archive::text_oarchive archive(stream); + archive << rules_->list_; + return stream.str(); +} + +std::vector<HighlightRule> HighlightManager::getDefaultRules() +{ + std::vector<HighlightRule> rules; + HighlightRule r; + r.setMatchChat(true); + r.getAction().setPlaySound(true); + rules.push_back(r); + return rules; +} + +HighlightRule HighlightManager::getRule(int index) const +{ + assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); + return rules_->getRule(static_cast<size_t>(index)); +} + +void HighlightManager::setRule(int index, const HighlightRule& rule) +{ + assert(index >= 0 && static_cast<size_t>(index) < rules_->getSize()); + rules_->list_[static_cast<size_t>(index)] = rule; +} + +void HighlightManager::insertRule(int index, const HighlightRule& rule) +{ + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) <= rules_->getSize()); + rules_->list_.insert(rules_->list_.begin() + index, rule); +} + +void HighlightManager::removeRule(int index) +{ + assert(index >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(index) < rules_->getSize()); + rules_->list_.erase(rules_->list_.begin() + index); +} + +void HighlightManager::swapRules(const size_t first, const size_t second) { + assert(first < rules_->getSize()); + assert(second < rules_->getSize()); + const HighlightRule swap = rules_->getRule(first); + rules_->setRule(first, rules_->getRule(second)); + rules_->setRule(second, swap); +} + +void HighlightManager::storeSettings() +{ + storingSettings_ = true; // don't reload settings while saving + settings_->storeSetting(SettingConstants::HIGHLIGHT_RULES, rulesToString()); + storingSettings_ = false; +} + +void HighlightManager::loadSettings() +{ + std::string rulesString = settings_->getSetting(SettingConstants::HIGHLIGHT_RULES); + std::stringstream stream; + stream << rulesString; + try { + boost::archive::text_iarchive archive(stream); + archive >> rules_->list_; + } catch (boost::archive::archive_exception&) { + rules_->list_ = getDefaultRules(); + } +} + +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..07a3fe3 --- /dev/null +++ b/Swift/Controllers/HighlightManager.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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: + + class HighlightRulesList { + public: + friend class HighlightManager; + size_t getSize() const { return list_.size(); } + const HighlightRule& getRule(const size_t index) const { return list_[index]; } + void addRule(const HighlightRule& rule) { list_.push_back(rule); } + void combineRules(const HighlightRulesList& rhs) { + list_.insert(list_.end(), rhs.list_.begin(), rhs.list_.end()); + } + void setRule(const size_t index, const HighlightRule& rule) { + list_[index] = rule; + } + private: + std::vector<HighlightRule> list_; + }; + + HighlightManager(SettingsProvider* settings); + + Highlighter* createHighlighter(); + + boost::shared_ptr<const HighlightManager::HighlightRulesList> 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); + void swapRules(const size_t first, const size_t second); + void storeSettings(); + void loadSettings(); + + boost::signal<void (const HighlightAction&)> onHighlight; + + private: + void handleSettingChanged(const std::string& settingPath); + + std::string rulesToString() const; + static std::vector<HighlightRule> getDefaultRules(); + + SettingsProvider* settings_; + bool storingSettings_; + + boost::shared_ptr<HighlightManager::HighlightRulesList> rules_; + }; + + typedef boost::shared_ptr<const HighlightManager::HighlightRulesList> HighlightRulesListPtr; + +} diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp new file mode 100644 index 0000000..3251067 --- /dev/null +++ b/Swift/Controllers/HighlightRule.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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"; +} + +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(); + + 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(); +} + +std::vector<boost::regex> HighlightRule::getKeywordRegex(const std::string& nick) const { + if (nickIsKeyword_) { + std::vector<boost::regex> regex; + if (!nick.empty()) { + regex.push_back(regexFromString(nick)); + } + return regex; + } else { + return keywordRegex_; + } +} + +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..c0226bc --- /dev/null +++ b/Swift/Controllers/HighlightRule.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <string> + +#include <boost/regex.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/archive/text_iarchive.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_; } + + const std::vector<std::string>& getSenders() const { return senders_; } + void setSenders(const std::vector<std::string>&); + const std::vector<boost::regex>& getSenderRegex() const { return senderRegex_; } + + const std::vector<std::string>& getKeywords() const { return keywords_; } + void setKeywords(const std::vector<std::string>&); + std::vector<boost::regex> getKeywordRegex(const std::string& nick) const; + + 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: + friend class boost::serialization::access; + template<class Archive> void serialize(Archive & ar, const unsigned int version); + + 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_; + }; + + template<class Archive> + void HighlightRule::serialize(Archive& ar, const unsigned int /*version*/) + { + ar & senders_; + ar & keywords_; + ar & nickIsKeyword_; + ar & matchChat_; + ar & matchMUC_; + ar & matchCase_; + ar & matchWholeWords_; + ar & action_; + updateRegex(); + } + +} diff --git a/Swift/Controllers/Highlighter.cpp b/Swift/Controllers/Highlighter.cpp new file mode 100644 index 0000000..efeeb6b --- /dev/null +++ b/Swift/Controllers/Highlighter.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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 +{ + HighlightRulesListPtr rules = manager_->getRules(); + for (size_t i = 0; i < rules->getSize(); ++i) { + const HighlightRule& rule = rules->getRule(i); + if (rule.isMatch(body, sender, nick_, messageType_)) { + return rule.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..d5d846b --- /dev/null +++ b/Swift/Controllers/Highlighter.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 <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; } + std::string getNick() const { return 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/HistoryController.cpp b/Swift/Controllers/HistoryController.cpp new file mode 100644 index 0000000..5732382 --- /dev/null +++ b/Swift/Controllers/HistoryController.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + + +#include <Swift/Controllers/HistoryController.h> +#include <Swiften/History/HistoryStorage.h> +#include <Swiften/History/HistoryMessage.h> +#include <boost/date_time/c_local_time_adjustor.hpp> + +namespace Swift { + +HistoryController::HistoryController(HistoryStorage* localHistoryStorage) : localHistory_(localHistoryStorage), remoteArchiveSupported_(false) { +} + +HistoryController::~HistoryController() { +} + +void HistoryController::addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp) { + // note: using localtime timestamps + boost::posix_time::ptime localTime = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timeStamp); + int offset = (localTime - timeStamp).hours(); + + HistoryMessage historyMessage(message, fromJID, toJID, type, localTime, offset); + + localHistory_->addMessage(historyMessage); + onNewMessage(historyMessage); +} + +std::vector<HistoryMessage> HistoryController::getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + return localHistory_->getMessagesFromDate(selfJID, contactJID, type, date); +} + +std::vector<HistoryMessage> HistoryController::getMUCContext(const JID& selfJID, const JID& mucJID, const boost::posix_time::ptime& timeStamp) const { + boost::posix_time::ptime localTime = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(timeStamp); + return getMessagesFromDate(selfJID, mucJID, HistoryMessage::Groupchat, localTime.date()); +} + +std::vector<HistoryMessage> HistoryController::getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + return localHistory_->getMessagesFromPreviousDate(selfJID, contactJID, type, date); +} + +std::vector<HistoryMessage> HistoryController::getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const { + return localHistory_->getMessagesFromNextDate(selfJID, contactJID, type, date); +} + +ContactsMap HistoryController::getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword) const { + return localHistory_->getContacts(selfJID, type, keyword); +} + +boost::posix_time::ptime HistoryController::getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID) { + return localHistory_->getLastTimeStampFromMUC(selfJID, mucJID); +} + +} diff --git a/Swift/Controllers/HistoryController.h b/Swift/Controllers/HistoryController.h new file mode 100644 index 0000000..8c86409 --- /dev/null +++ b/Swift/Controllers/HistoryController.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <vector> +#include <set> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/History/HistoryMessage.h> +#include <Swiften/History/HistoryStorage.h> + +namespace Swift { + class JID; + + class HistoryController { + public: + HistoryController(HistoryStorage* localHistoryStorage); + ~HistoryController(); + + void addMessage(const std::string& message, const JID& fromJID, const JID& toJID, HistoryMessage::Type type, const boost::posix_time::ptime& timeStamp); + std::vector<HistoryMessage> getMessagesFromDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + std::vector<HistoryMessage> getMessagesFromPreviousDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + std::vector<HistoryMessage> getMessagesFromNextDate(const JID& selfJID, const JID& contactJID, HistoryMessage::Type type, const boost::gregorian::date& date) const; + ContactsMap getContacts(const JID& selfJID, HistoryMessage::Type type, const std::string& keyword = std::string()) const; + std::vector<HistoryMessage> getMUCContext(const JID& selfJID, const JID& mucJID, const boost::posix_time::ptime& timeStamp) const; + + boost::posix_time::ptime getLastTimeStampFromMUC(const JID& selfJID, const JID& mucJID); + + boost::signal<void (const HistoryMessage&)> onNewMessage; + + private: + HistoryStorage* localHistory_; + bool remoteArchiveSupported_; + }; +} diff --git a/Swift/Controllers/HistoryViewController.cpp b/Swift/Controllers/HistoryViewController.cpp new file mode 100644 index 0000000..c24be50 --- /dev/null +++ b/Swift/Controllers/HistoryViewController.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * 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 <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Base/Path.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/History/HistoryMessage.h> + +#include <Swift/Controllers/HistoryController.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h> +#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h> + +namespace Swift { + static const std::string category[] = { "Contacts", "MUC", "Contacts" }; + +HistoryViewController::HistoryViewController( + const JID& selfJID, + UIEventStream* uiEventStream, + HistoryController* historyController, + NickResolver* nickResolver, + AvatarManager* avatarManager, + PresenceOracle* presenceOracle, + HistoryWindowFactory* historyWindowFactory) : + selfJID_(selfJID), + uiEventStream_(uiEventStream), + historyController_(historyController), + nickResolver_(nickResolver), + avatarManager_(avatarManager), + presenceOracle_(presenceOracle), + historyWindowFactory_(historyWindowFactory), + historyWindow_(NULL), + selectedItem_(NULL), + currentResultDate_(boost::gregorian::not_a_date_time) { + uiEventStream_->onUIEvent.connect(boost::bind(&HistoryViewController::handleUIEvent, this, _1)); + + roster_ = new Roster(false, true); +} + +HistoryViewController::~HistoryViewController() { + uiEventStream_->onUIEvent.disconnect(boost::bind(&HistoryViewController::handleUIEvent, this, _1)); + if (historyWindow_) { + historyWindow_->onSelectedContactChanged.disconnect(boost::bind(&HistoryViewController::handleSelectedContactChanged, this, _1)); + historyWindow_->onReturnPressed.disconnect(boost::bind(&HistoryViewController::handleReturnPressed, this, _1)); + historyWindow_->onScrollReachedTop.disconnect(boost::bind(&HistoryViewController::handleScrollReachedTop, this, _1)); + historyWindow_->onScrollReachedBottom.disconnect(boost::bind(&HistoryViewController::handleScrollReachedBottom, this, _1)); + historyWindow_->onPreviousButtonClicked.disconnect(boost::bind(&HistoryViewController::handlePreviousButtonClicked, this)); + historyWindow_->onNextButtonClicked.disconnect(boost::bind(&HistoryViewController::handleNextButtonClicked, this)); + historyWindow_->onCalendarClicked.disconnect(boost::bind(&HistoryViewController::handleCalendarClicked, this, _1)); + historyController_->onNewMessage.disconnect(boost::bind(&HistoryViewController::handleNewMessage, this, _1)); + + presenceOracle_->onPresenceChange.disconnect(boost::bind(&HistoryViewController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.disconnect(boost::bind(&HistoryViewController::handleAvatarChanged, this, _1)); + + delete historyWindow_; + } + delete roster_; +} + +void HistoryViewController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + boost::shared_ptr<RequestHistoryUIEvent> event = boost::dynamic_pointer_cast<RequestHistoryUIEvent>(rawEvent); + if (event != NULL) { + if (historyWindow_ == NULL) { + historyWindow_ = historyWindowFactory_->createHistoryWindow(uiEventStream_); + historyWindow_->onSelectedContactChanged.connect(boost::bind(&HistoryViewController::handleSelectedContactChanged, this, _1)); + historyWindow_->onReturnPressed.connect(boost::bind(&HistoryViewController::handleReturnPressed, this, _1)); + historyWindow_->onScrollReachedTop.connect(boost::bind(&HistoryViewController::handleScrollReachedTop, this, _1)); + historyWindow_->onScrollReachedBottom.connect(boost::bind(&HistoryViewController::handleScrollReachedBottom, this, _1)); + historyWindow_->onPreviousButtonClicked.connect(boost::bind(&HistoryViewController::handlePreviousButtonClicked, this)); + historyWindow_->onNextButtonClicked.connect(boost::bind(&HistoryViewController::handleNextButtonClicked, this)); + historyWindow_->onCalendarClicked.connect(boost::bind(&HistoryViewController::handleCalendarClicked, this, _1)); + historyController_->onNewMessage.connect(boost::bind(&HistoryViewController::handleNewMessage, this, _1)); + + presenceOracle_->onPresenceChange.connect(boost::bind(&HistoryViewController::handlePresenceChanged, this, _1)); + avatarManager_->onAvatarChanged.connect(boost::bind(&HistoryViewController::handleAvatarChanged, this, _1)); + + historyWindow_->setRosterModel(roster_); + } + + // populate roster by doing an empty search + handleReturnPressed(std::string()); + + historyWindow_->activate(); + } +} + +void HistoryViewController::handleSelectedContactChanged(RosterItem* newContact) { + // FIXME: signal is triggerd twice. + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(newContact); + + if (contact && selectedItem_ != contact) { + selectedItem_ = contact; + historyWindow_->resetConversationView(); + } + else { + return; + } + + JID contactJID = contact->getJID(); + + std::vector<HistoryMessage> messages; + for (int it = HistoryMessage::Chat; it <= HistoryMessage::PrivateMessage; it++) { + HistoryMessage::Type type = static_cast<HistoryMessage::Type>(it); + + if (contacts_[type].count(contactJID)) { + currentResultDate_ = *contacts_[type][contactJID].rbegin(); + selectedItemType_ = type; + messages = historyController_->getMessagesFromDate(selfJID_, contactJID, type, currentResultDate_); + } + } + + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handleNewMessage(const HistoryMessage& message) { + JID contactJID = message.getFromJID().toBare() == selfJID_ ? message.getToJID() : message.getFromJID(); + + JID displayJID; + if (message.getType() == HistoryMessage::PrivateMessage) { + displayJID = contactJID; + } + else { + displayJID = contactJID.toBare(); + } + + // check current conversation + if (selectedItem_ && selectedItem_->getJID() == displayJID) { + if (historyWindow_->getLastVisibleDate() == message.getTime().date()) { + addNewMessage(message, false); + } + } + + // check if the new message matches the query + if (message.getMessage().find(historyWindow_->getSearchBoxText()) == std::string::npos) { + return; + } + + // update contacts + if (!contacts_[message.getType()].count(displayJID)) { + roster_->addContact(displayJID, displayJID, nickResolver_->jidToNick(displayJID), category[message.getType()], avatarManager_->getAvatarPath(displayJID)); + } + + contacts_[message.getType()][displayJID].insert(message.getTime().date()); +} + +void HistoryViewController::addNewMessage(const HistoryMessage& message, bool addAtTheTop) { + bool senderIsSelf = message.getFromJID().toBare() == selfJID_; + 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); +} + +void HistoryViewController::handleReturnPressed(const std::string& keyword) { + reset(); + + for (int it = HistoryMessage::Chat; it <= HistoryMessage::PrivateMessage; it++) { + HistoryMessage::Type type = static_cast<HistoryMessage::Type>(it); + + contacts_[type] = historyController_->getContacts(selfJID_, type, keyword); + + for (ContactsMap::const_iterator contact = contacts_[type].begin(); contact != contacts_[type].end(); contact++) { + const JID& jid = contact->first; + std::string nick; + if (type == HistoryMessage::PrivateMessage) { + nick = jid.toString(); + } + else { + nick = nickResolver_->jidToNick(jid); + } + roster_->addContact(jid, jid, nick, category[type], avatarManager_->getAvatarPath(jid)); + + Presence::ref presence = getPresence(jid, type == HistoryMessage::Groupchat); + + if (presence.get()) { + roster_->applyOnItem(SetPresence(presence, JID::WithoutResource), jid); + } + } + } +} + +void HistoryViewController::handleScrollReachedTop(const boost::gregorian::date& date) { + if (!selectedItem_) { + return; + } + + std::vector<HistoryMessage> messages = historyController_->getMessagesFromPreviousDate(selfJID_, selectedItem_->getJID(), selectedItemType_, date); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, true); + } + historyWindow_->resetConversationViewTopInsertPoint(); +} + +void HistoryViewController::handleScrollReachedBottom(const boost::gregorian::date& date) { + if (!selectedItem_) { + return; + } + + std::vector<HistoryMessage> messages = historyController_->getMessagesFromNextDate(selfJID_, selectedItem_->getJID(), selectedItemType_, date); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handleNextButtonClicked() { + if (!selectedItem_) { + return; + } + + std::set<boost::gregorian::date>::iterator date = contacts_[selectedItemType_][selectedItem_->getJID()].find(currentResultDate_); + + if (*date == *contacts_[selectedItemType_][selectedItem_->getJID()].rbegin()) { + return; + } + + historyWindow_->resetConversationView(); + currentResultDate_ = *(++date); + std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_); + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handlePreviousButtonClicked() { + if (!selectedItem_) { + return; + } + + std::set<boost::gregorian::date>::iterator date = contacts_[selectedItemType_][selectedItem_->getJID()].find(currentResultDate_); + + if (date == contacts_[selectedItemType_][selectedItem_->getJID()].begin()) { + return; + } + + historyWindow_->resetConversationView(); + currentResultDate_ = *(--date); + std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_); + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::reset() { + roster_->removeAll(); + contacts_.clear(); + selectedItem_ = NULL; + historyWindow_->resetConversationView(); +} + +void HistoryViewController::handleCalendarClicked(const boost::gregorian::date& date) { + if (!selectedItem_) { + return; + } + + boost::gregorian::date newDate; + if (contacts_[selectedItemType_][selectedItem_->getJID()].count(date)) { + newDate = date; + } + else if (date < currentResultDate_) { + foreach(const boost::gregorian::date& current, contacts_[selectedItemType_][selectedItem_->getJID()]) { + if (current > date) { + newDate = current; + break; + } + } + } + else { + reverse_foreach(const boost::gregorian::date& current, contacts_[selectedItemType_][selectedItem_->getJID()]) { + if (current < date) { + newDate = current; + break; + } + } + } + + historyWindow_->setDate(newDate); + if (newDate == currentResultDate_) { + return; + } + currentResultDate_ = newDate; + historyWindow_->resetConversationView(); + + std::vector<HistoryMessage> messages = historyController_->getMessagesFromDate(selfJID_, selectedItem_->getJID(), selectedItemType_, currentResultDate_); + historyWindow_->setDate(currentResultDate_); + + foreach (const HistoryMessage& message, messages) { + addNewMessage(message, false); + } +} + +void HistoryViewController::handlePresenceChanged(Presence::ref presence) { + JID jid = presence->getFrom(); + + if (contacts_[HistoryMessage::Chat].count(jid.toBare())) { + roster_->applyOnItems(SetPresence(presence, JID::WithoutResource)); + return; + } + + if (contacts_[HistoryMessage::Groupchat].count(jid.toBare())) { + Presence::ref availablePresence = boost::make_shared<Presence>(Presence()); + availablePresence->setFrom(jid.toBare()); + roster_->applyOnItems(SetPresence(availablePresence, JID::WithResource)); + } + + if (contacts_[HistoryMessage::PrivateMessage].count(jid)) { + roster_->applyOnItems(SetPresence(presence, JID::WithResource)); + } +} + +void HistoryViewController::handleAvatarChanged(const JID& jid) { + roster_->applyOnItems(SetAvatar(jid, avatarManager_->getAvatarPath(jid))); +} + +Presence::ref HistoryViewController::getPresence(const JID& jid, bool isMUC) { + if (jid.isBare() && !isMUC) { + return presenceOracle_->getHighestPriorityPresence(jid); + } + + std::vector<Presence::ref> mucPresence = presenceOracle_->getAllPresence(jid.toBare()); + + if (isMUC && !mucPresence.empty()) { + Presence::ref presence = boost::make_shared<Presence>(Presence()); + presence->setFrom(jid); + return presence; + } + + foreach (Presence::ref presence, mucPresence) { + if (presence.get() && presence->getFrom() == jid) { + return presence; + } + } + + return Presence::create(); +} + +} diff --git a/Swift/Controllers/HistoryViewController.h b/Swift/Controllers/HistoryViewController.h new file mode 100644 index 0000000..f44c968 --- /dev/null +++ b/Swift/Controllers/HistoryViewController.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/History/HistoryStorage.h> +#include <set> + +namespace Swift { + class HistoryWindowFactory; + class HistoryWindow; + class Roster; + class RosterItem; + class ContactRosterItem; + class HistoryController; + class NickResolver; + class AvatarManager; + + class HistoryViewController { + public: + HistoryViewController(const JID& selfJID, UIEventStream* uiEventStream, HistoryController* historyController, NickResolver* nickResolver, AvatarManager* avatarManager, PresenceOracle* presenceOracle, HistoryWindowFactory* historyWindowFactory); + ~HistoryViewController(); + + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleSelectedContactChanged(RosterItem* item); + void handleNewMessage(const HistoryMessage& message); + void handleReturnPressed(const std::string& keyword); + void handleScrollReachedTop(const boost::gregorian::date& date); + void handleScrollReachedBottom(const boost::gregorian::date& date); + void handlePreviousButtonClicked(); + void handleNextButtonClicked(); + void handleCalendarClicked(const boost::gregorian::date& date); + void handlePresenceChanged(Presence::ref presence); + void handleAvatarChanged(const JID& jid); + + void addNewMessage(const HistoryMessage& message, bool addAtTheTop); + void reset(); + Presence::ref getPresence(const JID& jid, bool isMUC); + + private: + JID selfJID_; + UIEventStream* uiEventStream_; + HistoryController* historyController_; + NickResolver* nickResolver_; + AvatarManager* avatarManager_; + PresenceOracle* presenceOracle_; + HistoryWindowFactory* historyWindowFactory_; + HistoryWindow* historyWindow_; + Roster* roster_; + + std::map<HistoryMessage::Type, ContactsMap> contacts_; + ContactRosterItem* selectedItem_; + HistoryMessage::Type selectedItemType_; + boost::gregorian::date currentResultDate_; + }; +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 7bd89cb..95d8134 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith and Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,70 +7,85 @@ #include <Swift/Controllers/MainController.h> +#include <cstdlib> + #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/FileTransferListController.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/PresenceNotifier.h" -#include "Swift/Controllers/EventNotifier.h" -#include "Swift/Controllers/Storages/StoragesFactory.h" -#include "SwifTools/Dock/Dock.h" -#include "SwifTools/Notifier/TogglableNotifier.h" -#include "Swiften/Base/foreach.h" -#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/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 <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 <Swift/Controllers/HighlightManager.h> +#include <Swift/Controllers/HighlightEditorController.h> +#include <Swift/Controllers/BlockListController.h> +#include <Swift/Controllers/ContactSuggester.h> +#include <Swift/Controllers/ContactsFromXMPPRoster.h> namespace Swift { @@ -93,4 +108,5 @@ MainController::MainController( URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map<std::string, std::string>& emoticons, bool useDelayForLatency) : eventLoop_(eventLoop), @@ -104,5 +120,6 @@ MainController::MainController( loginWindow_(NULL) , useDelayForLatency_(useDelayForLatency), - ftOverview_(NULL) { + ftOverview_(NULL), + emoticons_(emoticons) { storages_ = NULL; certificateStorage_ = NULL; @@ -112,9 +129,18 @@ MainController::MainController( rosterController_ = NULL; chatsManager_ = NULL; + historyController_ = NULL; + 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; @@ -134,5 +160,9 @@ MainController::MainController( 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_); @@ -142,4 +172,5 @@ MainController::MainController( std::string cachedPassword; std::string cachedCertificate; + ClientOptions cachedOptions; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); if (!eagle) { @@ -149,8 +180,10 @@ MainController::MainController( std::string certificate = profileSettings.getStringSetting("certificate"); std::string jid = profileSettings.getStringSetting("jid"); - loginWindow_->addAvailableAccount(jid, password, certificate); + ClientOptions clientOptions = parseClientOptions(profileSettings.getStringSetting("options")); + loginWindow_->addAvailableAccount(jid, password, certificate, clientOptions); if (jid == selectedLoginJID) { cachedPassword = password; cachedCertificate = certificate; + cachedOptions = clientOptions; } } @@ -160,5 +193,5 @@ MainController::MainController( - loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6)); + loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6, _7)); loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1)); loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this)); @@ -177,5 +210,5 @@ MainController::MainController( profileSettings_ = new ProfileSettingsProvider(selectedLoginJID, settings_); /* FIXME: deal with autologin with a cert*/ - handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), true, true); + handleLoginRequest(selectedLoginJID, cachedPassword, cachedCertificate, CertificateWithKey::ref(), cachedOptions, true, true); } else { profileSettings_ = NULL; @@ -191,4 +224,6 @@ MainController::~MainController() { resetClient(); + delete highlightEditorController_; + delete highlightManager_; delete fileTransferListController_; delete xmlConsoleController_; @@ -214,10 +249,20 @@ void MainController::resetClient() { delete profileController_; profileController_ = NULL; + delete showProfileController_; + showProfileController_ = NULL; delete eventWindowController_; eventWindowController_ = NULL; delete chatsManager_; chatsManager_ = NULL; +#ifdef SWIFT_EXPERIMENTAL_HISTORY + delete historyViewController_; + historyViewController_ = NULL; + delete historyController_; + historyController_ = NULL; +#endif delete ftOverview_; ftOverview_ = NULL; + delete blockListController_; + blockListController_ = NULL; delete rosterController_; rosterController_ = NULL; @@ -238,6 +283,16 @@ void MainController::resetClient() { 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_; + whiteboardManager_ = NULL; clientInitialized_ = false; } @@ -278,16 +333,38 @@ void MainController::handleConnected() { 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(boundJID_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager(), client_->getVCardManager()); 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()); + + /* Doing this early as an ordering fix. Various things later will + * want to have the user's nick available and this means it will + * be before they receive stanzas that need it (e.g. bookmarks).*/ + client_->getVCardManager()->requestOwnVCard(); + + contactSuggesterWithoutRoster_ = new ContactSuggester(); + contactSuggesterWithRoster_ = new ContactSuggester(); - 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_); + 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_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager()); +#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_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_, client_->getVCardManager()); +#endif + contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle()); + contactSuggesterWithoutRoster_->addContactProvider(chatsManager_); + contactSuggesterWithRoster_->addContactProvider(chatsManager_); + contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_); + highlightEditorController_->setContactSuggester(contactSuggesterWithoutRoster_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); @@ -309,11 +386,16 @@ void MainController::handleConnected() { discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); #endif +#ifdef SWIFT_EXPERIMENTAL_WB + discoInfo.addFeature(DiscoInfo::WhiteboardFeature); +#endif discoInfo.addFeature(DiscoInfo::MessageDeliveryReceiptsFeature); 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); @@ -327,12 +409,16 @@ void MainController::handleConnected() { client_->getVCardManager()->requestOwnVCard(); + rosterController_->setJID(boundJID_); rosterController_->setEnabled(true); + rosterController_->getWindow()->setStreamEncryptionStatus(client_->isStreamEncrypted()); profileController_->setAvailable(true); 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); - + adHocManager_->setOnline(true); } @@ -381,7 +467,5 @@ void MainController::sendPresence(boost::shared_ptr<Presence> presence) { // Add information and send - if (!vCardPhotoHash_.empty()) { presence->updatePayload(boost::make_shared<VCardUpdate>(vCardPhotoHash_)); - } client_->getPresenceSender()->sendPresence(presence); if (presence->getType() == Presence::Unavailable) { @@ -403,5 +487,5 @@ void MainController::handleInputIdleChanged(bool idle) { else { if (idle) { - if (statusTracker_->goAutoAway()) { + if (statusTracker_->goAutoAway(idleDetector_->getIdleTimeSeconds())) { if (client_ && client_->isAvailable()) { sendPresence(statusTracker_->getNextPresence()); @@ -418,5 +502,10 @@ void MainController::handleInputIdleChanged(bool idle) { } -void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, bool remember, bool loginAutomatically) { +void MainController::handleShowCertificateRequest() { + std::vector<Certificate::ref> chain = client_->getStanzaChannel()->getPeerCertificateChain(); + rosterController_->getWindow()->openCertificateDialog(chain); +} + +void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically) { jid_ = JID(username); if (!jid_.isValid() || jid_.getNode().empty()) { @@ -431,11 +520,14 @@ void MainController::handleLoginRequest(const std::string &username, const std:: profileSettings_->storeString("certificate", certificatePath); profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : ""); + std::string optionString = serializeClientOptions(options); + profileSettings_->storeString("options", optionString); settings_->storeSetting(SettingConstants::LAST_LOGIN_JID, username); settings_->storeSetting(SettingConstants::LOGIN_AUTOMATICALLY, loginAutomatically); - loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate")); + loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate"), options); } password_ = password; certificate_ = certificate; + clientOptions_ = options; performLoginFromCachedCredentials(); } @@ -497,12 +589,18 @@ void MainController::performLoginFromCachedCredentials() { rosterController_->getWindow()->setConnecting(); } - ClientOptions clientOptions; + 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); } void MainController::handleDisconnected(const boost::optional<ClientError>& error) { + if (rosterController_) { + rosterController_->getWindow()->setStreamEncryptionStatus(false); + } + if (adHocManager_) { + adHocManager_->setOnline(false); + } if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) { purgeCachedCredentials(); @@ -552,7 +650,7 @@ void MainController::handleDisconnected(const boost::optional<ClientError>& erro bool forceReconnectAfterCertificateTrust = false; if (!certificateErrorMessage.empty()) { - Certificate::ref certificate = certificateTrustChecker_->getLastCertificate(); - if (loginWindow_->askUserToTrustCertificatePermanently(certificateErrorMessage, certificate)) { - certificateStorage_->addCertificate(certificate); + std::vector<Certificate::ref> certificates = certificateTrustChecker_->getLastCertificateChain(); + if (!certificates.empty() && loginWindow_->askUserToTrustCertificatePermanently(certificateErrorMessage, certificates)) { + certificateStorage_->addCertificate(certificates[0]); forceReconnectAfterCertificateTrust = true; } @@ -662,12 +760,19 @@ void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> chatsManager_->setServerDiscoInfo(info); adHocManager_->setServerDiscoInfo(info); + if (info->hasFeature(DiscoInfo::BlockingCommandFeature)) { + rosterController_->getWindow()->setBlockingCommandAvailable(true); + rosterController_->initBlockingCommand(); + } } } void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) { - if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().empty()) { + if (!jid.equals(jid_, JID::WithoutResource) || !vCard) { return; } - std::string hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + std::string hash; + if (!vCard->getPhoto().empty()) { + hash = Hexify::hexify(networkFactories_->getCryptoProvider()->getSHA1Hash(vCard->getPhoto())); + } if (hash != vCardPhotoHash_) { vCardPhotoHash_ = hash; @@ -701,3 +806,87 @@ void MainController::handleQuitRequest() { } +#define SERIALIZE_BOOL(option) result += options.option ? "1" : "0"; result += ","; +#define SERIALIZE_INT(option) result += boost::lexical_cast<std::string>(options.option); result += ","; +#define SERIALIZE_STRING(option) result += Base64::encode(createByteArray(options.option)); result += ","; +#define SERIALIZE_SAFE_STRING(option) result += safeByteArrayToString(Base64::encode(options.option)); result += ","; +#define SERIALIZE_URL(option) SERIALIZE_STRING(option.toString()) + +std::string MainController::serializeClientOptions(const ClientOptions& options) { + std::string result; + SERIALIZE_BOOL(useStreamCompression); + switch (options.useTLS) { + case ClientOptions::NeverUseTLS: result += "1";break; + case ClientOptions::UseTLSWhenAvailable: result += "2";break; + case ClientOptions::RequireTLS: result += "3";break; + } + result += ","; + SERIALIZE_BOOL(allowPLAINWithoutTLS); + SERIALIZE_BOOL(useStreamResumption); + SERIALIZE_BOOL(useAcks); + SERIALIZE_STRING(manualHostname); + SERIALIZE_INT(manualPort); + switch (options.proxyType) { + case ClientOptions::NoProxy: result += "1";break; + case ClientOptions::SystemConfiguredProxy: result += "2";break; + case ClientOptions::SOCKS5Proxy: result += "3";break; + case ClientOptions::HTTPConnectProxy: result += "4";break; + } + result += ","; + SERIALIZE_STRING(manualProxyHostname); + SERIALIZE_INT(manualProxyPort); + SERIALIZE_URL(boshURL); + SERIALIZE_URL(boshHTTPConnectProxyURL); + SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthID); + SERIALIZE_SAFE_STRING(boshHTTPConnectProxyAuthPassword); + return result; +} + +#define CHECK_PARSE_LENGTH if (i >= segments.size()) {return result;} +#define PARSE_INT_RAW(defaultValue) CHECK_PARSE_LENGTH intVal = defaultValue; try {intVal = boost::lexical_cast<int>(segments[i]);} catch(const boost::bad_lexical_cast&) {};i++; +#define PARSE_STRING_RAW CHECK_PARSE_LENGTH stringVal = byteArrayToString(Base64::decode(segments[i]));i++; + +#define PARSE_BOOL(option, defaultValue) PARSE_INT_RAW(defaultValue); result.option = (intVal == 1); +#define PARSE_INT(option, defaultValue) PARSE_INT_RAW(defaultValue); result.option = intVal; +#define PARSE_STRING(option) PARSE_STRING_RAW; result.option = stringVal; +#define PARSE_SAFE_STRING(option) PARSE_STRING_RAW; result.option = SafeString(createSafeByteArray(stringVal)); +#define PARSE_URL(option) {PARSE_STRING_RAW; result.option = URL::fromString(stringVal);} + + +ClientOptions MainController::parseClientOptions(const std::string& optionString) { + ClientOptions result; + size_t i = 0; + int intVal = 0; + std::string stringVal; + std::vector<std::string> segments = String::split(optionString, ','); + + PARSE_BOOL(useStreamCompression, 1); + PARSE_INT_RAW(-1); + switch (intVal) { + case 1: result.useTLS = ClientOptions::NeverUseTLS;break; + case 2: result.useTLS = ClientOptions::UseTLSWhenAvailable;break; + case 3: result.useTLS = ClientOptions::RequireTLS;break; + default:; + } + PARSE_BOOL(allowPLAINWithoutTLS, 0); + PARSE_BOOL(useStreamResumption, 0); + PARSE_BOOL(useAcks, 1); + PARSE_STRING(manualHostname); + PARSE_INT(manualPort, -1); + PARSE_INT_RAW(-1); + switch (intVal) { + case 1: result.proxyType = ClientOptions::NoProxy;break; + case 2: result.proxyType = ClientOptions::SystemConfiguredProxy;break; + case 3: result.proxyType = ClientOptions::SOCKS5Proxy;break; + case 4: result.proxyType = ClientOptions::HTTPConnectProxy;break; + } + PARSE_STRING(manualProxyHostname); + PARSE_INT(manualProxyPort, -1); + PARSE_URL(boshURL); + PARSE_URL(boshHTTPConnectProxyURL); + PARSE_SAFE_STRING(boshHTTPConnectProxyAuthID); + PARSE_SAFE_STRING(boshHTTPConnectProxyAuthPassword); + + return result; +} + } diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 14de4eb..6fbde6d 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -1,4 +1,4 @@ /* - * 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. @@ -7,22 +7,26 @@ #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 { @@ -44,4 +48,5 @@ namespace Swift { class Notifier; class ProfileController; + class ShowProfileController; class ContactEditController; class TogglableNotifier; @@ -53,4 +58,6 @@ namespace Swift { class SoundPlayer; class XMLConsoleController; + class HistoryViewController; + class HistoryController; class FileTransferListController; class UIEventStream; @@ -69,4 +76,10 @@ namespace Swift { class AdHocCommandWindowFactory; class FileTransferOverview; + class WhiteboardManager; + class HighlightManager; + class HighlightEditorController; + class BlockListController; + class ContactSuggester; + class ContactsFromXMPPRoster; class MainController { @@ -85,4 +98,5 @@ namespace Swift { URIHandler* uriHandler, IdleDetector* idleDetector, + const std::map<std::string, std::string>& emoticons, bool useDelayForLatency); ~MainController(); @@ -92,5 +106,5 @@ namespace Swift { void resetClient(); void handleConnected(); - void handleLoginRequest(const std::string& username, const std::string& password, const std::string& certificatePath, CertificateWithKey::ref certificate, bool remember, bool loginAutomatically); + void handleLoginRequest(const std::string& username, const std::string& password, const std::string& certificatePath, CertificateWithKey::ref certificate, const ClientOptions& options, bool remember, bool loginAutomatically); void handleCancelLoginRequest(); void handleQuitRequest(); @@ -104,4 +118,5 @@ namespace Swift { void sendPresence(boost::shared_ptr<Presence> presence); void handleInputIdleChanged(bool); + void handleShowCertificateRequest(); void logout(); void signOut(); @@ -116,4 +131,6 @@ namespace Swift { void handleForceQuit(); void purgeCachedCredentials(); + std::string serializeClientOptions(const ClientOptions& options); + ClientOptions parseClientOptions(const std::string& optionString); private: @@ -143,8 +160,15 @@ namespace Swift { UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; + 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_; @@ -155,8 +179,10 @@ namespace Swift { std::string password_; CertificateWithKey::ref certificate_; + ClientOptions clientOptions_; boost::shared_ptr<ErrorEvent> lastDisconnectError_; bool useDelayForLatency_; UserSearchController* userSearchControllerChat_; UserSearchController* userSearchControllerAdd_; + UserSearchController* userSearchControllerInvite_; int timeBeforeNextReconnect_; Timer::ref reconnectTimer_; @@ -167,4 +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 @@ -26,5 +26,5 @@ ProfileController::~ProfileController() { 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)); @@ -38,5 +38,7 @@ 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)); } @@ -76,4 +78,8 @@ void ProfileController::handleOwnVCardChanged(VCard::ref vcard) { } +void ProfileController::handleProfileWindowAboutToBeClosed(const JID&) { + profileWindow = NULL; +} + void ProfileController::setAvailable(bool b) { available = b; 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 @@ -30,4 +30,5 @@ namespace Swift { void handleSetVCardResponse(ErrorPayload::ref); void handleOwnVCardChanged(VCard::ref vcard); + void handleProfileWindowAboutToBeClosed(const JID&); void updateDialogStatus(); diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 8c388bf..e0651bc 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -5,13 +5,20 @@ */ -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +#include <boost/date_time/posix_time/posix_time.hpp> #include <Swiften/Base/foreach.h> +#include <Swiften/Base/DateTime.h> +#include <Swiften/Elements/Idle.h> +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> 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.toBare()), mucRole_(MUCOccupant::NoRole), mucAffiliation_(MUCOccupant::NoAffiliation), blockState_(BlockingNotSupported) +{ } @@ -25,10 +32,10 @@ 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); @@ -40,9 +47,28 @@ std::string ContactRosterItem::getStatusText() const { } -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()); + } +} + +std::string ContactRosterItem::getOfflineSinceText() const { + if (offlinePresence_) { + boost::optional<boost::posix_time::ptime> delay = offlinePresence_->getTimestamp(); + if (offlinePresence_->getType() == Presence::Unavailable && delay) { + return dateTimeToLocalString(*delay); + } + } + return ""; +} + +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_; } @@ -114,6 +140,41 @@ void ContactRosterItem::removeGroup(const std::string& group) { } +MUCOccupant::Role ContactRosterItem::getMUCRole() const +{ + return mucRole_; +} + +void ContactRosterItem::setMUCRole(const MUCOccupant::Role& role) +{ + mucRole_ = role; +} + +MUCOccupant::Affiliation ContactRosterItem::getMUCAffiliation() const +{ + return mucAffiliation_; +} + +void ContactRosterItem::setMUCAffiliation(const MUCOccupant::Affiliation& affiliation) +{ + mucAffiliation_ = affiliation; +} + +std::string ContactRosterItem::getMUCAffiliationText() const +{ + std::string affiliationString; + switch (mucAffiliation_) { + case MUCOccupant::Owner: affiliationString = QT_TRANSLATE_NOOP("", "Owner"); break; + case MUCOccupant::Admin: affiliationString = QT_TRANSLATE_NOOP("", "Admin"); break; + case MUCOccupant::Member: affiliationString = QT_TRANSLATE_NOOP("", "Member"); break; + case MUCOccupant::Outcast: affiliationString = QT_TRANSLATE_NOOP("", "Outcast"); break; + case MUCOccupant::NoAffiliation: affiliationString = ""; break; + } + + return affiliationString; +} + void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) { features_ = features; + onDataChanged(); } @@ -122,5 +183,21 @@ bool ContactRosterItem::supportsFeature(const Feature feature) const { } +void ContactRosterItem::setBlockState(BlockState state) { + blockState_ = state; + onDataChanged(); } +ContactRosterItem::BlockState ContactRosterItem::blockState() const { + return blockState_; +} + +VCard::ref ContactRosterItem::getVCard() const { + return vcard_; +} +void ContactRosterItem::setVCard(VCard::ref vcard) { + vcard_ = vcard; + onDataChanged(); +} + +} diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 9932dc4..ab10c66 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -1,4 +1,4 @@ /* - * 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. @@ -7,16 +7,22 @@ #pragma once -#include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItem.h" -#include "Swiften/Elements/StatusShow.h" -#include "Swiften/Elements/Presence.h" - #include <map> #include <set> +#include <string> + #include <boost/bind.hpp> -#include "Swiften/Base/boost_bsignals.h" +#include <boost/date_time/posix_time/ptime.hpp> +#include <boost/filesystem/path.hpp> #include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/RosterItem.h> + namespace Swift { @@ -26,4 +32,12 @@ class ContactRosterItem : public RosterItem { enum Feature { FileTransferFeature, + WhiteboardFeature + }; + + enum BlockState { + BlockingNotSupported, + IsBlocked, + IsUnblocked, + IsDomainBlocked }; @@ -35,6 +49,8 @@ class ContactRosterItem : public RosterItem { StatusShow::Type getSimplifiedStatusShow() const; std::string getStatusText() const; - void setAvatarPath(const std::string& path); - const std::string& getAvatarPath() const; + std::string getIdleText() const; + std::string getOfflineSinceText() const; + void setAvatarPath(const boost::filesystem::path& path); + const boost::filesystem::path& getAvatarPath() const; const JID& getJID() const; void setDisplayJID(const JID& jid); @@ -48,15 +64,35 @@ class ContactRosterItem : public RosterItem { void removeGroup(const std::string& group); + MUCOccupant::Role getMUCRole() const; + void setMUCRole(const MUCOccupant::Role& role); + MUCOccupant::Affiliation getMUCAffiliation() const; + void setMUCAffiliation(const MUCOccupant::Affiliation& affiliation); + std::string getMUCAffiliationText() const; + void setSupportedFeatures(const std::set<Feature>& features); bool supportsFeature(Feature feature) const; + + void setBlockState(BlockState state); + BlockState blockState() const; + + VCard::ref getVCard() const; + void setVCard(VCard::ref vcard); + + boost::signal<void ()> onVCardRequested; + 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_; + MUCOccupant::Role mucRole_; + MUCOccupant::Affiliation mucAffiliation_; std::set<Feature> features_; + BlockState blockState_; + VCard::ref vcard_; }; diff --git a/Swift/Controllers/Roster/FuzzyRosterFilter.h b/Swift/Controllers/Roster/FuzzyRosterFilter.h new file mode 100644 index 0000000..6710084 --- /dev/null +++ b/Swift/Controllers/Roster/FuzzyRosterFilter.h @@ -0,0 +1,38 @@ +/* + * 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 <Swift/Controllers/ContactSuggester.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/RosterItem.h> +#include <Swift/Controllers/Roster/RosterFilter.h> + +namespace Swift { + +class FuzzyRosterFilter : public RosterFilter { + public: + FuzzyRosterFilter(const std::string& query) : query_(query) { } + virtual ~FuzzyRosterFilter() {} + virtual bool operator() (RosterItem* item) const { + ContactRosterItem *contactItem = dynamic_cast<ContactRosterItem*>(item); + if (contactItem) { + const bool itemMatched = ContactSuggester::fuzzyMatch(contactItem->getDisplayName(), query_) || ContactSuggester::fuzzyMatch(contactItem->getDisplayJID(), query_); + return !itemMatched; + } else { + return false; + } + } + + private: + std::string query_; +}; + +} + + diff --git a/Swift/Controllers/Roster/AppearOffline.h b/Swift/Controllers/Roster/ItemOperations/AppearOffline.h index 8bd53d7..14beaa3 100644 --- a/Swift/Controllers/Roster/AppearOffline.h +++ b/Swift/Controllers/Roster/ItemOperations/AppearOffline.h @@ -7,5 +7,5 @@ #pragma once -#include <Swift/Controllers/Roster/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> diff --git a/Swift/Controllers/Roster/RosterItemOperation.h b/Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h index 691c8ef..f1dff8d 100644 --- a/Swift/Controllers/Roster/RosterItemOperation.h +++ b/Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h @@ -13,8 +13,8 @@ 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 diff --git a/Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h b/Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h new file mode 100644 index 0000000..620a1ae --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.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 <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetAvailableFeatures : public RosterItemOperation { + public: + SetAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& availableFeatures, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), availableFeatures_(availableFeatures), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setSupportedFeatures(availableFeatures_); + } + } + + private: + JID jid_; + std::set<ContactRosterItem::Feature> availableFeatures_; + JID::CompareType compareType_; +}; + +} diff --git a/Swift/Controllers/Roster/SetAvatar.h b/Swift/Controllers/Roster/ItemOperations/SetAvatar.h index 241b741..b3ec5f3 100644 --- a/Swift/Controllers/Roster/SetAvatar.h +++ b/Swift/Controllers/Roster/ItemOperations/SetAvatar.h @@ -1,4 +1,4 @@ /* - * 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. @@ -7,8 +7,11 @@ #pragma once -#include "Swiften/Elements/Presence.h" -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <boost/filesystem/path.hpp> + +#include <Swiften/Elements/Presence.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> namespace Swift { @@ -18,5 +21,5 @@ 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) { } @@ -30,5 +33,5 @@ class SetAvatar : public RosterItemOperation { private: JID jid_; - std::string path_; + boost::filesystem::path path_; JID::CompareType compareType_; }; diff --git a/Swift/Controllers/Roster/ItemOperations/SetBlockingState.h b/Swift/Controllers/Roster/ItemOperations/SetBlockingState.h new file mode 100644 index 0000000..ddb2c7a --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetBlockingState.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013 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/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetBlockingState : public RosterItemOperation { + public: + SetBlockingState(const JID& jid, ContactRosterItem::BlockState state, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(!jid.getNode().empty(), jid), jid_(jid), state_(state), compareType_(compareType) { + if (state_ == ContactRosterItem::IsBlocked && jid.getNode().empty()) { + state_ = ContactRosterItem::IsDomainBlocked; + } + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (jid_.getNode().empty()) { + if (contact && contact->getJID().getDomain() == jid_.getDomain()) { + contact->setBlockState(state_); + } + } else { + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setBlockState(state_); + } + } + } + + private: + JID jid_; + ContactRosterItem::BlockState state_; + JID::CompareType compareType_; +}; + +} diff --git a/Swift/Controllers/Roster/ItemOperations/SetMUC.h b/Swift/Controllers/Roster/ItemOperations/SetMUC.h new file mode 100644 index 0000000..598e5f5 --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetMUC.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetMUC : public RosterItemOperation { + public: + SetMUC(const JID& jid, const MUCOccupant::Role& role, const MUCOccupant::Affiliation& affiliation) + : RosterItemOperation(true, jid), jid_(jid), mucRole_(role), mucAffiliation_(affiliation) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, JID::WithResource)) { + contact->setMUCRole(mucRole_); + contact->setMUCAffiliation(mucAffiliation_); + } + } + + private: + JID jid_; + MUCOccupant::Role mucRole_; + MUCOccupant::Affiliation mucAffiliation_; +}; + +} diff --git a/Swift/Controllers/Roster/SetName.h b/Swift/Controllers/Roster/ItemOperations/SetName.h index aefb0dc..b21e4f2 100644 --- a/Swift/Controllers/Roster/SetName.h +++ b/Swift/Controllers/Roster/ItemOperations/SetName.h @@ -7,7 +7,8 @@ #pragma once -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> namespace Swift { diff --git a/Swift/Controllers/Roster/SetPresence.h b/Swift/Controllers/Roster/ItemOperations/SetPresence.h index 06adfa4..b298a88 100644 --- a/Swift/Controllers/Roster/SetPresence.h +++ b/Swift/Controllers/Roster/ItemOperations/SetPresence.h @@ -7,8 +7,9 @@ #pragma once -#include "Swiften/Elements/Presence.h" -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include <Swiften/Elements/Presence.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> namespace Swift { diff --git a/Swift/Controllers/Roster/ItemOperations/SetVCard.h b/Swift/Controllers/Roster/ItemOperations/SetVCard.h new file mode 100644 index 0000000..8ee73f9 --- /dev/null +++ b/Swift/Controllers/Roster/ItemOperations/SetVCard.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 <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +namespace Swift { + +class RosterItem; + +class SetVCard : public RosterItemOperation { + public: + SetVCard(const JID& jid, VCard::ref vcard, JID::CompareType compareType = JID::WithoutResource) : RosterItemOperation(true, jid), jid_(jid), vcard_(vcard), compareType_(compareType) { + } + + virtual void operator() (RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && contact->getJID().equals(jid_, compareType_)) { + contact->setVCard(vcard_); + } + } + + private: + JID jid_; + VCard::ref vcard_; + JID::CompareType compareType_; +}; + +} 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,4 +1,4 @@ /* - * 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. @@ -8,4 +8,5 @@ #include <vector> +#include <boost/numeric/conversion/cast.hpp> namespace Swift { @@ -15,6 +16,6 @@ namespace Swift { 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); @@ -31,5 +32,5 @@ namespace Swift { 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)]); } } @@ -47,11 +48,11 @@ namespace Swift { 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) @@ -60,6 +61,6 @@ namespace Swift { 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; @@ -68,6 +69,6 @@ namespace Swift { // 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 @@ -78,5 +79,5 @@ namespace Swift { 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])) { diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index 65cf4d2..4dbc453 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -1,27 +1,28 @@ /* - * 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 "Swift/Controllers/Roster/Roster.h" +#include <Swift/Controllers/Roster/Roster.h> -#include "Swiften/Base/foreach.h" #include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/RosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" - -#include <boost/bind.hpp> - #include <iostream> #include <set> #include <deque> +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/RosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> + namespace Swift { -Roster::Roster(bool sortByStatus, bool fullJIDMapping) { +Roster::Roster(bool sortByStatus, bool fullJIDMapping) : blockingSupported_(false) { sortByStatus_ = sortByStatus; fullJIDMapping_ = fullJIDMapping; @@ -40,4 +41,8 @@ Roster::~Roster() { queue.insert(queue.begin(), group->getChildren().begin(), group->getChildren().end()); } + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact) { + contact->onVCardRequested.disconnect(boost::bind(boost::ref(onVCardUpdateRequested), contact->getJID())); + } delete item; } @@ -62,12 +67,13 @@ GroupRosterItem* Roster::getGroup(const std::string& groupName) { } -void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features) { - ItemMap::const_iterator i = itemMap_.find(fullJIDMapping_ ? jid : jid.toBare()); - if (i == itemMap_.end()) { - return; +void Roster::setBlockingSupported(bool isSupported) { + if (!blockingSupported_) { + foreach(ItemMap::value_type i, itemMap_) { + foreach(ContactRosterItem* item, i.second) { + item->setBlockState(ContactRosterItem::IsUnblocked); + } } - foreach(ContactRosterItem* item, i->second) { - item->setSupportedFeatures(features); } + blockingSupported_ = isSupported; } @@ -84,8 +90,12 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) { } -void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const std::string& avatarPath) { +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->onVCardRequested.connect(boost::bind(boost::ref(onVCardUpdateRequested), jid)); 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; @@ -188,4 +198,10 @@ void Roster::applyOnAllItems(const RosterItemOperation& operation) { } +void Roster::addFilter(RosterFilter* filter) { + filters_.push_back(filter); + filterAll(); + onFilterAdded(filter); +} + void Roster::removeFilter(RosterFilter* filter) { for (unsigned int i = 0; i < filters_.size(); i++) { @@ -196,8 +212,9 @@ void Roster::removeFilter(RosterFilter *filter) { } filterAll(); + onFilterRemoved(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_) { @@ -205,5 +222,5 @@ void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) { } 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..9956cb9 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -1,4 +1,4 @@ /* - * 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,14 +8,16 @@ #include <string> -#include "Swiften/JID/JID.h" -#include "Swift/Controllers/Roster/RosterItemOperation.h" -#include "Swift/Controllers/Roster/RosterFilter.h" -#include <Swift/Controllers/Roster/ContactRosterItem.h> - #include <vector> #include <map> -#include "Swiften/Base/boost_bsignals.h" + #include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h> +#include <Swift/Controllers/Roster/RosterFilter.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + namespace Swift { @@ -29,5 +31,5 @@ class Roster { ~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); @@ -37,13 +39,16 @@ class Roster { 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); 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; + boost::signal<void (JID&)> onVCardUpdateRequested; + boost::signal<void (RosterFilter* filter)> onFilterAdded; + boost::signal<void (RosterFilter* filter)> onFilterRemoved; GroupRosterItem* getGroup(const std::string& groupName); - void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features); + void setBlockingSupported(bool isSupported); private: @@ -59,4 +64,5 @@ class Roster { bool fullJIDMapping_; bool sortByStatus_; + bool blockingSupported_; }; diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index 170bfd0..99be985 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -1,48 +1,55 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Roster/RosterController.h" +#include <Swift/Controllers/Roster/RosterController.h> #include <boost/bind.hpp> #include <boost/smart_ptr/make_shared.hpp> -#include "Swiften/JID/JID.h" -#include "Swiften/Base/foreach.h" -#include "Swift/Controllers/UIInterfaces/MainWindow.h" -#include "Swift/Controllers/UIInterfaces/MainWindowFactory.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/Roster/GetRosterRequest.h" -#include "Swiften/Roster/SetRosterRequest.h" -#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Presence/SubscriptionManager.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/SetPresence.h" -#include "Swift/Controllers/Roster/AppearOffline.h" -#include "Swift/Controllers/Roster/SetAvatar.h" -#include "Swift/Controllers/Roster/SetName.h" -#include "Swift/Controllers/Roster/OfflineRosterFilter.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swiften/Roster/XMPPRoster.h" -#include "Swiften/Roster/XMPPRosterItem.h" -#include "Swift/Controllers/UIEvents/AddContactUIEvent.h" -#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" -#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" -#include <Swiften/FileTransfer/FileTransferManager.h> -#include <Swiften/Client/NickManager.h> -#include <Swift/Controllers/Intl.h> +#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> -#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Base/Path.h> +#include <Swiften/Client/ClientBlockListManager.h> +#include <Swiften/Client/NickManager.h> +#include <Swiften/Client/NickResolver.h> #include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/JID/JID.h> #include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/SubscriptionManager.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Roster/GetRosterRequest.h> +#include <Swiften/Roster/SetRosterRequest.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/XMPPRosterItem.h> + +#include <Swift/Controllers/Intl.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/OfflineRosterFilter.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/RosterVCardProvider.h> +#include <Swift/Controllers/Roster/ItemOperations/AppearOffline.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> +#include <Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h> +#include <Swift/Controllers/Roster/ItemOperations/SetBlockingState.h> +#include <Swift/Controllers/Roster/ItemOperations/SetName.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetVCard.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/UIEvents/AddContactUIEvent.h> +#include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h> +#include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> +#include <Swift/Controllers/UIInterfaces/MainWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> namespace Swift { @@ -51,9 +58,8 @@ 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, VCardManager* vcardManager) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), vcardManager_(vcardManager), avatarManager_(avatarManager), nickManager_(nickManager), nickResolver_(nickResolver), presenceOracle_(presenceOracle), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) { assert(fileTransferOverview); iqRouter_ = iqRouter; - presenceOracle_ = presenceOracle; subscriptionManager_ = subscriptionManager; eventController_ = eventController; @@ -61,4 +67,5 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata expandiness_ = new RosterGroupExpandinessPersister(roster_, settings); mainWindow_->setRosterModel(roster_); + rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithoutResource); changeStatusConnection_ = mainWindow_->onChangeStatusRequest.connect(boost::bind(&RosterController::handleChangeStatusRequest, this, _1, _2)); @@ -71,7 +78,9 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handleIncomingPresence, this, _1)); uiEventConnection_ = uiEventStream->onUIEvent.connect(boost::bind(&RosterController::handleUIEvent, this, _1)); - avatarManager_ = avatarManager; + + vcardManager_->onOwnVCardChanged.connect(boost::bind(&RosterController::handleOwnVCardChanged, this, _1)); avatarManager_->onAvatarChanged.connect(boost::bind(&RosterController::handleAvatarChanged, this, _1)); - mainWindow_->setMyAvatarPath(avatarManager_->getAvatarPath(myJID_).string()); + presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handlePresenceChanged, this, _1)); + mainWindow_->setMyAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare()))); nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); @@ -84,4 +93,9 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata handleShowOfflineToggled(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); + + ownContact_ = boost::make_shared<ContactRosterItem>(myJID_.toBare(), myJID_.toBare(), nickManager_->getOwnNick(), static_cast<GroupRosterItem*>(0)); + ownContact_->setVCard(vcardManager_->getVCard(myJID_.toBare())); + ownContact_->setAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare()))); + mainWindow_->setMyContactRosterItem(ownContact_); } @@ -98,6 +112,6 @@ RosterController::~RosterController() { delete mainWindow_; } + delete rosterVCardProvider_; delete roster_; - } @@ -125,9 +139,9 @@ void RosterController::handleOnJIDAdded(const JID& 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); @@ -164,5 +178,5 @@ 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)); } } @@ -184,4 +198,20 @@ void RosterController::handleSettingChanged(const std::string& settingPath) { } +void RosterController::handleBlockingStateChanged() { + if (clientBlockListManager_->getBlockList()->getState() == BlockList::Available) { + foreach(const JID& jid, clientBlockListManager_->getBlockList()->getItems()) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); + } + } +} + +void RosterController::handleBlockingItemAdded(const JID& jid) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); +} + +void RosterController::handleBlockingItemRemoved(const JID& jid) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsUnblocked)); +} + void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { if (boost::shared_ptr<AddContactUIEvent> addContactEvent = boost::dynamic_pointer_cast<AddContactUIEvent>(event)) { @@ -253,8 +283,36 @@ void RosterController::updateItem(const XMPPRosterItem& item) { SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); - request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); + request->onResponse.connect(boost::bind(&RosterController::handleRosterItemUpdated, this, _1, roster)); request->send(); } +void RosterController::initBlockingCommand() { + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->requestBlockList(); + + 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)); + roster_->setBlockingSupported(true); + if (blockList->getState() == BlockList::Available) { + foreach(const JID& jid, blockList->getItems()) { + roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); + } + } +} + +void RosterController::handleRosterItemUpdated(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload) { + if (!!error) { + handleRosterSetError(error, rosterPayload); + } + boost::shared_ptr<BlockList> blockList = clientBlockListManager_->getBlockList(); + std::vector<RosterItemPayload> items = rosterPayload->getItems(); + if (blockList->getState() == BlockList::Available && items.size() > 0) { + std::vector<JID> jids = blockList->getItems(); + if (std::find(jids.begin(), jids.end(), items[0].getJID()) != jids.end()) { + roster_->applyOnItems(SetBlockingState(items[0].getJID(), ContactRosterItem::IsBlocked)); + } + } +} + void RosterController::handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload) { if (!error) { @@ -299,9 +357,23 @@ void RosterController::handleSubscriptionRequestDeclined(SubscriptionRequestEven } +void RosterController::handleOwnVCardChanged(VCard::ref vcard) { + ownContact_->setVCard(vcard); + mainWindow_->setMyContactRosterItem(ownContact_); +} + 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)); + ownContact_->setAvatarPath(pathToString(path)); + mainWindow_->setMyContactRosterItem(ownContact_); + } +} + +void RosterController::handlePresenceChanged(Presence::ref presence) { + if (presence->getFrom().equals(myJID_, JID::WithResource)) { + ownContact_->applyPresence(std::string(), presence); + mainWindow_->setMyContactRosterItem(ownContact_); } } @@ -319,8 +391,11 @@ void RosterController::handleOnCapsChanged(const JID& jid) { if (info) { std::set<ContactRosterItem::Feature> features; - if (info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { + if (FileTransferManager::isSupportedBy(info)) { features.insert(ContactRosterItem::FileTransferFeature); } - roster_->setAvailableFeatures(jid, features); + if (info->hasFeature(DiscoInfo::WhiteboardFeature)) { + features.insert(ContactRosterItem::WhiteboardFeature); + } + roster_->applyOnItems(SetAvailableFeatures(jid, features)); } } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 5e40124..3338d7f 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -7,18 +7,21 @@ #pragma once -#include "Swiften/JID/JID.h" #include <string> #include <set> -#include "Swiften/Elements/Presence.h" -#include "Swiften/Elements/ErrorPayload.h" -#include "Swiften/Elements/RosterPayload.h" -#include "Swiften/Avatars/AvatarManager.h" -#include "Swift/Controllers/UIEvents/UIEvent.h" -#include "RosterGroupExpandinessPersister.h" -#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" -#include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Elements/RosterPayload.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/VCards/VCardManager.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swift/Controllers/Roster/RosterGroupExpandinessPersister.h> + namespace Swift { class IQRouter; @@ -40,14 +43,19 @@ namespace Swift { class EntityCapsProvider; class FileTransferManager; + class ClientBlockListManager; + class RosterVCardProvider; 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, VCardManager* vcardManager); ~RosterController(); void showRosterWindow(); - MainWindow* getWindow() {return mainWindow_;}; + void setJID(const JID& jid) { myJID_ = jid; } + MainWindow* getWindow() {return mainWindow_;} boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signal<void ()> onSignOutRequest; + void handleOwnVCardChanged(VCard::ref vcard); void handleAvatarChanged(const JID& jid); + void handlePresenceChanged(Presence::ref presence); void setEnabled(bool enabled); @@ -58,4 +66,6 @@ namespace Swift { void updateItem(const XMPPRosterItem&); + void initBlockingCommand(); + private: void handleOnJIDAdded(const JID &jid); @@ -71,4 +81,5 @@ namespace Swift { void handleSubscriptionRequestDeclined(SubscriptionRequestEvent* event); void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleRosterItemUpdated(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void applyAllPresenceTo(const JID& jid); @@ -77,4 +88,8 @@ namespace Swift { void handleSettingChanged(const std::string& settingPath); + void handleBlockingStateChanged(); + void handleBlockingItemAdded(const JID& jid); + void handleBlockingItemRemoved(const JID& jid); + JID myJID_; XMPPRoster* xmppRoster_; @@ -83,4 +98,5 @@ namespace Swift { Roster* roster_; OfflineRosterFilter* offlineFilter_; + VCardManager* vcardManager_; AvatarManager* avatarManager_; NickManager* nickManager_; @@ -95,5 +111,11 @@ namespace Swift { EntityCapsProvider* entityCapsManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; + RosterVCardProvider* rosterVCardProvider_; + boost::shared_ptr<ContactRosterItem> ownContact_; + 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_; diff --git a/Swift/Controllers/Roster/RosterVCardProvider.cpp b/Swift/Controllers/Roster/RosterVCardProvider.cpp new file mode 100644 index 0000000..954ac68 --- /dev/null +++ b/Swift/Controllers/Roster/RosterVCardProvider.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/Roster/RosterVCardProvider.h> + +#include <Swiften/VCards/VCardManager.h> + +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/ItemOperations/SetVCard.h> + +namespace Swift { + +RosterVCardProvider::RosterVCardProvider(Roster* roster, VCardManager* vcardManager, JID::CompareType compareType) : roster_(roster), vcardManager_(vcardManager), compareType_(compareType) { + vcardUpdateRequestedConnection = roster_->onVCardUpdateRequested.connect(boost::bind(&RosterVCardProvider::handleVCardUpdateRequested, this, _1)); + vcardChangedConnection = vcardManager_->onVCardChanged.connect(boost::bind(&RosterVCardProvider::handleVCardChanged, this, _1, _2)); +} + +RosterVCardProvider::~RosterVCardProvider() { +} + +void RosterVCardProvider::handleVCardUpdateRequested(const JID& jid) { + VCard::ref vcard = vcardManager_->getVCardAndRequestWhenNeeded(jid); + if (vcard) { + handleVCardChanged(jid, vcard); + } +} + +void RosterVCardProvider::handleVCardChanged(const JID& jid, VCard::ref vcard) { + roster_->applyOnItem(SetVCard(jid, vcard, compareType_), jid); +} + + +} diff --git a/Swift/Controllers/Roster/RosterVCardProvider.h b/Swift/Controllers/Roster/RosterVCardProvider.h new file mode 100644 index 0000000..da41298 --- /dev/null +++ b/Swift/Controllers/Roster/RosterVCardProvider.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 <boost/signals/connection.hpp> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + +class Roster; +class VCardManager; + +class RosterVCardProvider { + public: + RosterVCardProvider(Roster* roster, VCardManager* vcardManager, JID::CompareType compareType); + ~RosterVCardProvider(); + + private: + void handleVCardUpdateRequested(const JID& jid); + void handleVCardChanged(const JID& jid, VCard::ref vcard); + + private: + Roster* roster_; + VCardManager* vcardManager_; + JID::CompareType compareType_; + boost::bsignals::scoped_connection vcardUpdateRequestedConnection; + boost::bsignals::scoped_connection vcardChangedConnection; +}; + +} 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 @@ -10,4 +10,5 @@ #include <cassert> #include <algorithm> +#include <boost/numeric/conversion/cast.hpp> #include <Swiften/Base/foreach.h> @@ -133,11 +134,11 @@ void TableRoster::handleUpdateTimerTick() { 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])); } 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 @@ -13,4 +13,5 @@ #include <Swiften/JID/JID.h> #include <Swiften/Elements/StatusShow.h> +#include <boost/filesystem/path.hpp> namespace Swift { @@ -22,5 +23,5 @@ namespace Swift { 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; @@ -28,5 +29,5 @@ namespace Swift { JID jid; StatusShow::Type status; - std::string avatarPath; + boost::filesystem::path avatarPath; }; diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index fbee894..392a426 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -9,33 +9,38 @@ #include <cppunit/extensions/TestFactoryRegistry.h> +#include <Swiften/Avatars/NullAvatarManager.h> +#include <Swiften/Base/Algorithm.h> #include <Swiften/Base/foreach.h> -#include "Swift/Controllers/Roster/RosterController.h" -#include "Swift/Controllers/UnitTest/MockMainWindowFactory.h" -// #include "Swiften/Elements/Payload.h" -// #include "Swiften/Elements/RosterItemPayload.h" -// #include "Swiften/Elements/RosterPayload.h" -#include "Swiften/Queries/DummyIQChannel.h" -#include "Swiften/Client/DummyStanzaChannel.h" -#include "Swiften/Queries/IQRouter.h" -#include "Swiften/Roster/XMPPRosterImpl.h" -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Settings/DummySettingsProvider.h" -#include "Swiften/Avatars/NullAvatarManager.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swiften/Presence/PresenceOracle.h" -#include "Swiften/Presence/SubscriptionManager.h" -#include "Swiften/Client/NickResolver.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" -#include "Swiften/MUC/MUCRegistry.h" +#include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/DummyNickManager.h> -#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/Crypto/CryptoProvider.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/Disco/CapsProvider.h> -#include <Swiften/Jingle/JingleSessionManager.h> -#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> -#include <Swiften/Base/Algorithm.h> +#include <Swiften/Disco/EntityCapsManager.h> #include <Swiften/EventLoop/DummyEventLoop.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/MUC/MUCRegistry.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Presence/SubscriptionManager.h> +#include <Swiften/Queries/DummyIQChannel.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Roster/XMPPRosterImpl.h> +#include <Swiften/VCards/VCardMemoryStorage.h> +// #include <Swiften/Elements/Payload.h> +// #include <Swiften/Elements/RosterItemPayload.h> +// #include <Swiften/Elements/RosterPayload.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/RosterController.h> +#include <Swift/Controllers/Settings/DummySettingsProvider.h> +#include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UnitTest/MockMainWindowFactory.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> using namespace Swift; @@ -83,13 +88,23 @@ 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_); + crypto_ = PlatformCryptoProvider::create(); + vcardStorage_ = new VCardMemoryStorage(crypto_); + vcardManager_ = new VCardManager(jid_, router_, vcardStorage_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_, vcardManager_); mainWindow_ = mainWindowFactory_->last; - }; + } void tearDown() { delete rosterController_; + delete vcardManager_; + delete vcardStorage_; + delete crypto_; + delete clientBlockListManager_; + delete ftOverview_; delete ftManager_; delete jingleSessionManager_; - + delete entityCapsManager_; + delete capsProvider_; delete nickManager_; delete nickResolver_; @@ -106,5 +121,5 @@ class RosterControllerTest : public CppUnit::TestFixture { delete settings_; delete xmppRoster_; - }; + } GroupRosterItem* groupChild(size_t i) { @@ -134,5 +149,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item2->getStatusText()); - }; + } void testHighestPresence() { @@ -154,5 +169,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); - }; + } void testNotHighestPresence() { @@ -174,5 +189,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); - }; + } void testUnavailablePresence() { @@ -216,5 +231,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(StatusShow::None, item->getStatusShow()); CPPUNIT_ASSERT_EQUAL(lowPresenceOffline->getStatus(), item->getStatusText()); - }; + } void testAdd() { @@ -226,5 +241,5 @@ 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() { @@ -243,5 +258,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); - }; + } void testReceiveRename() { @@ -257,5 +272,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("NewName"), groupChild(0)->getChildren()[0]->getDisplayName()); - }; + } void testReceiveRegroup() { @@ -283,5 +298,5 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(std::string("new name"), groupChild(0)->getChildren()[0]->getDisplayName()); CPPUNIT_ASSERT_EQUAL(std::string("Best Group"), groupChild(0)->getDisplayName()); - }; + } void testSendRename() { @@ -338,4 +353,8 @@ class RosterControllerTest : public CppUnit::TestFixture { FileTransferManager* ftManager_; FileTransferOverview* ftOverview_; + ClientBlockListManager* clientBlockListManager_; + CryptoProvider* crypto_; + VCardStorage* vcardStorage_; + VCardManager* vcardManager_; }; diff --git a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp index 0c3e769..7d86aaf 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp @@ -7,9 +7,10 @@ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> + #include <boost/shared_ptr.hpp> -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/Roster/SetPresence.h" +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> using namespace Swift; diff --git a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp index e433b50..f8e6a63 100644 --- a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp @@ -7,4 +7,6 @@ #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 << ")"; @@ -14,4 +16,5 @@ std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> + #include <boost/shared_ptr.hpp> #include <boost/variant.hpp> @@ -20,5 +23,5 @@ std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { #include <Swift/Controllers/Roster/Roster.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> -#include <Swift/Controllers/Roster/SetPresence.h> +#include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> using namespace Swift; diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 70085a6..5ebbdd3 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -20,5 +20,6 @@ if env["SCONS_STAGE"] == "build" : myenv = env.Clone() myenv.BuildVersion("BuildVersion.h", project = "swift") - myenv.MergeFlags(env["BOOST_FLAGS"]) + myenv.UseFlags(env["SWIFTEN_FLAGS"]) + myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"]) myenv.StaticLibrary("SwiftControllers", [ "Chat/ChatController.cpp", @@ -28,6 +29,9 @@ if env["SCONS_STAGE"] == "build" : "Chat/MUCSearchController.cpp", "Chat/UserSearchController.cpp", + "Chat/ChatMessageParser.cpp", + "ContactSuggester.cpp", "MainController.cpp", "ProfileController.cpp", + "ShowProfileController.cpp", "ContactEditController.cpp", "FileTransfer/FileTransferController.cpp", @@ -40,4 +44,5 @@ if env["SCONS_STAGE"] == "build" : "Roster/RosterItem.cpp", "Roster/Roster.cpp", + "Roster/RosterVCardProvider.cpp", "Roster/TableRoster.cpp", "EventWindowController.cpp", @@ -45,13 +50,18 @@ if env["SCONS_STAGE"] == "build" : "SystemTrayController.cpp", "XMLConsoleController.cpp", + "HistoryViewController.cpp", + "HistoryController.cpp", "FileTransferListController.cpp", + "BlockListController.cpp", "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", "AdHocManager.cpp", + "AdHocController.cpp", "XMPPEvents/EventController.cpp", "UIEvents/UIEvent.cpp", "UIInterfaces/XMLConsoleWidget.cpp", "UIInterfaces/ChatListWindow.cpp", + "UIInterfaces/HighlightEditorWindow.cpp", "PreviousStatusStore.cpp", "ProfileSettingsProvider.cpp", @@ -71,5 +81,15 @@ if env["SCONS_STAGE"] == "build" : "XMPPURIController.cpp", "ChatMessageSummarizer.cpp", - "SettingConstants.cpp" + "SettingConstants.cpp", + "WhiteboardManager.cpp", + "StatusCache.cpp", + "HighlightAction.cpp", + "HighlightEditorController.cpp", + "HighlightManager.cpp", + "HighlightRule.cpp", + "Highlighter.cpp", + "ContactsFromXMPPRoster.cpp", + "ContactProvider.cpp", + "Contact.cpp" ]) @@ -83,6 +103,9 @@ if env["SCONS_STAGE"] == "build" : 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"), + File("UnitTest/ContactSuggesterTest.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 @@ -20,3 +20,9 @@ const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOfflin 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 @@ -23,4 +23,10 @@ namespace Swift { 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 @@ -18,23 +18,23 @@ class DummySettingsProvider : public SettingsProvider { 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& ) {} 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 @@ -27,5 +27,5 @@ class SettingsProviderHierachyTest : public CppUnit::TestFixture { public: - SettingsProviderHierachyTest() : setting1("somekey", 42) {}; + SettingsProviderHierachyTest() : setting1("somekey", 42) {} void setUp() { diff --git a/Swift/Controllers/ShowProfileController.cpp b/Swift/Controllers/ShowProfileController.cpp new file mode 100644 index 0000000..d13e5a1 --- /dev/null +++ b/Swift/Controllers/ShowProfileController.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "ShowProfileController.h" + +#include <boost/bind.hpp> +#include <boost/date_time/posix_time/posix_time.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(), boost::posix_time::minutes(5)); + 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 @@ -13,12 +13,16 @@ #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)); @@ -26,7 +30,14 @@ SoundEventController::SoundEventController(EventController* eventController, Sou } -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 @@ -11,20 +11,24 @@ #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 @@ -7,10 +7,12 @@ #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 @@ -9,4 +9,6 @@ #include <boost/smart_ptr/make_shared.hpp> +#include <Swiften/Elements/Idle.h> + namespace Swift { @@ -22,4 +24,5 @@ boost::shared_ptr<Presence> StatusTracker::getNextPresence() { presence->setShow(StatusShow::Away); presence->setStatus(queuedPresence_->getStatus()); + presence->addPayload(boost::make_shared<Idle>(isAutoAwaySince_)); } else { presence = queuedPresence_; @@ -36,9 +39,10 @@ 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 @@ -11,4 +11,6 @@ #include "Swiften/Elements/Presence.h" +#include <boost/date_time/posix_time/posix_time_types.hpp> + namespace Swift { @@ -18,9 +20,10 @@ namespace Swift { 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 @@ -7,4 +7,5 @@ #include <Swift/Controllers/StatusUtil.h> +#include <cassert> #include <Swift/Controllers/Intl.h> @@ -20,4 +21,5 @@ std::string statusShowTypeToFriendlyName(StatusShow::Type type) { 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,4 +1,4 @@ /* - * 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,10 +13,10 @@ #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 { @@ -48,5 +48,5 @@ 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); @@ -70,5 +70,5 @@ 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,4 +1,4 @@ /* - * 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. @@ -16,7 +16,9 @@ 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; @@ -35,4 +37,5 @@ namespace Swift { 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..2c242cd 100644 --- a/Swift/Controllers/Storages/CertificateFileStorage.cpp +++ b/Swift/Controllers/Storages/CertificateFileStorage.cpp @@ -1,4 +1,4 @@ /* - * 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. @@ -9,13 +9,14 @@ #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) { } @@ -24,6 +25,6 @@ bool CertificateFileStorage::hasCertificate(Certificate::ref certificate) const if (boost::filesystem::exists(certificatePath)) { ByteArray data; - readByteArrayFromFile(data, certificatePath.string()); - Certificate::ref storedCertificate = certificateFactory->createCertificateFromDER(data); + readByteArrayFromFile(data, certificatePath); + Certificate::ref storedCertificate(certificateFactory->createCertificateFromDER(data)); if (storedCertificate && storedCertificate->toDER() == certificate->toDER()) { return true; @@ -51,10 +52,10 @@ 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,4 +1,4 @@ /* - * 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,8 +13,9 @@ 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; @@ -27,4 +28,5 @@ namespace Swift { 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,4 +1,4 @@ /* - * 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,13 @@ 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); } @@ -25,4 +26,5 @@ namespace Swift { boost::filesystem::path basePath; CertificateFactory* certificateFactory; + CryptoProvider* crypto; }; } diff --git a/Swift/Controllers/Storages/CertificateStorageTrustChecker.h b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h index 40838dd..df15575 100644 --- a/Swift/Controllers/Storages/CertificateStorageTrustChecker.h +++ b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h @@ -19,16 +19,16 @@ namespace Swift { } - virtual bool isCertificateTrusted(Certificate::ref certificate) { - lastCertificate = certificate; - return storage->hasCertificate(certificate); + virtual bool isCertificateTrusted(const std::vector<Certificate::ref>& certificateChain) { + lastCertificateChain = std::vector<Certificate::ref>(certificateChain.begin(), certificateChain.end()); + return certificateChain.empty() ? false : storage->hasCertificate(certificateChain[0]); } - Certificate::ref getLastCertificate() const { - return lastCertificate; + const std::vector<Certificate::ref>& getLastCertificateChain() const { + return lastCertificateChain; } private: CertificateStorage* storage; - Certificate::ref lastCertificate; + std::vector<Certificate::ref> lastCertificateChain; }; } diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp index 6447099..e1b681f 100644 --- a/Swift/Controllers/Storages/FileStorages.cpp +++ b/Swift/Controllers/Storages/FileStorages.cpp @@ -1,4 +1,4 @@ /* - * 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,13 +10,20 @@ #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"); +#else + historyStorage = NULL; +#endif } @@ -26,4 +33,5 @@ FileStorages::~FileStorages() { delete capsStorage; delete vcardStorage; + delete historyStorage; } @@ -44,3 +52,11 @@ RosterStorage* FileStorages::getRosterStorage() const { } +HistoryStorage* FileStorages::getHistoryStorage() const { +#ifdef SWIFT_EXPERIMENTAL_HISTORY + return historyStorage; +#else + return NULL; +#endif +} + } diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h index 28df314..1e914b9 100644 --- a/Swift/Controllers/Storages/FileStorages.h +++ b/Swift/Controllers/Storages/FileStorages.h @@ -1,4 +1,4 @@ /* - * 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. @@ -9,5 +9,5 @@ #include <boost/filesystem/path.hpp> -#include "Swiften/Client/Storages.h" +#include <Swiften/Client/Storages.h> namespace Swift { @@ -16,5 +16,7 @@ namespace Swift { class CapsFileStorage; class RosterFileStorage; + class HistoryStorage; class JID; + class CryptoProvider; /** @@ -37,5 +39,5 @@ namespace Swift { * 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(); @@ -44,4 +46,5 @@ namespace Swift { virtual CapsStorage* getCapsStorage() const; virtual RosterStorage* getRosterStorage() const; + virtual HistoryStorage* getHistoryStorage() const; private: @@ -50,4 +53,5 @@ namespace Swift { CapsFileStorage* capsStorage; RosterFileStorage* rosterStorage; + HistoryStorage* historyStorage; }; } 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,4 +1,4 @@ /* - * 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,14 +11,17 @@ 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/MemoryStoragesFactory.h b/Swift/Controllers/Storages/MemoryStoragesFactory.h index 0dea349..e18f9fc 100644 --- a/Swift/Controllers/Storages/MemoryStoragesFactory.h +++ b/Swift/Controllers/Storages/MemoryStoragesFactory.h @@ -1,4 +1,4 @@ /* - * 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,15 @@ namespace Swift { class JID; + class CryptoProvider; class MemoryStoragesFactory : public StoragesFactory { public: - MemoryStoragesFactory() {} + MemoryStoragesFactory(CryptoProvider* cryptoProvider) : cryptoProvider_(cryptoProvider) {} virtual Storages* createStorages(const JID& profile) const { - return new MemoryStorages(); + return new MemoryStorages(cryptoProvider_); } + private: + CryptoProvider* cryptoProvider_; }; } diff --git a/Swift/Controllers/Storages/VCardFileStorage.cpp b/Swift/Controllers/Storages/VCardFileStorage.cpp index d799a90..876d642 100644 --- a/Swift/Controllers/Storages/VCardFileStorage.cpp +++ b/Swift/Controllers/Storages/VCardFileStorage.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -14,6 +14,7 @@ #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" @@ -26,5 +27,5 @@ 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)) { @@ -58,5 +59,15 @@ boost::shared_ptr<VCard> VCardFileStorage::getVCard(const JID& jid) const { } +boost::posix_time::ptime VCardFileStorage::getVCardWriteTime(const JID& jid) const { + if (vcardWriteTimes.find(jid) == vcardWriteTimes.end()) { + return boost::posix_time::ptime(); + } + else { + return vcardWriteTimes.at(jid); + } +} + void VCardFileStorage::setVCard(const JID& jid, VCard::ref v) { + vcardWriteTimes[jid] = boost::posix_time::second_clock::universal_time(); VCardPersister().savePayload(v, getVCardPath(jid)); getAndUpdatePhotoHash(jid, v); @@ -67,5 +78,5 @@ boost::filesystem::path VCardFileStorage::getVCardPath(const JID& jid) const { std::string file(jid.toString()); String::replaceAll(file, '/', "%2f"); - return boost::filesystem::path(vcardsPath / (file + ".xml")); + return boost::filesystem::path(vcardsPath / stringToPath(file + ".xml")); } catch (const boost::filesystem::filesystem_error& e) { @@ -89,5 +100,5 @@ std::string VCardFileStorage::getAndUpdatePhotoHash(const JID& jid, VCard::ref v 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)); diff --git a/Swift/Controllers/Storages/VCardFileStorage.h b/Swift/Controllers/Storages/VCardFileStorage.h index ba422f4..8ba001e 100644 --- a/Swift/Controllers/Storages/VCardFileStorage.h +++ b/Swift/Controllers/Storages/VCardFileStorage.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -15,9 +15,12 @@ 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 boost::posix_time::ptime getVCardWriteTime(const JID& jid) const; virtual void setVCard(const JID& jid, VCard::ref v); @@ -32,7 +35,9 @@ namespace Swift { private: boost::filesystem::path vcardsPath; + CryptoProvider* crypto; boost::filesystem::path cacheFile; typedef std::map<JID, std::string> PhotoHashMap; mutable PhotoHashMap photoHashes; + std::map<JID, boost::posix_time::ptime> vcardWriteTimes; }; } 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 @@ -12,5 +12,5 @@ namespace Swift { class SystemTray { public: - virtual ~SystemTray(){}; + virtual ~SystemTray(){} virtual void setUnreadMessages(bool some) = 0; virtual void setStatusType(StatusShow::Type type) = 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 @@ -11,5 +11,5 @@ 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/AcceptWhiteboardSessionUIEvent.h b/Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h new file mode 100644 index 0000000..93cad03 --- /dev/null +++ b/Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class AcceptWhiteboardSessionUIEvent : public UIEvent { + typedef boost::shared_ptr<AcceptWhiteboardSessionUIEvent> ref; + public: + AcceptWhiteboardSessionUIEvent(const JID& jid) : jid_(jid) {} + const JID& getContact() const {return jid_;} + private: + JID jid_; + }; +} 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 @@ -15,13 +15,13 @@ 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 { 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 @@ -15,5 +15,5 @@ namespace Swift { class AddMUCBookmarkUIEvent : public UIEvent { public: - AddMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {}; + AddMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {} const MUCBookmark& getBookmark() { return bookmark; } diff --git a/Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h b/Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h new file mode 100644 index 0000000..f5c3b0e --- /dev/null +++ b/Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class CancelWhiteboardSessionUIEvent : public UIEvent { + typedef boost::shared_ptr<CancelWhiteboardSessionUIEvent> ref; + public: + CancelWhiteboardSessionUIEvent(const JID& jid) : jid_(jid) {} + const JID& getContact() const {return jid_;} + private: + JID jid_; + }; +} 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 @@ -15,8 +15,8 @@ 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: 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 @@ -19,5 +19,5 @@ namespace Swift { 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_;} @@ -25,4 +25,7 @@ namespace Swift { 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_; @@ -31,4 +34,6 @@ namespace Swift { 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 @@ -15,5 +15,5 @@ namespace Swift { class RemoveMUCBookmarkUIEvent : public UIEvent { public: - RemoveMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {}; + RemoveMUCBookmarkUIEvent(const MUCBookmark& bookmark) : bookmark(bookmark) {} const MUCBookmark& getBookmark() { return bookmark; } 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 @@ -14,7 +14,7 @@ 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 @@ -14,5 +14,5 @@ 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: diff --git a/Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h new file mode 100644 index 0000000..2b1fcea --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIEvents/UIEvent.h" + +namespace Swift { + class RequestAdHocWithJIDUIEvent : public UIEvent { + public: + RequestAdHocWithJIDUIEvent(const JID& jid, const std::string& node) : jid_(jid), node_(node) {} + JID getJID() const { return jid_; } + std::string getNode() const { return node_; } + private: + JID jid_; + std::string node_; + }; +} 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 @@ -16,9 +16,9 @@ namespace Swift { 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: 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 @@ -14,5 +14,5 @@ namespace Swift { class RequestChatUIEvent : public UIEvent { public: - RequestChatUIEvent(const JID& contact) : contact_(contact) {}; + RequestChatUIEvent(const JID& contact) : contact_(contact) {} JID getContact() {return contact_;} private: 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/RequestHistoryUIEvent.h b/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h new file mode 100644 index 0000000..025e91f --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestHistoryUIEvent.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2012 Catalin Badea + * 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 RequestHistoryUIEvent : public UIEvent { + }; +} diff --git a/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h new file mode 100644 index 0000000..58f45d1 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <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; + + enum ImpromptuMode { + Impromptu, + NotImpromptu + }; + + RequestInviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite, ImpromptuMode impromptu) : room_(room), invite_(JIDsToInvite) { + isImpromptu_ = impromptu == Impromptu; + } + + const JID& getRoom() const { + return room_; + } + + const std::vector<JID> getInvites() const { + return invite_; + } + + bool isImpromptu() const { + return isImpromptu_; + } + + private: + JID room_; + std::vector<JID> invite_; + bool isImpromptu_; + }; +} diff --git a/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h new file mode 100644 index 0000000..5c44da7 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * 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 RequestWhiteboardUIEvent : public UIEvent { + public: + 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 new file mode 100644 index 0000000..bb72d9b --- /dev/null +++ b/Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * 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 ShowWhiteboardUIEvent : public UIEvent { + public: + 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 @@ -19,5 +19,5 @@ 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..07319c2 100644 --- a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,8 +7,12 @@ #pragma once +#include <Swiften/Base/boost_bsignals.h> + namespace Swift { class AdHocCommandWindow { public: - virtual ~AdHocCommandWindow() {}; + virtual ~AdHocCommandWindow() {} + virtual void setOnline(bool /*online*/) {} + boost::signal<void ()> onClosing; }; } diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h index ae77180..eeefa2d 100644 --- a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -11,12 +11,9 @@ namespace Swift { +class AdHocCommandWindow; class AdHocCommandWindowFactory { public: virtual ~AdHocCommandWindowFactory() {} - /** - * The UI should deal with the lifetime of this window (i.e. DeleteOnClose), - * so the result isn't returned. - */ - virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0; + virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0; }; } diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h new file mode 100644 index 0000000..f8a4c59 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.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. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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 hide() = 0; + + virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs) = 0; + virtual void setBusy(bool isBusy) = 0; + virtual void setError(const std::string&) = 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 d047f8c..5a7b527 100644 --- a/Swift/Controllers/UIInterfaces/ChatListWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,9 +8,11 @@ #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> @@ -20,11 +22,29 @@ namespace Swift { class Chat { public: - Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, const std::string& nick = "") - : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), unreadCount(unreadCount), avatarPath(avatarPath) {} + Chat() : statusType(StatusShow::None), isMUC(false), unreadCount(0), isPrivateMessage(false) {} + Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, bool isPrivateMessage = false, const std::string& nick = "", const boost::optional<std::string> password = boost::optional<std::string>()) + : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), password(password), unreadCount(unreadCount), avatarPath(avatarPath), isPrivateMessage(isPrivateMessage) {} /** Assume that nicks and other transient features aren't important for equality */ bool operator==(const Chat& other) const { + if (impromptuJIDs.empty()) { return jid.toBare() == other.jid.toBare() && isMUC == other.isMUC; - }; + } else { /* compare the chat occupant lists */ + typedef std::map<std::string, JID> JIDMap; + foreach (const JIDMap::value_type& jid, impromptuJIDs) { + bool found = false; + foreach (const JIDMap::value_type& otherJID, other.impromptuJIDs) { + if (jid.second.toBare() == otherJID.second.toBare()) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + } void setUnreadCount(int unread) { unreadCount = unread; @@ -36,4 +56,16 @@ namespace Swift { 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; @@ -42,6 +74,9 @@ namespace Swift { bool isMUC; std::string nick; + boost::optional<std::string> password; int unreadCount; boost::filesystem::path avatarPath; + std::map<std::string, JID> impromptuJIDs; + bool isPrivateMessage; }; virtual ~ChatListWindow(); @@ -49,11 +84,15 @@ namespace Swift { virtual void setBookmarksEnabled(bool enabled) = 0; virtual void addMUCBookmark(const MUCBookmark& bookmark) = 0; + virtual void addWhiteboardSession(const ChatListWindow::Chat& chat) = 0; + virtual void removeWhiteboardSession(const JID& jid) = 0; virtual void removeMUCBookmark(const MUCBookmark& bookmark) = 0; virtual void setRecents(const std::list<Chat>& recents) = 0; virtual void setUnreadCount(int unread) = 0; virtual void clearBookmarks() = 0; + virtual void setOnline(bool isOnline) = 0; boost::signal<void (const MUCBookmark&)> onMUCBookmarkActivated; boost::signal<void (const Chat&)> onRecentActivated; + boost::signal<void (const JID&)> onWhiteboardActivated; boost::signal<void ()> onClearRecentsRequested; }; diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index b5b1604..6b2799b 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,15 +7,19 @@ #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 <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/HighlightManager.h> @@ -28,29 +32,87 @@ namespace Swift { class ContactRosterItem; class FileTransferController; + 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 ReceiptState {ReceiptRequested, ReceiptReceived, ReceiptFailed}; 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 }; + enum MUCType { StandardMUC, ImpromptuMUC }; + enum TimestampBehaviour { KeepTimestamp, UpdateTimestamp }; + 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 @@ -58,5 +120,8 @@ namespace Swift { 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; // message receipts @@ -66,10 +131,12 @@ namespace Swift { virtual void setName(const std::string& name) = 0; virtual void show() = 0; + virtual bool isVisible() const = 0; virtual void activate() = 0; virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0; virtual void setSecurityLabelsEnabled(bool enabled) = 0; virtual void setCorrectionEnabled(Tristate enabled) = 0; + virtual void setFileTransferEnabled(Tristate enabled) = 0; virtual void setUnreadMessageCount(int count) = 0; - virtual void convertToMUC() = 0; + virtual void convertToMUC(MUCType mucType) = 0; // virtual TreeWidget *getTreeWidget() = 0; virtual void setSecurityLabelsError() = 0; @@ -78,5 +145,5 @@ namespace Swift { 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, const TimestampBehaviour timestampBehaviour) = 0; virtual void setAckState(const std::string& id, AckState state) = 0; virtual void flash() = 0; @@ -84,14 +151,24 @@ namespace Swift { 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; + virtual void showBookmarkWindow(const MUCBookmark& bookmark) = 0; + + /** + * A handle that uniquely identities an alert message. + */ + typedef int AlertID; /** * Set an alert on the window. * @param alertText Description of alert (required). * @param buttonText Button text to use (optional, no button is shown if empty). + * @return A handle to the alert message. */ - virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0; + virtual AlertID addAlert(const std::string& alertText) = 0; /** * Removes an alert. + * @param id An alert ID previously returned from setAlert */ - virtual void cancelAlert() = 0; + virtual void removeAlert(const AlertID id) = 0; /** @@ -116,7 +193,8 @@ namespace Swift { boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; boost::signal<void (const std::string&)> onChangeSubjectRequest; + boost::signal<void ()> onBookmarkRequest; boost::signal<void (Form::ref)> onConfigureRequest; boost::signal<void ()> onDestroyRequest; - boost::signal<void (const JID&, const std::string& /*reason*/)> onInvitePersonToThisMUCRequest; + boost::signal<void (const std::vector<JID>&)> onInviteToChat; boost::signal<void ()> onConfigurationFormCancelled; boost::signal<void ()> onGetAffiliationsRequest; @@ -130,4 +208,13 @@ namespace Swift { boost::signal<void (std::string /* id */, std::string /* path */)> onFileTransferAccept; boost::signal<void (std::string /* path */)> onSendFileRequest; + + //Whiteboard related + 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 @@ -15,5 +15,5 @@ namespace Swift { 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 @@ -12,5 +12,5 @@ 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 @@ -20,5 +20,5 @@ namespace Swift { } - 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 @@ -12,5 +12,5 @@ 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/HighlightEditorWindow.cpp b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp new file mode 100644 index 0000000..f90903b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> + +namespace Swift { + +HighlightEditorWindow::HighlightEditorWindow() +{ +} + +HighlightEditorWindow::~HighlightEditorWindow() +{ +} + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h new file mode 100644 index 0000000..83ae959 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindow.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <vector> +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/Contact.h> + +namespace Swift { + +class HighlightManager; + +class HighlightEditorWindow { +public: + HighlightEditorWindow(); + virtual ~HighlightEditorWindow(); + virtual void show() = 0; + virtual void setHighlightManager(HighlightManager *highlightManager) = 0; + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) = 0; + +public: + boost::signal<void (const std::string&)> onContactSuggestionsRequested; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h new file mode 100644 index 0000000..e0aaaef --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class HighlightEditorWindow; + + class HighlightEditorWindowFactory { + public : + virtual ~HighlightEditorWindowFactory() {} + + virtual HighlightEditorWindow* createHighlightEditorWindow() = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/HistoryWindow.h b/Swift/Controllers/UIInterfaces/HistoryWindow.h new file mode 100644 index 0000000..6d50f4b --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HistoryWindow.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/Roster/Roster.h> + +namespace Swift { + class HistoryWindow { + public: + virtual ~HistoryWindow() {} + + virtual void activate() = 0; + virtual void setRosterModel(Roster*) = 0; + virtual void addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop) = 0; + virtual void resetConversationView() = 0; + virtual void resetConversationViewTopInsertPoint() = 0; // this is a temporary fix used in adding messages at the top + virtual void setDate(const boost::gregorian::date& date) = 0; + + virtual std::string getSearchBoxText() = 0; + virtual boost::gregorian::date getLastVisibleDate() = 0; + + boost::signal<void (RosterItem*)> onSelectedContactChanged; + boost::signal<void (const std::string&)> onReturnPressed; + boost::signal<void (const boost::gregorian::date&)> onScrollReachedTop; + boost::signal<void (const boost::gregorian::date&)> onScrollReachedBottom; + boost::signal<void ()> onPreviousButtonClicked; + boost::signal<void ()> onNextButtonClicked; + boost::signal<void (const boost::gregorian::date&)> onCalendarClicked; + }; +} diff --git a/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h new file mode 100644 index 0000000..807fec5 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/HistoryWindowFactory.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2012 Catalin Badea + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/HistoryWindow.h> + +namespace Swift { + class UIEventStream; + class HistoryWindowFactory { + public: + virtual ~HistoryWindowFactory() {} + virtual HistoryWindow* createHistoryWindow(UIEventStream* eventStream) = 0; + }; +} 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 @@ -16,5 +16,5 @@ namespace Swift { class JoinMUCWindow { public: - virtual ~JoinMUCWindow() {}; + virtual ~JoinMUCWindow() {} virtual void setNick(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 @@ -13,5 +13,5 @@ namespace Swift { 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 bbbbe35..e27c385 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindow.h +++ b/Swift/Controllers/UIInterfaces/LoginWindow.h @@ -13,4 +13,5 @@ #include <Swiften/TLS/Certificate.h> #include <Swiften/TLS/CertificateWithKey.h> +#include <Swiften/Client/ClientOptions.h> namespace Swift { @@ -18,5 +19,5 @@ namespace Swift { class LoginWindow { public: - virtual ~LoginWindow() {}; + virtual ~LoginWindow() {} virtual void selectUser(const std::string&) = 0; virtual void morphInto(MainWindow *mainWindow) = 0; @@ -25,12 +26,12 @@ namespace Swift { virtual void setMessage(const std::string&) = 0; virtual void setIsLoggingIn(bool loggingIn) = 0; - virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate) = 0; + virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options) = 0; virtual void removeAvailableAccount(const std::string& jid) = 0; /** The certificate is what is used for login, the certificatePath is used for remembering paths to populate the loginwindow with*/ - boost::signal<void (const std::string&, const std::string&, const std::string& /*CertificatePath*/, CertificateWithKey::ref /* clientCertificate */, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; + boost::signal<void (const std::string&, const std::string&, const std::string& /*CertificatePath*/, CertificateWithKey::ref /* clientCertificate */, const ClientOptions& /*options*/, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; virtual void setLoginAutomatically(bool loginAutomatically) = 0; virtual void quit() = 0; /** Blocking request whether a cert should be permanently trusted.*/ - virtual bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref) = 0; + virtual bool askUserToTrustCertificatePermanently(const std::string& message, const std::vector<Certificate::ref>& certificateChain) = 0; boost::signal<void ()> onCancelLoginRequest; 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 @@ -17,5 +17,5 @@ namespace Swift { class LoginWindowFactory { public: - virtual ~LoginWindowFactory() {}; + virtual ~LoginWindowFactory() {} /** 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 @@ -20,5 +20,5 @@ namespace Swift { class MUCSearchWindow { public: - virtual ~MUCSearchWindow() {}; + virtual ~MUCSearchWindow() {} virtual void clearList() = 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 @@ -13,5 +13,5 @@ namespace Swift { 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 93584e7..82750bf 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -8,11 +8,14 @@ #include <string> -#include "Swiften/JID/JID.h" -#include "Swiften/Elements/StatusShow.h" -#include "Swiften/Elements/DiscoItems.h" -#include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/TLS/Certificate.h> +#include <Swiften/Base/boost_bsignals.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> + namespace Swift { class Roster; @@ -21,5 +24,5 @@ namespace Swift { public: MainWindow(bool candelete = true) : canDelete_(candelete) {} - virtual ~MainWindow() {}; + virtual ~MainWindow() {} bool canDelete() const { @@ -32,11 +35,16 @@ namespace Swift { virtual void setMyStatusText(const std::string& status) = 0; virtual void setMyStatusType(StatusShow::Type type) = 0; + virtual void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) = 0; /** 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; boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signal<void ()> onSignOutRequest; + boost::signal<void ()> onShowCertificateRequest; private: 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 @@ -16,5 +16,5 @@ 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 @@ -13,8 +13,11 @@ 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; @@ -22,4 +25,5 @@ namespace Swift { virtual void setProcessing(bool b) = 0; virtual void setError(const std::string&) = 0; + virtual void setEditable(bool b) = 0; virtual void show() = 0; @@ -27,4 +31,5 @@ namespace Swift { 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 @@ -12,5 +12,5 @@ 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 cf89dab..54fa7ce 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,4 +9,5 @@ #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HistoryWindowFactory.h> #include <Swift/Controllers/UIInterfaces/EventWindowFactory.h> #include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h> @@ -20,4 +21,7 @@ #include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> #include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/BlockListEditorWidgetFactory.h> namespace Swift { @@ -25,4 +29,5 @@ namespace Swift { public ChatListWindowFactory, public ChatWindowFactory, + public HistoryWindowFactory, public EventWindowFactory, public LoginWindowFactory, @@ -35,5 +40,8 @@ namespace Swift { public ContactEditWindowFactory, public AdHocCommandWindowFactory, - public FileTransferListWidgetFactory { + public FileTransferListWidgetFactory, + public WhiteboardWindowFactory, + public HighlightEditorWindowFactory, + public BlockListEditorWidgetFactory { public: virtual ~UIFactory() {} diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h index a3d69d6..9a095aa 100644 --- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h +++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,11 +7,12 @@ #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 { @@ -19,5 +20,5 @@ namespace Swift { class UserSearchWindow { public: - enum Type {AddContact, ChatToContact}; + enum Type {AddContact, ChatToContact, InviteToChat}; virtual ~UserSearchWindow() {} @@ -32,4 +33,14 @@ namespace Swift { 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::ref>& 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::ref>& contacts) = 0; + virtual void addContacts(const std::vector<Contact::ref>& contacts) = 0; + virtual void setCanSupplyDescription(bool allowed) = 0; + virtual void show() = 0; @@ -37,4 +48,7 @@ namespace Swift { 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; + boost::signal<void (const std::vector<JID>&)> onJIDAddRequested; }; } 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 @@ -15,5 +15,5 @@ namespace Swift { 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/WhiteboardWindow.h b/Swift/Controllers/UIInterfaces/WhiteboardWindow.h new file mode 100644 index 0000000..a4a9ef0 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/WhiteboardWindow.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swiften/Base/boost_bsignals.h" + +#include <string> + +namespace Swift { + class WhiteboardSession; + class WhiteboardElement; + + class WhiteboardWindow { + public: + virtual ~WhiteboardWindow() {} + + virtual void show() = 0; + virtual void setSession(boost::shared_ptr<WhiteboardSession> session) = 0; + virtual void activateWindow() = 0; + virtual void setName(const std::string& name) = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h b/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h new file mode 100644 index 0000000..2be0f9c --- /dev/null +++ b/Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + class WhiteboardSession; + class WhiteboardWindow; + + class WhiteboardWindowFactory { + public : + 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 @@ -13,5 +13,5 @@ namespace Swift { 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 @@ -27,5 +27,5 @@ class ChatMessageSummarizerTest : public CppUnit::TestFixture { public: - ChatMessageSummarizerTest() {}; + ChatMessageSummarizerTest() {} void setUp() { @@ -73,5 +73,5 @@ public: 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)); } @@ -83,5 +83,5 @@ public: 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)); } diff --git a/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp b/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp new file mode 100644 index 0000000..222491b --- /dev/null +++ b/Swift/Controllers/UnitTest/ContactSuggesterTest.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2014 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 <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include "Swift/Controllers/ContactSuggester.h" + +using namespace Swift; + +class ContactSuggesterTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ContactSuggesterTest); + CPPUNIT_TEST(equalityTest); + CPPUNIT_TEST(lexicographicalSortTest); + CPPUNIT_TEST(sortTest); + CPPUNIT_TEST_SUITE_END(); + +public: + + std::vector<std::string> wordList() { + const std::string words[] = { + "abc", + "ab", + "bc", + "d" + }; + + return std::vector<std::string>(words, words+sizeof(words)/sizeof(*words)); + } + + std::vector<StatusShow::Type> statusList() { + StatusShow::Type types[] = { + StatusShow::Online, + StatusShow::Away, + StatusShow::FFC, + StatusShow::XA, + StatusShow::DND, + StatusShow::None + }; + + return std::vector<StatusShow::Type>(types, types+sizeof(types)/sizeof(*types)); + } + + std::vector<Contact::ref> contactList() { + std::vector<Contact::ref> contacts; + std::vector<std::string> words = wordList(); + std::vector<StatusShow::Type> statuses = statusList(); + foreach (const std::string& name, words) { + foreach (const std::string& jid, words) { + foreach (const StatusShow::Type& status, statuses) { + contacts.push_back(boost::make_shared<Contact>(name, jid, status, "")); + } + } + } + return contacts; + } + + /* a = a */ + bool isReflexive(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + if (!comparitor(a, a)) { + return false; + } + } + return true; + } + + /* a = b -> b = a */ + bool isSymmetric(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + foreach (const Contact::ref& b, contacts) { + if (comparitor(a, b)) { + if (!comparitor(b, a)) { + return false; + } + } + } + } + return true; + } + + /* a = b && b = c -> a = c */ + bool isTransitive(const boost::function<bool (const Contact::ref&, const Contact::ref&)>& comparitor) { + std::vector<Contact::ref> contacts = contactList(); + foreach (const Contact::ref& a, contacts) { + foreach (const Contact::ref& b, contacts) { + foreach (const Contact::ref& c, contacts) { + if (comparitor(a, b) && comparitor(b, c)) { + if (!comparitor(a, c)) { + return false; + } + } + } + } + } + return true; + } + + void equalityTest() { + CPPUNIT_ASSERT(isReflexive(Contact::equalityPredicate)); + CPPUNIT_ASSERT(isSymmetric(Contact::equalityPredicate)); + CPPUNIT_ASSERT(isTransitive(Contact::equalityPredicate)); + } + + void lexicographicalSortTest() { + CPPUNIT_ASSERT(isTransitive(Contact::lexicographicalSortPredicate)); + } + + void sortTest() { + std::vector<std::string> words = wordList(); + foreach (const std::string& word, words) { + CPPUNIT_ASSERT(isTransitive(boost::bind(Contact::sortPredicate, _1, _2, word))); + } + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ContactSuggesterTest); diff --git a/Swift/Controllers/UnitTest/HighlightRuleTest.cpp b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp new file mode 100644 index 0000000..c988b8d --- /dev/null +++ b/Swift/Controllers/UnitTest/HighlightRuleTest.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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), false); + 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), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abckeyword1xyz", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("KEYword1", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abc KEYword1 xyz", "from", "nick", HighlightRule::ChatMessage), false); + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("abcKEYword1xyz", "from", "nick", HighlightRule::ChatMessage), false); + + CPPUNIT_ASSERT_EQUAL(keywordChatRule->isMatch("keyword2", "from", "nick", HighlightRule::ChatMessage), false); + } + + 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), false); + + 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), false); + } + + 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), false); + 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), false); + 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), false); + CPPUNIT_ASSERT_EQUAL(senderKeywordNickCaseChatRule->isMatch("xkeyword1", "xsender1", "nick", HighlightRule::ChatMessage), false); + + 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), false); + 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), false); + 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 e0e18a0..774bdd9 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,52 +7,82 @@ #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*/, const TimestampBehaviour /*timestampBehaviour*/) {} // 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 bool isVisible() const { return true; } + 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(MUCType /*mucType*/) {} + 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) { roster_ = roster; } + Roster* getRosterModel() { return roster_; } + virtual void setTabComplete(TabComplete*) {} + + void setAckState(const std::string& /*id*/, AckState /*state*/) {} + virtual void flash() {} + virtual AlertID addAlert(const std::string& /*alertText*/) { return 0; } + virtual void removeAlert(const AlertID /*id*/) {} virtual void setCorrectionEnabled(Tristate /*enabled*/) {} + virtual void setFileTransferEnabled(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 void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) {} - virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {}; + virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {} + + virtual void setBlockingState(BlockingState) {} + virtual void setCanInitiateImpromptuChats(bool /*supportsImpromptu*/) {} + virtual void showBookmarkWindow(const MUCBookmark& /*bookmark*/) {} + + 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_; @@ -61,4 +91,5 @@ namespace Swift { bool labelsEnabled_; SecurityLabelsCatalog::Item label_; + Roster* roster_; }; } diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h index f773062..b56f352 100644 --- a/Swift/Controllers/UnitTest/MockMainWindow.h +++ b/Swift/Controllers/UnitTest/MockMainWindow.h @@ -7,5 +7,5 @@ #pragma once -#include "Swift/Controllers/UIInterfaces/MainWindow.h" +#include <Swift/Controllers/UIInterfaces/MainWindow.h> namespace Swift { @@ -13,14 +13,18 @@ namespace Swift { 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 setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> /*contact*/) {} + 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 @@ -14,12 +14,12 @@ 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/UnitTest/PreviousStatusStoreTest.cpp b/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp index 9489e5b..0ccbc4a 100644 --- a/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp +++ b/Swift/Controllers/UnitTest/PreviousStatusStoreTest.cpp @@ -31,5 +31,5 @@ public: void tearDown() { - + delete store_; } diff --git a/Swift/Controllers/WhiteboardManager.cpp b/Swift/Controllers/WhiteboardManager.cpp new file mode 100644 index 0000000..d8d89eb --- /dev/null +++ b/Swift/Controllers/WhiteboardManager.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/Controllers/WhiteboardManager.h> + +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h> +#include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h> +#include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> +#include "Swiften/Client/NickResolver.h" +#include <Swiften/Client/StanzaChannel.h> +#include <Swiften/Whiteboard/WhiteboardSessionManager.h> + +namespace Swift { + typedef std::pair<JID, WhiteboardWindow*> JIDWhiteboardWindowPair; + + WhiteboardManager::WhiteboardManager(WhiteboardWindowFactory* whiteboardWindowFactory, UIEventStream* uiEventStream, NickResolver* nickResolver, WhiteboardSessionManager* whiteboardSessionManager) : whiteboardWindowFactory_(whiteboardWindowFactory), uiEventStream_(uiEventStream), nickResolver_(nickResolver), whiteboardSessionManager_(whiteboardSessionManager) { + +#ifdef SWIFT_EXPERIMENTAL_WB + whiteboardSessionManager_->onSessionRequest.connect(boost::bind(&WhiteboardManager::handleIncomingSession, this, _1)); +#endif + uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&WhiteboardManager::handleUIEvent, this, _1)); + } + + WhiteboardManager::~WhiteboardManager() { + foreach (JIDWhiteboardWindowPair whiteboardWindowPair, whiteboardWindows_) { + delete whiteboardWindowPair.second; + } + } + + WhiteboardWindow* WhiteboardManager::createNewWhiteboardWindow(const JID& contact, WhiteboardSession::ref session) { + WhiteboardWindow *window = whiteboardWindowFactory_->createWhiteboardWindow(session); + window->setName(nickResolver_->jidToNick(contact)); + whiteboardWindows_[contact.toBare()] = window; + return window; + } + + WhiteboardWindow* WhiteboardManager::findWhiteboardWindow(const JID& contact) { + if (whiteboardWindows_.find(contact.toBare()) == whiteboardWindows_.end()) { + return NULL; + } + return whiteboardWindows_[contact.toBare()]; + } + + void WhiteboardManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<RequestWhiteboardUIEvent> requestWhiteboardEvent = boost::dynamic_pointer_cast<RequestWhiteboardUIEvent>(event); + if (requestWhiteboardEvent) { + requestSession(requestWhiteboardEvent->getContact()); + } + boost::shared_ptr<AcceptWhiteboardSessionUIEvent> sessionAcceptEvent = boost::dynamic_pointer_cast<AcceptWhiteboardSessionUIEvent>(event); + if (sessionAcceptEvent) { + acceptSession(sessionAcceptEvent->getContact()); + } + boost::shared_ptr<CancelWhiteboardSessionUIEvent> sessionCancelEvent = boost::dynamic_pointer_cast<CancelWhiteboardSessionUIEvent>(event); + if (sessionCancelEvent) { + cancelSession(sessionCancelEvent->getContact()); + } + boost::shared_ptr<ShowWhiteboardUIEvent> showWindowEvent = boost::dynamic_pointer_cast<ShowWhiteboardUIEvent>(event); + if (showWindowEvent) { + WhiteboardWindow* window = findWhiteboardWindow(showWindowEvent->getContact()); + if (window != NULL) { + window->activateWindow(); + } + } + } + + void WhiteboardManager::acceptSession(const JID& from) { + IncomingWhiteboardSession::ref session = boost::dynamic_pointer_cast<IncomingWhiteboardSession>(whiteboardSessionManager_->getSession(from)); + WhiteboardWindow* window = findWhiteboardWindow(from); + if (session && window) { + session->accept(); + window->show(); + } + } + + void WhiteboardManager::requestSession(const JID& contact) { + WhiteboardSession::ref session = whiteboardSessionManager_->requestSession(contact); + session->onSessionTerminated.connect(boost::bind(&WhiteboardManager::handleSessionTerminate, this, _1)); + session->onRequestAccepted.connect(boost::bind(&WhiteboardManager::handleSessionAccept, this, _1)); + session->onRequestRejected.connect(boost::bind(&WhiteboardManager::handleRequestReject, this, _1)); + + WhiteboardWindow* window = findWhiteboardWindow(contact); + if (window == NULL) { + createNewWhiteboardWindow(contact, session); + } else { + window->setSession(session); + } + onSessionRequest(session->getTo(), true); + } + + void WhiteboardManager::cancelSession(const JID& from) { + WhiteboardSession::ref session = whiteboardSessionManager_->getSession(from); + if (session) { + session->cancel(); + } + } + + void WhiteboardManager::handleIncomingSession(IncomingWhiteboardSession::ref session) { + session->onSessionTerminated.connect(boost::bind(&WhiteboardManager::handleSessionTerminate, this, _1)); + session->onRequestAccepted.connect(boost::bind(&WhiteboardManager::handleSessionAccept, this, _1)); + + WhiteboardWindow* window = findWhiteboardWindow(session->getTo()); + if (window == NULL) { + createNewWhiteboardWindow(session->getTo(), session); + } else { + window->setSession(session); + } + + onSessionRequest(session->getTo(), false); + } + + void WhiteboardManager::handleSessionTerminate(const JID& contact) { + onSessionTerminate(contact); + } + + void WhiteboardManager::handleSessionCancel(const JID& contact) { + onSessionTerminate(contact); + } + + void WhiteboardManager::handleSessionAccept(const JID& contact) { + WhiteboardWindow* window = findWhiteboardWindow(contact); + if (window != NULL) { + window->show(); + } + onRequestAccepted(contact); + } + + void WhiteboardManager::handleRequestReject(const JID& contact) { + onRequestRejected(contact); + } + +} diff --git a/Swift/Controllers/WhiteboardManager.h b/Swift/Controllers/WhiteboardManager.h new file mode 100644 index 0000000..2f5767b --- /dev/null +++ b/Swift/Controllers/WhiteboardManager.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#pragma once + +#include <map> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h> +#include <Swiften/Whiteboard/WhiteboardSession.h> +#include <Swiften/Whiteboard/IncomingWhiteboardSession.h> + +namespace Swift { + class WhiteboardSessionManager; + class NickResolver; + + class WhiteboardManager { + public: + WhiteboardManager(WhiteboardWindowFactory* whiteboardWindowFactory, UIEventStream* uiEventStream, NickResolver* nickResolver, WhiteboardSessionManager* whiteboardSessionManager); + ~WhiteboardManager(); + + WhiteboardWindow* createNewWhiteboardWindow(const JID& contact, WhiteboardSession::ref session); + + public: + boost::signal< void (const JID&, bool senderIsSelf)> onSessionRequest; + boost::signal< void (const JID&)> onSessionTerminate; + boost::signal< void (const JID&)> onRequestAccepted; + boost::signal< void (const JID&)> onRequestRejected; + + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleSessionTerminate(const JID& contact); + void handleSessionCancel(const JID& contact); + void handleSessionAccept(const JID& contact); + void handleRequestReject(const JID& contact); + void handleIncomingSession(IncomingWhiteboardSession::ref session); + void acceptSession(const JID& from); + void requestSession(const JID& contact); + void cancelSession(const JID& from); + WhiteboardWindow* findWhiteboardWindow(const JID& contact); + + private: + std::map<JID, WhiteboardWindow*> whiteboardWindows_; + WhiteboardWindowFactory* whiteboardWindowFactory_; + UIEventStream* uiEventStream_; + NickResolver* nickResolver_; + boost::bsignals::scoped_connection uiEventConnection_; + WhiteboardSessionManager* whiteboardSessionManager_; + }; +} 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 @@ -19,8 +19,8 @@ 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: diff --git a/Swift/Controllers/XMPPEvents/EventController.cpp b/Swift/Controllers/XMPPEvents/EventController.cpp index 9841923..8cb259b 100644 --- a/Swift/Controllers/XMPPEvents/EventController.cpp +++ b/Swift/Controllers/XMPPEvents/EventController.cpp @@ -8,4 +8,5 @@ #include <boost/bind.hpp> +#include <boost/numeric/conversion/cast.hpp> #include <algorithm> @@ -35,5 +36,6 @@ void EventController::handleIncomingEvent(boost::shared_ptr<StanzaEvent> sourceE /* If it's a duplicate subscription request, remove the previous request first */ if (subscriptionEvent) { - foreach(boost::shared_ptr<StanzaEvent> existingEvent, events_) { + EventList existingEvents(events_); + foreach(boost::shared_ptr<StanzaEvent> existingEvent, existingEvents) { boost::shared_ptr<SubscriptionRequestEvent> existingSubscriptionEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(existingEvent); if (existingSubscriptionEvent) { @@ -48,5 +50,5 @@ void EventController::handleIncomingEvent(boost::shared_ptr<StanzaEvent> sourceE 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()) { @@ -59,5 +61,5 @@ 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())); } 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 @@ -14,5 +14,5 @@ namespace Swift { 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_; } @@ -21,4 +21,5 @@ namespace Swift { const std::string& getPassword() const { return password_; } bool getDirect() const { return direct_; } + bool getImpromptu() const { return impromptu_; } private: @@ -28,4 +29,5 @@ namespace Swift { 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 @@ -18,5 +18,5 @@ namespace Swift { 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 @@ -15,11 +15,11 @@ 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: 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 @@ -19,8 +19,8 @@ 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; @@ -28,10 +28,10 @@ namespace Swift { onAccept(); conclude(); - }; + } void decline() { onDecline(); conclude(); - }; + } void defer() { diff --git a/Swift/Packaging/Debian/changelog.debian-unstable b/Swift/Packaging/Debian/changelog.debian-unstable index c8b4861..a609535 100644 --- a/Swift/Packaging/Debian/changelog.debian-unstable +++ b/Swift/Packaging/Debian/changelog.debian-unstable @@ -1,3 +1,30 @@ -swift-im (2.0~alpha+dev629-1) unstable; urgency=low +swift-im (2.0+dev6-1) unstable; urgency=low + + * Update from upstream. + * Don't conflict with libswiften2 overrides. Closes: 726289 + * Don't conflict with swift-im-dbg overrides. Closes: 726290 + * Don't conflict with libswiften-dev overrides. Closes: 726291 + + + -- Kevin Smith <kevin@kismith.co.uk> Mon, 14 Oct 2013 16:42:12 -0000 + +swift-im (2.0+dev5-1) unstable; urgency=low + + * Update from upstream. + * Compiles with boost >= 1.50.0; will build on sid again. Closes: #713725 + * Include dependencies for libswiften so that other packages + can depend on it. Closes: #714902 + + -- Kevin Smith <kevin@kismith.co.uk> Mon, 12 Aug 2013 16:18:09 -0000 + +swift-im (2.0~beta1+dev47-1) unstable; urgency=low + + * Update from upstream. + * Rename /usr/bin/swift binary to not conflict with + other package. Closes: #674504 + + -- Kevin Smith <kevin@kismith.co.uk> Tue, 12 Jun 2012 19:25:12 -0000 + +swift-im (2.0~beta1+dev26-1) unstable; urgency=low * Initial release. Closes: #631002 diff --git a/Swift/Packaging/Debian/debian/binary-overrides/libswiften-dev b/Swift/Packaging/Debian/debian/binary-overrides/libswiften-dev new file mode 100644 index 0000000..93497a5 --- /dev/null +++ b/Swift/Packaging/Debian/debian/binary-overrides/libswiften-dev @@ -0,0 +1,2 @@ +# The upstream numbering scheme is that 2.0~beta1 is prior to 2.0 but that 2.0~beta1+dev10 is ten commits later than 2.0~beta1, so in this case the warning is redundant. +libswiften-dev binary: rc-version-greater-than-expected-version diff --git a/Swift/Packaging/Debian/debian/binary-overrides/libswiften2 b/Swift/Packaging/Debian/debian/binary-overrides/libswiften2 new file mode 100644 index 0000000..ae16e89 --- /dev/null +++ b/Swift/Packaging/Debian/debian/binary-overrides/libswiften2 @@ -0,0 +1,2 @@ +# The upstream numbering scheme is that 2.0~beta1 is prior to 2.0 but that 2.0~beta1+dev10 is ten commits later than 2.0~beta1, so in this case the warning is redundant. +libswiften2 binary: rc-version-greater-than-expected-version diff --git a/Swift/Packaging/Debian/debian/binary-overrides/swift-im b/Swift/Packaging/Debian/debian/binary-overrides/swift-im new file mode 100644 index 0000000..11ca729 --- /dev/null +++ b/Swift/Packaging/Debian/debian/binary-overrides/swift-im @@ -0,0 +1,2 @@ +# The upstream numbering scheme is that 2.0~beta1 is prior to 2.0 but that 2.0~beta1+dev10 is ten commits later than 2.0~beta1, so in this case the warning is redundant. +swift-im binary: rc-version-greater-than-expected-version diff --git a/Swift/Packaging/Debian/debian/binary-overrides/swift-im-dbg b/Swift/Packaging/Debian/debian/binary-overrides/swift-im-dbg new file mode 100644 index 0000000..d993122 --- /dev/null +++ b/Swift/Packaging/Debian/debian/binary-overrides/swift-im-dbg @@ -0,0 +1,2 @@ +# The upstream numbering scheme is that 2.0~beta1 is prior to 2.0 but that 2.0~beta1+dev10 is ten commits later than 2.0~beta1, so in this case the warning is redundant. +swift-im-dbg binary: rc-version-greater-than-expected-version diff --git a/Swift/Packaging/Debian/debian/control.in b/Swift/Packaging/Debian/debian/control.in index 51a3cf3..fa734f6 100644 --- a/Swift/Packaging/Debian/debian/control.in +++ b/Swift/Packaging/Debian/debian/control.in @@ -2,8 +2,8 @@ Source: swift-im Section: net Priority: optional -Maintainer: Swift Package Maintainers <packages@swift.im> -Uploaders: Remko Tronçon <dev@el-tramo.be>, Kevin Smith <kevin@kismith.co.uk>, Olly Betts <olly@survex.com> -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% -Standards-Version: 3.9.3 +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, 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 @@ -12,5 +12,5 @@ Homepage: http://swift.im Package: libswiften%SWIFTEN_SOVERSION% Architecture: any -Section: net +Section: libs Priority: optional Depends: ${shlibs:Depends}, ${misc:Depends} @@ -24,5 +24,5 @@ Architecture: any Section: libdevel Priority: optional -Depends: libswiften%SWIFTEN_SOVERSION% (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} +Depends: libswiften%SWIFTEN_SOVERSION% (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, libboost-dev (>= 1.34.1), libboost-filesystem-dev (>= 1.34.1), libboost-regex-dev (>= 1.34.1), libboost-signals-dev (>= 1.34.1), libboost-thread-dev (>= 1.34.1), libboost-date-time-dev (>= 1.34.1) Description: XMPP library (development files) Swiften is a robust, high-quality, standards-compliant, cross-platform, diff --git a/Swift/Packaging/Debian/debian/libswiften-dev.install b/Swift/Packaging/Debian/debian/libswiften-dev.install index 545c6d9..273c86b 100644 --- a/Swift/Packaging/Debian/debian/libswiften-dev.install +++ b/Swift/Packaging/Debian/debian/libswiften-dev.install @@ -2,2 +2,3 @@ usr/lib/libSwiften.so usr/include usr/bin/swiften-config +usr/share/lintian/overrides/libswiften-dev diff --git a/Swift/Packaging/Debian/debian/libswiften.install b/Swift/Packaging/Debian/debian/libswiften.install index 2603436..878fe4f 100644 --- a/Swift/Packaging/Debian/debian/libswiften.install +++ b/Swift/Packaging/Debian/debian/libswiften.install @@ -1 +1,2 @@ usr/lib/libSwiften.so.* +usr/share/lintian/overrides/libswiften2 diff --git a/Swift/Packaging/Debian/debian/rules b/Swift/Packaging/Debian/debian/rules index d05e34a..0d42744 100755 --- a/Swift/Packaging/Debian/debian/rules +++ b/Swift/Packaging/Debian/debian/rules @@ -4,5 +4,5 @@ export PYTHONDONTWRITEBYTECODE=1 -SCONS_FLAGS=V=1 optimize=1 debug=1 allow_warnings=1 swiften_dll=1 qt=/usr/share/qt4 docbook_xsl=/usr/share/xml/docbook/stylesheet/docbook-xsl docbook_xml=/usr/share/xml/docbook/schema/dtd/4.5 +SCONS_FLAGS=V=1 optimize=1 debug=1 allow_warnings=1 swiften_dll=1 qt=/usr/share/qt4 docbook_xsl=/usr/share/xml/docbook/stylesheet/docbook-xsl docbook_xml=/usr/share/xml/docbook/schema/dtd/4.5 linkflags="$(shell dpkg-buildflags --get LDFLAGS)" ccflags="$(shell dpkg-buildflags --get CPPFLAGS) $(shell dpkg-buildflags --get CFLAGS)" clean: @@ -24,4 +24,6 @@ install: build dh_installdirs scons $(SCONS_FLAGS) $(SCONS_EXTRA_FLAGS) SWIFT_INSTALLDIR=$(CURDIR)/debian/tmp/usr SWIFTEN_INSTALLDIR=$(CURDIR)/debian/tmp/usr $(CURDIR)/debian/tmp + mkdir -p $(CURDIR)/debian/tmp/usr/share/lintian/overrides + cp debian/binary-overrides/* $(CURDIR)/debian/tmp/usr/share/lintian/overrides/ binary-indep: install diff --git a/Swift/Packaging/Debian/debian/source/lintian-overrides b/Swift/Packaging/Debian/debian/source/lintian-overrides new file mode 100644 index 0000000..b3f7bdd --- /dev/null +++ b/Swift/Packaging/Debian/debian/source/lintian-overrides @@ -0,0 +1,5 @@ +# The upstream numbering scheme is that 2.0~beta1 is prior to 2.0 but that 2.0~beta1+dev10 is ten commits later than 2.0~beta1, so in this case the warning is redundant. +swift-im source: rc-version-greater-than-expected-version +libswiften-dev source: rc-version-greater-than-expected-version +libswiften2 source: rc-version-greater-than-expected-version +swift-im-dbg source: rc-version-greater-than-expected-version diff --git a/Swift/Packaging/Debian/debian/swift-im-dbg.install b/Swift/Packaging/Debian/debian/swift-im-dbg.install new file mode 100644 index 0000000..e2db5d9 --- /dev/null +++ b/Swift/Packaging/Debian/debian/swift-im-dbg.install @@ -0,0 +1 @@ +usr/share/lintian/overrides/swift-im-dbg diff --git a/Swift/Packaging/Debian/debian/swift.1 b/Swift/Packaging/Debian/debian/swift-im.1 index 6626b40..b7262bf 100644 --- a/Swift/Packaging/Debian/debian/swift.1 +++ b/Swift/Packaging/Debian/debian/swift-im.1 @@ -2,7 +2,7 @@ .TH SWIFT "1" "June 2011" "Swift" "Swift Manual" .SH NAME -Swift \- swift +swift-im \- Swift .SH SYNOPSIS -.B swift +.B swift-im [\fIOPTIONS\fR]... .SH DESCRIPTION diff --git a/Swift/Packaging/Debian/debian/swift-im.install b/Swift/Packaging/Debian/debian/swift-im.install index 3144192..68e0f9e 100644 --- a/Swift/Packaging/Debian/debian/swift-im.install +++ b/Swift/Packaging/Debian/debian/swift-im.install @@ -1,2 +1,6 @@ -usr/bin/swift -usr/share +usr/bin/swift-im +usr/share/icons +usr/share/pixmaps +usr/share/swift +usr/share/applications +usr/share/lintian/overrides/swift-im diff --git a/Swift/Packaging/Debian/debian/swift-im.manpages b/Swift/Packaging/Debian/debian/swift-im.manpages index f3fb91e..287f11f 100644 --- a/Swift/Packaging/Debian/debian/swift-im.manpages +++ b/Swift/Packaging/Debian/debian/swift-im.manpages @@ -1 +1 @@ -debian/swift.1 +debian/swift-im.1 diff --git a/Swift/Packaging/Debian/debian/swift-im.menu b/Swift/Packaging/Debian/debian/swift-im.menu new file mode 100644 index 0000000..68b621d --- /dev/null +++ b/Swift/Packaging/Debian/debian/swift-im.menu @@ -0,0 +1,3 @@ +?package(swift-im):needs="X11" section="Applications/Network/Communication"\ + title="Swift IM" command="/usr/bin/swift-im"\ + icon="/usr/share/pixmaps/swift.xpm" diff --git a/Swift/Packaging/Debian/debian/swift.menu b/Swift/Packaging/Debian/debian/swift.menu deleted file mode 100644 index f9c2f26..0000000 --- a/Swift/Packaging/Debian/debian/swift.menu +++ /dev/null @@ -1,3 +0,0 @@ -?package(swift):needs="X11" section="Applications/Network/Communication"\ - title="Swift IM" command="/usr/bin/swift"\ - icon="/usr/share/pixmaps/swift.xpm" diff --git a/Swift/Packaging/Debian/package.sh b/Swift/Packaging/Debian/package.sh index 1edc4a0..8c7c89f 100755 --- a/Swift/Packaging/Debian/package.sh +++ b/Swift/Packaging/Debian/package.sh @@ -50,5 +50,9 @@ else rm -rf $DIRNAME/.git find $DIRNAME -name .gitignore | xargs rm -f - find $DIRNAME/3rdParty -type f | grep -v uuid | grep -v SConscript | xargs rm -f + if [ -z "$SWIFT_COPY_UUID" ]; then + 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 | grep -v miniupnp | grep -v natpmp || xargs rm -f + fi find $DIRNAME/3rdParty -depth -empty -type d -exec rmdir {} \; rm -rf $DIRNAME/3rdParty/SCons @@ -60,6 +64,8 @@ else # Fork local Boost UUID copy # FIXME: This shouldn't be necessary, but SCons isn't picking up the generated headers for compilation + if [ ! -z "$SWIFT_COPY_UUID" ]; then mkdir -p $DIRNAME/3rdParty/Boost/uuid/boost cp -r $DIRNAME/3rdParty/Boost/src/boost/uuid $DIRNAME/3rdParty/Boost/uuid/boost + fi # Create orig tarball @@ -87,5 +93,5 @@ cat $DIRNAME/debian/control.in | sed -e "s/%SWIFTEN_SOVERSION%/$SWIFTEN_SOVERSIO rm $DIRNAME/debian/control.in mv $DIRNAME/debian/libswiften.install $DIRNAME/debian/libswiften$SWIFTEN_SOVERSION.install -cat ../../../COPYING | awk '/--- END OF OpenSSL/,EOF' | tail -n +3 >> $DIRNAME/debian/copyright +cat ../../../COPYING.thirdparty | tail -n +3 >> $DIRNAME/debian/copyright # Build diff --git a/Swift/Packaging/MacOSX/Swift.dmg.gz b/Swift/Packaging/MacOSX/Swift.dmg.gz Binary files differindex 3aef6b1..0451101 100644 --- a/Swift/Packaging/MacOSX/Swift.dmg.gz +++ b/Swift/Packaging/MacOSX/Swift.dmg.gz diff --git a/Swift/Packaging/MacOSX/package.sh b/Swift/Packaging/MacOSX/package.sh index 884403b..d7a31d2 100755 --- a/Swift/Packaging/MacOSX/package.sh +++ b/Swift/Packaging/MacOSX/package.sh @@ -21,5 +21,5 @@ mkdir -p $WC_DIR hdiutil attach "$WC_DMG" -noautoopen -quiet -mountpoint "$WC_DIR" ditto -rsrc "$APP" "$WC_DIR"/`basename $APP` -$QTDIR/bin/macdeployqt "$WC_DIR"/`basename $APP` +$QTDIR/bin/macdeployqt "$WC_DIR"/`basename $APP` -no-strip hdiutil detach "$WC_DIR" -quiet -force rm -f $TARGET diff --git a/Swift/Packaging/WiX/Swift.wxs b/Swift/Packaging/WiX/Swift.wxs index 782f425..c14f23d 100644 --- a/Swift/Packaging/WiX/Swift.wxs +++ b/Swift/Packaging/WiX/Swift.wxs @@ -8,5 +8,5 @@ <Product Name='Swift' Id='*' UpgradeCode='D7F276D5-BA67-421E-817B-9E7AB4B7D2BF' Language='1033' Codepage='1252' Version='$(var.Version)' Manufacturer='Swift.im'> - <Package Id='*' Keywords='Installer' Description="Swift Installer" Comments="Swift is available under the GPL version 3" Manufacturer="Swift.im" InstallerVersion='300' Languages='1033' Compressed='yes' SummaryCodepage='1252'/> + <Package Id='*' Keywords='Installer' Description="Swift Installer" Comments="Swift is available under the GPL version 3" Manufacturer="Swift.im" InstallerVersion='300' Languages='1033' Compressed='yes' SummaryCodepage='1252' InstallScope="perMachine"/> <Media Id='1' Cabinet='Swift.cab' EmbedCab='yes'/> @@ -14,7 +14,10 @@ <Directory Id='TARGETDIR' Name='SourceDir'> + <!-- Disabling CRT merge module, because it's not working + <Merge Id="CRT" DiskId="1" Language="0" SourceFile="$(var.VCCRTFile)"/> + --> + <Directory Id='ProgramFilesFolder' Name='PFiles'> <!--<Directory Id='INSTALLDIR' Name='Swift'> - </Directory>--> </Directory> @@ -31,13 +34,9 @@ <Directory Id="DesktopFolder" Name="Desktop" /> - - <Merge Id="CRT" DiskId="1" Language="1033" SourceFile="$(var.VCCRTFile)"/> </Directory> <Feature Id='Core' Level='1' Title='Swift' Description='All necessary Swift files' Display='expand' ConfigurableDirectory='INSTALLDIR' AllowAdvertise='no' Absent='disallow'> <ComponentGroupRef Id='Files' /> - <!--<ComponentRef Id='Manual' />--> - <MergeRef Id="CRT"/> </Feature> @@ -53,4 +52,17 @@ <Icon Id="Swift.exe" SourceFile="Swift.exe" /> <Property Id="ARPPRODUCTICON" Value="Swift.exe"/> <!-- The icon in the "Programs" dialog --> + + <!-- + VC Redistributable + --> + + <Binary Id="CRTBinary" SourceFile="$(var.VCCRTFile)"/> + + <CustomAction Id="CRTAction" Impersonate="no" Return="asyncNoWait" Execute="deferred" BinaryKey="CRTBinary" ExeCommand="/passive"/> + + <InstallExecuteSequence> + <Custom Action='CRTAction' After='InstallInitialize'/> + </InstallExecuteSequence> + </Product> </Wix> diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp index bcd1585..5b03ac5 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.cpp +++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp @@ -13,4 +13,5 @@ #include "Swift/QtUI/ChatList/ChatListMUCItem.h" #include "Swift/QtUI/ChatList/ChatListRecentItem.h" +#include "Swift/QtUI/ChatList/ChatListWhiteboardItem.h" #include "Swift/QtUI/ChatList/ChatListGroupItem.h" @@ -40,4 +41,7 @@ QSize ChatListDelegate::sizeHint(const QStyleOptionViewItem& option, const QMode return groupDelegate_->sizeHint(option, index); } + else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) { + return common_.contactSizeHint(option, index, compact_); + } return QStyledItemDelegate::sizeHint(option, index); } @@ -66,4 +70,7 @@ void ChatListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& opti groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); } + else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) { + paintWhiteboard(painter, option, dynamic_cast<ChatListWhiteboardItem*>(item)); + } else { QStyledItemDelegate::paint(painter, option, index); @@ -114,5 +121,21 @@ void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem //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 { + QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); + QString avatarPath; + if (item->data(ChatListWhiteboardItem::AvatarRole).isValid() && !item->data(ChatListWhiteboardItem::AvatarRole).value<QString>().isNull()) { + avatarPath = item->data(ChatListWhiteboardItem::AvatarRole).value<QString>(); + } + QIcon presenceIcon;/* = item->data(ChatListWhiteboardItem::PresenceIconRole).isValid() && !item->data(ChatListWhiteboardItem::PresenceIconRole).value<QIcon>().isNull() + ? item->data(ChatListWhiteboardItem::PresenceIconRole).value<QIcon>() + : QIcon(":/icons/offline.png");*/ + 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, false, item->getChat().unreadCount, compact_); + } diff --git a/Swift/QtUI/ChatList/ChatListDelegate.h b/Swift/QtUI/ChatList/ChatListDelegate.h index 5ac45ce..9460c28 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.h +++ b/Swift/QtUI/ChatList/ChatListDelegate.h @@ -14,4 +14,5 @@ namespace Swift { class ChatListMUCItem; class ChatListRecentItem; + class ChatListWhiteboardItem; class ChatListDelegate : public QStyledItemDelegate { public: @@ -25,4 +26,5 @@ namespace Swift { void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, ChatListMUCItem* item) const; void paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const; + void paintWhiteboard(QPainter* painter, const QStyleOptionViewItem& option, ChatListWhiteboardItem* item) const; QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const; QSize recentSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const; diff --git a/Swift/QtUI/ChatList/ChatListGroupItem.h b/Swift/QtUI/ChatList/ChatListGroupItem.h index a1e479f..3bb4b8e 100644 --- a/Swift/QtUI/ChatList/ChatListGroupItem.h +++ b/Swift/QtUI/ChatList/ChatListGroupItem.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,4 +9,6 @@ #include <QList> +#include <Swiften/Base/foreach.h> + #include "Swift/QtUI/ChatList/ChatListItem.h" @@ -14,12 +16,20 @@ 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) {} + virtual ~ChatListGroupItem() {clear();} + 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() { + foreach (ChatListItem* item, items_) { + delete item; + } + items_.clear(); + } + + private: static bool pointerItemLessThan(const ChatListItem* first, const ChatListItem* second) { 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 @@ -14,8 +14,8 @@ namespace Swift { 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; diff --git a/Swift/QtUI/ChatList/ChatListModel.cpp b/Swift/QtUI/ChatList/ChatListModel.cpp index 681c1c2..f08478f 100644 --- a/Swift/QtUI/ChatList/ChatListModel.cpp +++ b/Swift/QtUI/ChatList/ChatListModel.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,30 +7,57 @@ #include <Swift/QtUI/ChatList/ChatListModel.h> +#include <QMimeData> +#include <QUrl> + #include <Swift/QtUI/ChatList/ChatListMUCItem.h> #include <Swift/QtUI/ChatList/ChatListRecentItem.h> +#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -ChatListModel::ChatListModel() { +ChatListModel::ChatListModel() : whiteboards_(NULL) { root_ = new ChatListGroupItem("", NULL, false); mucBookmarks_ = new ChatListGroupItem(tr("Bookmarked Rooms"), root_); recents_ = new ChatListGroupItem(tr("Recent Chats"), root_, false); +#ifdef SWIFT_EXPERIMENTAL_WB + whiteboards_ = new ChatListGroupItem(tr("Opened Whiteboards"), root_, false); + root_->addItem(whiteboards_); +#endif + root_->addItem(recents_); root_->addItem(mucBookmarks_); + + QModelIndex idx = index(0, 0, QModelIndex()); + while (idx.isValid()) { + if (idx.internalPointer() == mucBookmarks_) { + mucBookmarksIndex_ = idx; + } else if (idx.internalPointer() == recents_) { + recentsIndex_ = idx; + } else if (idx.internalPointer() == whiteboards_) { + whiteboardsIndex_ = idx; + } + idx = index(idx.row() + 1, 0, QModelIndex()); + } +} + +Qt::ItemFlags ChatListModel::flags(const QModelIndex& index) const { + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (dynamic_cast<ChatListRecentItem*>(getItemForIndex(index))) { + flags |= Qt::ItemIsDragEnabled; + } + return flags; } void ChatListModel::clearBookmarks() { - emit layoutAboutToBeChanged(); + beginRemoveRows(mucBookmarksIndex_, 0, mucBookmarks_->rowCount()); mucBookmarks_->clear(); - emit layoutChanged(); + endRemoveRows(); } void ChatListModel::addMUCBookmark(const Swift::MUCBookmark& bookmark) { - emit layoutAboutToBeChanged(); + beginInsertRows(mucBookmarksIndex_, 0, mucBookmarks_->rowCount()); mucBookmarks_->addItem(new ChatListMUCItem(bookmark, mucBookmarks_)); - emit layoutChanged(); - //QModelIndex index = createIndex(mucBookmarks_->rowCount() - 1, 0, mucBookmarks_); - //emit dataChanged(index, index); - //emit dataChanged(parent(index), parent(index)); + endInsertRows(); } @@ -39,7 +66,25 @@ void ChatListModel::removeMUCBookmark(const Swift::MUCBookmark& bookmark) { ChatListMUCItem* item = dynamic_cast<ChatListMUCItem*>(mucBookmarks_->item(i)); if (item->getBookmark() == bookmark) { - emit layoutAboutToBeChanged(); + beginRemoveRows(mucBookmarksIndex_, i, i+1); mucBookmarks_->remove(i); - emit layoutChanged(); + endRemoveRows(); + break; + } + } +} + +void ChatListModel::addWhiteboardSession(const ChatListWindow::Chat& chat) { + beginInsertRows(whiteboardsIndex_, 0, whiteboards_->rowCount()); + whiteboards_->addItem(new ChatListWhiteboardItem(chat, whiteboards_)); + endInsertRows(); +} + +void ChatListModel::removeWhiteboardSession(const JID& jid) { + for (int i = 0; i < whiteboards_->rowCount(); i++) { + ChatListWhiteboardItem* item = dynamic_cast<ChatListWhiteboardItem*>(whiteboards_->item(i)); + if (item->getChat().jid == jid) { + beginRemoveRows(whiteboardsIndex_, i, i+1); + whiteboards_->remove(i); + endRemoveRows(); break; } @@ -48,10 +93,41 @@ void ChatListModel::removeMUCBookmark(const Swift::MUCBookmark& bookmark) { void ChatListModel::setRecents(const std::list<ChatListWindow::Chat>& recents) { - emit layoutAboutToBeChanged(); + beginRemoveRows(recentsIndex_, 0, recents_->rowCount()); recents_->clear(); + endRemoveRows(); + beginInsertRows(recentsIndex_, 0, recents.size()); foreach (const ChatListWindow::Chat chat, recents) { recents_->addItem(new ChatListRecentItem(chat, recents_)); +//whiteboards_->addItem(new ChatListRecentItem(chat, whiteboards_)); + } + endInsertRows(); +} + +QMimeData* ChatListModel::mimeData(const QModelIndexList& indexes) const { + QMimeData* data = QAbstractItemModel::mimeData(indexes); + ChatListRecentItem *item = dynamic_cast<ChatListRecentItem*>(getItemForIndex(indexes.first())); + if (item == NULL) { + return data; + } + + QByteArray itemData; + QDataStream dataStream(&itemData, QIODevice::WriteOnly); + const ChatListWindow::Chat& chat = item->getChat(); + + QString mimeType = "application/vnd.swift.contact-jid-list"; + if (!chat.impromptuJIDs.size()) { + if (chat.isMUC) { + mimeType = "application/vnd.swift.contact-jid-muc"; + } + dataStream << P2QSTRING(chat.jid.toString()); + } else { + typedef std::map<std::string, JID> JIDMap; + foreach (const JIDMap::value_type& jid, chat.impromptuJIDs) { + dataStream << P2QSTRING(jid.second.toString()); + } } - emit layoutChanged(); + + data->setData(mimeType, itemData); + return data; } diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h index 8e7828c..00bd5eb 100644 --- a/Swift/QtUI/ChatList/ChatListModel.h +++ b/Swift/QtUI/ChatList/ChatListModel.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,8 +7,6 @@ #pragma once -#include <boost/shared_ptr.hpp> - #include <QAbstractItemModel> -#include <QList> +#include <QPersistentModelIndex> #include <Swiften/MUC/MUCBookmark.h> @@ -22,6 +20,9 @@ namespace Swift { public: ChatListModel(); + Qt::ItemFlags flags(const QModelIndex& index) const; void addMUCBookmark(const MUCBookmark& bookmark); void removeMUCBookmark(const MUCBookmark& bookmark); + void addWhiteboardSession(const ChatListWindow::Chat& chat); + void removeWhiteboardSession(const JID& jid); int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; @@ -32,8 +33,14 @@ namespace Swift { void clearBookmarks(); void setRecents(const std::list<ChatListWindow::Chat>& recents); + QMimeData* mimeData(const QModelIndexList& indexes) const; private: ChatListGroupItem* mucBookmarks_; ChatListGroupItem* recents_; + ChatListGroupItem* whiteboards_; ChatListGroupItem* root_; + + QPersistentModelIndex mucBookmarksIndex_; + QPersistentModelIndex recentsIndex_; + QPersistentModelIndex whiteboardsIndex_; }; diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.cpp b/Swift/QtUI/ChatList/ChatListRecentItem.cpp index 6c9807f..5497fdd 100644 --- a/Swift/QtUI/ChatList/ChatListRecentItem.cpp +++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp @@ -1,10 +1,12 @@ /* - * 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. */ -#include <Swift/QtUI/ChatList/ChatListRecentItem.h> +#include <Swiften/Base/Path.h> +#include <Swift/QtUI/ChatList/ChatListRecentItem.h> +#include <Swift/QtUI/QtResourceHelper.h> #include <Swift/QtUI/QtSwiftUtil.h> @@ -20,5 +22,5 @@ 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_; @@ -26,5 +28,5 @@ QVariant ChatListRecentItem::data(int role) const { 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(); @@ -33,14 +35,5 @@ QVariant ChatListRecentItem::data(int role) const { QIcon ChatListRecentItem::getPresenceIcon() const { - QString iconString; - switch (chat_.statusType) { - case StatusShow::Online: iconString = "online";break; - case StatusShow::Away: iconString = "away";break; - case StatusShow::XA: iconString = "away";break; - case StatusShow::FFC: iconString = "online";break; - case StatusShow::DND: iconString = "dnd";break; - case StatusShow::None: iconString = "offline";break; - } - return QIcon(":/icons/" + iconString + ".png"); + return QIcon(statusShowTypeToIconPath(chat_.statusType)); } 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 @@ -24,5 +24,6 @@ namespace Swift { 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); diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp new file mode 100644 index 0000000..05bf6c2 --- /dev/null +++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * 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 <Swiften/Base/Path.h> + +#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtResourceHelper.h> + +namespace Swift { + ChatListWhiteboardItem::ChatListWhiteboardItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) { + + } + + const ChatListWindow::Chat& ChatListWhiteboardItem::getChat() const { + return chat_; + } + + QVariant ChatListWhiteboardItem::data(int role) const { + switch (role) { + case Qt::DisplayRole: return P2QSTRING(chat_.chatName); + case DetailTextRole: return P2QSTRING(chat_.activity); + /*case Qt::TextColorRole: return textColor_; + case Qt::BackgroundColorRole: return backgroundColor_; + case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant(); + case StatusTextRole: return statusText_;*/ + case AvatarRole: return QVariant(P2QSTRING(pathToString(chat_.avatarPath))); + case PresenceIconRole: return getPresenceIcon(); + default: return QVariant(); + } + } + + QIcon ChatListWhiteboardItem::getPresenceIcon() const { + return QIcon(statusShowTypeToIconPath(chat_.statusType)); + } +} + diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.h b/Swift/QtUI/ChatList/ChatListWhiteboardItem.h new file mode 100644 index 0000000..2dc6255 --- /dev/null +++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QList> +#include <QIcon> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> + +#include <Swift/QtUI/ChatList/ChatListItem.h> + +namespace Swift { + class ChatListWhiteboardItem : public ChatListItem { + public: + enum RecentItemRoles { + DetailTextRole = Qt::UserRole, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2/*, + StatusShowTypeRole = Qt::UserRole + 3*/ + }; + ChatListWhiteboardItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent); + const ChatListWindow::Chat& getChat() const; + QVariant data(int role) const; + private: + QIcon getPresenceIcon() const; + ChatListWindow::Chat chat_; + }; +} diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp index 42eb43b..ea65dd6 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.cpp +++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -9,25 +9,28 @@ #include <boost/bind.hpp> -#include <QMenu> #include <QContextMenuEvent> +#include <QMenu> +#include <QMimeData> +#include <QUrl> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h> #include <Swift/QtUI/ChatList/ChatListMUCItem.h> #include <Swift/QtUI/ChatList/ChatListRecentItem.h> +#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h> #include <Swift/QtUI/QtAddBookmarkWindow.h> #include <Swift/QtUI/QtEditBookmarkWindow.h> #include <Swift/QtUI/QtUISettingConstants.h> -#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> -#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> -#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> -#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> -#include <Swift/Controllers/Settings/SettingsProvider.h> - namespace Swift { -QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) { +QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent), isOnline_(false) { eventStream_ = uiEventStream; - settings_ = settings;; + settings_ = settings; bookmarksEnabled_ = false; model_ = new ChatListModel(); @@ -42,4 +45,5 @@ QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvide setAnimated(true); setIndentation(0); + setDragEnabled(true); setRootIsDecorated(true); setupContextMenus(); @@ -55,4 +59,5 @@ QtChatListWindow::~QtChatListWindow() { delete delegate_; delete mucMenu_; + delete mucRecentsMenu_; delete emptyMenu_; } @@ -65,4 +70,8 @@ void QtChatListWindow::handleSettingChanged(const std::string& setting) { } +void QtChatListWindow::handleClearRecentsRequested() { + onClearRecentsRequested(); +} + void QtChatListWindow::setBookmarksEnabled(bool enabled) { bookmarksEnabled_ = enabled; @@ -78,10 +87,12 @@ void QtChatListWindow::handleClicked(const QModelIndex& index) { void QtChatListWindow::setupContextMenus() { mucMenu_ = new QMenu(); - mucMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); - mucMenu_->addAction(tr("Edit Bookmark"), this, SLOT(handleEditBookmark())); - mucMenu_->addAction(tr("Remove Bookmark"), this, SLOT(handleRemoveBookmark())); + onlineOnlyActions_ << mucMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); + onlineOnlyActions_ << mucMenu_->addAction(tr("Edit Bookmark"), this, SLOT(handleEditBookmark())); + onlineOnlyActions_ << mucMenu_->addAction(tr("Remove Bookmark"), this, SLOT(handleRemoveBookmark())); + mucRecentsMenu_ = new QMenu(); + onlineOnlyActions_ << mucRecentsMenu_->addAction(tr("Add to Bookmarks"), this, SLOT(handleAddBookmarkFromRecents())); + mucRecentsMenu_->addAction(tr("Clear recents"), this, SLOT(handleClearRecentsRequested())); emptyMenu_ = new QMenu(); - emptyMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); - + onlineOnlyActions_ << emptyMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); } @@ -98,4 +109,9 @@ void QtChatListWindow::handleItemActivated(const QModelIndex& index) { } } + else if (ChatListWhiteboardItem* whiteboardItem = dynamic_cast<ChatListWhiteboardItem*>(item)) { + if (!whiteboardItem->getChat().isMUC || bookmarksEnabled_) { + eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(whiteboardItem->getChat().jid)); + } + } } @@ -112,4 +128,12 @@ void QtChatListWindow::removeMUCBookmark(const MUCBookmark& bookmark) { } +void QtChatListWindow::addWhiteboardSession(const ChatListWindow::Chat& chat) { + model_->addWhiteboardSession(chat); +} + +void QtChatListWindow::removeWhiteboardSession(const JID& jid) { + model_->removeWhiteboardSession(jid); +} + void QtChatListWindow::setRecents(const std::list<ChatListWindow::Chat>& recents) { model_->setRecents(recents); @@ -120,4 +144,8 @@ void QtChatListWindow::setUnreadCount(int unread) { } +void QtChatListWindow::setOnline(bool isOnline) { + isOnline_ = isOnline; +} + void QtChatListWindow::handleRemoveBookmark() { ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(contextMenuItem_); @@ -126,4 +154,15 @@ void QtChatListWindow::handleRemoveBookmark() { } +void QtChatListWindow::handleAddBookmarkFromRecents() { + ChatListRecentItem* item = dynamic_cast<ChatListRecentItem*>(contextMenuItem_); + if (item) { + const ChatListWindow::Chat& chat = item->getChat(); + MUCBookmark bookmark(chat.jid, chat.jid.toBare().toString()); + bookmark.setNick(chat.nick); + bookmark.setPassword(chat.password); + eventStream_->send(boost::shared_ptr<UIEvent>(new AddMUCBookmarkUIEvent(bookmark))); + } +} + void QtChatListWindow::handleAddBookmark() { (new QtAddBookmarkWindow(eventStream_))->show(); @@ -138,4 +177,9 @@ void QtChatListWindow::handleEditBookmark() { } +void QtChatListWindow::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + event->acceptProposedAction(); + } +} void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) { @@ -143,8 +187,14 @@ void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) { ChatListItem* baseItem = index.isValid() ? static_cast<ChatListItem*>(index.internalPointer()) : NULL; contextMenuItem_ = baseItem; + + foreach(QAction* action, onlineOnlyActions_) { + action->setEnabled(isOnline_); + } + if (!baseItem) { emptyMenu_->exec(QCursor::pos()); return; } + ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(baseItem); if (mucItem) { @@ -153,14 +203,19 @@ void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) { } mucMenu_->exec(QCursor::pos()); + return; } - else { - QMenu menu; - QAction* clearRecents = menu.addAction(tr("Clear recents")); - menu.addAction(clearRecents); - QAction* result = menu.exec(event->globalPos()); - if (result == clearRecents) { - onClearRecentsRequested(); + + ChatListRecentItem* recentItem = dynamic_cast<ChatListRecentItem*>(baseItem); + if (recentItem) { + const ChatListWindow::Chat& chat = recentItem->getChat(); + if (chat.isMUC) { + mucRecentsMenu_->exec(QCursor::pos()); + return; } } + + QMenu menu; + menu.addAction(tr("Clear recents"), this, SLOT(handleClearRecentsRequested())); + menu.exec(event->globalPos()); } diff --git a/Swift/QtUI/ChatList/QtChatListWindow.h b/Swift/QtUI/ChatList/QtChatListWindow.h index 33131ce..823e6dc 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.h +++ b/Swift/QtUI/ChatList/QtChatListWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -23,8 +23,11 @@ namespace Swift { void addMUCBookmark(const MUCBookmark& bookmark); void removeMUCBookmark(const MUCBookmark& bookmark); + 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); void clearBookmarks(); + virtual void setOnline(bool isOnline); signals: @@ -35,8 +38,11 @@ namespace Swift { void handleEditBookmark(); void handleRemoveBookmark(); + void handleAddBookmarkFromRecents(); void handleClicked(const QModelIndex& index); void handleSettingChanged(const std::string& setting); + void handleClearRecentsRequested(); protected: + void dragEnterEvent(QDragEnterEvent* event); void contextMenuEvent(QContextMenuEvent* event); @@ -49,6 +55,9 @@ namespace Swift { QMenu* mucMenu_; QMenu* emptyMenu_; + QMenu* mucRecentsMenu_; ChatListItem* contextMenuItem_; SettingsProvider* settings_; + QList<QAction*> onlineOnlyActions_; + bool isOnline_; }; 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,11 +1,13 @@ /* - * 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 { @@ -40,3 +42,57 @@ QString ChatSnippet::wrapResizable(const QString& text) { } -}; +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 f79f487..f60d486 100644 --- a/Swift/QtUI/ChatSnippet.h +++ b/Swift/QtUI/ChatSnippet.h @@ -1,4 +1,4 @@ /* - * 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,18 @@ #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(); @@ -32,7 +41,9 @@ namespace Swift { result.replace("%message%", "%message%"); result.replace("%sender%", "%sender%"); + result.replace("%wrapped_sender%", "%wrapped_sender%"); result.replace("%time%", "%%time%"); result.replace("%shortTime%", "%%shortTime%"); result.replace("%userIconPath%", "%userIconPath%"); + result.replace("\t", " "); result.replace(" ", " "); return result; @@ -41,5 +52,11 @@ 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) { diff --git a/Swift/QtUI/CocoaApplicationActivateHelper.h b/Swift/QtUI/CocoaApplicationActivateHelper.h index c831183..8bc2af5 100644 --- a/Swift/QtUI/CocoaApplicationActivateHelper.h +++ b/Swift/QtUI/CocoaApplicationActivateHelper.h @@ -23,5 +23,5 @@ namespace Swift { private: - class Private; + struct Private; Private* p; }; diff --git a/Swift/QtUI/CocoaUIHelpers.h b/Swift/QtUI/CocoaUIHelpers.h new file mode 100644 index 0000000..25da0e3 --- /dev/null +++ b/Swift/QtUI/CocoaUIHelpers.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/TLS/Certificate.h> +#include <QWidget> + +namespace Swift { + +class CocoaUIHelpers { +public: + static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); +}; + +} + diff --git a/Swift/QtUI/CocoaUIHelpers.mm b/Swift/QtUI/CocoaUIHelpers.mm new file mode 100644 index 0000000..3cb62f3 --- /dev/null +++ b/Swift/QtUI/CocoaUIHelpers.mm @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "CocoaUIHelpers.h" + +#include <boost/shared_ptr.hpp> +#include <boost/type_traits.hpp> + +#include <Cocoa/Cocoa.h> + +#include <Security/Security.h> +#include <SecurityInterface/SFCertificatePanel.h> + +#include <Swiften/Base/foreach.h> + +#pragma GCC diagnostic ignored "-Wold-style-cast" + +namespace Swift { + +void CocoaUIHelpers::displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain) { + NSWindow* parentWindow = [((NSView*)parent->winId()) window]; + NSMutableArray* certificates = [[NSMutableArray alloc] init]; + foreach(Certificate::ref cert, chain) { + // convert chain to SecCertificateRef + ByteArray certAsDER = cert->toDER(); + boost::shared_ptr<boost::remove_pointer<CFDataRef>::type> certData(CFDataCreate(NULL, certAsDER.data(), certAsDER.size()), CFRelease); + boost::shared_ptr<OpaqueSecCertificateRef> macCert(SecCertificateCreateWithData(NULL, certData.get()), CFRelease); + + // add to NSMutable array + [certificates addObject: (id)macCert.get()]; + } + + + SFCertificatePanel* panel = [[SFCertificatePanel alloc] init]; + //[panel setPolicies:(id)policies.get()]; + [panel beginSheetForWindow:parentWindow modalDelegate:nil didEndSelector:NULL contextInfo:NULL certificates:certificates showGroup:YES]; + [certificates release]; +} + +} 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 @@ -30,6 +30,7 @@ QSize EventDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIn case ErrorEventType: return errorDelegate_.sizeHint(option, item); case MUCInviteEventType: return mucInviteDelegate_.sizeHint(option, item); - default: return QStyledItemDelegate::sizeHint(option, index); } + assert(false); + return QSize(); } @@ -45,5 +46,4 @@ void EventDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, case ErrorEventType: errorDelegate_.paint(painter, option, item);break; case MUCInviteEventType: mucInviteDelegate_.paint(painter, option, item);break; - default: QStyledItemDelegate::paint(painter, option, index); } } @@ -51,11 +51,19 @@ void EventDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, 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); 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 @@ -8,4 +8,5 @@ #include <QDateTime> +#include <QColor> #include "Swift/Controllers/XMPPEvents/MessageEvent.h" @@ -26,6 +27,6 @@ QVariant QtEvent::data(int 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_; 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 @@ -18,5 +18,5 @@ namespace Swift { 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,4 +1,4 @@ /* - * 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,4 +14,7 @@ #include <QtDBus/QtDBus> #include <algorithm> +#include <Swiften/Base/Path.h> + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -44,5 +47,5 @@ void FreeDesktopNotifier::showMessage(Type type, const std::string& subject, con 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 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,4 +1,4 @@ /* - * 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. @@ -16,7 +16,6 @@ namespace Swift { 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; 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 @@ -24,5 +24,5 @@ namespace Swift { QtMUCSearchWindow::QtMUCSearchWindow() { ui_.setupUi(this); -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); #endif diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp index 7505905..28c44c4 100644 --- a/Swift/QtUI/MessageSnippet.cpp +++ b/Swift/QtUI/MessageSnippet.cpp @@ -1,4 +1,4 @@ /* - * 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. @@ -12,7 +12,7 @@ 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) { @@ -33,9 +33,12 @@ 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("%sender%", wrapResizable(escape(sender))); + 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,4 +1,4 @@ /* - * 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. @@ -16,5 +16,5 @@ 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 { @@ -24,5 +24,5 @@ namespace Swift { QString getContinuationElementID() const { return "insert"; - }; + } private: 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 @@ -20,5 +20,5 @@ namespace Swift { QtAboutWidget::QtAboutWidget() : QDialog() { -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowTitle(QString(tr("About %1")).arg("Swift")); #endif diff --git a/Swift/QtUI/QtAdHocCommandWindow.cpp b/Swift/QtUI/QtAdHocCommandWindow.cpp index 88aa708..5f99dba 100644 --- a/Swift/QtUI/QtAdHocCommandWindow.cpp +++ b/Swift/QtUI/QtAdHocCommandWindow.cpp @@ -1,13 +1,14 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include <Swift/QtUI/QtAdHocCommandWindow.h> - #include <boost/bind.hpp> #include <QBoxLayout> +#include <Swift/QtUI/QtAdHocCommandWindow.h> #include <Swift/QtUI/QtFormWidget.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swiften/Base/format.h> #include <Swiften/Elements/Command.h> @@ -16,5 +17,4 @@ const int FormLayoutIndex = 1; namespace Swift { QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) : command_(command) { - someActions_ = false; formWidget_ = NULL; @@ -30,4 +30,11 @@ QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocComman label_->setTextFormat(Qt::PlainText); layout_->addWidget(label_); + + errorLabel_ = new QLabel(this); + errorLabel_->setText(QString("<b>%1</b>").arg(tr("Unable to complete the command because you have been disconnected"))); + errorLabel_->setVisible(false); + errorLabel_->setFrameStyle(QFrame::Box|QFrame::Sunken); + layout_->addWidget(errorLabel_); + QWidget* buttonsWidget = new QWidget(this); layout_->addWidget(buttonsWidget); @@ -49,4 +56,5 @@ QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocComman backButton_->setEnabled(false); completeButton_->setEnabled(false); + actions_[Command::Next] = nextButton_; actions_[Command::Prev] = backButton_; @@ -56,5 +64,17 @@ QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocComman QtAdHocCommandWindow::~QtAdHocCommandWindow() { +} + +void QtAdHocCommandWindow::setOnline(bool online) { + if (!online) { + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + errorLabel_->setVisible(true); + } +} +void QtAdHocCommandWindow::closeEvent(QCloseEvent*) { + onClosing(); } @@ -77,14 +97,10 @@ void QtAdHocCommandWindow::handleCompleteClicked() { void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) { - if (command->getForm()) { - setForm(command->getForm()); - } else { - setNoForm(); - } QString notes; foreach (Command::Note note, command->getNotes()) { if (!notes.isEmpty()) { notes += "\n"; - QString qNote(note.note.c_str()); + } + QString qNote(P2QSTRING(note.note)); switch (note.type) { case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break; @@ -93,6 +109,10 @@ void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) { } } - } label_->setText(notes); + if (command->getForm()) { + setForm(command->getForm()); + } else { + setNoForm(notes.isEmpty()); + } setAvailableActions(command); } @@ -106,15 +126,17 @@ void QtAdHocCommandWindow::handleError(ErrorPayload::ref /*error*/) { void QtAdHocCommandWindow::setForm(Form::ref form) { + form_ = form; delete formWidget_; formWidget_ = new QtFormWidget(form, this); layout_->insertWidget(FormLayoutIndex, formWidget_); show(); - formWidget_->setEditable(someActions_); } -void QtAdHocCommandWindow::setNoForm() { +void QtAdHocCommandWindow::setNoForm(bool andHide) { + form_.reset(); delete formWidget_; formWidget_ = NULL; - show(); + resize(minimumSize()); + setVisible(!andHide); } @@ -122,5 +144,4 @@ typedef std::pair<Command::Action, QPushButton*> ActionButton; void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) { - someActions_ = false; foreach (ActionButton pair, actions_) { OutgoingAdHocCommandSession::ActionState state = command_->getActionState(pair.first); @@ -133,5 +154,4 @@ void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) { if (state & OutgoingAdHocCommandSession::Enabled) { pair.second->setEnabled(true); - someActions_ = true; } else { @@ -139,7 +159,4 @@ void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) { } } - if (formWidget_) { - formWidget_->setEditable(someActions_); - } } diff --git a/Swift/QtUI/QtAdHocCommandWindow.h b/Swift/QtUI/QtAdHocCommandWindow.h index 7f824f8..0e398af 100644 --- a/Swift/QtUI/QtAdHocCommandWindow.h +++ b/Swift/QtUI/QtAdHocCommandWindow.h @@ -23,9 +23,11 @@ namespace Swift { QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); virtual ~QtAdHocCommandWindow(); + virtual void setOnline(bool online); private: + void closeEvent(QCloseEvent* event); void handleNextStageReceived(Command::ref command); void handleError(ErrorPayload::ref error); void setForm(Form::ref); - void setNoForm(); + void setNoForm(bool andHide); void setAvailableActions(Command::ref commandResult); private slots: @@ -39,4 +41,5 @@ namespace Swift { Form::ref form_; QLabel* label_; + QLabel* errorLabel_; QPushButton* backButton_; QPushButton* nextButton_; @@ -45,5 +48,4 @@ namespace Swift { std::map<Command::Action, QPushButton*> actions_; QBoxLayout* layout_; - bool someActions_; }; } diff --git a/Swift/QtUI/QtAdHocCommandWithJIDWindow.cpp b/Swift/QtUI/QtAdHocCommandWithJIDWindow.cpp new file mode 100644 index 0000000..7f33f77 --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWithJIDWindow.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2010-2014 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <boost/bind.hpp> +#include <QLabel> +#include <QPushButton> +#include <QBoxLayout> +#include <QDialogButtonBox> +#include <Swiften/Elements/Command.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h> +#include <Swift/QtUI/QtAdHocCommandWithJIDWindow.h> +#include <Swift/QtUI/QtFormWidget.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +const int FormLayoutIndex = 1; + +namespace Swift { +QtAdHocCommandWithJIDWindow::QtAdHocCommandWithJIDWindow(UIEventStream* uiEventStream) : uiEventStream_(uiEventStream) { + QVBoxLayout* hlayout = new QVBoxLayout(this); + + QLabel* jidLabel = new QLabel("JID:", this); + hlayout->addWidget(jidLabel); + jid_ = new QLineEdit(this); + hlayout->addWidget(jid_); + + QLabel* commandLabel = new QLabel("Command:", this); + hlayout->addWidget(commandLabel); + node_ = new QLineEdit(this); + hlayout->addWidget(node_); + + QDialogButtonBox* buttonBox = new QDialogButtonBox(this); + QPushButton* rejectButton = buttonBox->addButton("Cancel", QDialogButtonBox::RejectRole); + connect(rejectButton, SIGNAL(clicked()), this, SLOT(handleRejectClick())); + QPushButton* acceptButton = buttonBox->addButton("Complete", QDialogButtonBox::AcceptRole); + connect(acceptButton, SIGNAL(clicked()), this, SLOT(handleAcceptClick())); + hlayout->addWidget(buttonBox); + + setLayout(hlayout); + show(); +} + +QtAdHocCommandWithJIDWindow::~QtAdHocCommandWithJIDWindow() { +} + +void QtAdHocCommandWithJIDWindow::handleAcceptClick() { + const JID jid = JID(Q2PSTRING(jid_->text())); + const std::string node = Q2PSTRING(node_->text()); + boost::shared_ptr<UIEvent> event(new RequestAdHocWithJIDUIEvent(jid, node)); + uiEventStream_->send(event); + accept(); +} + +void QtAdHocCommandWithJIDWindow::handleRejectClick() { + reject(); +} + +} diff --git a/Swift/QtUI/QtAdHocCommandWithJIDWindow.h b/Swift/QtUI/QtAdHocCommandWithJIDWindow.h new file mode 100644 index 0000000..b168827 --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWithJIDWindow.h @@ -0,0 +1,32 @@ +/* + * 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 <QDialog> +#include <QLineEdit> + +#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> + +class QBoxLayout; + +namespace Swift { + class UIEventStream; + class QtFormWidget; + class QtAdHocCommandWithJIDWindow : public QDialog { + Q_OBJECT + public: + QtAdHocCommandWithJIDWindow(UIEventStream* eventStream); + virtual ~QtAdHocCommandWithJIDWindow(); + public slots: + void handleAcceptClick(); + void handleRejectClick(); + private: + UIEventStream* uiEventStream_; + QLineEdit* jid_; + QLineEdit* node_; + }; +} diff --git a/Swift/QtUI/QtAddBookmarkWindow.cpp b/Swift/QtUI/QtAddBookmarkWindow.cpp index 675ea03..230f2ed 100644 --- a/Swift/QtUI/QtAddBookmarkWindow.cpp +++ b/Swift/QtUI/QtAddBookmarkWindow.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -14,4 +14,8 @@ QtAddBookmarkWindow::QtAddBookmarkWindow(UIEventStream* eventStream) : eventStre } +QtAddBookmarkWindow::QtAddBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark) : eventStream_(eventStream) { + createFormFromBookmark(bookmark); +} + bool QtAddBookmarkWindow::commit() { boost::optional<MUCBookmark> bookmark = createBookmarkFromForm(); diff --git a/Swift/QtUI/QtAddBookmarkWindow.h b/Swift/QtUI/QtAddBookmarkWindow.h index f026cc3..c0dc214 100644 --- a/Swift/QtUI/QtAddBookmarkWindow.h +++ b/Swift/QtUI/QtAddBookmarkWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -17,4 +17,5 @@ namespace Swift { public: QtAddBookmarkWindow(UIEventStream* eventStream); + QtAddBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark); bool commit(); private: 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 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 diff --git a/Swift/QtUI/QtAffiliationEditor.ui b/Swift/QtUI/QtAffiliationEditor.ui index 1447884..0a9b7bf 100644 --- a/Swift/QtUI/QtAffiliationEditor.ui +++ b/Swift/QtUI/QtAffiliationEditor.ui @@ -12,5 +12,5 @@ </property> <property name="windowTitle"> - <string>Dialog</string> + <string>Edit Affiliations</string> </property> <layout class="QVBoxLayout" name="verticalLayout_2"> diff --git a/Swift/QtUI/QtAvatarWidget.cpp b/Swift/QtUI/QtAvatarWidget.cpp index f0bdf3c..fa08c27 100644 --- a/Swift/QtUI/QtAvatarWidget.cpp +++ b/Swift/QtUI/QtAvatarWidget.cpp @@ -1,8 +1,10 @@ /* - * Copyright (c) 2011 Remko Tronçon + * Copyright (c) 2011-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ + + #include "QtAvatarWidget.h" @@ -20,8 +22,9 @@ #include <QtSwiftUtil.h> +#include <Swiften/Base/Path.h> namespace Swift { -QtAvatarWidget::QtAvatarWidget(QWidget* parent) : QWidget(parent) { +QtAvatarWidget::QtAvatarWidget(QWidget* parent) : QWidget(parent), editable(false) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0,0,0,0); @@ -69,4 +72,7 @@ void QtAvatarWidget::setAvatar(const ByteArray& data, const std::string& type) { void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { + if (!editable) { + return; + } QMenu menu; @@ -82,5 +88,5 @@ void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { if (!fileName.isEmpty()) { ByteArray data; - readByteArrayFromFile(data, Q2PSTRING(fileName)); + readByteArrayFromFile(data, stringToPath(Q2PSTRING(fileName))); QBuffer buffer; 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 @@ -16,4 +16,5 @@ namespace Swift { class QtAvatarWidget : public QWidget { Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) public: QtAvatarWidget(QWidget* parent); @@ -29,7 +30,16 @@ namespace Swift { } + void setEditable(bool b) { + editable = b; + } + + bool isEditable() const { + return editable; + } + void mousePressEvent(QMouseEvent* event); private: + bool editable; ByteArray data; std::string type; diff --git a/Swift/QtUI/QtBlockListEditorWindow.cpp b/Swift/QtUI/QtBlockListEditorWindow.cpp new file mode 100644 index 0000000..82500df --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Client/ClientBlockListManager.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), removeItemDelegate(0), editItemDelegate(0) { + ui->setupUi(this); + + freshBlockListTemplate = tr("Double-click to add contact"); + + new QShortcut(QKeySequence::Close, this, SLOT(close())); + ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); + + removeItemDelegate = new QtRemovableItemDelegate(style()); + editItemDelegate = new QtJIDValidatedItemDelegate(this); + + connect(ui->savePushButton, SIGNAL(clicked()), SLOT(applyChanges())); + + ui->blockListTreeWidget->setColumnCount(2); + ui->blockListTreeWidget->header()->setStretchLastSection(false); + ui->blockListTreeWidget->header()->resizeSection(1, removeItemDelegate->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 | QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); + ui->blockListTreeWidget->setItemDelegateForColumn(0, editItemDelegate); + ui->blockListTreeWidget->setItemDelegateForColumn(1, removeItemDelegate); + connect(ui->blockListTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int))); + ui->blockListTreeWidget->installEventFilter(this); + + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(freshBlockListTemplate) << "x"); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); +} + +QtBlockListEditorWindow::~QtBlockListEditorWindow() { +} + +void QtBlockListEditorWindow::show() { + QWidget::showNormal(); + QWidget::activateWindow(); + QWidget::raise(); +} + +void QtBlockListEditorWindow::hide() { + QWidget::hide(); +} + +void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *item, int) { + // check whether changed item contains a valid JID and make it removable + if (item && item->text(0) != freshBlockListTemplate) { + item->setText(1, ""); + } + + // check for empty rows and add an empty one so the user can add items + bool hasEmptyRow = false; + for( int i = 0; i < ui->blockListTreeWidget->topLevelItemCount(); ++i ) { + QTreeWidgetItem* row = ui->blockListTreeWidget->topLevelItem(i); + if (row->text(0) == freshBlockListTemplate) { + hasEmptyRow = true; + } + else if (row->text(0).isEmpty()) { + ui->blockListTreeWidget->removeItemWidget(row, 0); + } + } + + if (!hasEmptyRow) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(freshBlockListTemplate) << "x"); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); + } + + if (!item) { + ui->blockListTreeWidget->setCurrentItem(ui->blockListTreeWidget->topLevelItem(0)); + } +} + +void QtBlockListEditorWindow::applyChanges() { + onSetNewBlockList(getCurrentBlockList()); +} + +void 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 QtBlockListEditorWindow::setBusy(bool isBusy) { + if (isBusy) { + ui->throbberLabel->movie()->start(); + ui->throbberLabel->show(); + ui->blockListTreeWidget->setEnabled(false); + ui->savePushButton->setEnabled(false); + } else { + ui->throbberLabel->movie()->stop(); + ui->throbberLabel->hide(); + ui->blockListTreeWidget->setEnabled(true); + ui->savePushButton->setEnabled(true); + } +} + +void QtBlockListEditorWindow::setError(const std::string& error) { + if (!error.empty()) { + ui->errorLabel->setText("<font color='red'>" + QtUtilities::htmlEscape(P2QSTRING(error)) + "</font>"); + } + else { + ui->errorLabel->setText(""); + } +} + +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); + JID jid = JID(Q2PSTRING(row->text(0))); + if (!jid.toString().empty() && jid.isValid()) { + futureBlockedJIDs.push_back(jid); + } + } + return futureBlockedJIDs; +} + +bool QtBlockListEditorWindow::eventFilter(QObject* target, QEvent* event) { + if (target == ui->blockListTreeWidget) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + if (keyEvent->key() == Qt::Key_Backspace) { + // remove currently selected item + QTreeWidgetItem* currentItem = ui->blockListTreeWidget->currentItem(); + if (currentItem->text(0) != freshBlockListTemplate) { + ui->blockListTreeWidget->takeTopLevelItem(ui->blockListTreeWidget->indexOfTopLevelItem(currentItem)); + return true; + } + } + else if (keyEvent->key() == Qt::Key_Return) { + // open editor for return key d + ui->blockListTreeWidget->editItem(ui->blockListTreeWidget->currentItem(), 0); + return true; + } + } + } + return QWidget::eventFilter(target, event); +} + +} diff --git a/Swift/QtUI/QtBlockListEditorWindow.h b/Swift/QtUI/QtBlockListEditorWindow.h new file mode 100644 index 0000000..dd083fd --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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 QtJIDValidatedItemDelegate; + +class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { + Q_OBJECT + + public: + QtBlockListEditorWindow(); + virtual ~QtBlockListEditorWindow(); + + virtual void show(); + virtual void hide(); + virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs); + virtual void setBusy(bool isBusy); + virtual void setError(const std::string& error); + virtual std::vector<JID> getCurrentBlockList() const; + virtual bool eventFilter(QObject* target, QEvent* event); + + private slots: + void handleItemChanged(QTreeWidgetItem*, int); + void applyChanges(); + + private: + Ui::QtBlockListEditorWindow* ui; + QtRemovableItemDelegate* removeItemDelegate; + QtJIDValidatedItemDelegate* editItemDelegate; + QString freshBlockListTemplate; +}; + +} diff --git a/Swift/QtUI/QtBlockListEditorWindow.ui b/Swift/QtUI/QtBlockListEditorWindow.ui new file mode 100644 index 0000000..a611890 --- /dev/null +++ b/Swift/QtUI/QtBlockListEditorWindow.ui @@ -0,0 +1,114 @@ +<?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>333</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="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> + <widget class="QLabel" name="label"> + <property name="text"> + <string><html><head/><body><p align="justify">The follwing list shows all contacts that you have currently blocked. You can add contacts to the list at the buttom of the list and remove contacts by clicking on the right column.</p></body></html></string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignJustify|Qt::AlignVCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <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> + <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> + <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="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/QtBookmarkDetailWindow.cpp b/Swift/QtUI/QtBookmarkDetailWindow.cpp index ae84b4b..1e84067 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.cpp +++ b/Swift/QtUI/QtBookmarkDetailWindow.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -16,4 +16,5 @@ QtBookmarkDetailWindow::QtBookmarkDetailWindow(QWidget* parent) : QDialog(parent //connect(buttons_, SIGNAL(accepted()), SLOT(accept())); //connect(buttons_, SIGNAL(rejected()), SLOT(reject())); + setFixedHeight(sizeHint().height()); } @@ -50,3 +51,27 @@ boost::optional<MUCBookmark> QtBookmarkDetailWindow::createBookmarkFromForm() { } +void QtBookmarkDetailWindow::createFormFromBookmark(const MUCBookmark& bookmark) { + if (bookmark.getRoom().isValid()) { + room_->setText(P2QSTRING(bookmark.getRoom().toString())); + } + + if (!bookmark.getName().empty()) { + name_->setText(P2QSTRING(bookmark.getName())); + } + + if (bookmark.getNick()) { + nick_->setText(P2QSTRING((*bookmark.getNick()))); + } + + if (bookmark.getPassword()) { + password_->setText(P2QSTRING((*bookmark.getPassword()))); + } + + if (bookmark.getAutojoin()) { + autojoin_->setCheckState(Qt::Checked); + } else { + autojoin_->setCheckState(Qt::Unchecked); + } +} + } diff --git a/Swift/QtUI/QtBookmarkDetailWindow.h b/Swift/QtUI/QtBookmarkDetailWindow.h index fd2b7b4..b223719 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.h +++ b/Swift/QtUI/QtBookmarkDetailWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -23,4 +23,7 @@ namespace Swift { boost::optional<MUCBookmark> createBookmarkFromForm(); + protected: + void createFormFromBookmark(const MUCBookmark& bookmark); + public slots: void accept(); diff --git a/Swift/QtUI/QtBookmarkDetailWindow.ui b/Swift/QtUI/QtBookmarkDetailWindow.ui index 4a37b2f..be55686 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.ui +++ b/Swift/QtUI/QtBookmarkDetailWindow.ui @@ -7,6 +7,6 @@ <x>0</x> <y>0</y> - <width>396</width> - <height>282</height> + <width>382</width> + <height>207</height> </rect> </property> @@ -23,16 +23,24 @@ <bool>false</bool> </property> - <widget class="QWidget" name="layoutWidget"> - <property name="geometry"> - <rect> - <x>10</x> - <y>20</y> - <width>371</width> - <height>241</height> - </rect> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>12</number> + </property> + <property name="topMargin"> + <number>12</number> + </property> + <property name="rightMargin"> + <number>12</number> + </property> + <property name="bottomMargin"> + <number>12</number> </property> + <item> <layout class="QVBoxLayout" name="verticalLayout"> <item> <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> <item row="0" column="0"> <widget class="QLabel" name="label"> @@ -111,5 +119,6 @@ </item> </layout> - </widget> + </item> + </layout> </widget> <resources/> 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,4 +1,4 @@ /* - * 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. @@ -9,4 +9,6 @@ #include <QImage> #include <boost/lexical_cast.hpp> +#include <Swiften/Base/Path.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -16,14 +18,16 @@ 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"); } } diff --git a/Swift/QtUI/QtCertificateViewerDialog.cpp b/Swift/QtUI/QtCertificateViewerDialog.cpp new file mode 100644 index 0000000..15a52ba --- /dev/null +++ b/Swift/QtUI/QtCertificateViewerDialog.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtCertificateViewerDialog.h" +#include "ui_QtCertificateViewerDialog.h" + +#include <Swiften/Base/foreach.h> + +#include <QTreeWidgetItem> +#include <QLabel> +#include <QDateTime> + +namespace Swift { + +QtCertificateViewerDialog::QtCertificateViewerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::QtCertificateViewerDialog) { + ui->setupUi(this); + connect(ui->certChainTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*))); + + setAttribute(Qt::WA_DeleteOnClose); +} + +QtCertificateViewerDialog::~QtCertificateViewerDialog() { + delete ui; +} + +void QtCertificateViewerDialog::setCertificateChain(const std::vector<Certificate::ref>& chain) { + // clean widgets + ui->certChainTreeWidget->clear(); + + if (chain.empty()) return; + + // convert Swift certificate chain to qt certificate chain (root goes first) + currentChain.clear(); + foreach(Certificate::ref cert, chain) { + ByteArray certAsDer = cert->toDER(); + QByteArray dataArray(reinterpret_cast<const char*>(certAsDer.data()), certAsDer.size()); + currentChain.push_front(QSslCertificate(dataArray, QSsl::Der)); + } + + // fill treeWidget + QTreeWidgetItem* root = new QTreeWidgetItem(ui->certChainTreeWidget, QStringList(currentChain.at(0).subjectInfo(QSslCertificate::CommonName))); + root->setData(0, Qt::UserRole, QVariant(0)); + root->setExpanded(true); + ui->certChainTreeWidget->addTopLevelItem(root); + if (currentChain.size() > 1) { + QTreeWidgetItem* parent = root; + for (int n = 1; n < currentChain.size(); n++) { + QTreeWidgetItem* link = new QTreeWidgetItem(parent, QStringList(QString("↳ ") + currentChain.at(n).subjectInfo(QSslCertificate::CommonName))); + link->setExpanded(true); + link->setData(0, Qt::UserRole, QVariant(n)); + parent = link; + } + ui->certChainTreeWidget->setCurrentItem(parent); + } else { + ui->certChainTreeWidget->setCurrentItem(root); + } +} + +void QtCertificateViewerDialog::displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain) { + QtCertificateViewerDialog* dialog = new QtCertificateViewerDialog(parent); + dialog->setCertificateChain(chain); + dialog->show(); +} + +void QtCertificateViewerDialog::currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem*) { + setCertificateDetails(currentChain.at(current->data(0, Qt::UserRole).toInt())); +} + +#define ADD_SECTION( TITLE ) \ + ui->certGridLayout->addWidget(new QLabel("<strong>" + TITLE + "</strong>"), rowCount++, 0, 1, 2); + +#define ADD_FIELD( TITLE, VALUE) \ + ui->certGridLayout->addWidget(new QLabel(TITLE), rowCount, 0, 1, 1, Qt::AlignRight); \ + valueLabel = new QLabel(VALUE); \ + valueLabel->setTextFormat(Qt::PlainText); \ + valueLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); \ + ui->certGridLayout->addWidget(valueLabel, rowCount++, 1, 1, 1, Qt::AlignLeft); + +void QtCertificateViewerDialog::setCertificateDetails(const QSslCertificate& cert) { + QLayoutItem* item; + while ((item = ui->certGridLayout->takeAt(0)) != NULL ) { + delete item->widget(); + delete item; + } + + int rowCount = 0; + + ui->certGridLayout->setColumnStretch(2, 1); + + QLabel* valueLabel = 0; + + ADD_SECTION(tr("General")); + ADD_FIELD(tr("Valid From"), cert.effectiveDate().toString(Qt::TextDate)); + ADD_FIELD(tr("Valid To"), cert.expiryDate().toString(Qt::TextDate)); + ADD_FIELD(tr("Serial Number"), QString(cert.serialNumber().toHex())); + ADD_FIELD(tr("Version"), QString(cert.version())); + + ADD_SECTION(tr("Subject")); + ADD_FIELD(tr("Organization"), cert.subjectInfo(QSslCertificate::Organization)); + ADD_FIELD(tr("Common Name"), cert.subjectInfo(QSslCertificate::CommonName)); + ADD_FIELD(tr("Locality"), cert.subjectInfo(QSslCertificate::LocalityName)); + ADD_FIELD(tr("Organizational Unit"), cert.subjectInfo(QSslCertificate::OrganizationalUnitName)); + ADD_FIELD(tr("Country"), cert.subjectInfo(QSslCertificate::CountryName)); + ADD_FIELD(tr("State"), cert.subjectInfo(QSslCertificate::StateOrProvinceName)); + + if (!cert.alternateSubjectNames().empty()) { + ADD_SECTION(tr("Alternate Subject Names")); + QMultiMap<QSsl::AlternateNameEntryType, QString> altNames = cert.alternateSubjectNames(); + foreach (const QSsl::AlternateNameEntryType &type, altNames.uniqueKeys()) { + foreach (QString name, altNames.values(type)) { + if (type == QSsl::EmailEntry) { + ADD_FIELD(tr("E-mail Address"), name); + } else { + ADD_FIELD(tr("DNS Name"), name); + } + } + } + } + + ADD_SECTION(tr("Issuer")); + ADD_FIELD(tr("Organization"), cert.issuerInfo(QSslCertificate::Organization)); + ADD_FIELD(tr("Common Name"), cert.issuerInfo(QSslCertificate::CommonName)); + ADD_FIELD(tr("Locality"), cert.issuerInfo(QSslCertificate::LocalityName)); + ADD_FIELD(tr("Organizational Unit"), cert.issuerInfo(QSslCertificate::OrganizationalUnitName)); + ADD_FIELD(tr("Country"), cert.issuerInfo(QSslCertificate::CountryName)); + ADD_FIELD(tr("State"), cert.issuerInfo(QSslCertificate::StateOrProvinceName)); + + ui->certGridLayout->setRowStretch(rowCount + 1, 1); +} + +} diff --git a/Swift/QtUI/QtCertificateViewerDialog.h b/Swift/QtUI/QtCertificateViewerDialog.h new file mode 100644 index 0000000..9475a83 --- /dev/null +++ b/Swift/QtUI/QtCertificateViewerDialog.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QDialog> +#include <QList> +#include <QSslCertificate> +#include <QTreeWidgetItem> + +#include <Swiften/TLS/Certificate.h> + +namespace Ui { +class QtCertificateViewerDialog; +} + +namespace Swift { + +class QtCertificateViewerDialog : public QDialog { + Q_OBJECT + + public: + explicit QtCertificateViewerDialog(QWidget* parent = 0); + ~QtCertificateViewerDialog(); + + void setCertificateChain(const std::vector<Certificate::ref>& chain); + + static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); + + private slots: + void currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); + + private: + void setCertificateDetails(const QSslCertificate& cert); + + private: + Ui::QtCertificateViewerDialog *ui; + QList<QSslCertificate> currentChain; +}; + +} diff --git a/Swift/QtUI/QtCertificateViewerDialog.ui b/Swift/QtUI/QtCertificateViewerDialog.ui new file mode 100644 index 0000000..63a96bf --- /dev/null +++ b/Swift/QtUI/QtCertificateViewerDialog.ui @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtCertificateViewerDialog</class> + <widget class="QDialog" name="QtCertificateViewerDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>655</width> + <height>514</height> + </rect> + </property> + <property name="windowTitle"> + <string>Certificate Viewer</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0"> + <item row="3" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QScrollArea" name="scrollArea"> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>629</width> + <height>368</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="certGridLayout"/> + </item> + </layout> + </widget> + </widget> + </item> + <item row="0" column="0"> + <widget class="QTreeWidget" name="certChainTreeWidget"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>70</height> + </size> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="showDropIndicator" stdset="0"> + <bool>false</bool> + </property> + <property name="dragEnabled"> + <bool>false</bool> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>QtCertificateViewerDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>QtCertificateViewerDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp index 9921754..cf335e1 100644 --- a/Swift/QtUI/QtChatTabs.cpp +++ b/Swift/QtUI/QtChatTabs.cpp @@ -23,7 +23,9 @@ 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); #endif @@ -36,4 +38,5 @@ QtChatTabs::QtChatTabs() : QWidget() { /*Closable tabs are only in Qt4.5 and later*/ tabs_->setTabsClosable(true); + tabs_->setMovable(true); connect(tabs_, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int))); #else @@ -45,5 +48,4 @@ QtChatTabs::QtChatTabs() : QWidget() { layout->addWidget(tabs_); setLayout(layout); - //resize(400, 300); } @@ -109,17 +111,21 @@ void QtChatTabs::handleWantsToActivate() { void QtChatTabs::handleTabClosing() { QWidget* widget = qobject_cast<QWidget*>(sender()); - if (!widget) { - return; - } - int index = tabs_->indexOf(widget); - if (index < 0) { - return; - } + int index; + if (widget && ((index = tabs_->indexOf(widget)) >= 0)) { tabs_->removeTab(index); if (tabs_->count() == 0) { + if (!singleWindow_) { hide(); } + else { + setWindowTitle(""); + onTitleChanged(""); + } + } + else { handleTabTitleUpdated(tabs_->currentWidget()); } + } +} void QtChatTabs::handleRequestedPreviousTab() { @@ -177,8 +183,7 @@ 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 && @@ -189,8 +194,6 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { tabText = hasTrailingGarbage.cap(1).simplified(); } - // QTabBar interprets &, so escape that tabText.replace("&", "&&"); - // see which alt[a-z] keys other tabs use bool accelsTaken[26]; @@ -238,5 +241,5 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { 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); @@ -252,5 +255,7 @@ 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); } @@ -271,5 +276,18 @@ void QtChatTabs::moveEvent(QMoveEvent*) { void QtChatTabs::checkForFirstShow() { if (!isVisible()) { +#ifndef Q_OS_MAC showMinimized(); +#else + /* https://bugreports.qt-project.org/browse/QTBUG-19194 + * ^ When the above is fixed we can swap the below for just show(); + * WA_ShowWithoutActivating seems to helpfully not work, so... */ + + QWidget* currentWindow = QApplication::activeWindow(); /* Remember who had focus if we're the current application*/ + show(); + QCoreApplication::processEvents(); /* Run through the eventloop to clear the show() */ + if (currentWindow) { + currentWindow->activateWindow(); /* Set focus back */ + } +#endif } } 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 @@ -18,5 +18,5 @@ namespace Swift { Q_OBJECT public: - QtChatTabs(); + QtChatTabs(bool singleWindow); void addTab(QtTabbable* tab); void minimise(); @@ -24,4 +24,5 @@ namespace Swift { signals: void geometryChanged(); + void onTitleChanged(const QString& title); protected slots: @@ -45,4 +46,5 @@ namespace Swift { 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 @@ -14,18 +14,18 @@ namespace Swift { 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];} diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 54bce09..db4fe51 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -1,371 +1,19 @@ /* - * 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) : QWidget(parent), fontSizeSteps_(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())); -#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())); +QtChatView::QtChatView(QWidget* parent) : QWidget(parent) { - 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::addMessage(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); - } -} - -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_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::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"); -} +QtChatView::~QtChatView() { -void QtChatView::rememberScrolledToBottom() { - isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); -} - -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 (isAtBottom_) { - 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(); - 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(); - QWebElement chatElement = document_.findFirst("#Chat"); - newInsertPoint_ = chatElement.clone(); - newInsertPoint_.setOuterXml("<div id='swift_insert'/>"); - chatElement.appendInside(newInsertPoint_); - Q_ASSERT(!newInsertPoint_.isNull()); - - connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); -} - -QWebElement findDivElementWithID(QWebElement document, QString id) { - QWebElementCollection divs = document.findAll("div"); - foreach(QWebElement div, divs) { - if (div.attribute("id") == id) { - return div; - } - } - return QWebElement(); -} - -void QtChatView::setFileTransferProgress(QString id, const int percentageDone) { - QWebElement ftElement = findDivElementWithID(document_, id); - if (ftElement.isNull()) { - SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl; - return; - } - QWebElement progressBar = ftElement.findFirst("div.progressbar"); - progressBar.setStyleProperty("width", QString::number(percentageDone) + "%"); - - QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value"); - progressBarValue.setInnerXml(QString::number(percentageDone) + " %"); -} - -void QtChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) { - QWebElement ftElement = findDivElementWithID(document_, id); - if (ftElement.isNull()) { - SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id) << std::endl; - return; - } - - QString newInnerHTML = ""; - if (state == ChatWindow::WaitingForAccept) { - newInnerHTML = 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::setMUCInvitationJoined(QString id) { - QWebElement divElement = findDivElementWithID(document_, id); - QWebElement buttonElement = divElement.findFirst("input#mucinvite"); - if (!buttonElement.isNull()) { - buttonElement.setAttribute("value", tr("Return to room")); - } } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index 2e64593..52125b7 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -1,86 +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; - namespace Swift { - class QtWebView; - class QtChatTheme; + class HighlightAction; + class SecurityLabel; + class QtChatView : public QWidget { Q_OBJECT public: - QtChatView(QtChatTheme* theme, QWidget* parent); - void addMessage(boost::shared_ptr<ChatSnippet> snippet); - void addLastSeenLine(); - void replaceLastMessage(const QString& newMessage); - void replaceLastMessage(const QString& newMessage, const QString& note); - void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time); - void rememberScrolledToBottom(); - void setAckXML(const QString& id, const QString& xml); - void setReceiptXML(const QString& id, const QString& xml); - void displayReceiptInfo(const QString& id, bool showIt); + QtChatView(QWidget* parent); + virtual ~QtChatView(); - 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 setMUCInvitationJoined(QString id); + /** 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; - signals: - void gotFocus(); - void fontResized(int); - void logCleared(); + virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; + virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; - public slots: - void copySelectionToClipboard(); - void scrollToBottom(); - void handleLinkClicked(const QUrl&); - void handleKeyPressEvent(QKeyEvent* event); - void resetView(); - void increaseFontSize(int numSteps = 1); - void decreaseFontSize(); - void resizeFont(int fontSizeSteps); + 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, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/) = 0; + virtual void setAckState(const std::string& id, ChatWindow::AckState state) = 0; - private slots: - void handleViewLoadFinished(bool); - void handleFrameSizeChanged(); - void handleClearRequested(); + 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(const std::string& id, const ChatWindow::WhiteboardSessionState state) = 0; + virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0; - private: - void headerEncode(); - void messageEncode(); - void addToDOM(boost::shared_ptr<ChatSnippet> snippet); - QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); + virtual void showEmoticons(bool show) = 0; + virtual void addLastSeenLine() = 0; + + public slots: + virtual void resizeFont(int fontSizeSteps) = 0; + virtual void scrollToBottom() = 0; + virtual void handleKeyPressEvent(QKeyEvent* event) = 0; - bool viewReady_; - bool isAtBottom_; - QtWebView* webView_; - QWebPage* webPage_; - int fontSizeSteps_; - QtChatTheme* theme_; - QWebElement newInsertPoint_; - QWebElement lineSeparator_; - QWebElement lastElement_; - QWebElement document_; }; } - -#endif diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 1a82a66..68104b4 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -1,66 +1,64 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtChatWindow.h" -#include "QtSwiftUtil.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 <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 <QMessageBox> -#include <QInputDialog> +#include <qdebug.h> #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 <QMimeData> + #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> + +#include <SwifTools/TabComplete.h> -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 <Swift/QtUI/Roster/QtOccupantListWidget.h> +#include <Swift/QtUI/QtAddBookmarkWindow.h> +#include <Swift/QtUI/QtPlainChatView.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) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream) { +namespace Swift { + +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), nextAlertId_(0), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) { settings_ = settings; unreadCount_ = 0; - idCounter_ = 0; inputEnabled_ = true; completer_ = NULL; @@ -68,5 +66,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt theme_ = theme; isCorrection_ = false; + labelModel_ = NULL; correctionEnabled_ = Maybe; + fileTransferEnabled_ = Maybe; updateTitleWithUnreadCount(); @@ -81,42 +81,32 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt layout->setSpacing(2); - alertWidget_ = new QWidget(this); - QHBoxLayout* alertLayout = new QHBoxLayout(alertWidget_); - layout->addWidget(alertWidget_); - alertLabel_ = new QLabel(this); - alertLayout->addWidget(alertLabel_); - alertButton_ = new QPushButton(this); - connect (alertButton_, SIGNAL(clicked()), this, SLOT(handleAlertButtonClicked())); - alertLayout->addWidget(alertButton_); - QPalette palette = alertWidget_->palette(); - palette.setColor(QPalette::Window, QColor(Qt::yellow)); - palette.setColor(QPalette::WindowText, QColor(Qt::black)); - alertWidget_->setStyleSheet(alertStyleSheet_); - alertLabel_->setStyleSheet(alertStyleSheet_); - alertWidget_->hide(); + alertLayout_ = new QVBoxLayout(); + layout->addLayout(alertLayout_); - 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); + if (settings_->getSetting(QtUISettingConstants::USE_PLAIN_CHATS) || settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)) { + messageLog_ = new QtPlainChatView(this, eventStream_); + } + else { + 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); + treeWidget_ = new QtOccupantListWidget(eventStream_, settings_, QtTreeWidget::MessageDefaultJID, this); treeWidget_->hide(); logRosterSplitter_->addWidget(treeWidget_); @@ -124,11 +114,11 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt connect(logRosterSplitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int))); - QWidget* midBar = new QWidget(this); - layout->addWidget(midBar); - midBar->setAutoFillBackground(true); - QHBoxLayout *midBarLayout = new QHBoxLayout(midBar); + midBar_ = new QWidget(this); + //layout->addWidget(midBar); + midBar_->setAutoFillBackground(true); + QHBoxLayout *midBarLayout = new QHBoxLayout(midBar_); midBarLayout->setContentsMargins(0,0,0,0); midBarLayout->setSpacing(2); - midBarLayout->addStretch(); + //midBarLayout->addStretch(); labelsWidget_ = new QComboBox(this); @@ -137,14 +127,23 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt labelsWidget_->setSizeAdjustPolicy(QComboBox::AdjustToContents); midBarLayout->addWidget(labelsWidget_,0); + connect(labelsWidget_, SIGNAL(currentIndexChanged(int)), this, SLOT(handleCurrentLabelChanged(int))); + defaultLabelsPalette_ = labelsWidget_->palette(); 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); @@ -159,5 +158,5 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt setFocusProxy(input_); logRosterSplitter_->setFocusProxy(input_); - midBar->setFocusProxy(input_); + midBar_->setFocusProxy(input_); messageLog_->setFocusProxy(input_); connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(qAppFocusChanged(QWidget*, QWidget*))); @@ -170,11 +169,10 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt 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)); + messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); + } QtChatWindow::~QtChatWindow() { - delete jsBridge; if (mucConfigurationWindow_) { delete mucConfigurationWindow_.data(); @@ -182,4 +180,11 @@ QtChatWindow::~QtChatWindow() { } +void QtChatWindow::handleSettingChanged(const std::string& setting) { + if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { + bool showEmoticons = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); + messageLog_->showEmoticons(showEmoticons); + } +} + void QtChatWindow::handleLogCleared() { onLogCleared(); @@ -190,8 +195,4 @@ void QtChatWindow::handleOccupantSelectionChanged(RosterItem* 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); @@ -199,20 +200,48 @@ void QtChatWindow::handleFontResized(int fontSizeSteps) { void QtChatWindow::handleAlertButtonClicked() { - onAlertButtonClicked(); + const QObject* alertWidget = QObject::sender()->parent(); + std::map<AlertID, QWidget*>::const_iterator i = alertWidgets_.begin(); + for ( ; i != alertWidgets_.end(); ++i) { + if (i->second == alertWidget) { + removeAlert(i->first); + break; } - -void QtChatWindow::setAlert(const std::string& alertText, const std::string& buttonText) { - alertLabel_->setText(alertText.c_str()); - if (buttonText.empty()) { - alertButton_->hide(); - } else { - alertButton_->setText(buttonText.c_str()); - alertButton_->show(); } - alertWidget_->show(); } -void QtChatWindow::cancelAlert() { - alertWidget_->hide(); +QtChatWindow::AlertID QtChatWindow::addAlert(const std::string& alertText) { + QWidget* alertWidget = new QWidget(this); + QHBoxLayout* alertLayout = new QHBoxLayout(alertWidget); + alertLayout_->addWidget(alertWidget); + QLabel* alertLabel = new QLabel(this); + alertLabel->setText(alertText.c_str()); + alertLayout->addWidget(alertLabel); + + QToolButton* closeButton = new QToolButton(alertWidget); + closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + closeButton->setIconSize(QSize(16,16)); + closeButton->setCursor(Qt::ArrowCursor); + closeButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); + connect (closeButton, SIGNAL(clicked()), this, SLOT(handleAlertButtonClicked())); + + alertLayout->addWidget(closeButton); + QPalette palette = alertWidget->palette(); + palette.setColor(QPalette::Window, QColor(Qt::yellow)); + palette.setColor(QPalette::WindowText, QColor(Qt::black)); + alertWidget->setStyleSheet(alertStyleSheet_); + alertLabel->setStyleSheet(alertStyleSheet_); + + AlertID id = nextAlertId_++; + alertWidgets_[id] = alertWidget; + return id; +} + +void QtChatWindow::removeAlert(const AlertID id) { + std::map<AlertID, QWidget*>::iterator i = alertWidgets_.find(id); + if (i != alertWidgets_.end()) { + alertLayout_->removeWidget(i->second); + delete i->second; + alertWidgets_.erase(i); + } } @@ -223,5 +252,4 @@ void QtChatWindow::setTabComplete(TabComplete* completer) { void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) { event->ignore(); - QtTabbable::handleKeyPressEvent(event); if (event->isAccepted()) { return; @@ -232,11 +260,15 @@ void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) { if (key == Qt::Key_Tab) { tabComplete(); - } else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) { + } + else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) { beginCorrection(); - } else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) { + } + else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) { cancelCorrection(); - } else if (key == Qt::Key_Down || key == Qt::Key_Up) { + } + else if (key == Qt::Key_Down || key == Qt::Key_Up) { /* Drop */ - } else { + } + else { messageLog_->handleKeyPressEvent(event); } @@ -244,9 +276,19 @@ void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) { void QtChatWindow::beginCorrection() { + boost::optional<AlertID> newCorrectingAlert; if (correctionEnabled_ == ChatWindow::Maybe) { - setAlert(Q2PSTRING(tr("This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message"))); - } else if (correctionEnabled_ == ChatWindow::No) { - setAlert(Q2PSTRING(tr("This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message"))); + newCorrectingAlert = addAlert(Q2PSTRING(tr("This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message"))); + } + else if (correctionEnabled_ == ChatWindow::No) { + newCorrectingAlert = addAlert(Q2PSTRING(tr("This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message"))); } + + if (newCorrectingAlert) { + if (correctingAlert_) { + removeAlert(*correctingAlert_); + } + correctingAlert_ = newCorrectingAlert; + } + QTextCursor cursor = input_->textCursor(); cursor.select(QTextCursor::Document); @@ -257,8 +299,12 @@ void QtChatWindow::beginCorrection() { correctingLabel_->show(); input_->setStyleSheet(alertStyleSheet_); + labelsWidget_->setEnabled(false); } void QtChatWindow::cancelCorrection() { - cancelAlert(); + if (correctingAlert_) { + removeAlert(*correctingAlert_); + correctingAlert_.reset(); + } QTextCursor cursor = input_->textCursor(); cursor.select(QTextCursor::Document); @@ -267,4 +313,5 @@ void QtChatWindow::cancelCorrection() { correctingLabel_->hide(); input_->setStyleSheet(qApp->styleSheet()); + labelsWidget_->setEnabled(true); } @@ -289,7 +336,8 @@ void QtChatWindow::tabComplete() { if (tabCompleteCursor_.hasSelection()) { cursor = tabCompleteCursor_; - } else { + } + else { cursor = input_->textCursor(); - cursor.select(QTextCursor::WordUnderCursor); + while(cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor) && cursor.document()->characterAt(cursor.position() - 1) != ' ') { } } QString root = cursor.selectedText(); @@ -319,15 +367,14 @@ void QtChatWindow::setRosterModel(Roster* roster) { void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) { - availableLabels_ = labels; - labelsWidget_->clear(); + delete labelModel_; + labelModel_ = new LabelModel(); + labelModel_->availableLabels_ = labels; int i = 0; int defaultIndex = 0; + labelsWidget_->setModel(labelModel_); foreach (SecurityLabelsCatalog::Item label, labels) { - std::string selector = label.getSelector(); - std::string displayMarking = label.getLabel() ? label.getLabel()->getDisplayMarking() : ""; - QString labelName = selector.empty() ? displayMarking.c_str() : selector.c_str(); - labelsWidget_->addItem(labelName, QVariant(i)); if (label.getIsDefault()) { defaultIndex = i; + break; } i++; @@ -336,4 +383,25 @@ void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCa } +void QtChatWindow::handleCurrentLabelChanged(int index) { + if (static_cast<size_t>(index) >= labelModel_->availableLabels_.size()) { + qDebug() << "User selected a label that doesn't exist"; + return; + } + const SecurityLabelsCatalog::Item& label = labelModel_->availableLabels_[index]; + if (label.getLabel()) { + QPalette palette = labelsWidget_->palette(); + //palette.setColor(QPalette::Base, P2QSTRING(label.getLabel()->getBackgroundColor())); + palette.setColor(labelsWidget_->backgroundRole(), P2QSTRING(label.getLabel()->getBackgroundColor())); + palette.setColor(labelsWidget_->foregroundRole(), P2QSTRING(label.getLabel()->getForegroundColor())); + labelsWidget_->setPalette(palette); + midBar_->setPalette(palette); + labelsWidget_->setAutoFillBackground(true); + } + else { + labelsWidget_->setAutoFillBackground(false); + labelsWidget_->setPalette(defaultLabelsPalette_); + midBar_->setPalette(defaultLabelsPalette_); + } +} void QtChatWindow::setSecurityLabelsError() { @@ -345,5 +413,6 @@ void QtChatWindow::setSecurityLabelsEnabled(bool enabled) { labelsWidget_->setEnabled(true); labelsWidget_->show(); - } else { + } + else { labelsWidget_->hide(); } @@ -354,8 +423,12 @@ void QtChatWindow::setCorrectionEnabled(Tristate enabled) { } +void QtChatWindow::setFileTransferEnabled(Tristate enabled) { + fileTransferEnabled_ = enabled; +} + SecurityLabelsCatalog::Item QtChatWindow::getSelectedSecurityLabel() { assert(labelsWidget_->isEnabled()); - assert(labelsWidget_->currentIndex() >= 0 && static_cast<size_t>(labelsWidget_->currentIndex()) < availableLabels_.size()); - return availableLabels_[labelsWidget_->currentIndex()]; + assert(labelsWidget_->currentIndex() >= 0 && static_cast<size_t>(labelsWidget_->currentIndex()) < labelModel_->availableLabels_.size()); + return labelModel_->availableLabels_[labelsWidget_->currentIndex()]; } @@ -366,9 +439,10 @@ void QtChatWindow::closeEvent(QCloseEvent* event) { } -void QtChatWindow::convertToMUC() { - setAcceptDrops(false); +void QtChatWindow::convertToMUC(MUCType mucType) { + impromptu_ = (mucType == ImpromptuMUC); + treeWidget_->setMessageTarget(impromptu_ ? QtTreeWidget::MessageDisplayJID : QtTreeWidget::MessageDefaultJID); + isMUC_ = true; treeWidget_->show(); - subject_->show(); - actionButton_->show(); + subject_->setVisible(!impromptu_); } @@ -432,5 +506,6 @@ void QtChatWindow::updateTitleWithUnreadCount() { if (isWindow()) { setWindowTitle(unreadCount_ > 0 ? QString("(%1) %2").arg(unreadCount_).arg(contact_) : contact_); - } else { + } + else { setWindowTitle(contact_); } @@ -438,42 +513,5 @@ void QtChatWindow::updateTitleWithUnreadCount() { } -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(message, senderName, senderIsSelf, label, avatarPath, "", time); -} -std::string QtChatWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time) { - 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; \">").arg(Qt::escape(P2QSTRING(label->getForegroundColor()))).arg(Qt::escape(P2QSTRING(label->getBackgroundColor()))); - htmlString += QString("%3</span> ").arg(Qt::escape(P2QSTRING(label->getDisplayMarking()))); - } - QString messageHTML(Qt::escape(P2QSTRING(message))); - messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); - messageHTML.replace("\n","<br/>"); - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - htmlString += styleSpanStart + messageHTML + styleSpanEnd; - - 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_->addMessage(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() { @@ -481,230 +519,8 @@ void QtChatWindow::flash() { } -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(" *" + 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_->addMessage(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)); -} - -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(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_->addMessage(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(Qt::escape(P2QSTRING(message))); - messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); - messageHTML.replace("\n","<br/>"); - messageLog_->addMessage(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(" *" + message + "*", id, time, "font-style:italic "); -} - -void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { - replaceMessage(message, id, time, ""); -} - -void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style) { - if (!id.empty()) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - - QString messageHTML(Qt::escape(P2QSTRING(message))); - messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); - messageHTML.replace("\n","<br/>"); - - 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(Qt::escape(P2QSTRING(message))); - messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); - messageHTML.replace("\n","<br/>"); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); - - previousMessageKind_ = PreviousMessageWasPresence; -} - void QtChatWindow::returnPressed() { @@ -727,5 +543,6 @@ void QtChatWindow::handleInputChanged() { if (input_->toPlainText().isEmpty()) { onUserCancelsTyping(); - } else { + } + else { onUserTyping(); } @@ -746,4 +563,8 @@ void QtChatWindow::show() { } +bool QtChatWindow::isVisible() const { + return QWidget::isVisible(); +} + void QtChatWindow::activate() { if (isWindow()) { @@ -763,20 +584,42 @@ void QtChatWindow::moveEvent(QMoveEvent*) { void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) { + if (inputEnabled_) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { // TODO: check whether contact actually supports file transfer + if (!isMUC_) { + event->acceptProposedAction(); + } + } + else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list")) { + if (isMUC_ || supportsImpromptuChat_) { event->acceptProposedAction(); } } + } +} void QtChatWindow::dropEvent(QDropEvent *event) { + if (fileTransferEnabled_ == ChatWindow::Yes && event->mimeData()->hasUrls()) { 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."); } + 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-list")) { + QByteArray dataBytes = event->mimeData()->data("application/vnd.swift.contact-jid-list"); + QDataStream dataStream(&dataBytes, QIODevice::ReadOnly); + std::vector<JID> invites; + while (!dataStream.atEnd()) { + QString jidString; + dataStream >> jidString; + invites.push_back(Q2PSTRING(jidString)); + } + onInviteToChat(invites); } - -void QtChatWindow::replaceLastMessage(const std::string& message) { - messageLog_->replaceLastMessage(P2QSTRING(Linkify::linkify(message))); } @@ -788,4 +631,6 @@ void QtChatWindow::setSubject(const std::string& subject) { //subject_->setVisible(!subject.empty()); subject_->setText(P2QSTRING(subject)); + subject_->setToolTip(P2QSTRING(subject)); + subject_->setCursorPosition(0); } @@ -798,16 +643,64 @@ void QtChatWindow::handleActionButtonClicked() { QAction* invite = NULL; + QAction* block = NULL; + QAction* unblock = NULL; + + if (availableRoomActions_.empty()) { + if (blockingState_ == IsBlocked) { + unblock = contextMenu.addAction(tr("Unblock")); + unblock->setEnabled(inputEnabled_); + } + else if (blockingState_ == IsUnblocked) { + block = contextMenu.addAction(tr("Block")); + block->setEnabled(inputEnabled_); + } + + if (supportsImpromptuChat_) { + invite = contextMenu.addAction(tr("Invite person to this chat…")); + invite->setEnabled(inputEnabled_); + } + + } + else { foreach(ChatWindow::RoomAction availableAction, availableRoomActions_) { + 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; + case ChatWindow::ChangeSubject: + changeSubject = contextMenu.addAction(tr("Change subject…")); + changeSubject->setEnabled(inputEnabled_); + break; + case ChatWindow::Configure: + configure = contextMenu.addAction(tr("Configure room…")); + configure->setEnabled(inputEnabled_); + break; + case ChatWindow::Affiliations: + affiliations = contextMenu.addAction(tr("Edit affiliations…")); + affiliations->setEnabled(inputEnabled_); + break; + case ChatWindow::Destroy: + destroy = contextMenu.addAction(tr("Destroy room")); + destroy->setEnabled(inputEnabled_); + break; + case ChatWindow::Invite: + invite = contextMenu.addAction(tr("Invite person to this room…")); + invite->setEnabled(inputEnabled_); + break; + } } } + QAction* bookmark = contextMenu.addAction(tr("Add boomark...")); + bookmark->setEnabled(inputEnabled_); + QAction* result = contextMenu.exec(QCursor::pos()); if (result == NULL) { @@ -844,9 +737,14 @@ void QtChatWindow::handleActionButtonClicked() { } else if (result == invite) { - bool ok; - QString jid = QInputDialog::getText(this, tr("Enter person's address"), tr("Address:"), QLineEdit::Normal, "", &ok); - if (ok) { - onInvitePersonToThisMUCRequest(JID(Q2PSTRING(jid)), ""); + onInviteToChat(std::vector<JID>()); + } + else if (result == block) { + onBlockUserRequest(); } + else if (result == unblock) { + onUnblockUserRequest(); + } + else if (result == bookmark) { + onBookmarkRequest(); } } @@ -861,9 +759,21 @@ void QtChatWindow::setAffiliations(MUCOccupant::Affiliation affiliation, const s } -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::showBookmarkWindow(const MUCBookmark& bookmark) { + QtAddBookmarkWindow* window = new QtAddBookmarkWindow(eventStream_, bookmark); + window->show(); +} + void QtChatWindow::showRoomConfigurationForm(Form::ref form) { if (mucConfigurationWindow_) { @@ -875,36 +785,89 @@ void QtChatWindow::showRoomConfigurationForm(Form::ref form) { } -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/>"; +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); } - if (!direct) { - htmlString += QObject::tr("This person may not have really sent this invitation!") + "<br/>"; + +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); } - QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); +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); +} - htmlString += "<div id='" + id + "'>" + - buildChatWindowButton(tr("Accept Invite"), ButtonMUCInvite, Qt::escape(P2QSTRING(jid.toString())), Qt::escape(P2QSTRING(password)), id) + - "</div>"; - 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; +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); } - QString qAvatarPath = "qrc:/icons/avatar.png"; - messageLog_->addMessage(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::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, const TimestampBehaviour timestampBehaviour) { + messageLog_->replaceLastMessage(message, timestampBehaviour); +} + +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 4b888eb..bf37557 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,15 +7,21 @@ #pragma once -#include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/QtUI/QtMUCConfigurationWindow.h> -#include <Swift/QtUI/QtAffiliationEditor.h> +#include <map> -#include <QtTabbable.h> +#include <QPointer> +#include <QTextCursor> +#include <QMap> #include <SwifTools/LastLineTracker.h> -#include <map> -#include <QPointer> -#include <QTextCursor> +#include <Swift/Controllers/UIInterfaces/ChatWindow.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> + + class QTextEdit; @@ -36,24 +42,51 @@ namespace Swift { class SettingsProvider; - class QtChatWindow : public QtTabbable, public ChatWindow { + class LabelModel : public QAbstractListModel { Q_OBJECT - public: - static const QString ButtonFileTransferCancel; - static const QString ButtonFileTransferSetDescription; - static const QString ButtonFileTransferSendRequest; - static const QString ButtonFileTransferAcceptRequest; - static const QString ButtonMUCInvite; + LabelModel(QObject* parent = NULL) : QAbstractListModel(parent) {} + + virtual int rowCount(const QModelIndex& /*index*/) const { + return static_cast<int>(availableLabels_.size()); + } + + virtual QVariant data(const QModelIndex& index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + boost::shared_ptr<SecurityLabel> label = availableLabels_[index.row()].getLabel(); + if (label && role == Qt::TextColorRole) { + return P2QSTRING(label->getForegroundColor()); + } + if (label && role == Qt::TextColorRole) { + return P2QSTRING(label->getBackgroundColor()); + } + if (role == Qt::DisplayRole) { + std::string selector = availableLabels_[index.row()].getSelector(); + std::string displayMarking = label ? label->getDisplayMarking() : ""; + QString labelName = selector.empty() ? displayMarking.c_str() : selector.c_str(); + return labelName; + } + return QVariant(); + } + + std::vector<SecurityLabelsCatalog::Item> availableLabels_; + }; + + class QtChatWindow : public QtTabbable, public ChatWindow { + Q_OBJECT public: 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); @@ -61,8 +94,12 @@ namespace Swift { void setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg); + std::string addWhiteboardRequest(bool senderIsSelf); + void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state); + void show(); + bool isVisible() const; void activate(); void setUnreadMessageCount(int count); - void convertToMUC(); + void convertToMUC(MUCType mucType); // TreeWidget *getTreeWidget(); void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels); @@ -77,5 +114,5 @@ namespace Swift { void setTabComplete(TabComplete* completer); int getCount(); - void replaceLastMessage(const std::string& message); + void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour timestampBehaviour); void setAckState(const std::string& id, AckState state); @@ -88,16 +125,18 @@ namespace Swift { 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); - - static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString()); + void setBlockingState(BlockingState state); + virtual void setCanInitiateImpromptuChats(bool supportsImpromptu); + virtual void showBookmarkWindow(const MUCBookmark& bookmark); public slots: void handleChangeSplitterState(QByteArray state); void handleFontResized(int fontSizeSteps); - void setAlert(const std::string& alertText, const std::string& buttonText = ""); - void cancelAlert(); + AlertID addAlert(const std::string& alertText); + void removeAlert(const AlertID id); void setCorrectionEnabled(Tristate enabled); + void setFileTransferEnabled(Tristate enabled); signals: @@ -127,17 +166,6 @@ namespace Swift { void handleAlertButtonClicked(); void handleActionButtonClicked(); - - void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3); void handleAffiliationEditorAccepted(); - - private: - enum PreviousMessageKind { - PreviosuMessageWasNone, - PreviousMessageWasMessage, - PreviousMessageWasSystem, - PreviousMessageWasPresence, - PreviousMessageWasFileTransfer, - PreviousMessageWasMUCInvite - }; + void handleCurrentLabelChanged(int); private: @@ -146,8 +174,9 @@ namespace Swift { void beginCorrection(); void cancelCorrection(); - std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time); - void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style); + void handleSettingChanged(const std::string& setting); + void handleOccupantSelectionChanged(RosterItem* item); - bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const; + void handleAppendedToLog(); + int unreadCount_; @@ -160,18 +189,16 @@ namespace Swift { QtChatTheme* theme_; QtTextEdit* input_; + QWidget* midBar_; + QBoxLayout* subjectLayout_; QComboBox* labelsWidget_; QtOccupantListWidget* treeWidget_; QLabel* correctingLabel_; - QLabel* alertLabel_; - QWidget* alertWidget_; - QPushButton* alertButton_; + boost::optional<AlertID> correctingAlert_; + QVBoxLayout* alertLayout_; + std::map<AlertID, QWidget*> alertWidgets_; + AlertID nextAlertId_; TabComplete* completer_; QLineEdit* subject_; - QPushButton* actionButton_; - std::vector<SecurityLabelsCatalog::Item> availableLabels_; bool isCorrection_; - bool previousMessageWasSelf_; - PreviousMessageKind previousMessageKind_; - QString previousSenderName_; bool inputClearing_; bool tabCompletion_; @@ -180,12 +207,16 @@ namespace Swift { QSplitter *logRosterSplitter_; Tristate correctionEnabled_; + Tristate fileTransferEnabled_; QString alertStyleSheet_; - std::map<QString, QString> descriptions; - QtChatWindowJSBridge* jsBridge; QPointer<QtMUCConfigurationWindow> mucConfigurationWindow_; QPointer<QtAffiliationEditor> affiliationEditor_; - int idCounter_; SettingsProvider* settings_; std::vector<ChatWindow::RoomAction> availableRoomActions_; + 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 7610082..78c04c9 100644 --- a/Swift/QtUI/QtChatWindowFactory.cpp +++ b/Swift/QtUI/QtChatWindowFactory.cpp @@ -5,14 +5,15 @@ */ -#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 { @@ -21,5 +22,5 @@ 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) : themePath_(themePath) { +QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) { qtOnlySettings_ = qtSettings; settings_ = settings; diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h index 2a16c3b..63da514 100644 --- a/Swift/QtUI/QtChatWindowFactory.h +++ b/Swift/QtUI/QtChatWindowFactory.h @@ -7,10 +7,12 @@ #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; @@ -18,8 +20,9 @@ namespace Swift { 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); + QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath); ~QtChatWindowFactory(); ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); 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 @@ -21,5 +21,5 @@ public: 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/QtClosableLineEdit.cpp b/Swift/QtUI/QtClosableLineEdit.cpp new file mode 100644 index 0000000..512194b --- /dev/null +++ b/Swift/QtUI/QtClosableLineEdit.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* Contains demo Trolltech code from http://git.forwardbias.in/?p=lineeditclearbutton.git with license: */ +/**************************************************************************** +** +** Copyright (c) 2007 Trolltech ASA <info@trolltech.com> +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#include <Swift/QtUI/QtClosableLineEdit.h> +#include <QApplication> +#include <QToolButton> +#include <QStyle> +#include <QKeyEvent> + +namespace Swift { + +const int QtClosableLineEdit::clearButtonPadding = 2; + +QtClosableLineEdit::QtClosableLineEdit(QWidget *parent) : QLineEdit(parent) { + clearButton = new QToolButton(this); + clearButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + clearButton->setIconSize(QSize(16,16)); + clearButton->setCursor(Qt::ArrowCursor); + clearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); + clearButton->hide(); + connect(clearButton, SIGNAL(clicked()), this, SLOT(handleCloseButtonClicked())); + connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateCloseButton(const QString&))); + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + setStyleSheet(QString("QLineEdit { padding-right: %1px; } ").arg(clearButton->sizeHint().width() + frameWidth + 1)); + QSize minimumSize = minimumSizeHint(); + setMinimumSize(qMax(minimumSize.width(), clearButton->sizeHint().width() + frameWidth * 2 + clearButtonPadding), + qMax(minimumSize.height(), clearButton->sizeHint().height() + frameWidth * 2 + clearButtonPadding)); +} + +void QtClosableLineEdit::resizeEvent(QResizeEvent *) { + QSize size = clearButton->sizeHint(); + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + clearButton->move(rect().right() - frameWidth - size.width(), (rect().bottom() + 1 - size.height())/2); +} + +void QtClosableLineEdit::updateCloseButton(const QString& text) { + clearButton->setVisible(!text.isEmpty()); +} + +void QtClosableLineEdit::handleCloseButtonClicked() { + clear(); + QApplication::postEvent(this, new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier)); + QApplication::postEvent(this, new QKeyEvent(QEvent::KeyRelease, Qt::Key_Escape, Qt::NoModifier)); +} + +} diff --git a/Swift/QtUI/QtClosableLineEdit.h b/Swift/QtUI/QtClosableLineEdit.h new file mode 100644 index 0000000..91b4f0e --- /dev/null +++ b/Swift/QtUI/QtClosableLineEdit.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* Contains demo Trolltech code from http://git.forwardbias.in/?p=lineeditclearbutton.git with license: */ +/**************************************************************************** +** +** Copyright (c) 2007 Trolltech ASA <info@trolltech.com> +** +** Use, modification and distribution is allowed without limitation, +** warranty, liability or support of any kind. +** +****************************************************************************/ + +#pragma once + +#include <QLineEdit> + +class QToolButton; + +namespace Swift { + +class QtClosableLineEdit : public QLineEdit +{ + Q_OBJECT + public: + QtClosableLineEdit(QWidget *parent = 0); + + protected: + void resizeEvent(QResizeEvent *); + + private slots: + void updateCloseButton(const QString &text); + void handleCloseButtonClicked(); + + private: + static const int clearButtonPadding; + QToolButton *clearButton; +}; + +} 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/QtConnectionSettings.ui b/Swift/QtUI/QtConnectionSettings.ui new file mode 100644 index 0000000..2dc46d1 --- /dev/null +++ b/Swift/QtUI/QtConnectionSettings.ui @@ -0,0 +1,529 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtConnectionSettings</class> + <widget class="QDialog" name="QtConnectionSettings"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>406</width> + <height>454</height> + </rect> + </property> + <property name="windowTitle"> + <string>Connection Options</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Connection Method:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="connectionMethod"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <item> + <property name="text"> + <string>Automatic</string> + </property> + </item> + <item> + <property name="text"> + <string>Manual</string> + </property> + </item> + <item> + <property name="text"> + <string>BOSH</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>238</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="page_3"/> + <widget class="QWidget" name="page_2"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <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> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Secure connection:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="manual_useTLS"> + <item> + <property name="text"> + <string>Never</string> + </property> + </item> + <item> + <property name="text"> + <string>Encrypt when possible</string> + </property> + </item> + <item> + <property name="text"> + <string>Always encrypt</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="manual_allowCompression"> + <property name="text"> + <string>Allow Compression</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="manual_allowPLAINWithoutTLS"> + <property name="text"> + <string>Allow sending password over insecure connection</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>4</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QCheckBox" name="manual_manualHost"> + <property name="text"> + <string>Manually select server</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <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>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="manual_manualHostNameLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualHostName"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="manual_manualHostPortLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualHostPort"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Connection Proxy</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_8"> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Proxy type:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="manual_proxyType"> + <property name="currentIndex"> + <number>1</number> + </property> + <item> + <property name="text"> + <string>None</string> + </property> + </item> + <item> + <property name="text"> + <string>Use system-configured proxy</string> + </property> + </item> + <item> + <property name="text"> + <string>SOCKS5</string> + </property> + </item> + <item> + <property name="text"> + <string>HTTP Connect</string> + </property> + </item> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_7"> + <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> + <widget class="QCheckBox" name="manual_manualProxy"> + <property name="text"> + <string>Override system-configured proxy</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="manual_manualProxyHostLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualProxyHost"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="manual_manualProxyPortLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="manual_manualProxyPort"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </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>88</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_4"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QLabel" name="bosh_uriLabel"> + <property name="text"> + <string>BOSH URI:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="bosh_uri"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="bosh_manualProxy"> + <property name="text"> + <string>Manually select HTTP proxy</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>18</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="bosh_manualProxyHostLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Hostname:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="bosh_manualProxyHost"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="bosh_manualProxyPortLabel"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="bosh_manualProxyPort"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>80</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Connection options will be saved when next connecting to this account.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>QtConnectionSettings</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/Swift/QtUI/QtConnectionSettingsWindow.cpp b/Swift/QtUI/QtConnectionSettingsWindow.cpp new file mode 100644 index 0000000..737a79c --- /dev/null +++ b/Swift/QtUI/QtConnectionSettingsWindow.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/QtUI/QtConnectionSettingsWindow.h" + +#include <boost/lexical_cast.hpp> + +#include <QCoreApplication> +#include <QIcon> +#include <QLabel> +#include <QVBoxLayout> +#include <QtGlobal> +#include <QPushButton> +#include <QTextEdit> +#include <QFile> +#include <QTextStream> +#include <QMessageBox> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtURLValidator.h> + +namespace Swift { + +QtConnectionSettingsWindow::QtConnectionSettingsWindow(const ClientOptions& options) : QDialog() { + ui.setupUi(this); + + connect(ui.connectionMethod, SIGNAL(currentIndexChanged(int)), ui.stackedWidget, SLOT(setCurrentIndex(int))); + + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostNameLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostName, SLOT(setEnabled(bool))); + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostPortLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualHost, SIGNAL(toggled(bool)), ui.manual_manualHostPort, SLOT(setEnabled(bool))); + + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyHostLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyHost, SLOT(setEnabled(bool))); + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyPortLabel, SLOT(setEnabled(bool))); + connect(ui.manual_manualProxy, SIGNAL(toggled(bool)), ui.manual_manualProxyPort, SLOT(setEnabled(bool))); + + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyHostLabel, SLOT(setEnabled(bool))); + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyHost, SLOT(setEnabled(bool))); + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyPortLabel, SLOT(setEnabled(bool))); + connect(ui.bosh_manualProxy, SIGNAL(toggled(bool)), ui.bosh_manualProxyPort, SLOT(setEnabled(bool))); + + connect(ui.manual_proxyType, SIGNAL(currentIndexChanged(int)), SLOT(handleProxyTypeChanged(int))); + + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(handleAcceptRequested())); + + QtURLValidator* urlValidator = new QtURLValidator(this); + ui.bosh_uri->setValidator(urlValidator); + + ui.manual_useTLS->setCurrentIndex(2); + + ui.manual_proxyType->setCurrentIndex(0); + + ClientOptions defaults; + if (options.boshURL.isEmpty()) { + bool isDefault = options.useStreamCompression == defaults.useStreamCompression; + isDefault &= options.useTLS == defaults.useTLS; + isDefault &= options.allowPLAINWithoutTLS == defaults.allowPLAINWithoutTLS; + isDefault &= options.useStreamCompression == defaults.useStreamCompression; + isDefault &= options.useAcks == defaults.useAcks; + isDefault &= options.manualHostname == defaults.manualHostname; + isDefault &= options.manualPort == defaults.manualPort; + isDefault &= options.proxyType == defaults.proxyType; + isDefault &= options.manualProxyHostname == defaults.manualProxyHostname; + isDefault &= options.manualProxyPort == defaults.manualProxyPort; + if (isDefault) { + ui.connectionMethod->setCurrentIndex(0); + } + else { + ui.connectionMethod->setCurrentIndex(1); + ui.manual_useTLS->setCurrentIndex(options.useTLS); + ui.manual_allowPLAINWithoutTLS->setChecked(options.allowPLAINWithoutTLS); + ui.manual_allowCompression->setChecked(options.useStreamCompression); + if (!options.manualHostname.empty()) { + ui.manual_manualHost->setChecked(true); + ui.manual_manualHostName->setText(P2QSTRING(options.manualHostname)); + if (options.manualPort >=0) { + ui.manual_manualHostPort->setText(P2QSTRING(boost::lexical_cast<std::string>(options.manualPort))); + } + } + ui.manual_proxyType->setCurrentIndex(options.proxyType); + if (!options.manualProxyHostname.empty()) { + ui.manual_manualProxy->setChecked(true); + ui.manual_manualProxyHost->setText(P2QSTRING(options.manualProxyHostname)); + ui.manual_manualProxyPort->setText(P2QSTRING(boost::lexical_cast<std::string>(options.manualProxyPort))); + } + } + } else { + ui.connectionMethod->setCurrentIndex(2); + ui.bosh_uri->setText(P2QSTRING(options.boshURL.toString())); + if (!options.boshHTTPConnectProxyURL.isEmpty()) { + ui.bosh_manualProxy->setChecked(true); + ui.bosh_manualProxyHost->setText(P2QSTRING(options.boshHTTPConnectProxyURL.getHost())); + if (options.boshHTTPConnectProxyURL.getPort()) { + ui.bosh_manualProxyPort->setText(P2QSTRING(boost::lexical_cast<std::string>(*options.boshHTTPConnectProxyURL.getPort()))); + } + } + } +} + +void QtConnectionSettingsWindow::handleProxyTypeChanged(int index) { + bool proxySettingsVisible = index != NoProxy && index != SystemProxy; + ui.manual_manualProxy->setVisible(proxySettingsVisible); + ui.manual_manualProxyHostLabel->setVisible(proxySettingsVisible); + ui.manual_manualProxyHost->setVisible(proxySettingsVisible); + ui.manual_manualProxyPortLabel->setVisible(proxySettingsVisible); + ui.manual_manualProxyPort->setVisible(proxySettingsVisible); +} + +void QtConnectionSettingsWindow::handleAcceptRequested() { + if (ui.connectionMethod->currentIndex() != 2 || ui.bosh_uri->hasAcceptableInput()) { + accept(); + } + else { + QMessageBox::critical(this, tr("Configuration invalid"), tr("The provided BOSH URL is not valid.")); + } +} + +ClientOptions QtConnectionSettingsWindow::getOptions() { + ClientOptions options; + if (ui.connectionMethod->currentIndex() > 0) { + /* Not automatic */ + if (ui.connectionMethod->currentIndex() == 1) { + /* Manual */ + options.useTLS = static_cast<ClientOptions::UseTLS>(ui.manual_useTLS->currentIndex()); + options.useStreamCompression = ui.manual_allowCompression->isChecked(); + options.allowPLAINWithoutTLS = ui.manual_allowPLAINWithoutTLS->isChecked(); + if (ui.manual_manualHost->isChecked()) { + options.manualHostname = Q2PSTRING(ui.manual_manualHostName->text()); + try { + options.manualPort = boost::lexical_cast<int>(Q2PSTRING(ui.manual_manualHostPort->text())); + } catch (const boost::bad_lexical_cast&) { + options.manualPort = -1; + } + } + options.proxyType = static_cast<ClientOptions::ProxyType>(ui.manual_proxyType->currentIndex()); + if (ui.manual_manualProxy->isChecked()) { + options.manualProxyHostname = Q2PSTRING(ui.manual_manualProxyHost->text()); + try { + options.manualProxyPort = boost::lexical_cast<int>(Q2PSTRING(ui.manual_manualProxyPort->text())); + } catch (const boost::bad_lexical_cast&) {} + } + } + else { + /* BOSH */ + options.boshURL = URL::fromString(Q2PSTRING(ui.bosh_uri->text())); + if (ui.bosh_manualProxy->isChecked()) { + std::string host = Q2PSTRING(ui.bosh_manualProxyHost->text()); + try { + int port = boost::lexical_cast<int>(Q2PSTRING(ui.bosh_manualProxyPort->text())); + options.boshHTTPConnectProxyURL = URL("http", host, port, ""); + } catch (const boost::bad_lexical_cast&) { + options.boshHTTPConnectProxyURL = URL("http", host, ""); + } + } + } + } + return options; +} + +} diff --git a/Swift/QtUI/QtConnectionSettingsWindow.h b/Swift/QtUI/QtConnectionSettingsWindow.h new file mode 100644 index 0000000..3b017ab --- /dev/null +++ b/Swift/QtUI/QtConnectionSettingsWindow.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QDialog> + +#include "ui_QtConnectionSettings.h" + +#include <Swiften/Client/ClientOptions.h> + +namespace Swift { + class QtConnectionSettingsWindow : public QDialog { + Q_OBJECT + + public: + QtConnectionSettingsWindow(const ClientOptions& options); + + ClientOptions getOptions(); + + private slots: + void handleProxyTypeChanged(int); + void handleAcceptRequested(); + + private: + enum { + NoProxy = 0, + SystemProxy = 1, + SOCKS5Proxy = 2, + HTTPProxy = 3 + }; + Ui::QtConnectionSettings ui; + }; +} diff --git a/Swift/QtUI/QtContactEditWidget.cpp b/Swift/QtUI/QtContactEditWidget.cpp index 9d071b9..a347a00 100644 --- a/Swift/QtUI/QtContactEditWidget.cpp +++ b/Swift/QtUI/QtContactEditWidget.cpp @@ -53,6 +53,7 @@ QtContactEditWidget::QtContactEditWidget(const std::set<std::string>& allGroups, foreach (std::string group, allGroups) { + QString groupName = doubleAmpersand(group); QCheckBox* check = new QCheckBox(groups); - check->setText(P2QSTRING(group)); + check->setText(groupName); check->setCheckState(Qt::Unchecked); checkBoxes_[group] = check; @@ -84,5 +85,5 @@ std::string QtContactEditWidget::getName() const { name = Q2PSTRING(name_->text()); } else { - name = Q2PSTRING(button->text()); + name = singleAmpersand(button->text()); } break; @@ -116,5 +117,5 @@ void QtContactEditWidget::setNameSuggestions(const std::vector<std::string>& sug foreach(const std::string& name, suggestions) { - suggestionsLayout_->insertWidget(nameLayout_->count() - 2, new QRadioButton(P2QSTRING(name), this)); + suggestionsLayout_->insertWidget(nameLayout_->count() - 2, new QRadioButton(doubleAmpersand(name), this)); } @@ -136,4 +137,13 @@ void QtContactEditWidget::setNameSuggestions(const std::vector<std::string>& sug } } +QString QtContactEditWidget::doubleAmpersand(const std::string& name) const { + return P2QSTRING(name).replace("&", "&&"); + +} + +std::string QtContactEditWidget::singleAmpersand(const QString& name) const { + return Q2PSTRING(QString(name).replace("&&", "&")); +} + void QtContactEditWidget::clear() { diff --git a/Swift/QtUI/QtContactEditWidget.h b/Swift/QtUI/QtContactEditWidget.h index 5350762..6d55b80 100644 --- a/Swift/QtUI/QtContactEditWidget.h +++ b/Swift/QtUI/QtContactEditWidget.h @@ -39,4 +39,8 @@ namespace Swift { void clear(); + + private: + QString doubleAmpersand(const std::string& name) const; + std::string singleAmpersand(const QString& name) const; private: typedef std::map<std::string, QCheckBox*> CheckBoxMap; diff --git a/Swift/QtUI/QtContactEditWindow.cpp b/Swift/QtUI/QtContactEditWindow.cpp index 6520c0a..c175934 100644 --- a/Swift/QtUI/QtContactEditWindow.cpp +++ b/Swift/QtUI/QtContactEditWindow.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -76,6 +76,7 @@ void QtContactEditWindow::setEnabled(bool b) { void QtContactEditWindow::show() { - QWidget::show(); + QWidget::showNormal(); QWidget::activateWindow(); + QWidget::raise(); } diff --git a/Swift/QtUI/QtFileTransferListItemModel.cpp b/Swift/QtUI/QtFileTransferListItemModel.cpp index cf1de07..b9b9fd1 100644 --- a/Swift/QtUI/QtFileTransferListItemModel.cpp +++ b/Swift/QtUI/QtFileTransferListItemModel.cpp @@ -11,11 +11,11 @@ #include <Swiften/Base/boost_bsignals.h> +#include <Swiften/Base/FileSize.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) { } @@ -66,9 +66,12 @@ QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) c } 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")); @@ -92,5 +95,5 @@ QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) c } if (index.column() == OverallSize) { - return QVariant(QString::fromStdString(formatSize((controller->getSize())))); + return QVariant(P2QSTRING(formatSize((controller->getSize())))); } return QVariant(); @@ -106,5 +109,5 @@ 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, static_cast<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 @@ -35,5 +35,5 @@ private: Progress, OverallSize, - NoOfColumns, + NoOfColumns }; 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 @@ -5,4 +5,10 @@ */ +/* + * 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" @@ -36,5 +42,5 @@ QVariant QtFormResultItemModel::headerData(int section, Qt::Orientation /*orient 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())); } @@ -70,13 +76,14 @@ const std::string QtFormResultItemModel::getFieldValue(const Form::FormItem& ite 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 158bc9d..b4840e9 100644 --- a/Swift/QtUI/QtFormWidget.cpp +++ b/Swift/QtUI/QtFormWidget.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -18,4 +18,6 @@ #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 { @@ -36,6 +38,8 @@ QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), f QWidget* scroll = new QWidget(this); QGridLayout* layout = new QGridLayout(scroll); - foreach (boost::shared_ptr<FormField> field, form->getFields()) { - QWidget* widget = createWidget(field); + const std::vector<Form::FormItem> items = form->getItems(); + if (items.empty()) { /* single item forms */ + foreach (FormField::ref field, form->getFields()) { + QWidget* widget = createWidget(field, field->getType(), 0); if (widget) { layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0); @@ -43,6 +47,21 @@ QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), f } } + } else { /* multi-item forms */ + const Form::FormItem& headers = form->getFields(); + for (size_t i = 0; i < items.size(); ++i) { + const Form::FormItem& item = items[i]; + assert(item.size() == headers.size()); + for (size_t j = 0; j < item.size(); ++j) { + QWidget* widget = createWidget(item[j], headers[j]->getType(), i); + if (widget) { + layout->addWidget(new QLabel(item[j]->getLabel().c_str(), this), row, 0); + layout->addWidget(widget, row++, 1); + } + } + } + } scrollArea->setWidget(scroll); scrollArea->setWidgetResizable(true); + setEditable(form->getType() != Form::CancelType && form->getType() != Form::ResultType); } @@ -54,18 +73,23 @@ 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; + /* if this is an editable form, use the 'options' list, otherwise use the 'values' list */ + if (form_->getType() != Form::FormType) { + foreach (const std::string& value, field->getValues()) { + listWidget->addItem(P2QSTRING(value)); + selected.push_back(false); + } + } else { 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()); + } } - } for (int i = 0; i < listWidget->count(); i++) { @@ -76,64 +100,53 @@ QListWidget* QtFormWidget::createList(FormField::ref field) { } -QWidget* QtFormWidget::createWidget(FormField::ref field) { +QWidget* QtFormWidget::createWidget(FormField::ref field, const FormField::Type type, const size_t index) { QWidget* widget = NULL; - boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field); - if (booleanField) { + if (type == 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 (type == 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 (type == FormField::ListSingleType) { widget = createList(field); } - boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field); - if (textMultiField) { - QString value = textMultiField->getValue().c_str(); - widget = new QTextEdit(value, this); + if (type == 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 (type == 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 (type == 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 (type == 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 (type == FormField::JIDMultiType) { + QString text = boost::join(field->getValues(), "\n").c_str(); + QTextEdit* textWidget = new QTextEdit(this); + textWidget->setPlainText(text); + widget = textWidget; } - widget = new QTextEdit(text, this); - } - boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field); - if (listMultiField) { + if (type == FormField::ListMultiType) { widget = createList(field); } - boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field); - if (hiddenField) { + std::string indexString; + if (index) { + /* for multi-item forms we need to distinguish between the different rows */ + indexString = boost::lexical_cast<std::string>(index); } - fields_[field->getName()] = widget; + fields_[field->getName() + indexString] = widget; return widget; } @@ -142,97 +155,47 @@ 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))); + if (!string.isEmpty()) { + resultField->setTextMultiValue(Q2PSTRING(string)); } } - boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field); - if (textPrivateField) { + 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(TextPrivateFormField::create()); + if (!string.isEmpty()) { + resultField->addValue(Q2PSTRING(string)); } - else { - resultField = FormField::ref(TextPrivateFormField::create(Q2PSTRING(string))); } - } - boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field); - if (textSingleField) { - QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]); - QString string = widget->text(); - if (string.isEmpty()) { - resultField = FormField::ref(TextSingleFormField::create()); - } - else { - resultField = FormField::ref(TextSingleFormField::create(Q2PSTRING(string))); - } - } - boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field); - if (jidSingleField) { - QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]); - QString string = widget->text(); - JID jid(Q2PSTRING(string)); - if (string.isEmpty()) { - resultField = FormField::ref(JIDSingleFormField::create()); - } - else { - resultField = FormField::ref(JIDSingleFormField::create(jid)); - } - } - boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field); - if (jidMultiField) { + 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 = FormField::ref(ListMultiFormField::create(values)); + resultField->addValue(field->getOptions()[listWidget->row(item)].value); } - boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field); - if (hiddenField) { - resultField = FormField::ref(HiddenFormField::create(hiddenField->getValue())); } resultField->setName(field->getName()); diff --git a/Swift/QtUI/QtFormWidget.h b/Swift/QtUI/QtFormWidget.h index c7aae73..f58ff4b 100644 --- a/Swift/QtUI/QtFormWidget.h +++ b/Swift/QtUI/QtFormWidget.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -24,5 +24,5 @@ class QtFormWidget : public QWidget { void setEditable(bool editable); private: - QWidget* createWidget(FormField::ref field); + QWidget* createWidget(FormField::ref field, const FormField::Type type, const size_t index); QListWidget* createList(FormField::ref field); template<class T> void setEnabled(QWidget* rawWidget, bool editable); diff --git a/Swift/QtUI/QtHighlightEditor.cpp b/Swift/QtUI/QtHighlightEditor.cpp new file mode 100644 index 0000000..97774be --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.cpp @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cassert> + +#include <boost/lexical_cast.hpp> + +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> +#include <Swift/Controllers/HighlightManager.cpp> +#include <Swift/QtUI/QtHighlightEditor.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSettingsProvider.h> + +#include <QTreeWidgetItem> +#include <QFileDialog> + +namespace Swift { + +QtHighlightEditor::QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent) + : QWidget(parent), settings_(settings), previousRow_(-1) +{ + ui_.setupUi(this); + + connect(ui_.listWidget, SIGNAL(currentRowChanged(int)), SLOT(onCurrentRowChanged(int))); + + connect(ui_.newButton, SIGNAL(clicked()), SLOT(onNewButtonClicked())); + connect(ui_.deleteButton, SIGNAL(clicked()), SLOT(onDeleteButtonClicked())); + + connect(ui_.buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), SLOT(onApplyButtonClick())); + connect(ui_.buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), SLOT(onCancelButtonClick())); + connect(ui_.buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), SLOT(onOkButtonClick())); + + connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(colorOtherSelect())); + connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(colorCustomSelect())); + + connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect())); + connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(soundOtherSelect())); + connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(soundCustomSelect())); + + /* replace the static line-edit control with the roster autocompleter */ + ui_.dummySenderName->setVisible(false); + jid_ = new QtSuggestingJIDInput(this, settings); + ui_.senderName->addWidget(jid_); + jid_->onUserSelected.connect(boost::bind(&QtHighlightEditor::handleOnUserSelected, this, _1)); + + /* handle autocomplete */ + connect(jid_, SIGNAL(textEdited(QString)), SLOT(handleContactSuggestionRequested(QString))); + + /* we need to be notified if any of the state changes so that we can update our textual rule description */ + connect(ui_.chatRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.roomRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.nickIsKeyword, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.allMsgRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.senderRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(jid_, SIGNAL(textChanged(const QString&)), SLOT(widgetClick())); + connect(ui_.keywordRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.keyword, SIGNAL(textChanged(const QString&)), SLOT(widgetClick())); + connect(ui_.matchPartialWords, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.matchCase, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.noColorRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.customColorRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.noSoundRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.defaultSoundRadio, SIGNAL(clicked()), SLOT(widgetClick())); + connect(ui_.customSoundRadio, SIGNAL(clicked()), SLOT(widgetClick())); + + /* allow selection of a custom sound file */ + connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(selectSoundFile())); + + /* allowing reordering items */ + connect(ui_.moveUpButton, SIGNAL(clicked()), this, SLOT(onUpButtonClicked())); + connect(ui_.moveDownButton, SIGNAL(clicked()), this, SLOT(onDownButtonClicked())); + + setWindowTitle(tr("Highlight Rules")); +} + +QtHighlightEditor::~QtHighlightEditor() +{ +} + +std::string formatShortDescription(const HighlightRule &rule) +{ + const std::string chatOrRoom = (rule.getMatchChat() ? "chat" : "room"); + + std::vector<std::string> senders = rule.getSenders(); + std::vector<std::string> keywords = rule.getKeywords(); + + if (senders.empty() && keywords.empty() && !rule.getNickIsKeyword()) { + return std::string("All ") + chatOrRoom + " messages."; + } + + if (rule.getNickIsKeyword()) { + return std::string("All ") + chatOrRoom + " messages that mention my name."; + } + + if (!senders.empty()) { + return std::string("All ") + chatOrRoom + " messages from " + senders[0] + "."; + } + + if (!keywords.empty()) { + return std::string("All ") + chatOrRoom + " messages mentioning the keyword '" + keywords[0] + "'."; + } + + return "Unknown Rule"; +} + +void QtHighlightEditor::show() +{ + highlightManager_->loadSettings(); + + populateList(); + + if (ui_.listWidget->count()) { + selectRow(0); + } + + /* prepare default states */ + widgetClick(); + + QWidget::show(); + QWidget::activateWindow(); +} + +void QtHighlightEditor::setHighlightManager(HighlightManager* highlightManager) +{ + highlightManager_ = highlightManager; +} + +void QtHighlightEditor::setContactSuggestions(const std::vector<Contact::ref>& suggestions) +{ + jid_->setSuggestions(suggestions); +} + +void QtHighlightEditor::colorOtherSelect() +{ + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); +} + +void QtHighlightEditor::colorCustomSelect() +{ + ui_.foregroundColor->setEnabled(true); + ui_.backgroundColor->setEnabled(true); +} + +void QtHighlightEditor::soundOtherSelect() +{ + ui_.soundFile->setEnabled(false); + ui_.soundFileButton->setEnabled(false); +} + +void QtHighlightEditor::soundCustomSelect() +{ + ui_.soundFile->setEnabled(true); + ui_.soundFileButton->setEnabled(true); +} + +void QtHighlightEditor::onNewButtonClicked() +{ + int row = getSelectedRow() + 1; + populateList(); + HighlightRule newRule; + newRule.setMatchMUC(true); + highlightManager_->insertRule(row, newRule); + QListWidgetItem *item = new QListWidgetItem(); + item->setText(P2QSTRING(formatShortDescription(newRule))); + QFont font; + font.setItalic(true); + item->setFont(font); + ui_.listWidget->insertItem(row, item); + selectRow(row); +} + +void QtHighlightEditor::onDeleteButtonClicked() +{ + int selectedRow = getSelectedRow(); + assert(selectedRow>=0 && selectedRow<ui_.listWidget->count()); + delete ui_.listWidget->takeItem(selectedRow); + highlightManager_->removeRule(selectedRow); + + if (!ui_.listWidget->count()) { + disableDialog(); + ui_.deleteButton->setEnabled(false); + } else { + if (selectedRow == ui_.listWidget->count()) { + selectRow(ui_.listWidget->count() - 1); + } else { + selectRow(selectedRow); + } + } +} + +void QtHighlightEditor::onUpButtonClicked() { + const size_t moveFrom = ui_.listWidget->currentRow(); + const size_t moveTo = moveFrom - 1; + highlightManager_->swapRules(moveFrom, moveTo); + populateList(); + selectRow(moveTo); +} + +void QtHighlightEditor::onDownButtonClicked() { + const size_t moveFrom = ui_.listWidget->currentRow(); + const size_t moveTo = moveFrom + 1; + highlightManager_->swapRules(moveFrom, moveTo); + populateList(); + selectRow(moveTo); +} + +void QtHighlightEditor::onCurrentRowChanged(int currentRow) +{ + ui_.deleteButton->setEnabled(currentRow != -1); + ui_.moveUpButton->setEnabled(currentRow != -1 && currentRow != 0); + ui_.moveDownButton->setEnabled(currentRow != -1 && currentRow != (ui_.listWidget->count()-1)); + + if (previousRow_ != -1) { + if (ui_.listWidget->count() > previousRow_) { + QFont font; + font.setItalic(false); + ui_.listWidget->item(previousRow_)->setFont(font); + highlightManager_->setRule(previousRow_, ruleFromDialog()); + } + } + + if (currentRow != -1) { + HighlightRule rule = highlightManager_->getRule(currentRow); + ruleToDialog(rule); + if (ui_.listWidget->currentItem()) { + QFont font; + font.setItalic(true); + ui_.listWidget->currentItem()->setFont(font); + ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule))); + } + } + + /* grey the dialog if we have nothing selected */ + if (currentRow == -1) { + disableDialog(); + } + + previousRow_ = currentRow; +} + +void QtHighlightEditor::onApplyButtonClick() +{ + selectRow(getSelectedRow()); /* force save */ + highlightManager_->storeSettings(); +} + +void QtHighlightEditor::onCancelButtonClick() +{ + close(); +} + +void QtHighlightEditor::onOkButtonClick() +{ + onApplyButtonClick(); + close(); +} + +void QtHighlightEditor::setChildWidgetStates() +{ + /* disable appropriate radio button child widgets */ + + if (ui_.chatRadio->isChecked()) { + if (ui_.nickIsKeyword->isChecked()) { + /* switch to another choice before we disable this button */ + ui_.allMsgRadio->setChecked(true); + } + ui_.nickIsKeyword->setEnabled(false); + } else if (ui_.roomRadio->isChecked()) { + ui_.nickIsKeyword->setEnabled(true); + } else { /* chats and rooms */ + ui_.nickIsKeyword->setEnabled(true); + } + + if (ui_.senderRadio->isChecked()) { + jid_->setEnabled(true); + } else { + jid_->setEnabled(false); + } + + if (ui_.keywordRadio->isChecked()) { + ui_.keyword->setEnabled(true); + ui_.matchPartialWords->setEnabled(true); + ui_.matchCase->setEnabled(true); + } else { + ui_.keyword->setEnabled(false); + ui_.matchPartialWords->setEnabled(false); + ui_.matchCase->setEnabled(false); + } + + if (ui_.chatRadio->isChecked()) { + ui_.allMsgRadio->setText(tr("Apply to all chat messages")); + } else { + ui_.allMsgRadio->setText(tr("Apply to all room messages")); + } +} + +void QtHighlightEditor::widgetClick() +{ + setChildWidgetStates(); + + HighlightRule rule = ruleFromDialog(); + + if (ui_.listWidget->currentItem()) { + ui_.listWidget->currentItem()->setText(P2QSTRING(formatShortDescription(rule))); + } +} + +void QtHighlightEditor::disableDialog() +{ + ui_.chatRadio->setEnabled(false); + ui_.roomRadio->setEnabled(false); + ui_.allMsgRadio->setEnabled(false); + ui_.nickIsKeyword->setEnabled(false); + ui_.senderRadio->setEnabled(false); + ui_.dummySenderName->setEnabled(false); + ui_.keywordRadio->setEnabled(false); + ui_.keyword->setEnabled(false); + ui_.matchPartialWords->setEnabled(false); + ui_.matchCase->setEnabled(false); + ui_.noColorRadio->setEnabled(false); + ui_.customColorRadio->setEnabled(false); + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); + ui_.noSoundRadio->setEnabled(false); + ui_.defaultSoundRadio->setEnabled(false); + ui_.customSoundRadio->setEnabled(false); + ui_.soundFile->setEnabled(false); + ui_.soundFileButton->setEnabled(false); +} + +void QtHighlightEditor::handleContactSuggestionRequested(const QString& text) +{ + std::string stdText = Q2PSTRING(text); + onContactSuggestionsRequested(stdText); +} + +void QtHighlightEditor::selectSoundFile() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Select sound file..."), QString(), "Sounds (*.wav)"); + ui_.soundFile->setText(path); +} + +void QtHighlightEditor::handleOnUserSelected(const Contact::ref& contact) { + /* this might seem like it should be standard behaviour for the suggesting input box, but is not desirable in all cases */ + if (contact->jid.isValid()) { + jid_->setText(P2QSTRING(contact->jid.toString())); + } else { + jid_->setText(P2QSTRING(contact->name)); + } +} + +void QtHighlightEditor::populateList() +{ + previousRow_ = -1; + ui_.listWidget->clear(); + HighlightRulesListPtr rules = highlightManager_->getRules(); + for (size_t i = 0; i < rules->getSize(); ++i) { + const HighlightRule& rule = rules->getRule(i); + QListWidgetItem *item = new QListWidgetItem(); + item->setText(P2QSTRING(formatShortDescription(rule))); + ui_.listWidget->addItem(item); + } +} + +void QtHighlightEditor::selectRow(int row) +{ + for (int i = 0; i < ui_.listWidget->count(); ++i) { + if (i == row) { + ui_.listWidget->item(i)->setSelected(true); + onCurrentRowChanged(i); + } else { + ui_.listWidget->item(i)->setSelected(false); + } + } + ui_.listWidget->setCurrentRow(row); +} + +int QtHighlightEditor::getSelectedRow() const +{ + for (int i = 0; i < ui_.listWidget->count(); ++i) { + if (ui_.listWidget->item(i)->isSelected()) { + return i; + } + } + return -1; +} + +HighlightRule QtHighlightEditor::ruleFromDialog() +{ + HighlightRule rule; + HighlightAction& action = rule.getAction(); + + if (ui_.chatRadio->isChecked()) { + rule.setMatchChat(true); + rule.setMatchMUC(false); + } else { + rule.setMatchChat(false); + rule.setMatchMUC(true); + } + + if (ui_.senderRadio->isChecked()) { + QString senderName = jid_->text(); + if (!senderName.isEmpty()) { + std::vector<std::string> senders; + senders.push_back(Q2PSTRING(senderName)); + rule.setSenders(senders); + action.setHighlightAllText(true); + } + } + + if (ui_.keywordRadio->isChecked()) { + QString keywordString = ui_.keyword->text(); + if (!keywordString.isEmpty()) { + std::vector<std::string> keywords; + keywords.push_back(Q2PSTRING(keywordString)); + rule.setKeywords(keywords); + } + } + + if (ui_.nickIsKeyword->isChecked()) { + rule.setNickIsKeyword(true); + rule.setMatchWholeWords(true); + rule.setMatchCase(true); + } else { + rule.setMatchWholeWords(!ui_.matchPartialWords->isChecked()); + rule.setMatchCase(ui_.matchCase->isChecked()); + } + + if (ui_.noColorRadio->isChecked()) { + action.setTextColor(""); + action.setTextBackground(""); + } else { + action.setTextColor(Q2PSTRING(ui_.foregroundColor->getColor().name())); + action.setTextBackground(Q2PSTRING(ui_.backgroundColor->getColor().name())); + } + + if (ui_.noSoundRadio->isChecked()) { + action.setPlaySound(false); + } else if (ui_.defaultSoundRadio->isChecked()) { + action.setPlaySound(true); + action.setSoundFile(""); + } else { + action.setPlaySound(true); + action.setSoundFile(Q2PSTRING(ui_.soundFile->text())); + } + + return rule; +} + +void QtHighlightEditor::ruleToDialog(const HighlightRule& rule) +{ + ui_.chatRadio->setEnabled(true); + ui_.roomRadio->setEnabled(true); + + if (rule.getMatchMUC()) { + ui_.chatRadio->setChecked(false); + ui_.roomRadio->setChecked(true); + } else { + ui_.chatRadio->setChecked(true); + ui_.roomRadio->setChecked(false); + } + + ui_.allMsgRadio->setEnabled(true); + ui_.allMsgRadio->setChecked(true); /* this is the default radio button */ + jid_->setText(""); + ui_.keyword->setText(""); + ui_.matchPartialWords->setChecked(false); + ui_.matchCase->setChecked(false); + + ui_.nickIsKeyword->setEnabled(true); + if (rule.getNickIsKeyword()) { + ui_.nickIsKeyword->setChecked(true); + } + + ui_.senderRadio->setEnabled(true); + std::vector<std::string> senders = rule.getSenders(); + if (!senders.empty()) { + ui_.senderRadio->setChecked(true); + jid_->setText(P2QSTRING(senders[0])); + } + + ui_.keywordRadio->setEnabled(true); + std::vector<std::string> keywords = rule.getKeywords(); + if (!keywords.empty()) { + ui_.keywordRadio->setChecked(true); + ui_.keyword->setText(P2QSTRING(keywords[0])); + ui_.matchPartialWords->setChecked(!rule.getMatchWholeWords()); + ui_.matchCase->setChecked(rule.getMatchCase()); + } + + const HighlightAction& action = rule.getAction(); + + ui_.noColorRadio->setEnabled(true); + ui_.customColorRadio->setEnabled(true); + if (action.getTextColor().empty() && action.getTextBackground().empty()) { + ui_.noColorRadio->setChecked(true); + ui_.foregroundColor->setEnabled(false); + ui_.backgroundColor->setEnabled(false); + } else { + ui_.foregroundColor->setEnabled(true); + ui_.backgroundColor->setEnabled(true); + QColor foregroundColor(P2QSTRING(action.getTextColor())); + ui_.foregroundColor->setColor(foregroundColor); + QColor backgroundColor(P2QSTRING(action.getTextBackground())); + ui_.backgroundColor->setColor(backgroundColor); + ui_.customColorRadio->setChecked(true); + } + + ui_.noSoundRadio->setEnabled(true); + ui_.defaultSoundRadio->setEnabled(true); + ui_.customSoundRadio->setEnabled(true); + ui_.soundFile->setText(""); + ui_.soundFile->setEnabled(false); + ui_.soundFileButton->setEnabled(false); + if (action.playSound()) { + if (action.getSoundFile().empty()) { + ui_.defaultSoundRadio->setChecked(true); + } else { + ui_.customSoundRadio->setChecked(true); + ui_.soundFile->setText(P2QSTRING(action.getSoundFile())); + ui_.soundFile->setEnabled(true); + ui_.soundFileButton->setEnabled(true); + } + } else { + ui_.noSoundRadio->setChecked(true); + } + + /* set radio button child option states */ + setChildWidgetStates(); +} + +} diff --git a/Swift/QtUI/QtHighlightEditor.h b/Swift/QtUI/QtHighlightEditor.h new file mode 100644 index 0000000..93a19b6 --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2012 Maciej Niedzielski + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/HighlightRule.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> +#include <Swift/QtUI/ui_QtHighlightEditor.h> + +namespace Swift { + + class QtSettingsProvider; + class QtSuggestingJIDInput; + class QtWebKitChatView; + + class QtHighlightEditor : public QWidget, public HighlightEditorWindow { + Q_OBJECT + + public: + QtHighlightEditor(QtSettingsProvider* settings, QWidget* parent = NULL); + virtual ~QtHighlightEditor(); + + virtual void show(); + virtual void setHighlightManager(HighlightManager* highlightManager); + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions); + + private slots: + void colorOtherSelect(); + void colorCustomSelect(); + void soundOtherSelect(); + void soundCustomSelect(); + void onNewButtonClicked(); + void onDeleteButtonClicked(); + void onUpButtonClicked(); + void onDownButtonClicked(); + void onCurrentRowChanged(int currentRow); + void onApplyButtonClick(); + void onCancelButtonClick(); + void onOkButtonClick(); + void setChildWidgetStates(); + void widgetClick(); + void disableDialog(); + void handleContactSuggestionRequested(const QString& text); + void selectSoundFile(); + + private: + void handleOnUserSelected(const Contact::ref& contact); + void populateList(); + void selectRow(int row); + int getSelectedRow() const; + HighlightRule ruleFromDialog(); + void ruleToDialog(const HighlightRule& rule); + + Ui::QtHighlightEditor ui_; + QtSettingsProvider* settings_; + HighlightManager* highlightManager_; + QtSuggestingJIDInput* jid_; + int previousRow_; + }; + +} diff --git a/Swift/QtUI/QtHighlightEditor.ui b/Swift/QtUI/QtHighlightEditor.ui new file mode 100644 index 0000000..775771f --- /dev/null +++ b/Swift/QtUI/QtHighlightEditor.ui @@ -0,0 +1,479 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHighlightEditor</class> + <widget class="QWidget" name="QtHighlightEditor"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>600</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>500</width> + <height>600</height> + </size> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>463</width> + <height>792</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLabel" name="label_5"> + <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="QListWidget" name="listWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_8"> + <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="newButton"> + <property name="text"> + <string>New Rule</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="deleteButton"> + <property name="text"> + <string>Remove Rule</string> + </property> + </widget> + </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> + </layout> + </item> + <item> + <widget class="Line" name="line_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Apply Rule To</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="roomRadio"> + <property name="text"> + <string>Rooms</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="chatRadio"> + <property name="text"> + <string>Chats</string> + </property> + <property name="checked"> + <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>246</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Rule Conditions</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QRadioButton" name="allMsgRadio"> + <property name="text"> + <string>Apply to all messages</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="nickIsKeyword"> + <property name="text"> + <string>Only messages mentioning me</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="senderRadio"> + <property name="text"> + <string>Messages from this sender:</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="senderName"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <item> + <widget class="QLineEdit" name="dummySenderName"/> + </item> + </layout> + </item> + <item> + <widget class="QRadioButton" name="keywordRadio"> + <property name="text"> + <string>Messages containing this keyword:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="keyword"/> + </item> + <item> + <widget class="QCheckBox" name="matchPartialWords"> + <property name="text"> + <string>Match keyword within longer words</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="matchCase"> + <property name="text"> + <string>Keyword is case sensitive</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Highlight Action</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QRadioButton" name="noColorRadio"> + <property name="text"> + <string>No Highlight</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="customColorRadio"> + <property name="text"> + <string>Custom Color</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <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_6"> + <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="Swift::QtColorToolButton" name="foregroundColor"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Text</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtColorToolButton" name="backgroundColor"> + <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> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Sound Action</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QRadioButton" name="noSoundRadio"> + <property name="text"> + <string>No Sound</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="defaultSoundRadio"> + <property name="text"> + <string>Default Sound</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="customSoundRadio"> + <property name="text"> + <string>Custom Sound</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <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_8"> + <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> + <item> + <widget class="QLineEdit" name="soundFile"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>false</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> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <spacer name="horizontalSpacer_4"> + <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="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </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/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/QtHighlightRulesItemModel.cpp b/Swift/QtUI/QtHighlightRulesItemModel.cpp new file mode 100644 index 0000000..fcbaddc --- /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, static_cast<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, static_cast<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 new file mode 100644 index 0000000..75eeaad --- /dev/null +++ b/Swift/QtUI/QtHistoryWindow.cpp @@ -0,0 +1,264 @@ +/* + * 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 <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 <QLineEdit> + +#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 { + +QtHistoryWindow::QtHistoryWindow(SettingsProvider* settings, UIEventStream* eventStream) : + previousTopMessageWasSelf_(false), + previousBottomMessageWasSelf_(false) { + ui_.setupUi(this); + + theme_ = new QtChatTheme(""); + idCounter_ = 0; + + delete ui_.conversation_; + 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); + conversation_->setSizePolicy(sizePolicy); + + ui_.conversation_ = conversation_; + ui_.bottomLayout_->addWidget(conversation_); + + delete ui_.conversationRoster_; + conversationRoster_ = new QtTreeWidget(eventStream, settings, QtTreeWidget::MessageDefaultJID, this); + QSizePolicy sizePolicy2(QSizePolicy::Preferred, QSizePolicy::Expanding); + sizePolicy2.setVerticalStretch(80); + conversationRoster_->setSizePolicy(sizePolicy2); + ui_.conversationRoster_ = conversationRoster_; + ui_.bottomLeftLayout_->setDirection(QBoxLayout::BottomToTop); + ui_.bottomLeftLayout_->addWidget(conversationRoster_); + + setWindowTitle(tr("History")); + + conversationRoster_->onSomethingSelectedChanged.connect(boost::bind(&QtHistoryWindow::handleSomethingSelectedChanged, this, _1)); + connect(conversation_, SIGNAL(scrollRequested(int)), this, SLOT(handleScrollRequested(int))); + connect(conversation_, SIGNAL(scrollReachedTop()), this, SLOT(handleScrollReachedTop())); + connect(conversation_, SIGNAL(scrollReachedBottom()), this, SLOT(handleScrollReachedBottom())); + connect(conversation_, SIGNAL(fontResized(int)), this, SLOT(handleFontResized(int))); + connect(ui_.searchBox_->lineEdit(), SIGNAL(returnPressed()), this, SLOT(handleReturnPressed())); + connect(ui_.calendarWidget_, SIGNAL(clicked(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + connect(ui_.calendarWidget_, SIGNAL(activated(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + connect(ui_.previousButton_, SIGNAL(clicked(bool)), this, SLOT(handlePreviousButtonClicked())); + connect(ui_.nextButton_, SIGNAL(clicked(bool)), this, SLOT(handleNextButtonClicked())); +} + +QtHistoryWindow::~QtHistoryWindow() { + disconnect(conversation_, SIGNAL(scrollRequested(int)), this, SLOT(handleScrollRequested(int))); + disconnect(conversation_, SIGNAL(scrollReachedTop()), this, SLOT(handleScrollReachedTop())); + disconnect(conversation_, SIGNAL(scrollReachedBottom()), this, SLOT(handleScrollReachedBottom())); + disconnect(conversation_, SIGNAL(fontResized(int)), this, SLOT(handleFontResized(int))); + disconnect(ui_.searchBox_->lineEdit(), SIGNAL(returnPressed()), this, SLOT(handleReturnPressed())); + disconnect(ui_.calendarWidget_, SIGNAL(clicked(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + disconnect(ui_.calendarWidget_, SIGNAL(activated(const QDate&)), this, SLOT(handleCalendarClicked(const QDate&))); + disconnect(ui_.previousButton_, SIGNAL(clicked(bool)), this, SLOT(handlePreviousButtonClicked())); + disconnect(ui_.nextButton_, SIGNAL(clicked(bool)), this, SLOT(handleNextButtonClicked())); + + delete theme_; + delete conversation_; + // TODO: delete ui_ +} + +void QtHistoryWindow::activate() { + emit wantsToActivate(); +} + +void QtHistoryWindow::showEvent(QShowEvent* event) { + emit windowOpening(); + emit titleUpdated(); + QWidget::showEvent(event); +} + +void QtHistoryWindow::closeEvent(QCloseEvent* event) { + emit windowClosing(); + event->accept(); +} + +void QtHistoryWindow::setRosterModel(Roster* model) { + conversationRoster_->setRosterModel(model); +} + +void QtHistoryWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop) { + QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); + + QString messageHTML(P2QSTRING(message)); + messageHTML = QtUtilities::htmlEscape(messageHTML); + QString searchTerm = ui_.searchBox_->lineEdit()->text(); + if (searchTerm.length()) { + messageHTML.replace(searchTerm, "<span style='background-color: yellow'>" + searchTerm + "</span>"); + } + + // note: time uses localtime + QDate date = QDate(time.date().year(), time.date().month(), time.date().day()); + QTime dayTime = QTime(time.time_of_day().hours(), time.time_of_day().minutes(), time.time_of_day().seconds()); + QDateTime qTime = QDateTime(date, dayTime); + + std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + + QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); + + if (addAtTheTop) { + bool appendToPrevious = ((senderIsSelf && previousTopMessageWasSelf_) || (!senderIsSelf && !previousTopMessageWasSelf_&& previousTopSenderName_ == P2QSTRING(senderName))); + 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::make_shared<MessageSnippet>(messageHTML, QtUtilities::htmlEscape(P2QSTRING(senderName)), qTime, qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(message))); + previousBottomMessageWasSelf_ = senderIsSelf; + previousBottomSenderName_ = P2QSTRING(senderName); + } + + // keep track of the days viewable in the chatView + if (!dates_.count(date)) { + dates_.insert(date); + } +} + +void QtHistoryWindow::handleSomethingSelectedChanged(RosterItem* item) { + onSelectedContactChanged(item); +} + +void QtHistoryWindow::resetConversationView() { + previousTopMessageWasSelf_ = false; + previousBottomMessageWasSelf_ = false; + previousTopSenderName_.clear(); + previousBottomSenderName_.clear(); + + dates_.clear(); + conversation_->resetView(); +} + +void QtHistoryWindow::handleScrollRequested(int pos) { + // first message starts with offset 5 + if (pos < 5) { + pos = 5; + } + + QDate currentDate; + foreach (const QDate& date, dates_) { + int snippetPosition = conversation_->getSnippetPositionByDate(date); + if (snippetPosition <= pos) { + currentDate = date; + } + } + + if (ui_.calendarWidget_->selectedDate() != currentDate) { + ui_.calendarWidget_->setSelectedDate(currentDate); + } +} + +void QtHistoryWindow::handleScrollReachedTop() { + if (dates_.empty()) { + return; + } + + int year, month, day; + QDate firstDate = *dates_.begin(); + firstDate.getDate(&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() { + if (dates_.empty()) { + return; + } + + int year, month, day; + QDate lastDate = *dates_.rbegin(); + lastDate.getDate(&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() { + onReturnPressed(ui_.searchBox_->lineEdit()->text().toStdString()); +} + +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(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) { + ui_.calendarWidget_->setSelectedDate(QDate::fromJulianDay(date.julian_day())); +} + +void QtHistoryWindow::handleNextButtonClicked() { + onNextButtonClicked(); +} + +void QtHistoryWindow::handlePreviousButtonClicked() { + onPreviousButtonClicked(); +} + +void QtHistoryWindow::handleFontResized(int fontSizeSteps) { + conversation_->resizeFont(fontSizeSteps); + + emit fontResized(fontSizeSteps); +} + +void QtHistoryWindow::resetConversationViewTopInsertPoint() { + previousTopMessageWasSelf_ = false; + previousTopSenderName_ = QString(); + conversation_->resetTopInsertPoint(); +} + +std::string QtHistoryWindow::getSearchBoxText() { + return ui_.searchBox_->lineEdit()->text().toStdString(); +} + +boost::gregorian::date QtHistoryWindow::getLastVisibleDate() { + if (!dates_.empty()) { + QDate lastDate = *dates_.rbegin(); + int year, month, day; + lastDate.getDate(&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 new file mode 100644 index 0000000..fcbfd7e --- /dev/null +++ b/Swift/QtUI/QtHistoryWindow.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2012 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. + */ + +#pragma once + +#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 + + public: + QtHistoryWindow(SettingsProvider*, UIEventStream*); + ~QtHistoryWindow(); + void activate(); + void setRosterModel(Roster*); + void addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const std::string& avatarPath, const boost::posix_time::ptime& time, bool addAtTheTop); + void resetConversationView(); + void resetConversationViewTopInsertPoint(); + void setDate(const boost::gregorian::date& date); + + void closeEvent(QCloseEvent* event); + void showEvent(QShowEvent* event); + + std::string getSearchBoxText(); + boost::gregorian::date getLastVisibleDate(); + + signals: + void fontResized(int); + + public slots: + void handleFontResized(int fontSizeSteps); + + protected slots: + void handleScrollRequested(int pos); + void handleScrollReachedTop(); + void handleScrollReachedBottom(); + void handleReturnPressed(); + void handleCalendarClicked(const QDate& date); + void handlePreviousButtonClicked(); + void handleNextButtonClicked(); + + private: + void handleSomethingSelectedChanged(RosterItem* item); + + Ui::QtHistoryWindow ui_; + QtChatTheme* theme_; + QtWebKitChatView* conversation_; + QtTreeWidget* conversationRoster_; + std::set<QDate> dates_; + int idCounter_; + bool previousTopMessageWasSelf_; + QString previousTopSenderName_; + bool previousBottomMessageWasSelf_; + QString previousBottomSenderName_; + }; +} diff --git a/Swift/QtUI/QtHistoryWindow.ui b/Swift/QtUI/QtHistoryWindow.ui new file mode 100644 index 0000000..71a4577 --- /dev/null +++ b/Swift/QtUI/QtHistoryWindow.ui @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHistoryWindow</class> + <widget class="QWidget" name="QtHistoryWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>608</width> + <height>522</height> + </rect> + </property> + <property name="windowTitle"> + <string>History</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="topLayout_"> + <item> + <widget class="QLabel" name="label_"> + <property name="text"> + <string>Search:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="searchBox_"> + <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> + <item> + <widget class="QPushButton" name="nextButton_"> + <property name="text"> + <string>Next</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="previousButton_"> + <property name="text"> + <string>Previous</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QSplitter" name="bottomLayout_"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QWidget" name="layoutWidget"> + <layout class="QVBoxLayout" name="bottomLeftLayout_" stretch="0,0"> + <item> + <widget class="QWidget" name="conversationRoster_" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>5</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QCalendarWidget" name="calendarWidget_"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="conversation_" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>85</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtJoinMUCWindow.ui b/Swift/QtUI/QtJoinMUCWindow.ui index 4c4935a..f294f8c 100644 --- a/Swift/QtUI/QtJoinMUCWindow.ui +++ b/Swift/QtUI/QtJoinMUCWindow.ui @@ -26,5 +26,5 @@ <widget class="QLabel" name="label_4"> <property name="text"> - <string>Room:</string> + <string>Room Address:</string> </property> </widget> @@ -40,11 +40,8 @@ <widget class="QLabel" name="label_5"> <property name="text"> - <string>Nickname:</string> + <string>Your Nickname:</string> </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"> @@ -57,5 +54,5 @@ <widget class="QLabel" name="label"> <property name="text"> - <string>Password:</string> + <string>Room Password:</string> </property> </widget> @@ -64,4 +61,7 @@ <widget class="QLineEdit" name="password"/> </item> + <item row="1" column="1" colspan="2"> + <widget class="QLineEdit" name="nickName"/> + </item> </layout> </item> @@ -121,8 +121,10 @@ <tabstops> <tabstop>room</tabstop> + <tabstop>searchButton</tabstop> <tabstop>nickName</tabstop> + <tabstop>password</tabstop> + <tabstop>instantRoom</tabstop> <tabstop>joinAutomatically</tabstop> <tabstop>joinButton</tabstop> - <tabstop>searchButton</tabstop> </tabstops> <resources/> diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index 094f96c..eeb4317 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -31,4 +31,5 @@ #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> @@ -41,4 +42,5 @@ #include <QtMainWindow.h> #include <QtUtilities.h> +#include <QtConnectionSettingsWindow.h> #ifdef HAVE_SCHANNEL @@ -54,8 +56,10 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set setWindowTitle("Swift"); -#ifndef Q_WS_MAC +#ifndef Q_OS_MAC setWindowIcon(QIcon(":/logo-icon-16.png")); #endif QtUtilities::setX11Resource(this, "Main"); + setAccessibleName(tr("Swift Login Window")); + //setAccessibleDescription(tr("This window is used for providing credentials to log into your XMPP service")); resize(200, 500); @@ -92,4 +96,5 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set layout->addWidget(jidLabel); + username_ = new QComboBox(this); username_->setEditable(true); @@ -97,4 +102,6 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set username_->setToolTip(tr("User address - looks like someuser@someserver.com")); username_->view()->installEventFilter(this); + username_->setAccessibleName(tr("User address (of the form someuser@someserver.com)")); + username_->setAccessibleDescription(tr("This is the user address that you'll be using to log in with")); layout->addWidget(username_); QLabel* jidHintLabel = new QLabel(this); @@ -106,4 +113,6 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set QLabel* passwordLabel = new QLabel(); passwordLabel->setText("<font size='-1'>" + tr("Password:") + "</font>"); + passwordLabel->setAccessibleName(tr("User password")); + passwordLabel->setAccessibleDescription(tr("This is the password you'll use to log in to the XMPP service")); layout->addWidget(passwordLabel); @@ -128,4 +137,6 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set certificateButton_->setToolTip(tr("Click if you have a personal certificate used for login to the service.")); certificateButton_->setWhatsThis(tr("Click if you have a personal certificate used for login to the service.")); + certificateButton_->setAccessibleName(tr("Login with certificate")); + certificateButton_->setAccessibleDescription(tr("Click if you have a personal certificate used for login to the service.")); credentialsLayout->addWidget(certificateButton_); @@ -136,6 +147,14 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set loginButton_->setAutoDefault(true); loginButton_->setDefault(true); + loginButton_->setAccessibleName(tr("Connect now")); layout->addWidget(loginButton_); + QLabel* connectionOptionsLabel = new QLabel(this); + connectionOptionsLabel->setText("<a href=\"#\"><font size='-1'>" + QObject::tr("Connection Options") + "</font></a>"); + connectionOptionsLabel->setTextFormat(Qt::RichText); + connectionOptionsLabel->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + layout->addWidget(connectionOptionsLabel); + connect(connectionOptionsLabel, SIGNAL(linkActivated(const QString&)), SLOT(handleOpenConnectionOptions())); + message_ = new QLabel(this); message_->setTextFormat(Qt::RichText); @@ -183,4 +202,8 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* set #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); @@ -297,5 +320,5 @@ void QtLoginWindow::removeAvailableAccount(const std::string& jid) { } -void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate) { +void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options) { QString username = P2QSTRING(defaultJID); int index = -1; @@ -309,4 +332,5 @@ void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std passwords_.append(P2QSTRING(defaultPassword)); certificateFiles_.append(P2QSTRING(defaultCertificate)); + options_.push_back(options); username_->addItem(username); } else { @@ -314,4 +338,5 @@ void QtLoginWindow::addAvailableAccount(const std::string& defaultJID, const std passwords_[index] = P2QSTRING(defaultPassword); certificateFiles_[index] = P2QSTRING(defaultCertificate); + options_[index] = options; } } @@ -324,9 +349,8 @@ void QtLoginWindow::handleUsernameTextChanged() { password_->setText(passwords_[i]); remember_->setChecked(password_->text() != ""); + currentOptions_ = options_[i]; } } - if (!certificateFile_.isEmpty()) { - certificateButton_->setChecked(true); - } + certificateButton_->setChecked(!certificateFile_.isEmpty()); } @@ -377,5 +401,5 @@ void QtLoginWindow::loginClicked() { #endif - onLoginRequest(Q2PSTRING(username_->currentText()), Q2PSTRING(password_->text()), certificateString, certificate, remember_->isChecked(), loginAutomatically_->isChecked()); + onLoginRequest(Q2PSTRING(username_->currentText()), Q2PSTRING(password_->text()), certificateString, certificate, currentOptions_, remember_->isChecked(), loginAutomatically_->isChecked()); if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) { /* Mustn't remember logins */ username_->clearEditText(); @@ -399,5 +423,5 @@ void QtLoginWindow::handleCertficateChecked(bool checked) { } #else - certificateFile_ = QFileDialog::getOpenFileName(this, tr("Select an authentication certificate"), QString(), QString("*.cert;*.p12;*.pfx")); + certificateFile_ = QFileDialog::getOpenFileName(this, tr("Select an authentication certificate"), QString(), tr("P12 files (*.cert *.p12 *.pfx);;All files (*.*)")); if (certificateFile_.isEmpty()) { certificateButton_->setChecked(false); @@ -430,4 +454,8 @@ void QtLoginWindow::handleShowFileTransferOverview() { } +void QtLoginWindow::handleShowHighlightEditor() { + uiEventStream_->send(boost::make_shared<RequestHighlightEditorUIEvent>()); +} + void QtLoginWindow::handleToggleSounds(bool enabled) { settings_->storeSetting(SettingConstants::PLAY_SOUNDS, enabled); @@ -455,4 +483,5 @@ void QtLoginWindow::setInitialMenus() { void QtLoginWindow::morphInto(MainWindow *mainWindow) { + setEnabled(false); QtMainWindow *qtMainWindow = dynamic_cast<QtMainWindow*>(mainWindow); assert(qtMainWindow); @@ -465,4 +494,5 @@ void QtLoginWindow::morphInto(MainWindow *mainWindow) { menuBar_->addMenu(menu); } + setFocus(); } @@ -507,5 +537,5 @@ void QtLoginWindow::moveEvent(QMoveEvent*) { } -bool QtLoginWindow::askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate) { +bool QtLoginWindow::askUserToTrustCertificatePermanently(const std::string& message, const std::vector<Certificate::ref>& certificates) { QMessageBox dialog(this); @@ -513,12 +543,24 @@ bool QtLoginWindow::askUserToTrustCertificatePermanently(const std::string& mess dialog.setInformativeText(P2QSTRING(message) + "\n\n" + tr("Would you like to permanently trust this certificate? This must only be done if you know it is correct.")); - QString detailedText = tr("Subject: %1").arg(P2QSTRING(certificate->getSubjectName())) + "\n"; - detailedText += tr("SHA-1 Fingerprint: %1").arg(P2QSTRING(certificate->getSHA1Fingerprint())); - dialog.setDetailedText(detailedText); - - dialog.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + dialog.addButton(tr("Show Certificate"), QMessageBox::HelpRole); + dialog.addButton(QMessageBox::Yes); + dialog.addButton(QMessageBox::No); dialog.setDefaultButton(QMessageBox::No); + while (true) { + int result = dialog.exec(); + if (result == QMessageBox::Yes || result == QMessageBox::No) { + return result == QMessageBox::Yes; + } + // FIXME: This isn't very nice, because the dialog disappears every time. We actually need a real + // dialog with a custom button. + QtMainWindow::openCertificateDialog(certificates, &dialog); + } +} - return dialog.exec() == QMessageBox::Yes; +void QtLoginWindow::handleOpenConnectionOptions() { + QtConnectionSettingsWindow connectionSettings(currentOptions_); + if (connectionSettings.exec() == QDialog::Accepted) { + currentOptions_ = connectionSettings.getOptions(); + } } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 8f50a35..7415fbf 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -44,10 +44,10 @@ namespace Swift { virtual void setShowNotificationToggle(bool); virtual void setMessage(const std::string& message); - virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate); + virtual void addAvailableAccount(const std::string& defaultJID, const std::string& defaultPassword, const std::string& defaultCertificate, const ClientOptions& options); virtual void removeAvailableAccount(const std::string& jid); virtual void setLoginAutomatically(bool loginAutomatically); virtual void setIsLoggingIn(bool loggingIn); void selectUser(const std::string& user); - bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate); + bool askUserToTrustCertificatePermanently(const std::string& message, const std::vector<Certificate::ref>& certificate); void hide(); QtMenus getMenus() const; @@ -63,4 +63,5 @@ namespace Swift { void handleShowXMLConsole(); void handleShowFileTransferOverview(); + void handleShowHighlightEditor(); void handleToggleSounds(bool enabled); void handleToggleNotifications(bool enabled); @@ -72,4 +73,5 @@ namespace Swift { void moveEvent(QMoveEvent* event); void handleSettingChanged(const std::string& settingPath); + void handleOpenConnectionOptions(); protected: @@ -82,4 +84,5 @@ namespace Swift { QStringList passwords_; QStringList certificateFiles_; + std::vector<ClientOptions> options_; QComboBox* username_; QLineEdit* password_; @@ -102,5 +105,7 @@ namespace Swift { QAction* xmlConsoleAction_; QAction* fileTransferOverviewAction_; + QAction* highlightEditorAction_; TimerFactory* timerFactory_; + ClientOptions currentOptions_; }; } diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 05c78b3..52b6bcc 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtMainWindow.h" +#include <Swift/QtUI/QtMainWindow.h> #include <boost/optional.hpp> @@ -22,10 +22,8 @@ #include <QTabWidget> -#include <Swift/QtUI/QtSwiftUtil.h> -#include <Swift/QtUI/QtTabWidget.h> -#include <Swift/QtUI/QtSettingsProvider.h> -#include <Swift/QtUI/QtLoginWindow.h> -#include <Roster/QtRosterWidget.h> +#include <Swiften/Base/Platform.h> + #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> @@ -33,10 +31,26 @@ #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> -#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h> #include <Swift/Controllers/SettingConstants.h> +#include <Swift/QtUI/Roster/QtFilterWidget.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTabWidget.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/Roster/QtRosterWidget.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtAdHocCommandWithJIDWindow.h> +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include <Swift/QtUI/CocoaUIHelpers.h> +#elif defined(SWIFTEN_PLATFORM_WINDOWS) +#include <Swift/QtUI/WinUIHelpers.h> +#else +#include <Swift/QtUI/QtCertificateViewerDialog.h> +#endif + namespace Swift { -QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { +QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist, bool enableAdHocCommandOnJID) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { uiEventStream_ = uiEventStream; settings_ = settings; @@ -45,8 +59,9 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr 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())); + connect(meView_, SIGNAL(onShowCertificateInfo()), this, SLOT(handleShowCertificateInfo())); tabs_ = new QtTabWidget(this); @@ -64,5 +79,7 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr treeWidget_ = new QtRosterWidget(uiEventStream_, settings_, this); + contactTabLayout->addWidget(treeWidget_); + new QtFilterWidget(this, treeWidget_, uiEventStream_, contactTabLayout); tabs_->addTab(contactsTabWidget_, tr("&Contacts")); @@ -81,8 +98,30 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr connect(tabs_, SIGNAL(currentChanged(int)), this, SLOT(handleTabChanged(int))); + tabBarCombo_ = NULL; + if (settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)) { + tabs_->tabBar()->hide(); + tabBarCombo_ = new QComboBox(this); + tabBarCombo_->setAccessibleName("Current View"); + tabBarCombo_->addItem(tr("Contacts")); + tabBarCombo_->addItem(tr("Chats")); + tabBarCombo_->addItem(tr("Notices")); + tabBarCombo_->setCurrentIndex(tabs_->currentIndex()); + mainLayout->addWidget(tabBarCombo_); + connect(tabBarCombo_, SIGNAL(currentIndexChanged(int)), tabs_, SLOT(setCurrentIndex(int))); + } + + this->setLayout(mainLayout); 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); @@ -92,4 +131,13 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr handleShowOfflineToggled(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); + if (emoticonsExist) { + showEmoticonsAction_ = new QAction(tr("&Show Emoticons"), this); + showEmoticonsAction_->setCheckable(true); + showEmoticonsAction_->setChecked(false); + connect(showEmoticonsAction_, SIGNAL(toggled(bool)), SLOT(handleShowEmoticonsToggled(bool))); + viewMenu->addAction(showEmoticonsAction_); + handleShowEmoticonsToggled(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); + } + //QAction* compactRosterAction_ = new QAction(tr("&Compact Roster"), this); //compactRosterAction_->setCheckable(true); @@ -100,20 +148,45 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr QMenu* actionsMenu = new QMenu(tr("&Actions"), this); menus_.push_back(actionsMenu); - QAction* editProfileAction = new QAction(tr("Edit &Profile"), this); + QAction* editProfileAction = new QAction(tr("Edit &Profile…"), this); connect(editProfileAction, SIGNAL(triggered()), SLOT(handleEditProfileAction())); actionsMenu->addAction(editProfileAction); - QAction* joinMUCAction = new QAction(tr("Enter &Room"), this); + onlineOnlyActions_ << editProfileAction; + QAction* joinMUCAction = new QAction(tr("Enter &Room…"), this); connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction())); actionsMenu->addAction(joinMUCAction); - addUserAction_ = new QAction(tr("&Add Contact"), this); + onlineOnlyActions_ << joinMUCAction; +#ifdef SWIFT_EXPERIMENTAL_HISTORY + QAction* viewLogsAction = new QAction(tr("&View History…"), this); + 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_); + onlineOnlyActions_ << openBlockingListEditor_; + openBlockingListEditor_->setVisible(false); + addUserAction_ = new QAction(tr("&Add Contact…"), this); + addUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D)); + addUserAction_->setShortcutContext(Qt::ApplicationShortcut); connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool))); actionsMenu->addAction(addUserAction_); - editUserAction_ = new QAction(tr("&Edit Selected Contact"), this); + onlineOnlyActions_ << addUserAction_; + editUserAction_ = new QAction(tr("&Edit Selected Contact…"), this); connect(editUserAction_, SIGNAL(triggered(bool)), treeWidget_, SLOT(handleEditUserActionTriggered(bool))); actionsMenu->addAction(editUserAction_); + onlineOnlyActions_ << editUserAction_; editUserAction_->setEnabled(false); - chatUserAction_ = new QAction(tr("Start &Chat"), this); + chatUserAction_ = new QAction(tr("Start &Chat…"), this); + chatUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N)); + chatUserAction_->setShortcutContext(Qt::ApplicationShortcut); connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool))); actionsMenu->addAction(chatUserAction_); + onlineOnlyActions_ << chatUserAction_; + if (enableAdHocCommandOnJID) { + otherAdHocAction_ = new QAction(tr("Run Other Command"), this); + connect(otherAdHocAction_, SIGNAL(triggered()), this, SLOT(handleOtherAdHocActionTriggered())); + actionsMenu->addAction(otherAdHocAction_); + onlineOnlyActions_ << otherAdHocAction_; + } serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this); actionsMenu->addMenu(serverAdHocMenu_); @@ -131,5 +204,5 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr loginMenus_.generalMenu->insertAction(generalMenuActions.at(generalMenuActions.count()-2),toggleRequestDeliveryReceipts_); - treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1)); + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtMainWindow::handleSomethingSelectedChanged, this, _1)); setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); @@ -154,4 +227,17 @@ void QtMainWindow::handleToggleRequestDeliveryReceipts(bool enabled) { } +void QtMainWindow::handleShowCertificateInfo() { + onShowCertificateRequest(); +} + +void QtMainWindow::handleEditBlockingList() { + uiEventStream_->send(boost::make_shared<RequestBlockListDialogUIEvent>()); +} + +void QtMainWindow::handleSomethingSelectedChanged(bool itemSelected) { + bool isOnline = addUserAction_->isEnabled(); + editUserAction_->setEnabled(isOnline && itemSelected); +} + QtEventWindow* QtMainWindow::getEventWindow() { return eventWindow_; @@ -202,4 +288,8 @@ void QtMainWindow::handleChatUserActionTriggered(bool /*checked*/) { } +void QtMainWindow::handleOtherAdHocActionTriggered() { + new QtAdHocCommandWithJIDWindow(uiEventStream_); +} + void QtMainWindow::handleSignOutAction() { loginMenus_.generalMenu->removeAction(toggleRequestDeliveryReceipts_); @@ -215,4 +305,8 @@ void QtMainWindow::handleJoinMUCAction() { } +void QtMainWindow::handleViewLogsAction() { + uiEventStream_->send(boost::make_shared<RequestHistoryUIEvent>()); +} + void QtMainWindow::handleStatusChanged(StatusShow::Type showType, const QString &statusMessage) { onChangeStatusRequest(showType, Q2PSTRING(statusMessage)); @@ -223,7 +317,18 @@ void QtMainWindow::handleSettingChanged(const std::string& settingPath) { handleShowOfflineToggled(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); } + if (settingPath == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { + handleShowEmoticonsToggled(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); + } 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)); } @@ -233,4 +338,9 @@ void QtMainWindow::handleShowOfflineToggled(bool state) { } +void QtMainWindow::handleShowEmoticonsToggled(bool state) { + settings_->storeSetting(QtUISettingConstants::SHOW_EMOTICONS, state); + showEmoticonsAction_->setChecked(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); +} + void QtMainWindow::setMyNick(const std::string& nick) { meView_->setNick(P2QSTRING(nick)); @@ -251,4 +361,15 @@ void QtMainWindow::setMyStatusText(const std::string& status) { void QtMainWindow::setMyStatusType(StatusShow::Type type) { meView_->setStatusType(type); + const bool online = (type != StatusShow::None); + treeWidget_->setOnline(online); + chatListWindow_->setOnline(online); + foreach (QAction *action, onlineOnlyActions_) { + action->setEnabled(online); + } + serverAdHocMenu_->setEnabled(online); +} + +void QtMainWindow::setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) { + meView_->setContactRosterItem(contact); } @@ -257,4 +378,22 @@ void QtMainWindow::setConnecting() { } +void QtMainWindow::setStreamEncryptionStatus(bool tlsInPlaceAndValid) { + meView_->setStreamEncryptionStatus(tlsInPlaceAndValid); +} + +void QtMainWindow::openCertificateDialog(const std::vector<Certificate::ref>& chain) { + openCertificateDialog(chain, this); +} + +void QtMainWindow::openCertificateDialog(const std::vector<Certificate::ref>& chain, QWidget* parent) { +#if defined(SWIFTEN_PLATFORM_MACOSX) + CocoaUIHelpers::displayCertificateChainAsSheet(parent, chain); +#elif defined(SWIFTEN_PLATFORM_WINDOWS) + WinUIHelpers::displayCertificateChainAsSheet(parent, chain); +#else + QtCertificateViewerDialog::displayCertificateChainAsSheet(parent, chain); +#endif +} + void QtMainWindow::handleAdHocActionTriggered(bool /*checked*/) { QAction* action = qobject_cast<QAction*>(sender()); @@ -285,4 +424,8 @@ 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 bef483d..ea92c79 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2011 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,14 +7,16 @@ #pragma once +#include <vector> + #include <QWidget> #include <QMenu> #include <QList> -#include "Swift/Controllers/UIInterfaces/MainWindow.h" -#include "Swift/QtUI/QtRosterHeader.h" -#include "Swift/QtUI/EventViewer/QtEventWindow.h" -#include "Swift/QtUI/ChatList/QtChatListWindow.h" -#include "Swift/QtUI/QtLoginWindow.h" -#include <vector> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> + +#include <Swift/QtUI/QtRosterHeader.h> +#include <Swift/QtUI/EventViewer/QtEventWindow.h> +#include <Swift/QtUI/ChatList/QtChatListWindow.h> +#include <Swift/QtUI/QtLoginWindow.h> class QComboBox; @@ -33,9 +35,10 @@ namespace Swift { class SettingsProvider; class QtUIPreferences; + class StatusCache; class QtMainWindow : public QWidget, public MainWindow { Q_OBJECT public: - QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus); + QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist, bool enableAdHocCommandOnJID); virtual ~QtMainWindow(); std::vector<QMenu*> getMenus() {return menus_;} @@ -45,18 +48,27 @@ namespace Swift { void setMyStatusText(const std::string& status); void setMyStatusType(StatusShow::Type type); + void setMyContactRosterItem(boost::shared_ptr<ContactRosterItem> contact); void setConnecting(); + void setStreamEncryptionStatus(bool tlsInPlaceAndValid); + void openCertificateDialog(const std::vector<Certificate::ref>& chain); + static void openCertificateDialog(const std::vector<Certificate::ref>& chain, QWidget* parent); QtEventWindow* getEventWindow(); 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(); + void handleViewLogsAction(); void handleSignOutAction(); void handleEditProfileAction(); void handleAddUserActionTriggered(bool checked); void handleChatUserActionTriggered(bool checked); + void handleOtherAdHocActionTriggered(); void handleAdHocActionTriggered(bool checked); void handleEventCountUpdated(int count); @@ -65,4 +77,7 @@ namespace Swift { void handleTabChanged(int index); void handleToggleRequestDeliveryReceipts(bool enabled); + void handleShowCertificateInfo(); + void handleEditBlockingList(); + void handleSomethingSelectedChanged(bool itemSelected); private: @@ -75,8 +90,13 @@ namespace Swift { QAction* editUserAction_; QAction* chatUserAction_; + QAction* otherAdHocAction_; QAction* showOfflineAction_; + QAction* compactRosterAction_; + QAction* showEmoticonsAction_; + QAction* openBlockingListEditor_; QAction* toggleRequestDeliveryReceipts_; QMenu* serverAdHocMenu_; QtTabWidget* tabs_; + QComboBox* tabBarCombo_; QWidget* contactsTabWidget_; QWidget* eventsTabWidget_; @@ -86,4 +106,5 @@ namespace Swift { std::vector<DiscoItems::Item> serverAdHocCommands_; QList<QAction*> serverAdHocCommandActions_; + QList<QAction*> onlineOnlyActions_; }; } diff --git a/Swift/QtUI/QtNameWidget.cpp b/Swift/QtUI/QtNameWidget.cpp index 08e32f5..c172caa 100644 --- a/Swift/QtUI/QtNameWidget.cpp +++ b/Swift/QtUI/QtNameWidget.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -18,5 +18,5 @@ namespace Swift { -QtNameWidget::QtNameWidget(SettingsProvider* settings, QWidget *parent) : QWidget(parent), settings(settings) { +QtNameWidget::QtNameWidget(SettingsProvider* settings, QWidget *parent) : QWidget(parent), settings(settings), isOnline_(false) { QHBoxLayout* mainLayout = new QHBoxLayout(this); mainLayout->setSpacing(0); @@ -42,4 +42,8 @@ void QtNameWidget::setJID(const QString& jid) { } +void QtNameWidget::setOnline(const bool isOnline) { + isOnline_ = isOnline; +} + void QtNameWidget::mousePressEvent(QMouseEvent* event) { QMenu menu; @@ -63,4 +67,5 @@ void QtNameWidget::mousePressEvent(QMouseEvent* event) { QAction* editProfile = new QAction(tr("Edit Profile"), this); menu.addAction(editProfile); + editProfile->setEnabled(isOnline_); QAction* result = menu.exec(event->globalPos()); diff --git a/Swift/QtUI/QtNameWidget.h b/Swift/QtUI/QtNameWidget.h index 0f00c41..460cc9a 100644 --- a/Swift/QtUI/QtNameWidget.h +++ b/Swift/QtUI/QtNameWidget.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -21,4 +21,5 @@ namespace Swift { void setNick(const QString& text); void setJID(const QString& jid); + void setOnline(const bool isOnline); signals: @@ -32,5 +33,5 @@ namespace Swift { enum Mode { ShowNick, - ShowJID, + ShowJID }; @@ -40,4 +41,5 @@ namespace Swift { QString jid; QString nick; + bool isOnline_; }; } diff --git a/Swift/QtUI/QtPlainChatView.cpp b/Swift/QtUI/QtPlainChatView.cpp new file mode 100644 index 0000000..23bf0af --- /dev/null +++ b/Swift/QtUI/QtPlainChatView.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/QtPlainChatView.h> + +#include <QTextEdit> +#include <QScrollBar> +#include <QVBoxLayout> +#include <QPushButton> +#include <QLabel> +#include <QDialog> +#include <QProgressBar> +#include <QFileDialog> +#include <QInputDialog> +#include <QMenu> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/FileSize.h> + +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> + +#include <Swift/QtUI/ChatSnippet.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> + +namespace Swift { + +QtPlainChatView::QtPlainChatView(QtChatWindow *window, UIEventStream* eventStream) +: QtChatView(window), window_(window), eventStream_(eventStream), idGenerator_(0) { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + log_ = new LogTextEdit(this); + log_->setReadOnly(true); + log_->setAccessibleName(tr("Chat Messages")); + mainLayout->addWidget(log_); +} + +QtPlainChatView::~QtPlainChatView() { +} + +QString chatMessageToString(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))) { + result += P2QSTRING(emoticonPart->alternativeText); + continue; + } + if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) { + //FIXME: Maybe do something here. Anything, really. + continue; + } + } + return result; +} + +std::string QtPlainChatView::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*/) { + QString text = "<p>"; + if (label) { + text += P2QSTRING(label->getLabel()) + "<br/>"; + } + QString name = senderIsSelf ? "you" : P2QSTRING(senderName); + text += QString(tr("At %1 %2 said:")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name) + "<br/>"; + text += chatMessageToString(message); + text += "</p>"; + log_->append(text); + const std::string idx = senderIsSelf ? "" : senderName; + lastMessageLabel_[idx] = label; + return idx; +} + +std::string QtPlainChatView::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*/) { + QString text = "<p>"; + if (label) { + text += P2QSTRING(label->getLabel()) + "<br/>"; + } + QString name = senderIsSelf ? "you" : P2QSTRING(senderName); + text += QString(tr("At %1 <i>%2 ")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name); + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); + const std::string idx = senderIsSelf ? "" : senderName; + lastMessageLabel_[idx] = label; + return idx; +} + +void QtPlainChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::addErrorMessage(const ChatWindow::ChatMessage& message) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) +{ + QString text = "<p>"; + if (lastMessageLabel_[id]) { + text += P2QSTRING(lastMessageLabel_[id]->getLabel()) + "<br/>"; + } + QString name = id.empty() ? "you" : P2QSTRING(id); + text += QString(tr("At %1 %2 corrected the last message to:")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name) + "<br/>"; + text += chatMessageToString(message); + text += "</p>"; + log_->append(text); +} + +void QtPlainChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) +{ + QString text = "<p>"; + if (lastMessageLabel_[id]) { + text += P2QSTRING(lastMessageLabel_[id]->getLabel()) + "<br/>"; + } + QString name = id.empty() ? "you" : P2QSTRING(id); + text += QString(tr("At %1 %2 corrected the last action to: <i>")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name); + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); +} + +void QtPlainChatView::replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/) +{ + QString text = "<p>The last message was corrected to:<br/>"; + text += chatMessageToString(message); + text += "</p>"; + log_->append(text); +} + +void QtPlainChatView::setAckState(const std::string& /*id*/, ChatWindow::AckState state) +{ + if (state == ChatWindow::Failed) { + addSystemMessage(ChatWindow::ChatMessage("Message delivery failed due to disconnection from server."), ChatWindow::DefaultDirection); + } +} + +std::string QtPlainChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) +{ + const std::string ftId = "ft" + boost::lexical_cast<std::string>(idGenerator_++); + const std::string sizeString = formatSize(sizeInBytes); + + FileTransfer* transfer; + if (senderIsSelf) { + QString description = QInputDialog::getText(this, tr("File transfer description"), + tr("Description:"), QLineEdit::Normal, ""); + /* NOTE: it is not possible to abort if description is not provided, since we must always return a valid transfer id */ + const std::string message = std::string() + "Confirm file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>"; + transfer = new FileTransfer(this, senderIsSelf, ftId, filename, ChatWindow::WaitingForAccept, Q2PSTRING(description), message, true); + addSystemMessage(ChatWindow::ChatMessage("Preparing to start file transfer..."), ChatWindow::DefaultDirection); + } else { /* incoming transfer */ + const std::string message = std::string() + "Incoming file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>"; + transfer = new FileTransfer(this, senderIsSelf, ftId, filename, ChatWindow::WaitingForAccept, "", message, true); + addSystemMessage("Incoming file transfer from " + senderName + "...", ChatWindow::DefaultDirection); + } + + fileTransfers_[ftId] = transfer; + layout()->addWidget(transfer->dialog_); + + return ftId; +} + +void QtPlainChatView::setFileTransferProgress(std::string id, const int percentageDone) +{ + FileTransferMap::iterator transfer = fileTransfers_.find(id); + if (transfer != fileTransfers_.end()) { + transfer->second->bar_->setValue(percentageDone); + } +} + +void QtPlainChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) +{ + FileTransferMap::iterator transferIter = fileTransfers_.find(id); + if (transferIter == fileTransfers_.end()) { + return; + } + + /* store the layout index so we can restore it to the same location */ + FileTransfer* oldTransfer = transferIter->second; + const int layoutIndex = layout()->indexOf(oldTransfer->dialog_); + layout()->removeWidget(oldTransfer->dialog_); + const std::string &label = (!msg.empty() ? msg : oldTransfer->message_); + FileTransfer* transfer = new FileTransfer(this, oldTransfer->senderIsSelf_, oldTransfer->ftId_, oldTransfer->filename_, state, oldTransfer->description_, label, false); + fileTransfers_[oldTransfer->ftId_] = transfer; /* replace the transfer object for this file id */ + delete oldTransfer; + + /* insert the new dialog at the old position in the layout list */ + QBoxLayout* parentLayout = dynamic_cast<QBoxLayout*>(layout()); + assert(parentLayout); + parentLayout->insertWidget(layoutIndex, transfer->dialog_); + + /* log the transfer end result as a system message */ + if (state == ChatWindow::Finished) { + addSystemMessage(ChatWindow::ChatMessage("The file transfer completed successfully."), ChatWindow::DefaultDirection); + } else if (state == ChatWindow::Canceled) { + addSystemMessage(ChatWindow::ChatMessage("The file transfer was canceled."), ChatWindow::DefaultDirection); + } else if (state == ChatWindow::FTFailed) { + addSystemMessage(ChatWindow::ChatMessage("The file transfer failed."), ChatWindow::DefaultDirection); + } +} + +void QtPlainChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& /*reason*/, const std::string& password, bool /*direct*/, bool isImpromptu, bool isContinuation) +{ + PopupDialog *invite = new PopupDialog(this); + + QLabel* statusLabel = new QLabel; + std::string msg = senderName + " has invited you to join " + jid.toString() + "..."; + statusLabel->setText(P2QSTRING(msg)); + invite->layout_->addWidget(statusLabel); + invite->layout_->addWidget(new QLabel); /* padding */ + + AcceptMUCInviteAction* accept = new AcceptMUCInviteAction(invite, "Accept", jid, senderName, password, isImpromptu, isContinuation); + connect(accept, SIGNAL(clicked()), SLOT(acceptMUCInvite())); + invite->layout_->addWidget(accept); + + AcceptMUCInviteAction* reject = new AcceptMUCInviteAction(invite, "Reject", jid, senderName, password, isImpromptu, isContinuation); + connect(reject, SIGNAL(clicked()), SLOT(rejectMUCInvite())); + invite->layout_->addWidget(reject); + statusLabel->setText(P2QSTRING(msg)); + + layout()->addWidget(invite->dialog_); + + addSystemMessage(ChatWindow::ChatMessage(msg), ChatWindow::DefaultDirection); +} + +void QtPlainChatView::scrollToBottom() +{ + log_->ensureCursorVisible(); + log_->verticalScrollBar()->setValue(log_->verticalScrollBar()->maximum()); +} + +void QtPlainChatView::fileTransferAccept() +{ + FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender()); + if (!action) { + return; + } + + FileTransferMap::iterator transferIter = fileTransfers_.find(action->id_); + if (transferIter == fileTransfers_.end()) { + return; + } + + FileTransfer* transfer = transferIter->second; + + if (transfer->senderIsSelf_) { /* if we are the sender, kick of the transfer */ + window_->onFileTransferStart(transfer->ftId_, transfer->description_); + } else { /* ask the user where to save the file first */ + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), P2QSTRING(transfer->filename_)); + if (path.isEmpty()) { + fileTransferReject(); /* because the user did not select a desintation path */ + return; + } + window_->onFileTransferAccept(transfer->ftId_, Q2PSTRING(path)); + } + + setFileTransferStatus(transfer->ftId_, ChatWindow::Negotiating, transfer->message_); +} + +void QtPlainChatView::fileTransferReject() +{ + FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender()); + if (action) { + window_->onFileTransferCancel(action->id_); + fileTransferFinish(); + } +} + +void QtPlainChatView::fileTransferFinish() +{ + FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender()); + if (action) { + FileTransferMap::iterator transferIter = fileTransfers_.find(action->id_); + if (transferIter != fileTransfers_.end()) { + delete transferIter->second; /* cause the dialog to close */ + fileTransfers_.erase(transferIter); + } + } +} + +void QtPlainChatView::acceptMUCInvite() +{ + AcceptMUCInviteAction *action = dynamic_cast<AcceptMUCInviteAction*>(sender()); + if (action) { + eventStream_->send(boost::make_shared<JoinMUCUIEvent>(action->jid_.toString(), action->password_, boost::optional<std::string>(), false, false, action->isImpromptu_, action->isContinuation_)); + delete action->parent_; + } +} + +void QtPlainChatView::rejectMUCInvite() +{ + AcceptMUCInviteAction *action = dynamic_cast<AcceptMUCInviteAction*>(sender()); + if (action) { + /* NOTE: no action required to reject an invite? */ + delete action->parent_; + } +} + +QtPlainChatView::FileTransfer::FileTransfer(QtPlainChatView* parent, bool senderIsSelf, const std::string& ftId, const std::string& filename, const ChatWindow::FileTransferState state, const std::string &desc, const std::string& msg, bool initializing) +: PopupDialog(parent), bar_(0), senderIsSelf_(senderIsSelf), ftId_(ftId), filename_(filename), description_(desc), message_(msg), initializing_(initializing) +{ + QHBoxLayout* layout = new QHBoxLayout; + QLabel* statusLabel = new QLabel; + layout_->addWidget(statusLabel); + layout_->addWidget(new QLabel); /* padding */ + + if (initializing_) { + FileTransfer::Action* accept = new FileTransfer::Action(senderIsSelf?"Confirm":"Accept", ftId); + parent->connect(accept, SIGNAL(clicked()), SLOT(fileTransferAccept())); + layout_->addWidget(accept); + FileTransfer::Action* reject = new FileTransfer::Action(senderIsSelf?"Cancel":"Reject", ftId); + parent->connect(reject, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(reject); + statusLabel->setText(P2QSTRING(msg)); + return; + } + + std::string status = msg; + + switch (state) { + case ChatWindow::WaitingForAccept: { + status = "Waiting for user to accept <i>" + filename + "</i>..."; + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + case ChatWindow::Negotiating: { + status = "Preparing to transfer <i>" + filename + "</i>..."; + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + case ChatWindow::Transferring: { + status = "Transferring <i>" + filename + "</i>..."; + bar_ = new QProgressBar; + bar_->setRange(0, 100); + bar_->setValue(0); + layout->addWidget(bar_); + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + case ChatWindow::Canceled: { + status = "File <i>" + filename + "</i> was canceled."; + FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId); + parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish())); + layout_->addWidget(finish); + break; + } + case ChatWindow::Finished: { + status = "File <i>" + filename + "</i> was transfered successfully."; + FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId); + parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish())); + layout_->addWidget(finish); + break; + } + case ChatWindow::FTFailed: { + status = "File transfer failed: <i>" + filename + "</i>"; + FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId); + parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish())); + layout_->addWidget(finish); + break; + } + } + + statusLabel->setText(P2QSTRING(status)); +} + +void QtPlainChatView::LogTextEdit::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu *menu = createStandardContextMenu(); + menu->exec(event->globalPos()); + delete menu; +} + +} diff --git a/Swift/QtUI/QtPlainChatView.h b/Swift/QtUI/QtPlainChatView.h new file mode 100644 index 0000000..06613f9 --- /dev/null +++ b/Swift/QtUI/QtPlainChatView.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <boost/shared_ptr.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <QWidget> +#include <QTextEdit> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + +#include <Swift/QtUI/QtChatView.h> +#include <Swift/QtUI/QtChatWindow.h> + +class QTextEdit; +class QProgressBar; + +namespace Swift { + class HighlightAction; + class SecurityLabel; + + class QtPlainChatView : public QtChatView { + Q_OBJECT + public: + QtPlainChatView(QtChatWindow *window, UIEventStream* eventStream); + virtual ~QtPlainChatView(); + + /** 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*/); + /** 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*/); + + virtual void addSystemMessage(const ChatWindow::ChatMessage& /*message*/, ChatWindow::Direction /*direction*/); + virtual void addPresenceMessage(const ChatWindow::ChatMessage& /*message*/, ChatWindow::Direction /*direction*/); + virtual void addErrorMessage(const ChatWindow::ChatMessage& /*message*/); + + virtual void replaceMessage(const ChatWindow::ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/); + virtual void replaceWithAction(const ChatWindow::ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/); + virtual void replaceLastMessage(const ChatWindow::ChatMessage& /*message*/, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/); + virtual 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*/); + virtual void setFileTransferProgress(std::string, const int /*percentageDone*/); + virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState /*state*/, const std::string& /*msg*/ = ""); + virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool /*direct*/, bool /*isImpromptu*/, bool /*isContinuation*/); + virtual std::string addWhiteboardRequest(const QString& /*contact*/, bool /*senderIsSelf*/) {return "";}; + virtual void setWhiteboardSessionStatus(const std::string& /*id*/, const ChatWindow::WhiteboardSessionState /*state*/) {}; + virtual void setMessageReceiptState(const std::string& /*id*/, ChatWindow::ReceiptState /*state*/) {}; + + virtual void showEmoticons(bool /*show*/) {}; + virtual void addLastSeenLine() {}; + + public slots: + virtual void resizeFont(int /*fontSizeSteps*/) {}; + virtual void scrollToBottom(); + virtual void handleKeyPressEvent(QKeyEvent* /*event*/) {}; + virtual void fileTransferAccept(); + virtual void fileTransferReject(); + virtual void fileTransferFinish(); + virtual void acceptMUCInvite(); + virtual void rejectMUCInvite(); + + private: + struct PopupDialog { + PopupDialog(QtPlainChatView* parent) { + dialog_ = new QFrame(parent); + dialog_->setFrameShape(QFrame::Panel); + dialog_->setFrameShadow(QFrame::Raised); + layout_ = new QHBoxLayout; + dialog_->setLayout(layout_); + } + virtual ~PopupDialog() { + delete dialog_; + } + QFrame* dialog_; + QHBoxLayout* layout_; + }; + + struct AcceptMUCInviteAction : public QPushButton { + AcceptMUCInviteAction(PopupDialog* parent, const std::string& text, const JID& jid, const std::string& senderName, const std::string& password, bool isImpromptu, bool isContinuation) + : QPushButton(P2QSTRING(text)), parent_(parent), jid_(jid), senderName_(senderName), password_(password), isImpromptu_(isImpromptu), isContinuation_(isContinuation) {} + PopupDialog *parent_; + JID jid_; + std::string senderName_; + std::string password_; + bool isImpromptu_; + bool isContinuation_; + }; + + struct FileTransfer : public PopupDialog { + struct Action : QPushButton { + Action(const std::string& text, const std::string& id) + : QPushButton(P2QSTRING(text)), id_(id) {} + std::string id_; + }; + FileTransfer(QtPlainChatView* parent, bool senderIsSelf, const std::string& ftId, const std::string& filename, const ChatWindow::FileTransferState state, const std::string& desc, const std::string& msg, bool initializing); + QProgressBar* bar_; + bool senderIsSelf_; + std::string ftId_; + std::string filename_; + std::string description_; + std::string message_; + bool initializing_; + }; + + class LogTextEdit : public QTextEdit { + public: + LogTextEdit(QWidget* parent) : QTextEdit(parent) {} + virtual ~LogTextEdit() {} + virtual void contextMenuEvent(QContextMenuEvent *event); + }; + + typedef std::map<std::string, FileTransfer*> FileTransferMap; + QtChatWindow* window_; + UIEventStream* eventStream_; + LogTextEdit* log_; + FileTransferMap fileTransfers_; + std::map<std::string, boost::shared_ptr<SecurityLabel> > lastMessageLabel_; + int idGenerator_; + + }; +} diff --git a/Swift/QtUI/QtProfileWindow.cpp b/Swift/QtUI/QtProfileWindow.cpp index 0faa78f..9526d63 100644 --- a/Swift/QtUI/QtProfileWindow.cpp +++ b/Swift/QtUI/QtProfileWindow.cpp @@ -1,109 +1,110 @@ /* - * Copyright (c) 2011 Remko Tronçon + * Copyright (c) 2011-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * 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); +QtProfileWindow::QtProfileWindow() : + QWidget(), + ui(new Ui::QtProfileWindow) { + ui->setupUi(this); - errorLabel = new QLabel(this); - errorLabel->setAlignment(Qt::AlignHCenter); - fieldsLayout->addWidget(errorLabel); + ui->statusLabel->setText(tr("Retrieving profile information for this user.")); + ui->statusLabel->setVisible(false); - fieldsLayout->addItem(new QSpacerItem(198, 17, QSizePolicy::Minimum, QSizePolicy::Expanding)); - topLayout->addLayout(fieldsLayout); + ui->emptyLabel->setText(tr("No profile information is available for this user.")); + ui->emptyLabel->setVisible(false); - 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); + 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); +} - saveButton = new QPushButton(tr("Save"), this); - saveButton->setDefault( true ); - connect(saveButton, SIGNAL(clicked()), SLOT(handleSave())); - horizontalLayout->addWidget(saveButton); +QtProfileWindow::~QtProfileWindow() { + delete ui; +} - fieldsLayout->addLayout(horizontalLayout); +void QtProfileWindow::setJID(const JID& jid) { + this->jid = jid; + updateTitle(); +} - resize(360, 120); +void QtProfileWindow::setVCard(VCard::ref vcard) { + ui->vcard->setVCard(vcard); + if (vcard->isEmpty()) { + ui->vcard->setVisible(false); + ui->emptyLabel->setVisible(true); + } else { + ui->vcard->setVisible(true); + ui->emptyLabel->setVisible(false); } -void QtProfileWindow::setVCard(Swift::VCard::ref vcard) { - this->vcard = vcard; - nickname->setText(P2QSTRING(vcard->getNickname())); - avatar->setAvatar(vcard->getPhoto(), vcard->getPhotoType()); + updateWindowSize(); } 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(); + ui->statusLabel->setVisible(true); + ui->vcard->setVisible(false); } else { - throbberLabel->hide(); - throbberLabel->movie()->stop(); + ui->throbberLabel->hide(); + ui->throbberLabel->movie()->stop(); + ui->statusLabel->setVisible(false); + ui->vcard->setVisible(true); } + + updateWindowSize(); } -void QtProfileWindow::show() { - QWidget::show(); - QWidget::activateWindow(); +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(""); + } } -void QtProfileWindow::hideEvent(QHideEvent* event) { - QWidget::hideEvent(event); +void QtProfileWindow::show() { + QWidget::showNormal(); + QWidget::activateWindow(); + QWidget::raise(); } @@ -112,22 +113,44 @@ void QtProfileWindow::hide() { } -void QtProfileWindow::handleSave() { - assert(vcard); - vcard->setNickname(Q2PSTRING(nickname->text())); - vcard->setPhoto(avatar->getAvatarData()); - vcard->setPhotoType(avatar->getAvatarType()); - onVCardChangeRequest(vcard); +void QtProfileWindow::updateTitle() { + QString jidString; + if (jid.isValid()) { + jidString = QString(" ( %1 )").arg(P2QSTRING(jid.toString())); } -void QtProfileWindow::setError(const std::string& error) { - if (!error.empty()) { - errorLabel->setText("<font color='red'>" + P2QSTRING(error) + "</font>"); + if (ui->vcard->isEditable()) { + setWindowTitle(tr("Edit Profile") + jidString); + } else { + setWindowTitle(tr("Show Profile") + jidString); } - else { - errorLabel->setText(""); } + +void QtProfileWindow::updateWindowSize() { + int width = 0; + int height = 0; + + QSize size = ui->statusLabel->size(); + width = std::max(width, size.width()); + height = std::max(height, size.height() * 3); + + size = ui->emptyLabel->size(); + width = std::max(width, size.width()); + height = std::max(height, size.height() * 3); + + size = ui->vcard->size(); + width = std::max(width, size.width()); + height = std::max(height, size.height()); + + resize(width, height); } +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..57cc2df 100644 --- a/Swift/QtUI/QtProfileWindow.h +++ b/Swift/QtUI/QtProfileWindow.h @@ -5,31 +5,46 @@ */ +/* + * 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(); + virtual ~QtProfileWindow(); + + virtual void setJID(const JID& jid); + virtual void setVCard(VCard::ref vcard); - void setVCard(Swift::VCard::ref); - void setEnabled(bool); - void setProcessing(bool); - virtual void setError(const std::string&); - void show(); - void hide(); + virtual void setEnabled(bool b); + virtual void setProcessing(bool processing); + virtual void setError(const std::string& error); + virtual void setEditable(bool b); - void hideEvent (QHideEvent* event); + virtual void show(); + virtual void hide(); + + private: + void updateTitle(); + void updateWindowSize(); + virtual void closeEvent(QCloseEvent* event); private slots: @@ -37,12 +52,7 @@ namespace Swift { private: - VCard::ref vcard; - QtAvatarWidget* avatar; - QLabel* nicknameLabel; - QLineEdit* nickname; - QLabel* throbberLabel; - QLabel* errorLabel; - QHBoxLayout* horizontalLayout; - QPushButton* saveButton; + Ui::QtProfileWindow* ui; + JID jid; }; + } diff --git a/Swift/QtUI/QtProfileWindow.ui b/Swift/QtUI/QtProfileWindow.ui new file mode 100644 index 0000000..ed2986d --- /dev/null +++ b/Swift/QtUI/QtProfileWindow.ui @@ -0,0 +1,105 @@ +<?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,0,0,0"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="Swift::QtVCardWidget" name="vcard" native="true"/> + </item> + <item> + <widget class="QLabel" name="statusLabel"> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="emptyLabel"> + <property name="text"> + <string>TextLabel</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </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> + <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="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/QtResourceHelper.cpp b/Swift/QtUI/QtResourceHelper.cpp new file mode 100644 index 0000000..f76c438 --- /dev/null +++ b/Swift/QtUI/QtResourceHelper.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/QtResourceHelper.h> + +namespace Swift { + +QString statusShowTypeToIconPath(StatusShow::Type type) { + QString iconString; + switch (type) { + 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 QString(":/icons/%1.png").arg(iconString); +} + +} + diff --git a/Swift/QtUI/QtResourceHelper.h b/Swift/QtUI/QtResourceHelper.h new file mode 100644 index 0000000..034a941 --- /dev/null +++ b/Swift/QtUI/QtResourceHelper.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QString> + +#include <Swiften/Elements/StatusShow.h> + +namespace Swift { + +QString statusShowTypeToIconPath(StatusShow::Type type); + +} diff --git a/Swift/QtUI/QtRosterHeader.cpp b/Swift/QtUI/QtRosterHeader.cpp index 98e75c2..d5029ad 100644 --- a/Swift/QtUI/QtRosterHeader.cpp +++ b/Swift/QtUI/QtRosterHeader.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,13 +7,15 @@ #include "QtRosterHeader.h" -#include <QHBoxLayout> -#include <QVBoxLayout> +#include <QBitmap> +#include <qdebug.h> #include <QFileInfo> +#include <QHBoxLayout> +#include <QHelpEvent> #include <QIcon> -#include <QSizePolicy> -#include <qdebug.h> #include <QMouseEvent> #include <QPainter> -#include <QBitmap> +#include <QSizePolicy> +#include <QToolTip> +#include <QVBoxLayout> #include "QtStatusWidget.h" @@ -21,8 +23,9 @@ #include <Swift/QtUI/QtClickableLabel.h> #include <Swift/QtUI/QtNameWidget.h> +#include <Swift/QtUI/Roster/RosterTooltip.h> #include "QtScaledAvatarCache.h" namespace Swift { -QtRosterHeader::QtRosterHeader(SettingsProvider* settings, QWidget* parent) : QWidget(parent) { +QtRosterHeader::QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent) : QWidget(parent), statusEdit_(NULL) { QHBoxLayout* topLayout = new QHBoxLayout(); topLayout->setSpacing(3); @@ -46,12 +49,25 @@ QtRosterHeader::QtRosterHeader(SettingsProvider* settings, QWidget* parent) : QW topLayout->addLayout(rightLayout); + QHBoxLayout* nameAndSecurityLayout = new QHBoxLayout(); + nameAndSecurityLayout->setContentsMargins(4,0,0,0); + nameWidget_ = new QtNameWidget(settings, this); connect(nameWidget_, SIGNAL(onChangeNickRequest()), this, SIGNAL(onEditProfileRequest())); - rightLayout->addWidget(nameWidget_); + nameAndSecurityLayout->addWidget(nameWidget_); + + securityInfoButton_ = new QToolButton(this); + securityInfoButton_->setStyleSheet("QToolButton { border: none; } QToolButton:hover { border: 1px solid #bebebe; } QToolButton:pressed { border: 1px solid #757575; background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #777777, stop: 1 #d4d4d4);}"); + //securityInfoButton_->setAutoRaise(true); + securityInfoButton_->setIcon(QIcon(":/icons/lock.png")); + securityInfoButton_->setToolTip(tr("Connection is secured")); + connect(securityInfoButton_, SIGNAL(clicked()), this, SIGNAL(onShowCertificateInfo())); + nameAndSecurityLayout->addWidget(securityInfoButton_); + 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_); + show(); } @@ -67,4 +83,12 @@ void QtRosterHeader::setStatusText(const QString& statusMessage) { void QtRosterHeader::setStatusType(StatusShow::Type type) { statusWidget_->setStatusType(type); + if (type == StatusShow::None) { + nameWidget_->setOnline(false); + disconnect(avatarLabel_, SIGNAL(clicked()), this, SIGNAL(onEditProfileRequest())); + } + else { + nameWidget_->setOnline(true); + connect(avatarLabel_, SIGNAL(clicked()), this, SIGNAL(onEditProfileRequest()), Qt::UniqueConnection); + } } @@ -73,4 +97,19 @@ void QtRosterHeader::setConnecting() { } +void QtRosterHeader::setStreamEncryptionStatus(bool tlsInPlace) { + securityInfoButton_->setVisible(tlsInPlace); +} + +bool QtRosterHeader::event(QEvent* event) { + if (event->type() == QEvent::ToolTip) { + QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); + QtScaledAvatarCache scaledAvatarCache(avatarSize_); + QString text = RosterTooltip::buildDetailedTooltip(contact_.get(), &scaledAvatarCache); + QToolTip::showText(helpEvent->globalPos(), text); + return true; + } + return QWidget::event(event); +} + void QtRosterHeader::setAvatar(const QString& path) { QString scaledAvatarPath = QtScaledAvatarCache(avatarSize_).getScaledAvatarPath(path); @@ -89,4 +128,8 @@ void QtRosterHeader::setNick(const QString& nick) { } +void QtRosterHeader::setContactRosterItem(boost::shared_ptr<ContactRosterItem> contact) { + contact_ = contact; +} + void QtRosterHeader::setJID(const QString& jid) { nameWidget_->setJID(jid); diff --git a/Swift/QtUI/QtRosterHeader.h b/Swift/QtUI/QtRosterHeader.h index 050460c..eafbc02 100644 --- a/Swift/QtUI/QtRosterHeader.h +++ b/Swift/QtUI/QtRosterHeader.h @@ -11,8 +11,10 @@ #include <QPixmap> #include <QSize> -#include <QToolBar> +#include <QToolButton> #include <string> -#include "Swiften/Elements/StatusShow.h" +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/VCard.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> #include "QtTextEdit.h" @@ -25,20 +27,26 @@ namespace Swift { 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); void setNick(const QString& nick); + void setContactRosterItem(boost::shared_ptr<ContactRosterItem> contact); void setStatusText(const QString& statusMessage); void setStatusType(StatusShow::Type type); void setConnecting(); + void setStreamEncryptionStatus(bool tlsInPlace); + private: + bool event(QEvent* event); signals: void onChangeStatusRequest(StatusShow::Type showType, const QString &statusMessage); void onEditProfileRequest(); + void onShowCertificateInfo(); private slots: @@ -49,7 +57,8 @@ namespace Swift { QtNameWidget* nameWidget_; QtTextEdit* statusEdit_; - QToolBar* toolBar_; QtStatusWidget* statusWidget_; + QToolButton* securityInfoButton_; static const int avatarSize_; + boost::shared_ptr<ContactRosterItem> contact_; }; } 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 @@ -15,4 +15,7 @@ #include <QByteArray> +#include <Swiften/Base/Log.h> +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { @@ -32,5 +35,5 @@ QString QtScaledAvatarCache::getScaledAvatarPath(const QString& path) { if (!QFileInfo(targetFile).exists()) { QPixmap avatarPixmap; - avatarPixmap.load(path); + if (avatarPixmap.load(path)) { QPixmap maskedAvatar(avatarPixmap.size()); maskedAvatar.fill(QColor(0, 0, 0, 0)); @@ -45,4 +48,7 @@ QString QtScaledAvatarCache::getScaledAvatarPath(const QString& path) { 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 @@ -109,5 +109,5 @@ 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()) { 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,4 +1,4 @@ /* - * 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,5 +10,7 @@ #include <iostream> -#include "SwifTools/Application/ApplicationPathProvider.h" +#include <SwifTools/Application/ApplicationPathProvider.h> +#include <QtSwiftUtil.h> +#include <Swiften/Base/Path.h> namespace Swift { @@ -17,8 +19,9 @@ QtSoundPlayer::QtSoundPlayer(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; } @@ -28,5 +31,8 @@ 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 { 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 @@ -20,5 +20,5 @@ namespace Swift { QtSoundPlayer(ApplicationPathProvider* applicationPathProvider); - void playSound(SoundEffect sound); + void playSound(SoundEffect sound, const std::string& soundResource); private: 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 @@ -7,4 +7,8 @@ #include "QtStatusWidget.h" +#include <algorithm> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> + #include <QBoxLayout> #include <QComboBox> @@ -24,8 +28,18 @@ #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; @@ -135,5 +149,20 @@ void QtStatusWidget::generateList() { 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]); @@ -142,6 +171,18 @@ void QtStatusWidget::generateList() { 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() { @@ -160,16 +201,9 @@ void QtStatusWidget::handleClicked() { 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(); @@ -206,4 +240,5 @@ void QtStatusWidget::handleEditComplete() { viewMode(); emit onChangeStatusRequest(selectedStatusType_, statusText_); + statusCache_->addRecent(Q2PSTRING(statusText_), selectedStatusType_); } 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 @@ -23,8 +23,10 @@ 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(); @@ -46,7 +48,9 @@ namespace Swift { static QString getNoMessage(); private: + void resizeMenu(); void viewMode(); void setNewToolTip(); //QComboBox *types_; + StatusCache* statusCache_; QStackedWidget* stack_; QLabel* statusIcon_; @@ -65,4 +69,5 @@ namespace Swift { bool connecting_; static const QString NO_MESSAGE; + std::vector<StatusShow::Type> allTypes_; }; } diff --git a/Swift/QtUI/QtSubscriptionRequestWindow.cpp b/Swift/QtUI/QtSubscriptionRequestWindow.cpp index d22cbd0..460366b 100644 --- a/Swift/QtUI/QtSubscriptionRequestWindow.cpp +++ b/Swift/QtUI/QtSubscriptionRequestWindow.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -12,12 +12,15 @@ #include <QLabel> -#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtSubscriptionRequestWindow::QtSubscriptionRequestWindow(boost::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent) : QDialog(parent), event_(event) { - QString text = QString(tr("%1 would like to add you to their contact list.\n Would you like to add them to your contact list and share your status when you're online? \n\nIf you choose to defer this choice, you will be asked again when you next login.")).arg(event->getJID().toString().c_str()); + QString text = QString(tr("%1 would like to add you to their contact list.")).arg(P2QSTRING(event->getJID().toString())); QVBoxLayout* layout = new QVBoxLayout(); QLabel* label = new QLabel(text, this); layout->addWidget(label); + label = new QLabel(tr("Would you like to add them to your contact list and share your status when you're online?")); + //layout->addWidget(new QLabel); + layout->addWidget(label); if (event_->getConcluded()) { @@ -28,8 +31,8 @@ QtSubscriptionRequestWindow::QtSubscriptionRequestWindow(boost::shared_ptr<Subsc layout->addWidget(okButton); } else { - QPushButton* yesButton = new QPushButton(tr("Yes"), this); + QPushButton* yesButton = new QPushButton(tr("Accept"), this); yesButton->setDefault(true); connect(yesButton, SIGNAL(clicked()), this, SLOT(handleYes())); - QPushButton* noButton = new QPushButton(tr("No"), this); + QPushButton* noButton = new QPushButton(tr("Reject"), this); connect(noButton, SIGNAL(clicked()), this, SLOT(handleNo())); QPushButton* deferButton = new QPushButton(tr("Defer"), this); @@ -41,6 +44,9 @@ QtSubscriptionRequestWindow::QtSubscriptionRequestWindow(boost::shared_ptr<Subsc buttonLayout->addWidget(noButton); buttonLayout->addWidget(deferButton); - + layout->addWidget(new QLabel); layout->addLayout(buttonLayout); + layout->addWidget(new QLabel); + QLabel* footer = new QLabel(tr("(If you choose to defer this choice, you will be asked again when you next login.)")); + layout->addWidget(footer); } diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 9602336..d2224ba 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -1,32 +1,34 @@ /* - * 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> @@ -34,30 +36,37 @@ #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 @@ -65,7 +74,7 @@ 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 @@ -81,4 +90,8 @@ po::options_description QtSwift::getOptionsDescription() { ("multi-account", po::value<int>()->default_value(1), "Number of accounts to open windows for (unsupported)") ("start-minimized", "Don't show the login/roster window at startup") + ("enable-jid-adhocs", "Enable AdHoc commands to custom JID's.") +#if QT_VERSION >= 0x040800 + ("language", po::value<std::string>(), "Use a specific language, instead of the system-wide one") +#endif ; return result; @@ -98,10 +111,24 @@ XMLSettingsProvider* QtSwift::loadSettingsFile(const QString& fileName) { } -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; +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", ""); + QStringList tokens = line.split(" "); + if (tokens.size() == 2) { + QString emoticonFile = tokens[1]; + if (!emoticonFile.startsWith(":/") && !emoticonFile.startsWith("qrc:/")) { + emoticonFile = "file://" + emoticonFile; + } + emoticons[Q2PSTRING(tokens[0])] = Q2PSTRING(emoticonFile); + } + } + } } + +QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMainThreadCaller_), autoUpdater_(NULL), idleDetector_(&idleQuerier_, networkFactories_.getTimerFactory(), 1000) { QCoreApplication::setApplicationName(SWIFT_APPLICATION_NAME); QCoreApplication::setOrganizationName(SWIFT_ORGANIZATION_NAME); @@ -110,9 +137,19 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa 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_); + 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 { @@ -124,12 +161,13 @@ 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_ > 0) ? NULL : new QtChatTabs(); + bool enableAdHocCommandOnJID = options.count("enable-jid-adhocs") > 0; + 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()); + 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_); @@ -164,4 +202,6 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa #endif + statusCache_ = new StatusCache(applicationPathProvider_); + if (splitter_) { splitter_->show(); @@ -173,5 +213,5 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa systemTrays_.push_back(new QtSystemTray()); } - QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), startMinimized); + QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), statusCache_, startMinimized, !emoticons.empty(), enableAdHocCommandOnJID); uiFactories_.push_back(uiFactory); MainController* mainController = new MainController( @@ -188,4 +228,5 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa uriHandler_, &idleDetector_, + emoticons, options.count("latency-debug") > 0); mainControllers_.push_back(mainController); @@ -209,7 +250,4 @@ QtSwift::~QtSwift() { } delete notifier_; - delete settingsHierachy_; - delete qtSettings_; - delete xmlSettings_; foreach (QtSystemTray* tray, systemTrays_) { delete tray; @@ -217,4 +255,8 @@ QtSwift::~QtSwift() { delete tabs_; delete splitter_; + delete settingsHierachy_; + delete qtSettings_; + delete xmlSettings_; + delete statusCache_; delete uriHandler_; delete dock_; diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index d30ed7c..1ea8886 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -51,4 +51,6 @@ namespace Swift { class SettingsProviderHierachy; class XMLSettingsProvider; + class StatusCache; + class QtSingleWindow; class QtSwift : public QObject { @@ -60,4 +62,5 @@ namespace Swift { private: XMLSettingsProvider* loadSettingsFile(const QString& fileName); + void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons); private: QtEventLoop clientMainThreadCaller_; @@ -71,5 +74,5 @@ namespace Swift { XMLSettingsProvider* xmlSettings_; SettingsProviderHierachy* settingsHierachy_; - QSplitter* splitter_; + QtSingleWindow* splitter_; QtSoundPlayer* soundPlayer_; Dock* dock_; @@ -81,4 +84,5 @@ namespace Swift { AutoUpdater* autoUpdater_; Notifier* notifier_; + StatusCache* statusCache_; PlatformIdleQuerier idleQuerier_; ActualIdleDetector idleDetector_; 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 @@ -10,3 +10,5 @@ #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 123e6a3..83018b8 100644 --- a/Swift/QtUI/QtSystemTray.cpp +++ b/Swift/QtUI/QtSystemTray.cpp @@ -1,24 +1,45 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ +#pragma GCC diagnostic ignored "-Wredundant-decls" + #include "Swift/QtUI/QtSystemTray.h" +#include <QtDebug> +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) +#include <QDBusInterface> +#endif #include <QIcon> #include <QPixmap> #include <QResource> +#include <QMenu> +#include <QAction> namespace Swift { -QtSystemTray::QtSystemTray() : QObject(), onlineIcon_(":icons/online.png"), awayIcon_(":icons/away.png"), dndIcon_(":icons/dnd.png"), offlineIcon_(":icons/offline.png"), newMessageIcon_(":icons/new-chat.png"), throbberMovie_(":/icons/connecting.mng"), unreadMessages_(false), connecting_(false) { +QtSystemTray::QtSystemTray() : QObject(), statusType_(StatusShow::None), trayMenu_(0), onlineIcon_(":icons/online.png"), awayIcon_(":icons/away.png"), dndIcon_(":icons/dnd.png"), offlineIcon_(":icons/offline.png"), newMessageIcon_(":icons/new-chat.png"), throbberMovie_(":/icons/connecting.mng"), unreadMessages_(false), connecting_(false) { trayIcon_ = new QSystemTrayIcon(offlineIcon_); trayIcon_->setToolTip("Swift"); connect(trayIcon_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(handleIconActivated(QSystemTrayIcon::ActivationReason))); connect(&throbberMovie_, SIGNAL(frameChanged(int)), this, SLOT(handleThrobberFrameChanged(int))); +#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 + // front on Unity. See the README for sni-qt (which handles Qt tray icons for Unity) + trayMenu_ = new QMenu(); + QAction* showAction = new QAction(QString("Show/Hide"), this); + connect(showAction, SIGNAL(triggered()), SIGNAL(clicked())); + trayMenu_->addAction(showAction); + trayIcon_->setContextMenu(trayMenu_); + } +#endif trayIcon_->show(); } QtSystemTray::~QtSystemTray() { + delete trayMenu_; delete trayIcon_; } diff --git a/Swift/QtUI/QtSystemTray.h b/Swift/QtUI/QtSystemTray.h index cc7321b..8691e19 100644 --- a/Swift/QtUI/QtSystemTray.h +++ b/Swift/QtUI/QtSystemTray.h @@ -13,6 +13,8 @@ class QIcon; +class QMenu; namespace Swift { + class QtSystemTray : public QObject, public SystemTray { Q_OBJECT @@ -36,4 +38,5 @@ namespace Swift { StatusShow::Type statusType_; QSystemTrayIcon* trayIcon_; + QMenu* trayMenu_; QIcon onlineIcon_; QIcon awayIcon_; diff --git a/Swift/QtUI/QtTabbable.cpp b/Swift/QtUI/QtTabbable.cpp index 84a5100..124d1b4 100644 --- a/Swift/QtUI/QtTabbable.cpp +++ b/Swift/QtUI/QtTabbable.cpp @@ -5,13 +5,28 @@ */ -#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> +#include <Swift/QtUI/QtUtilities.h> namespace Swift { +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(); } @@ -26,34 +41,18 @@ bool QtTabbable::isWidgetSelected() { } -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) - ) { +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() == QtUtilities::ctrlHardwareKeyModifier && keyEvent->key() == Qt::Key_Tab && event->type() != QEvent::KeyRelease) { +#else + if (keyEvent->modifiers() == QtUtilities::ctrlHardwareKeyModifier && keyEvent->key() == Qt::Key_Tab) { +#endif emit requestNextTab(); - event->accept(); - } else if ( - (key == Qt::Key_A && modifiers == Qt::AltModifier) - ) { - emit requestActiveTab(); - event->accept(); + 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 @@ -7,6 +7,7 @@ #pragma once -#include <QKeyEvent> #include <QWidget> +#include <QShortcut> +#include <QList> @@ -16,14 +17,11 @@ namespace Swift { 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: @@ -37,4 +35,7 @@ namespace Swift { void requestActiveTab(); void requestFlash(); + + private: + QList<QShortcut*> shortcuts; }; } diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 3a62325..8551f3d 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -1,4 +1,4 @@ /* - * 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. @@ -7,13 +7,40 @@ #include <Swift/QtUI/QtTextEdit.h> +#include <boost/tuple/tuple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +#include <QApplication> #include <QFontMetrics> #include <QKeyEvent> +#include <QDebug> +#include <QMenu> + +#include <Swiften/Base/foreach.h> + +#include <SwifTools/SpellCheckerFactory.h> +#include <SwifTools/SpellChecker.h> + +#include <Swift/Controllers/SettingConstants.h> + +#include <Swift/QtUI/QtSpellCheckerWindow.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> 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) { @@ -36,11 +63,44 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) { } else if ((key == Qt::Key_Up) - || (key == Qt::Key_Down) - ){ + || (key == Qt::Key_Down)) { emit unhandledKeyPressEvent(event); QTextEdit::keyPressEvent(event); } + else if ((key == Qt::Key_K && modifiers == QtUtilities::ctrlHardwareKeyModifier)) { + QTextCursor cursor = textCursor(); + cursor.setPosition(toPlainText().size(), QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + } 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); } } @@ -54,4 +114,22 @@ 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()); @@ -67,6 +145,95 @@ QSize QtTextEdit::sizeHint() const { } +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() +{ + delete checker_; + checker_ = NULL; + if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { + std::string dictPath = settings_->getSetting(SettingConstants::DICT_PATH); + std::string dictFile = settings_->getSetting(SettingConstants::DICT_FILE); + checker_ = SpellCheckerFactory().createSpellChecker(dictPath + dictFile); + } +} +#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 @@ -6,19 +6,45 @@ #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 @@ -17,5 +17,9 @@ 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 2a50592..b0c1492 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -1,36 +1,43 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * 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 <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/QtHighlightEditor.h> +#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h> #include <Swift/Controllers/Settings/SettingsProviderHierachy.h> #include <Swift/QtUI/QtUISettingConstants.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) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized) { +QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) { chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); + historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); } @@ -45,4 +52,23 @@ XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { } +HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) { + QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream); + tabs->addTab(window); + if (!tabs->isVisible()) { + tabs->show(); + } + + connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int))); + + window->handleFontResized(historyFontSize_); + window->show(); + return window; +} + +void QtUIFactory::handleHistoryWindowFontResized(int size) { + historyFontSize_ = size; + settings->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size); +} + FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { QtFileTransferListWidget* widget = new QtFileTransferListWidget(); @@ -56,5 +82,5 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { - lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus()); + lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_, enableAdHocCommandOnJID_); return lastMainWindow; } @@ -63,5 +89,5 @@ 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())); @@ -120,6 +146,6 @@ 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) { @@ -135,6 +161,18 @@ ContactEditWindow* QtUIFactory::createContactEditWindow() { } -void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) { - new QtAdHocCommandWindow(command); +WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) { + return new QtWhiteboardWindow(whiteboardSession); +} + +HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() { + return new QtHighlightEditor(qtOnlySettings); +} + +BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { + return new QtBlockListEditorWindow(); +} + +AdHocCommandWindow* QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) { + return new QtAdHocCommandWindow(command); } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 29d9d1c..9c07e76 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -25,11 +25,16 @@ namespace Swift { class QtChatWindow; 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); + QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID); virtual XMLConsoleWidget* createXMLConsoleWidget(); + virtual HistoryWindow* createHistoryWindow(UIEventStream*); virtual MainWindow* createMainWindow(UIEventStream* eventStream); virtual LoginWindow* createLoginWindow(UIEventStream* eventStream); @@ -43,9 +48,13 @@ namespace Swift { virtual ContactEditWindow* createContactEditWindow(); virtual FileTransferListWidget* createFileTransferListWidget(); - virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); + virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession); + virtual HighlightEditorWindow* createHighlightEditorWindow(); + virtual BlockListEditorWidget* createBlockListEditorWidget(); + virtual AdHocCommandWindow* createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); private slots: void handleLoginWindowGeometryChanged(); void handleChatWindowFontResized(int); + void handleHistoryWindowFontResized(int); private: @@ -53,5 +62,5 @@ namespace Swift { QtSettingsProvider* qtOnlySettings; QtChatTabs* tabs; - QSplitter* netbookSplitter; + QtSingleWindow* netbookSplitter; QtSystemTray* systemTray; QtChatWindowFactory* chatWindowFactory; @@ -59,7 +68,11 @@ namespace Swift { QtMainWindow* lastMainWindow; QtLoginWindow* loginWindow; + StatusCache* statusCache; std::vector<QPointer<QtChatWindow> > chatWindows; bool startMinimized; int chatFontSize; + int historyFontSize_; + bool emoticonsExist_; + bool enableAdHocCommandOnJID_; }; } diff --git a/Swift/QtUI/QtUISettingConstants.cpp b/Swift/QtUI/QtUISettingConstants.cpp index 046ccc1..6b4f870 100644 --- a/Swift/QtUI/QtUISettingConstants.cpp +++ b/Swift/QtUI/QtUISettingConstants.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2012 Kevin Smith + * Copyright (c) 2012-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -14,3 +14,7 @@ const SettingsProvider::Setting<int> QtUISettingConstants::CURRENT_ROSTER_TAB("c const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER("showNickInRosterHeader", true); const SettingsProvider::Setting<int> QtUISettingConstants::CHATWINDOW_FONT_SIZE("chatWindowFontSize", 0); +const SettingsProvider::Setting<int> QtUISettingConstants::HISTORYWINDOW_FONT_SIZE("historyWindowFontSize", 0); +const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_EMOTICONS("showEmoticons", true); +const SettingsProvider::Setting<bool> QtUISettingConstants::USE_PLAIN_CHATS("plainChats", false); +const SettingsProvider::Setting<bool> QtUISettingConstants::USE_SCREENREADER("screenreader", false); } diff --git a/Swift/QtUI/QtUISettingConstants.h b/Swift/QtUI/QtUISettingConstants.h index 82f98bb..d0329fe 100644 --- a/Swift/QtUI/QtUISettingConstants.h +++ b/Swift/QtUI/QtUISettingConstants.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2012 Kevin Smith + * Copyright (c) 2012-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -17,4 +17,8 @@ namespace Swift { static const SettingsProvider::Setting<bool> SHOW_NICK_IN_ROSTER_HEADER; static const SettingsProvider::Setting<int> CHATWINDOW_FONT_SIZE; + static const SettingsProvider::Setting<int> HISTORYWINDOW_FONT_SIZE; + static const SettingsProvider::Setting<bool> SHOW_EMOTICONS; + static const SettingsProvider::Setting<bool> USE_PLAIN_CHATS; + static const SettingsProvider::Setting<bool> USE_SCREENREADER; }; } diff --git a/Swift/QtUI/QtURLValidator.cpp b/Swift/QtUI/QtURLValidator.cpp new file mode 100644 index 0000000..4d56b98 --- /dev/null +++ b/Swift/QtUI/QtURLValidator.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/QtURLValidator.h> + +#include <Swiften/Base/URL.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { +QtURLValidator::QtURLValidator(QObject* parent) : QValidator(parent) { + +} + +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"); + return valid ? Acceptable : Intermediate; +} + +} + diff --git a/Swift/QtUI/QtURLValidator.h b/Swift/QtUI/QtURLValidator.h new file mode 100644 index 0000000..fa17253 --- /dev/null +++ b/Swift/QtUI/QtURLValidator.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2012 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <QValidator> + +namespace Swift { + class QtURLValidator : public QValidator { + Q_OBJECT + public: + QtURLValidator(QObject* parent); + virtual QValidator::State validate(QString& input, int& pos) const; + }; +} + diff --git a/Swift/QtUI/QtUtilities.cpp b/Swift/QtUI/QtUtilities.cpp index be9d179..0fe2bf2 100644 --- a/Swift/QtUI/QtUtilities.cpp +++ b/Swift/QtUI/QtUtilities.cpp @@ -1,4 +1,4 @@ /* - * 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. @@ -7,6 +7,7 @@ #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> @@ -18,9 +19,11 @@ 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(); + QByteArray resName = (QString(SWIFT_APPLICATION_NAME) + "-" + c).toUtf8(); + hint.res_name = resName.data(); hint.res_class = res_class; XSetClassHint(widget->x11Info().display(), widget->winId(), &hint); @@ -31,3 +34,11 @@ void setX11Resource(QWidget* widget, const QString& c) { } +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..6f82dfd 100644 --- a/Swift/QtUI/QtUtilities.h +++ b/Swift/QtUI/QtUtilities.h @@ -1,4 +1,4 @@ /* - * 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,5 +10,13 @@ class QWidget; class QString; +#include <QKeyEvent> + namespace QtUtilities { void setX11Resource(QWidget* widget, const QString& c); -}; + QString htmlEscape(const QString& s); + #ifdef SWIFTEN_PLATFORM_MACOSX + const Qt::KeyboardModifier ctrlHardwareKeyModifier = Qt::MetaModifier; + #else + const Qt::KeyboardModifier ctrlHardwareKeyModifier = Qt::ControlModifier; + #endif +} 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..d50585c --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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& index) 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()); + if (index.data().toString().isEmpty()) { +#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 (index.data().toString().isEmpty() && 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..877a598 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + + +#include "QtResizableLineEdit.h" + +namespace Swift { + +QtResizableLineEdit::QtResizableLineEdit(QWidget* parent) : + QLineEdit(parent), editable(false) { + 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..af17d97 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2012-2014 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")), streetLineEdit(NULL), poboxLineEdit(NULL), addressextLineEdit(NULL), cityLineEdit(NULL), pocodeLineEdit(NULL), regionLineEdit(NULL), countryLineEdit(NULL), textFieldGridLayout(NULL), textFieldGridLayoutItem(NULL), deliveryTypeLabel(NULL), domesticRadioButton(NULL), internationalRadioButton(NULL), buttonGroup(NULL) { + 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..ba3d25f --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012-2014 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")), addressLabelPlainTextEdit(NULL), deliveryTypeLabel(NULL), domesticRadioButton(NULL), internationalRadioButton(NULL), buttonGroup(NULL) { + 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..7b0d02e --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2012-2014 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), birthdayLabel(NULL), birthdayDateEdit(NULL) { + 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..183e64d --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012-2014 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), descriptionPlainTextEdit(NULL) { + 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..155bd4f --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2012-2014 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> + +#include <cassert> + +#include <QHBoxLayout> +#include <QToolTip> + +namespace Swift { + +QtVCardGeneralField::QtVCardGeneralField(QWidget* parent, QGridLayout* layout, bool editable, int row, QString label, bool preferrable, bool taggable) : + QWidget(parent), editable(editable), preferrable(preferrable), starVisible(false), taggable(taggable), layout(layout), row(row), preferredCheckBox(0), label(0), labelText(label), + tagComboBox(0), tagLabel(NULL), closeButton(0) { +} + +QtVCardGeneralField::~QtVCardGeneralField() { + +} + +void QtVCardGeneralField::initialize() { + if (preferrable) { + preferredCheckBox = new QCheckBox(this); + preferredCheckBox->setToolTip(tr("Stars can be used to mark preferred contact details.")); + 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; + connect(preferredCheckBox, SIGNAL(stateChanged(int)), SLOT(handlePreferredStarStateChanged(int))); + } + 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); + updatePreferredStarVisibility(); + editableChanged(this->editable); +} + +void QtVCardGeneralField::setStarVisible(const bool isVisible) { + starVisible = isVisible; + updatePreferredStarVisibility(); +} + +bool QtVCardGeneralField::getStarVisible() const { + return starVisible; +} + +void QtVCardGeneralField::setPreferred(const bool preferred) { + if (preferredCheckBox) preferredCheckBox->setChecked(preferred); + updatePreferredStarVisibility(); +} + +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); +} + +void QtVCardGeneralField::handlePreferredStarStateChanged(int state) { + if (state == Qt::Checked) { + QToolTip::showText(QCursor::pos(), tr("Marked as your preferred %1. Click again to undo.").arg(labelText)); + } +} + +void QtVCardGeneralField::updatePreferredStarVisibility() { + if (preferredCheckBox) { + bool showStar = false; + if (editable) { + if (starVisible) { + showStar = true; + } + else { + showStar = preferredCheckBox->isChecked(); + } + } + else { + showStar = preferredCheckBox->isChecked(); + } + preferredCheckBox->setVisible(showStar); + preferredCheckBox->setEnabled(editable); + } +} + +} diff --git a/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h new file mode 100644 index 0000000..4f4cccd --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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 setStarVisible(const bool isVisible); + bool getStarVisible() const; + + 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(); + void handlePreferredStarStateChanged(int statte); + + protected: + QList<QWidget*> childWidgets; + + private: + void updatePreferredStarVisibility(); + + private: + bool editable; + bool preferrable; + bool starVisible; + 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..bae9771 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2012-2014 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")), emailLineEdit(NULL), emailLabel(NULL) { + 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..28a4438 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012-2014 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), jidLabel(NULL), jidLineEdit(NULL) { + 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..d5a7262 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2012-2014 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), organizationLabel(NULL), organizationLineEdit(NULL), unitsTreeWidget(NULL), itemDelegate(NULL) { + 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..b29171f --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2012-2014 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 + + setEditable(false); +} + +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(); + for (QStringList::iterator i = fullname.begin(); i != fullname.end(); i++) { + *i = i->trimmed(); + } + 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..af28d28 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012-2014 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), roleLineEdit(NULL) { + 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..401d0a0 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2012-2014 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")), telephoneLineEdit(NULL) { + 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..64b05c0 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012-2014 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), titleLineEdit(NULL) { + 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..18241b4 --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2012-2014 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), urlLabel(NULL), urlLineEdit(NULL) { + 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..7dfc06a --- /dev/null +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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) { + int sameFields = 0; + QtVCardGeneralField* fieldToChange = NULL; + foreach (QtVCardGeneralField* vcardField, fields) { + if ((vcardField != field) && (typeid(*vcardField) == typeid(*field))) { + sameFields++; + fieldToChange = vcardField; + } + } + + if ((sameFields == 1) && fieldToChange) { + fieldToChange->setStarVisible(false); + } + + 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*))); + + QtVCardGeneralField* fieldToChange = NULL; + foreach (QtVCardGeneralField* vcardField, fields) { + if (typeid(*vcardField) == typeid(*field)) { + fieldToChange = vcardField; + break; + } + } + + if (fieldToChange) { + fieldToChange->setStarVisible(true); + field->setStarVisible(true); + } + + 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..a510e34 --- /dev/null +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -0,0 +1,945 @@ +/* + * Copyright (c) 2010-2014 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtWebKitChatView.h" + +#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/Base/FileSize.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, const ChatWindow::TimestampBehaviour timestampBehaviour) { + 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)); + if (timestampBehaviour == ChatWindow::UpdateTimestamp) { + replace = lastElement_.findFirst("span.swift_time"); + assert(!replace.isNull()); + replace.setInnerXml(ChatSnippet::timeToEscapedString(QDateTime::currentDateTime())); + } +} + +void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) { + rememberScrolledToBottom(); + replaceLastMessage(newMessage, ChatWindow::KeepTimestamp); + 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::getHighlightSpanStart(const std::string& text, const std::string& background) { + QString ecsapeColor = QtUtilities::htmlEscape(P2QSTRING(text)); + QString escapeBackground = QtUtilities::htmlEscape(P2QSTRING(background)); + if (ecsapeColor.isEmpty()) { + ecsapeColor = "black"; + } + if (escapeBackground.isEmpty()) { + escapeBackground = "yellow"; + } + return QString("<span style=\"color: %1; background: %2\">").arg(ecsapeColor).arg(escapeBackground); +} + +QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { + return getHighlightSpanStart(highlight.getTextColor(), highlight.getTextBackground()); +} + +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))) { + QString spanStart = getHighlightSpanStart(highlightPart->foregroundColor, highlightPart->backgroundColor); + result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>"; + continue; + } + + } + return result; +} + +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.highlightAllText() ? getHighlightSpanStart(highlight) : ""; + QString highlightSpanEnd = highlight.highlightAllText() ? "</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)); +} + +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(const 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.highlightAllText() ? getHighlightSpanStart(highlight) : ""; + QString highlightSpanEnd = highlight.highlightAllText() ? "</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, const ChatWindow::TimestampBehaviour timestampBehaviour) { + replaceLastMessage(chatMessageToHTML(message), timestampBehaviour); +} + +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; + case ChatWindow::ReceiptFailed: + xml = "<img src='qrc:/icons/error.png' title='" + tr("Failed to transmit message to the receipient(s).") + "'/>"; + } + 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..925ceeb --- /dev/null +++ b/Swift/QtUI/QtWebKitChatView.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2010-2014 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, const ChatWindow::TimestampBehaviour timestampBehaviour); + 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(const 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, const ChatWindow::TimestampBehaviour timestampBehaviour); + 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 getHighlightSpanStart(const std::string& text, const std::string& background); + QString getHighlightSpanStart(const HighlightAction& highlight); + QString chatMessageToHTML(const ChatWindow::ChatMessage& message); + 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 @@ -10,5 +10,7 @@ #include <QKeyEvent> #include <QFocusEvent> +#include <boost/numeric/conversion/cast.hpp> #include <QMenu> +#include <Swiften/Base/Log.h> namespace Swift { @@ -18,4 +20,7 @@ QtWebView::QtWebView(QWidget* parent) : QWebView(parent), fontSizeIsMinimal(fals filteredActions.push_back(QWebPage::CopyImageToClipboard); filteredActions.push_back(QWebPage::Copy); + if (Log::getLogLevel() == Log::debug) { + filteredActions.push_back(QWebPage::InspectElement); + } } @@ -31,5 +36,5 @@ void QtWebView::keyPressEvent(QKeyEvent* event) { 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 @@ -24,5 +24,5 @@ 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..f8ab28e 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.cpp +++ b/Swift/QtUI/QtXMLConsoleWidget.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Remko Tronçon + * Copyright (c) 2010-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,4 +7,6 @@ #include "QtXMLConsoleWidget.h" +#include <string> + #include <QCloseEvent> #include <QTextEdit> @@ -14,6 +16,7 @@ #include <QCheckBox> -#include "QtSwiftUtil.h" -#include <string> +#include <Swiften/Base/format.h> + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -76,9 +79,13 @@ void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) { void QtXMLConsoleWidget::handleDataRead(const SafeByteArray& data) { - appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(33,98,33)); + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + std::string tag = Q2PSTRING(tr("<!-- IN %1 -->").arg(P2QSTRING(boost::posix_time::to_iso_extended_string(now)))); + appendTextIfEnabled(tag + "\n" + safeByteArrayToString(data) + "\n", QColor(33,98,33)); } void QtXMLConsoleWidget::handleDataWritten(const SafeByteArray& data) { - appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(155,1,0)); + boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); + std::string tag = Q2PSTRING(tr("<!-- OUT %1 -->").arg(P2QSTRING(boost::posix_time::to_iso_extended_string(now)))); + appendTextIfEnabled(tag + "\n" + safeByteArrayToString(data) + "\n", QColor(155,1,0)); } @@ -97,5 +104,9 @@ void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QCol 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 @@ -15,8 +15,10 @@ 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 { +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); @@ -30,4 +32,5 @@ 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 @@ -52,4 +55,8 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + if (isIdle) { + idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + } + QFontMetrics nameMetrics(nameFont); painter->setFont(nameFont); 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 @@ -18,5 +18,5 @@ 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); @@ -27,5 +27,5 @@ namespace Swift { 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; @@ -39,4 +39,5 @@ namespace Swift { static const int presenceIconWidth; static const int unreadCountSize; + QIcon idleIcon; }; } diff --git a/Swift/QtUI/Roster/QtFilterWidget.cpp b/Swift/QtUI/Roster/QtFilterWidget.cpp new file mode 100644 index 0000000..64eb312 --- /dev/null +++ b/Swift/QtUI/Roster/QtFilterWidget.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <QEvent> +#include <QKeyEvent> +#include <QLayout> +#include <QString> +#include <QVBoxLayout> + +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/QtUI/QtClosableLineEdit.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/Roster/QtFilterWidget.h> + +namespace Swift { + +QtFilterWidget::QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, UIEventStream* eventStream, QBoxLayout* layout) : QWidget(parent), treeView_(treeView), eventStream_(eventStream), fuzzyRosterFilter_(0), isModifierSinglePressed_(false) { + int targetIndex = layout->indexOf(treeView); + + QVBoxLayout* vboxLayout = new QVBoxLayout(this); + vboxLayout->setSpacing(0); + vboxLayout->setContentsMargins(0,0,0,0); + + filterLineEdit_ = new QtClosableLineEdit(this); + filterLineEdit_->hide(); + vboxLayout->addWidget(filterLineEdit_); + + vboxLayout->addWidget(treeView); + setLayout(vboxLayout); + layout->insertWidget(targetIndex, this); + + filterLineEdit_->installEventFilter(this); + treeView->installEventFilter(this); + + sourceModel_ = treeView_->model(); +} + +QtFilterWidget::~QtFilterWidget() { + +} + +bool QtFilterWidget::eventFilter(QObject*, QEvent* event) { + if (event->type() == QEvent::KeyPress || + event->type() == QEvent::KeyRelease || + // InputMethodQuery got introduced in Qt 5. +#if QT_VERSION >= 0x050000 + event->type() == QEvent::InputMethodQuery || +#endif + event->type() == QEvent::InputMethod) { + event->ignore(); + QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event); + if (keyEvent) { + if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { + return false; + } else if ((keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right) && filterLineEdit_->text().isEmpty()) { + return false; + } else if (keyEvent->key() == Qt::Key_Alt && event->type() == QEvent::KeyPress) { + isModifierSinglePressed_ = true; + } else if (keyEvent->key() == Qt::Key_Alt && event->type() == QEvent::KeyRelease && isModifierSinglePressed_) { + QPoint itemOffset(2,2); + QPoint contextMenuPosition = treeView_->visualRect(treeView_->currentIndex()).topLeft() + itemOffset;; + QApplication::postEvent(treeView_, new QContextMenuEvent(QContextMenuEvent::Keyboard, contextMenuPosition, treeView_->mapToGlobal(contextMenuPosition))); + return true; + } else if (keyEvent->key() == Qt::Key_Return) { + JID target = treeView_->selectedJID(); + if (target.isValid()) { + eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(target))); + } + filterLineEdit_->setText(""); + updateRosterFilters(); + } else if (keyEvent->key() == Qt::Key_Escape) { + filterLineEdit_->setText(""); + } else { + isModifierSinglePressed_ = false; + } + } + + filterLineEdit_->event(event); + + if (event->type() == QEvent::KeyRelease) { + updateRosterFilters(); + } + return true; + } + return false; +} + +void QtFilterWidget::popAllFilters() { + std::vector<RosterFilter*> filters = treeView_->getRoster()->getFilters(); + foreach(RosterFilter* filter, filters) { + filters_.push_back(filter); + treeView_->getRoster()->removeFilter(filter); + } + treeView_->getRoster()->onFilterAdded.connect(boost::bind(&QtFilterWidget::handleFilterAdded, this, _1)); + treeView_->getRoster()->onFilterRemoved.connect(boost::bind(&QtFilterWidget::handleFilterRemoved, this, _1)); +} + +void QtFilterWidget::pushAllFilters() { + treeView_->getRoster()->onFilterAdded.disconnect(boost::bind(&QtFilterWidget::handleFilterAdded, this, _1)); + treeView_->getRoster()->onFilterRemoved.disconnect(boost::bind(&QtFilterWidget::handleFilterRemoved, this, _1)); + foreach(RosterFilter* filter, filters_) { + treeView_->getRoster()->addFilter(filter); + } + filters_.clear(); +} + +void QtFilterWidget::updateRosterFilters() { + if (fuzzyRosterFilter_) { + if (filterLineEdit_->text().isEmpty()) { + // remove currently installed search filter and put old filters back + treeView_->getRoster()->removeFilter(fuzzyRosterFilter_); + delete fuzzyRosterFilter_; + fuzzyRosterFilter_ = NULL; + pushAllFilters(); + } else { + // remove currently intsalled search filter and put new search filter in place + updateSearchFilter(); + } + } else { + if (!filterLineEdit_->text().isEmpty()) { + // remove currently installed filters and put a search filter in place + popAllFilters(); + updateSearchFilter(); + } + } + filterLineEdit_->setVisible(!filterLineEdit_->text().isEmpty()); +} + +void QtFilterWidget::updateSearchFilter() { + if (fuzzyRosterFilter_) { + treeView_->getRoster()->removeFilter(fuzzyRosterFilter_); + delete fuzzyRosterFilter_; + fuzzyRosterFilter_ = NULL; + } + fuzzyRosterFilter_ = new FuzzyRosterFilter(Q2PSTRING(filterLineEdit_->text())); + treeView_->getRoster()->addFilter(fuzzyRosterFilter_); + treeView_->setCurrentIndex(sourceModel_->index(0, 0, sourceModel_->index(0,0))); +} + +void QtFilterWidget::handleFilterAdded(RosterFilter* filter) { + if (filter != fuzzyRosterFilter_) { + filterLineEdit_->setText(""); + updateRosterFilters(); + } +} + +void QtFilterWidget::handleFilterRemoved(RosterFilter* filter) { + /* make sure we don't end up adding this one back in later */ + filters_.erase(std::remove(filters_.begin(), filters_.end(), filter), filters_.end()); + if (filter != fuzzyRosterFilter_) { + filterLineEdit_->setText(""); + updateRosterFilters(); + } +} + +} diff --git a/Swift/QtUI/Roster/QtFilterWidget.h b/Swift/QtUI/Roster/QtFilterWidget.h new file mode 100644 index 0000000..d0307ea --- /dev/null +++ b/Swift/QtUI/Roster/QtFilterWidget.h @@ -0,0 +1,51 @@ +/* + * 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 <QBoxLayout> +#include <QWidget> + +#include <Swift/Controllers/Roster/RosterFilter.h> +#include <Swift/Controllers/Roster/FuzzyRosterFilter.h> + +#include <Swift/QtUI/Roster/QtTreeWidget.h> + +namespace Swift { +class UIEventStream; +class QtClosableLineEdit; +class QtFilterWidget : public QWidget { + Q_OBJECT + public: + QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, UIEventStream* eventStream, QBoxLayout* layout = 0); + virtual ~QtFilterWidget(); + + protected: + bool eventFilter(QObject*, QEvent* event); + + private: + void popAllFilters(); + void pushAllFilters(); + + void updateRosterFilters(); + void updateSearchFilter(); + + void handleFilterAdded(RosterFilter* filter); + void handleFilterRemoved(RosterFilter* filter); + + private: + QtClosableLineEdit* filterLineEdit_; + QtTreeWidget* treeView_; + UIEventStream* eventStream_; + std::vector<RosterFilter*> filters_; + QAbstractItemModel* sourceModel_; + FuzzyRosterFilter* fuzzyRosterFilter_; + bool isModifierSinglePressed_; +}; + +} diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.cpp b/Swift/QtUI/Roster/QtOccupantListWidget.cpp index 5d26c46..4f1baf3 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.cpp +++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011-2012 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -6,5 +6,5 @@ -#include "Roster/QtOccupantListWidget.h" +#include <Swift/QtUI/Roster/QtOccupantListWidget.h> #include <QContextMenuEvent> @@ -13,12 +13,13 @@ #include <QInputDialog> -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "QtSwiftUtil.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 { -QtOccupantListWidget::QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QtTreeWidget(eventStream, settings, parent) { +QtOccupantListWidget::QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget privateMessageTarget, QWidget* parent) : QtTreeWidget(eventStream, settings, privateMessageTarget, parent) { } @@ -59,4 +60,5 @@ void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) { 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); diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.h b/Swift/QtUI/Roster/QtOccupantListWidget.h index 729115a..c944658 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.h +++ b/Swift/QtUI/Roster/QtOccupantListWidget.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011-2012 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,8 +7,9 @@ #pragma once -#include "Swift/QtUI/Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> -#include "Swiften/Base/boost_bsignals.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" +#include <Swiften/Base/boost_bsignals.h> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { @@ -18,5 +19,5 @@ class QtOccupantListWidget : public QtTreeWidget { Q_OBJECT public: - QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); + QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget privateMessageTarget, QWidget* parent = NULL); virtual ~QtOccupantListWidget(); void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions); diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index e3fee24..8c296e5 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -1,28 +1,33 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Roster/QtRosterWidget.h" +#include <Swift/QtUI/Roster/QtRosterWidget.h> #include <QContextMenuEvent> #include <QMenu> +#include <QMessageBox> #include <QInputDialog> #include <QFileDialog> +#include <QPushButton> -#include "Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h" -#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" -#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" -#include "QtContactEditWindow.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "QtSwiftUtil.h" +#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 { -QtRosterWidget::QtRosterWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QtTreeWidget(eventStream, settings, parent) { +QtRosterWidget::QtRosterWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QtTreeWidget(eventStream, settings, MessageDefaultJID, parent) { } @@ -55,10 +60,35 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { QMenu contextMenu; if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { - QAction* editContact = contextMenu.addAction(tr("Edit")); + QAction* editContact = contextMenu.addAction(tr("Edit…")); + editContact->setEnabled(isOnline()); QAction* removeContact = contextMenu.addAction(tr("Remove")); + removeContact->setEnabled(isOnline()); + QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile")); + + QAction* unblockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsBlocked || + contact->blockState() == ContactRosterItem::IsDomainBlocked) { + unblockContact = contextMenu.addAction(tr("Unblock")); + unblockContact->setEnabled(isOnline()); + } + + QAction* blockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsUnblocked) { + blockContact = contextMenu.addAction(tr("Block")); + blockContact->setEnabled(isOnline()); + } + #ifdef SWIFT_EXPERIMENTAL_FT QAction* sendFile = NULL; if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { sendFile = contextMenu.addAction(tr("Send File")); + sendFile->setEnabled(isOnline()); + } +#endif +#ifdef SWIFT_EXPERIMENTAL_WB + QAction* startWhiteboardChat = NULL; + if (contact->supportsFeature(ContactRosterItem::WhiteboardFeature)) { + startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat")); + startWhiteboardChat->setEnabled(isOnline()); } #endif @@ -72,4 +102,24 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { } } + else if (result == showProfileForContact) { + eventStream_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(contact->getJID())); + } + else if (unblockContact && result == unblockContact) { + if (contact->blockState() == ContactRosterItem::IsDomainBlocked) { + QMessageBox messageBox(QMessageBox::Question, tr("Swift"), tr("%2 is currently blocked because of a block on all users of the %1 service.\n %2 cannot be unblocked individually; do you want to unblock all %1 users?").arg(P2QSTRING(contact->getJID().getDomain()), P2QSTRING(contact->getJID().toString())), QMessageBox::NoButton, this); + QPushButton* unblockDomainButton = messageBox.addButton(tr("Unblock %1 domain").arg(P2QSTRING(contact->getJID().getDomain())), QMessageBox::AcceptRole); + messageBox.addButton(QMessageBox::Abort); + + messageBox.exec(); + if (messageBox.clickedButton() == unblockDomainButton) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID().getDomain())); + } + } else { + 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) { @@ -80,7 +130,18 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { } #endif +#ifdef SWIFT_EXPERIMENTAL_WB + else if (startWhiteboardChat && result == startWhiteboardChat) { + eventStream_->send(boost::make_shared<RequestWhiteboardUIEvent>(contact->getJID())); + } +#endif } else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) { QAction* renameGroupAction = contextMenu.addAction(tr("Rename")); + if (P2QSTRING(group->getDisplayName()) == tr("Contacts")) { + renameGroupAction->setEnabled(false); + } + else { + renameGroupAction->setEnabled(isOnline()); + } QAction* result = contextMenu.exec(event->globalPos()); if (result == renameGroupAction) { diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 5fdf138..f296088 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> #include <boost/smart_ptr/make_shared.hpp> @@ -11,6 +11,12 @@ #include <QUrl> +#include <QMimeData> +#include <QObject> +#include <QLabel> +#include <QTimer> +#include <QToolTip> #include <Swiften/Base/Platform.h> + #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> @@ -18,14 +24,16 @@ #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> -#include <QtSwiftUtil.h> #include <Swift/Controllers/Settings/SettingsProvider.h> + #include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/Roster/RosterModel.h> +#include <QtSwiftUtil.h> namespace Swift { -QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) { +QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget messageTarget, QWidget* parent) : QTreeView(parent), tooltipShown_(false), messageTarget_(messageTarget) { eventStream_ = eventStream; settings_ = settings; - model_ = new RosterModel(this); + model_ = new RosterModel(this, settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)); setModel(model_); delegate_ = new RosterDelegate(this, settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); @@ -42,4 +50,5 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* setting setAcceptDrops(true); #endif + setDragEnabled(true); setRootIsDecorated(true); connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); @@ -65,4 +74,12 @@ void QtTreeWidget::handleSettingChanged(const std::string& setting) { } +void QtTreeWidget::handleRefreshTooltip() { + if (tooltipShown_) { + QPoint position = QCursor::pos(); + QModelIndex index = indexAt(mapFromGlobal(position)); + QToolTip::showText(position, model_->data(index, Qt::ToolTipRole).toString()); + } +} + void QtTreeWidget::setRosterModel(Roster* roster) { roster_ = roster; @@ -71,4 +88,9 @@ void QtTreeWidget::setRosterModel(Roster* roster) { } +void QtTreeWidget::refreshTooltip() { + // Qt needs some time to emit the events we need to detect tooltip's visibility correctly; 20 ms should be enough + QTimer::singleShot(20, this, SLOT(handleRefreshTooltip())); +} + QtTreeWidgetItem* QtTreeWidget::getRoot() { return treeRoot_; @@ -116,10 +138,8 @@ void QtTreeWidget::currentChanged(const QModelIndex& current, const QModelIndex& } - void QtTreeWidget::handleItemActivated(const QModelIndex& index) { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - if (contact) { - eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(contact->getJID()))); + JID target = jidFromIndex(index); + if (target.isValid()) { + eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(target))); } } @@ -157,5 +177,22 @@ void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) { } } - event->ignore(); + QTreeView::dragMoveEvent(event); +} + +bool QtTreeWidget::event(QEvent* event) { + QChildEvent* childEvent = NULL; + if ((childEvent = dynamic_cast<QChildEvent*>(event))) { + if (childEvent->polished()) { + if (dynamic_cast<QLabel*>(childEvent->child())) { + tooltipShown_ = true; + } + } + else if (childEvent->removed()) { + if (childEvent->child()->objectName() == "qtooltip_label") { + tooltipShown_ = false; + } + } + } + return QAbstractItemView::event(event); } @@ -192,3 +229,35 @@ void QtTreeWidget::show() { } +void QtTreeWidget::setMessageTarget(MessageTarget messageTarget) { + messageTarget_ = messageTarget; +} + +JID QtTreeWidget::jidFromIndex(const QModelIndex& index) const { + JID target; + if (messageTarget_ == MessageDisplayJID) { + target = JID(Q2PSTRING(index.data(DisplayJIDRole).toString())); + target = target.toBare(); + } + if (!target.isValid()) { + target = JID(Q2PSTRING(index.data(JIDRole).toString())); + } + return target; +} + +JID QtTreeWidget::selectedJID() const { + QModelIndexList list = selectedIndexes(); + if (list.size() != 1) { + return JID(); + } + return jidFromIndex(list[0]); +} + +void QtTreeWidget::setOnline(bool isOnline) { + isOnline_ = isOnline; +} + +bool QtTreeWidget::isOnline() const { + return isOnline_; +} + } diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h index 7c10a6a..12d34f5 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.h +++ b/Swift/QtUI/Roster/QtTreeWidget.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,11 +7,14 @@ #pragma once -#include <QTreeView> -#include <QModelIndex> #include <QDragEnterEvent> #include <QDropEvent> #include <QDragMoveEvent> -#include "Swift/QtUI/Roster/RosterModel.h" -#include "Swift/QtUI/Roster/RosterDelegate.h" +#include <QModelIndex> +#include <QTreeView> + +#include <Swift/QtUI/Roster/RosterDelegate.h> +#include <Swift/QtUI/Roster/RosterModel.h> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { @@ -22,5 +25,7 @@ class QtTreeWidget : public QTreeView{ Q_OBJECT public: - QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); + enum MessageTarget {MessageDefaultJID, MessageDisplayJID}; + + QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget messageTarget, QWidget* parent = 0); ~QtTreeWidget(); void show(); @@ -28,4 +33,11 @@ class QtTreeWidget : public QTreeView{ void setRosterModel(Roster* roster); Roster* getRoster() {return roster_;} + void refreshTooltip(); + void setMessageTarget(MessageTarget messageTarget); + JID jidFromIndex(const QModelIndex& index) const; + JID selectedJID() const; + void setOnline(bool isOnline); + + public: boost::signal<void (RosterItem*)> onSomethingSelectedChanged; @@ -37,13 +49,17 @@ class QtTreeWidget : public QTreeView{ void handleClicked(const QModelIndex&); void handleSettingChanged(const std::string& setting); + void handleRefreshTooltip(); + protected: void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent* event); void dragMoveEvent(QDragMoveEvent* event); - - protected: + bool event(QEvent* event); QModelIndexList getSelectedIndexes() const; + bool isOnline() const; + private: void drawBranches(QPainter*, const QRect&, const QModelIndex&) const; + protected slots: virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); @@ -57,4 +73,7 @@ class QtTreeWidget : public QTreeView{ QtTreeWidgetItem* treeRoot_; SettingsProvider* settings_; + bool tooltipShown_; + MessageTarget messageTarget_; + bool isOnline_; }; 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 @@ -36,4 +36,5 @@ RosterDelegate::~RosterDelegate() { void RosterDelegate::setCompact(bool compact) { compact_ = compact; + emit sizeHintChanged(QModelIndex()); } @@ -74,7 +75,8 @@ void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& ? 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..730ffbb 100644 --- a/Swift/QtUI/Roster/RosterModel.cpp +++ b/Swift/QtUI/Roster/RosterModel.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "RosterModel.h" +#include <Swift/QtUI/Roster/RosterModel.h> #include <boost/bind.hpp> @@ -11,21 +11,28 @@ #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 <Swiften/Elements/StatusShow.h> +#include <Swiften/Base/Path.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/StatusUtil.h> -#include "QtSwiftUtil.h" -#include "Swift/QtUI/Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <Swift/QtUI/Roster/RosterTooltip.h> +#include <Swift/QtUI/QtResourceHelper.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -RosterModel::RosterModel(QtTreeWidget* view) : view_(view) { - roster_ = NULL; +RosterModel::RosterModel(QtTreeWidget* view, bool screenReaderMode) : roster_(NULL), view_(view), screenReader_(screenReaderMode) { + const int tooltipAvatarSize = 96; // maximal suggested size according to XEP-0153 + cachedImageScaler_ = new QtScaledAvatarCache(tooltipAvatarSize); } RosterModel::~RosterModel() { + delete cachedImageScaler_; } @@ -41,5 +48,6 @@ 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; @@ -61,5 +69,14 @@ void RosterModel::handleDataChanged(RosterItem* item) { if (modelIndex.isValid()) { emit dataChanged(modelIndex, modelIndex); + view_->refreshTooltip(); + } +} + +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; } @@ -77,5 +94,5 @@ QVariant RosterModel::data(const QModelIndex& index, int role) const { switch (role) { - case Qt::DisplayRole: return P2QSTRING(item->getDisplayName()); + case Qt::DisplayRole: return getScreenReaderTextOr(item, P2QSTRING(item->getDisplayName())); case Qt::TextColorRole: return getTextColor(item); case Qt::BackgroundColorRole: return getBackgroundColor(item); @@ -85,8 +102,26 @@ QVariant RosterModel::data(const QModelIndex& index, int role) const { case PresenceIconRole: return getPresenceIcon(item); case ChildCountRole: return getChildCount(item); + case IdleRole: return getIsIdle(item); + case JIDRole: return getJID(item); + case DisplayJIDRole: return getDisplayJID(item); default: return QVariant(); } } +QString RosterModel::getScreenReaderTextOr(RosterItem* item, const QString& alternative) const { + QString name = P2QSTRING(item->getDisplayName()); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && screenReader_) { + name += ": " + P2QSTRING(statusShowTypeToFriendlyName(contact->getStatusShow())); + if (!contact->getStatusText().empty()) { + name += " (" + P2QSTRING(contact->getStatusText()) + ")"; + } + return name; + } + else { + return alternative; + } +} + int RosterModel::getChildCount(RosterItem* item) const { GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); @@ -94,4 +129,9 @@ int RosterModel::getChildCount(RosterItem* item) const { } +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( @@ -125,11 +165,5 @@ QString RosterModel::getToolTip(RosterItem* item) const { ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); if (contact) { - if (contact->getDisplayJID().isValid()) { - tip += "\n" + P2QSTRING(contact->getDisplayJID().toBare().toString()); - } - tip += "\n " + P2QSTRING(statusShowTypeToFriendlyName(contact->getStatusShow())); - if (!getStatusText(item).isEmpty()) { - tip += ": " + getStatusText(item); - } + return RosterTooltip::buildDetailedTooltip(contact, cachedImageScaler_); } return tip; @@ -141,5 +175,5 @@ QString RosterModel::getAvatar(RosterItem* item) const { return ""; } - return QString(contact->getAvatarPath().c_str()); + return P2QSTRING(pathToString(contact->getAvatarPath())); } @@ -150,17 +184,27 @@ QString RosterModel::getStatusText(RosterItem* item) const { } +QString RosterModel::getJID(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + return contact ? P2QSTRING(contact->getJID().toString()) : QString(); +} + +QString RosterModel::getDisplayJID(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + QString result = contact ? P2QSTRING(contact->getDisplayJID().toString()) : QString(); + if (result.isEmpty()) { + result = getJID(item); + } + return result; +} + QIcon RosterModel::getPresenceIcon(RosterItem* item) const { ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); if (!contact) return QIcon(); - 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; + if (contact->blockState() == ContactRosterItem::IsBlocked || + contact->blockState() == ContactRosterItem::IsDomainBlocked) { + return QIcon(":/icons/stop.png"); } - return QIcon(":/icons/" + iconString + ".png"); + + return QIcon(statusShowTypeToIconPath(contact->getStatusShow())); } @@ -216,3 +260,19 @@ int RosterModel::rowCount(const QModelIndex& parent) const { } +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; + } + + /* only a single JID in this list */ + QByteArray itemData; + QDataStream dataStream(&itemData, QIODevice::WriteOnly); + dataStream << P2QSTRING(item->getJID().toString()); + data->setData("application/vnd.swift.contact-jid-list", itemData); + return data; +} + } diff --git a/Swift/QtUI/Roster/RosterModel.h b/Swift/QtUI/Roster/RosterModel.h index bd34e9c..2b05044 100644 --- a/Swift/QtUI/Roster/RosterModel.h +++ b/Swift/QtUI/Roster/RosterModel.h @@ -1,4 +1,4 @@ /* - * 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. @@ -7,9 +7,11 @@ #pragma once -#include "Swift/Controllers/Roster/Roster.h" - #include <QAbstractItemModel> #include <QList> +#include <Swift/Controllers/Roster/Roster.h> + +#include <Swift/QtUI/QtScaledAvatarCache.h> + namespace Swift { enum RosterRoles { @@ -19,4 +21,7 @@ namespace Swift { StatusShowTypeRole = Qt::UserRole + 3, ChildCountRole = Qt::UserRole + 4, + IdleRole = Qt::UserRole + 5, + JIDRole = Qt::UserRole + 6, + DisplayJIDRole = Qt::UserRole + 7 }; @@ -26,7 +31,8 @@ namespace Swift { Q_OBJECT public: - RosterModel(QtTreeWidget* view); + RosterModel(QtTreeWidget* view, bool screenReaderMode); ~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; @@ -35,4 +41,6 @@ namespace Swift { 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); @@ -47,9 +55,17 @@ namespace Swift { QString getAvatar(RosterItem* item) const; QString getStatusText(RosterItem* item) const; + QString getJID(RosterItem* item) const; + QString getDisplayJID(RosterItem* item) const; QIcon getPresenceIcon(RosterItem* item) const; int getChildCount(RosterItem* item) const; + bool getIsIdle(RosterItem* item) const; void reLayout(); + /** calculates screenreader-friendly text if in screenreader mode, otherwise uses alternative text */ + QString getScreenReaderTextOr(RosterItem* item, const QString& alternative) const; + private: Roster* roster_; QtTreeWidget* view_; + QtScaledAvatarCache* cachedImageScaler_; + bool screenReader_; }; } diff --git a/Swift/QtUI/Roster/RosterTooltip.cpp b/Swift/QtUI/Roster/RosterTooltip.cpp new file mode 100644 index 0000000..86f175d --- /dev/null +++ b/Swift/QtUI/Roster/RosterTooltip.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/Roster/RosterTooltip.h> + +#include <QObject> +#include <QString> +#include <QApplication> + +#include <Swiften/Base/Path.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/StatusUtil.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/QtResourceHelper.h> + +using namespace QtUtilities; + +namespace Swift { + +QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaledAvatarCache* cachedImageScaler) { + QString tooltipTemplate; + if (QApplication::layoutDirection() == Qt::RightToLeft) { + tooltipTemplate = QString( + "<table style='white-space:pre'>" + "<tr>" + "<td>" + "<img src=\"%1\" />" + "</td>" + "<td>" + "<p style='font-size: 14px'>%3 %2</p>" + "<table><tr><td valign='middle'>%5</td><td valign='middle'>%4</td></tr></table>" + "%6" + "%7" + "%8" + "%9" + "</td>" + "</tr>" + "</table>"); + } else { + tooltipTemplate = QString( + "<table style='white-space:pre'>" + "<tr>" + "<td>" + "<img src=\"%1\" />" + "</td>" + "<td>" + "<p style='font-size: 14px'>%2 %3</p>" + "<table><tr><td valign='middle'>%4</td><td valign='middle'>%5</td></tr></table>" + "%6" + "%7" + "%8" + "%9" + "</td>" + "</tr>" + "</table>"); + } + // prepare tooltip + QString fullName = P2QSTRING(contact->getDisplayName()); + + QString vCardSummary; + VCard::ref vCard = contact->getVCard(); + if (vCard) { + fullName = P2QSTRING(vCard->getFullName()).trimmed(); + if (fullName.isEmpty()) { + fullName = (P2QSTRING(vCard->getGivenName()) + " " + P2QSTRING(vCard->getFamilyName())).trimmed(); + } + if (fullName.isEmpty()) { + fullName = P2QSTRING(contact->getDisplayName()); + } + vCardSummary = buildVCardSummary(vCard); + } else { + contact->onVCardRequested(); + } + + QString scaledAvatarPath = cachedImageScaler->getScaledAvatarPath(P2QSTRING(contact->getAvatarPath().empty() ? ":/icons/avatar.png" : pathToString(contact->getAvatarPath()))); + + QString bareJID = contact->getDisplayJID().toString().empty() ? "" : "( " + P2QSTRING(contact->getDisplayJID().toString()) + " )"; + + QString presenceIconTag = QString("<img src='%1' />").arg(statusShowTypeToIconPath(contact->getStatusShow())); + + QString statusMessage = contact->getStatusText().empty() ? QObject::tr("(No message)") : P2QSTRING(contact->getStatusText()); + + QString idleString = P2QSTRING(contact->getIdleText()); + if (!idleString.isEmpty()) { + idleString = QObject::tr("Idle since %1").arg(idleString); + idleString = htmlEscape(idleString) + "<br/>"; + } + + QString lastSeen = P2QSTRING(contact->getOfflineSinceText()); + if (!lastSeen.isEmpty()) { + lastSeen = QObject::tr("Last seen %1").arg(lastSeen); + lastSeen = htmlEscape(lastSeen) + "<br/>"; + } + + QString mucOccupant= P2QSTRING(contact->getMUCAffiliationText()); + if (!mucOccupant.isEmpty()) { + mucOccupant = htmlEscape(mucOccupant) + "<br/>"; + } + + return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), mucOccupant, idleString, lastSeen, vCardSummary); +} + +QString RosterTooltip::buildVCardSummary(VCard::ref vcard) { + QString summary; + summary = "<table>"; + + // star | name | content + QString currentBlock; + foreach (const VCard::Telephone& tel, vcard->getTelephones()) { + QString type = tel.isFax ? QObject::tr("Fax") : QObject::tr("Telephone"); + QString field = buildVCardField(tel.isPreferred, type, htmlEscape(P2QSTRING(tel.number))); + if (tel.isPreferred) { + currentBlock = field; + break; + } + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + foreach (const VCard::EMailAddress& mail, vcard->getEMailAddresses()) { + QString field = buildVCardField(mail.isPreferred, QObject::tr("E-Mail"), htmlEscape(P2QSTRING(mail.address))); + if (mail.isPreferred) { + currentBlock = field; + break; + } + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + foreach (const VCard::Organization& org, vcard->getOrganizations()) { + QString field = buildVCardField(false, QObject::tr("Organization"), htmlEscape(P2QSTRING(org.name))); + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + foreach(const std::string& title, vcard->getTitles()) { + QString field = buildVCardField(false, QObject::tr("Title"), htmlEscape(P2QSTRING(title))); + currentBlock += field; + } + summary += currentBlock; + + summary += "</table>"; + return summary; +} + +QString RosterTooltip::buildVCardField(bool preferred, const QString& name, const QString& content) { + QString rowTemplate; + if (QApplication::layoutDirection() == Qt::RightToLeft) { + rowTemplate = QString("<tr><td>%3</td><td valign='middle'><strong>%2</strong></td><td valign='middle'>%1</td></tr>"); + } else { + rowTemplate = QString("<tr><td>%1</td><td valign='middle'><strong>%2</strong></td><td valign='middle'>%3</td></tr>"); + } + return rowTemplate.arg(preferred ? "<img src=':/icons/star-checked.png' />" : "", name, content); +} + +} diff --git a/Swift/QtUI/Roster/RosterTooltip.h b/Swift/QtUI/Roster/RosterTooltip.h new file mode 100644 index 0000000..37f5da2 --- /dev/null +++ b/Swift/QtUI/Roster/RosterTooltip.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QString> + +#include <Swiften/Elements/VCard.h> + +namespace Swift { + +class ContactRosterItem; +class QtScaledAvatarCache; + +class RosterTooltip { + public: + static QString buildDetailedTooltip(ContactRosterItem* contact, QtScaledAvatarCache* cachedImageScaler); + + private: + static QString buildVCardSummary(VCard::ref vcard); + static QString buildVCardField(bool preferred, const QString& name, const QString& content); +}; + +} diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 68a0782..26e738a 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -1,3 +1,3 @@ -import os, shutil, datetime, re +import os, shutil, datetime, re, time import Version @@ -21,5 +21,10 @@ 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"]) @@ -32,4 +37,6 @@ if myenv["HAVE_SPARKLE"] : myenv.UseFlags(env["SWIFTEN_FLAGS"]) myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"]) +if myenv.get("HAVE_BREAKPAD") : + myenv.UseFlags(env["BREAKPAD_FLAGS"]) if myenv.get("HAVE_GROWL", False) : myenv.UseFlags(myenv["GROWL_FLAGS"]) @@ -40,4 +47,7 @@ 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"]) @@ -47,13 +57,22 @@ myenv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"]) myenv.Tool("nsis", toolpath = ["#/BuildTools/SCons/Tools"]) myenv.Tool("wix", toolpath = ["#/BuildTools/SCons/Tools"]) -qt4modules = ['QtCore', 'QtGui', 'QtWebKit'] +myenv.Tool("textfile", toolpath = ["#/BuildTools/SCons/Tools"]) +qt4modules = ['QtCore', 'QtWebKit', 'QtGui'] +if myenv["qt5"] : + qt_version = '5' + qt4modules += ['QtWidgets', 'QtWebKitWidgets', 'QtMultimedia'] +else : + qt_version = '4' if env["PLATFORM"] == "posix" : qt4modules += ["QtDBus"] -myenv.EnableQt4Modules(qt4modules, debug = False) +if env["PLATFORM"] != "win32" and env["PLATFORM"] != "darwin" : + qt4modules += ["QtNetwork"] + +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") @@ -61,5 +80,5 @@ if env["PLATFORM"] == "win32" : myenv.Append(LIBS = "Cryptui") myenv.Append(CPPDEFINES = "HAVE_SCHANNEL") - if env["debug"] : + if env["debug"] and not env["optimize"]: myenv.Append(LINKFLAGS = ["/NODEFAULTLIB:msvcrt"]) @@ -69,12 +88,13 @@ 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", @@ -83,5 +103,8 @@ sources = [ "QtSwift.cpp", "QtURIHandler.cpp", + "QtChatWindow.cpp", "QtChatView.cpp", + "QtWebKitChatView.cpp", + "QtPlainChatView.cpp", "QtChatTheme.cpp", "QtChatTabs.cpp", @@ -93,7 +116,9 @@ sources = [ "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", + "QtHistoryWindow.cpp", "QtFileTransferListWidget.cpp", "QtFileTransferListItemModel.cpp", "QtAdHocCommandWindow.cpp", + "QtAdHocCommandWithJIDWindow.cpp", "QtUtilities.cpp", "QtBookmarkDetailWindow.cpp", @@ -102,4 +127,8 @@ sources = [ "QtContactEditWindow.cpp", "QtContactEditWidget.cpp", + "QtSingleWindow.cpp", + "QtHighlightEditor.cpp", + "QtColorToolButton.cpp", + "QtClosableLineEdit.cpp", "ChatSnippet.cpp", "MessageSnippet.cpp", @@ -110,4 +139,5 @@ sources = [ "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", + "QtConnectionSettingsWindow.cpp", "Roster/RosterModel.cpp", "Roster/QtTreeWidget.cpp", @@ -116,6 +146,8 @@ sources = [ "Roster/GroupItemDelegate.cpp", "Roster/DelegateCommons.cpp", + "Roster/QtFilterWidget.cpp", "Roster/QtRosterWidget.cpp", "Roster/QtOccupantListWidget.cpp", + "Roster/RosterTooltip.cpp", "EventViewer/EventModel.cpp", "EventViewer/EventDelegate.cpp", @@ -128,4 +160,5 @@ sources = [ "ChatList/ChatListMUCItem.cpp", "ChatList/ChatListRecentItem.cpp", + "ChatList/ChatListWhiteboardItem.cpp", "MUCSearch/QtMUCSearchWindow.cpp", "MUCSearch/MUCSearchModel.cpp", @@ -133,5 +166,10 @@ sources = [ "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", @@ -140,4 +178,9 @@ sources = [ "UserSearch/UserSearchModel.cpp", "UserSearch/UserSearchDelegate.cpp", + "Whiteboard/FreehandLineItem.cpp", + "Whiteboard/GView.cpp", + "Whiteboard/TextDialog.cpp", + "Whiteboard/QtWhiteboardWindow.cpp", + "Whiteboard/ColorWidget.cpp", "QtSubscriptionRequestWindow.cpp", "QtRosterHeader.cpp", @@ -148,18 +191,60 @@ sources = [ "QtMUCConfigurationWindow.cpp", "QtAffiliationEditor.cpp", - "QtUISettingConstants.cpp" + "QtUISettingConstants.cpp", + "QtURLValidator.cpp", + "QtResourceHelper.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") -version_match = re.match("(\d+)\.(\d+).*", myenv["SWIFT_VERSION"]) -myenv["SWIFT_VERSION_MAJOR"] = int(version_match.group(1)) if version_match else 0 -myenv["SWIFT_VERSION_MINOR"] = int(version_match.group(2)) if version_match else 0 +if env["PLATFORM"] == "win32" : + swift_windows_version = Version.convertToWindowsVersion(myenv["SWIFT_VERSION"]) + myenv["SWIFT_VERSION_MAJOR"] = swift_windows_version[0] + myenv["SWIFT_VERSION_MINOR"] = swift_windows_version[1] + myenv["SWIFT_VERSION_PATCH"] = swift_windows_version[2] + if env["PLATFORM"] == "win32" : - res = myenv.RES("#/Swift/resources/Windows/Swift.rc") + res_env = myenv.Clone() + res_env.Append(CPPDEFINES = [ + ("SWIFT_COPYRIGHT_YEAR", "\"\\\"2010-%s\\\"\"" % str(time.localtime()[0])), + ("SWIFT_VERSION_MAJOR", "${SWIFT_VERSION_MAJOR}"), + ("SWIFT_VERSION_MINOR", "${SWIFT_VERSION_MINOR}"), + ("SWIFT_VERSION_PATCH", "${SWIFT_VERSION_PATCH}"), + ]) + res = res_env.RES("#/Swift/resources/Windows/Swift.rc") # For some reason, SCons isn't picking up the dependency correctly # Adding it explicitly until i figure out why myenv.Depends(res, "../Controllers/BuildVersion.h") sources += [ + "WinUIHelpers.cpp", "CAPICertificateSelector.cpp", "WindowsNotifier.cpp", @@ -175,9 +260,12 @@ if env["PLATFORM"] == "posix" : if env["PLATFORM"] == "darwin" : sources += ["CocoaApplicationActivateHelper.mm"] + sources += ["CocoaUIHelpers.mm"] if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" : swiftProgram = myenv.Program("Swift", sources) else : - swiftProgram = myenv.Program("swift", sources) + sources += ["QtCertificateViewerDialog.cpp"]; + myenv.Uic4("QtCertificateViewerDialog.ui"); + swiftProgram = myenv.Program("swift-im", sources) if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" : @@ -189,4 +277,5 @@ 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") @@ -194,4 +283,9 @@ myenv.Uic4("QtBookmarkDetailWindow.ui") myenv.Uic4("QtAffiliationEditor.ui") myenv.Uic4("QtJoinMUCWindow.ui") +myenv.Uic4("QtHistoryWindow.ui") +myenv.Uic4("QtConnectionSettings.ui") +myenv.Uic4("QtHighlightEditor.ui") +myenv.Uic4("QtBlockListEditorWindow.ui") +myenv.Uic4("QtSpellCheckerWindow.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") @@ -202,4 +296,7 @@ commonResources = { } +myenv["TEXTFILESUFFIX"] = "" +myenv.MyTextfile(target = "COPYING", source = [myenv.File("../../COPYING.gpl"), myenv.File("../../COPYING.thirdparty")], LINESEPARATOR = "\n\n========\n\n\n") + ################################################################################ # Translation @@ -229,5 +326,6 @@ if ARGUMENTS.get("update_translations", False) : if ARGUMENTS.get("remove_obsolete_translations", False) : remove_obsolete_option = " -no-obsolete" - t = myenv.Command(translation_sources, [], [myenv.Action("$QT4_LUPDATE -I " + env.Dir("#").abspath + remove_obsolete_option + " -silent -no-ui-lines -codecfortr utf-8 -recursive Swift -ts " + " ".join(translation_sources), cmdstr = "$QT4_LUPDATECOMSTR")]) + for translation_source in filter(lambda x: not x.endswith("_en.ts"), translation_sources) : + t = myenv.Command([translation_source], [], [myenv.Action("$QT4_LUPDATE -I " + env.Dir("#").abspath + remove_obsolete_option + " -silent -codecfortr utf-8 -recursive Swift -ts " + translation_source, cmdstr = "$QT4_LUPDATECOMSTR")]) myenv.AlwaysBuild(t) @@ -254,5 +352,7 @@ if env["PLATFORM"] == "darwin" : app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True) if env["DIST"] : - myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"]) + myenv.Command(["#/Packages/Swift/Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"]) + dsym = myenv.Command(["Swift-${SWIFT_VERSION}.dSYM"], ["Swift"], ["dsymutil -o ${TARGET} ${SOURCE}"]) + myenv.Command(["#/Packages/Swift/Swift-${SWIFT_VERSION}.dSYM.zip"], dsym, ["cd ${SOURCE.dir} && zip -r ${TARGET.abspath} ${SOURCE.name}"]) if env.get("SWIFT_INSTALLDIR", "") : @@ -269,5 +369,5 @@ if env.get("SWIFT_INSTALLDIR", "") : 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"), @@ -275,11 +375,25 @@ if env["PLATFORM"] == "win32" : "#/Swift/resources/images", ] - qtimageformats = ["gif", "ico", "jpeg", "mng", "svg", "tiff"] - qtlibs = ["QtCore4", "QtGui4", "QtNetwork4", "QtWebKit4", "QtXMLPatterns4", "phonon4"] - myenv.WindowsBundle("Swift", + if env["SWIFTEN_DLL"] : + commonResources[""] = commonResources.get("", []) + ["#/Swiften/${SWIFTEN_LIBRARY_FILE}"] + 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', 'QtPositioning', '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"] + "\"", @@ -287,5 +401,4 @@ if env["PLATFORM"] == "win32" : # ]) #myenv.Nsis("../Packaging/nsis/swift.nsi") - #myenv.WiX("../Packaging/wix/swift.msi", ["../Packaging/WiX/Swift.wxs"]) if env["SCONS_STAGE"] == "build" and env.get("wix_bindir", None): def convertToRTF(env, target, source) : @@ -306,11 +419,9 @@ if env["PLATFORM"] == "win32" : outfile.close() infile.close() - env.Command(["Swift/COPYING.rtf"], ["../../COPYING"], convertToRTF) + copying = env.Command(["Swift/COPYING.rtf"], ["COPYING"], convertToRTF) wixvariables = { - 'VCCRTFile': env.get("vcredist", "c:\\Program Files\\Common Files\\Merge Modules") + "\\Microsoft_VC90_CRT_x86.msm", - # FIXME: Not including patch version, but that shouldn't be - # a problem. It just allows downgrading between development versions - 'Version': str(myenv["SWIFT_VERSION_MAJOR"]) + "." + str(myenv["SWIFT_VERSION_MINOR"]) + ".0" + 'VCCRTFile': env["vcredist"], + 'Version': str(myenv["SWIFT_VERSION_MAJOR"]) + "." + str(myenv["SWIFT_VERSION_MINOR"]) + "." + str(myenv["SWIFT_VERSION_PATCH"]) } wixincludecontent = "<Include>" @@ -319,24 +430,12 @@ if env["PLATFORM"] == "win32" : wixincludecontent += "</Include>" myenv.WriteVal("..\\Packaging\\Wix\\variables.wxs", env.Value(wixincludecontent)) - heatDependencies = ['Swift/COPYING.rtf', 'Swift/Swift.exe'] - for dir, resourceFiles in commonResources.items(): - for resource in resourceFiles: - e = env.Entry(resource) - if e.isdir(): - for subresource in env.Glob(str(e) + "/*") : - heatDependencies.append("Swift/" + e.name + "/" + subresource.name) - else: - if resource[-3:] != ".qm": - heatDependencies.append("Swift/" + resource) - for resource in qtlibs: - heatDependencies.append("Swift/" + resource + ".dll") - for resource in qtimageformats: - heatDependencies.append("Swift/imageformats/q" + resource + "4.dll") - for lang in translation_languages: - heatDependencies.append("Swift/translations/swift_" + lang + ".qm") - myenv.WiX_Heat('..\\Packaging\\WiX\\gen_files.wxs', heatDependencies) + myenv["WIX_SOURCE_OBJECT_DIR"] = "Swift\\QtUI\\Swift" + 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('..\\Packaging\\WiX\\Swift-' + myenv["SWIFT_VERSION"] + '.msi', ['..\\Packaging\\WiX\\gen_files.wixobj','..\\Packaging\\WiX\\Swift.wixobj']) + myenv.WiX_Light('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.msi', ['..\\Packaging\\WiX\\gen_files.wixobj','..\\Packaging\\WiX\\Swift.wixobj']) + + if myenv["debug"] : + myenv.InstallAs('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.pdb', "Swift.pdb") diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index 61d8cc0..eeef80d 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -11,4 +11,5 @@ <file alias="icons/offline.png">../resources/icons/offline.png</file> <file alias="icons/certificate.png">../resources/icons/certificate.png</file> + <file alias="icons/lock.png">../resources/icons/lock.png</file> <file alias="icons/error.png">../resources/icons/error.png</file> <file alias="icons/warn.png">../resources/icons/warn.png</file> @@ -20,5 +21,26 @@ <file alias="icons/new-chat.png">../resources/icons/new-chat.png</file> <file alias="icons/actions.png">../resources/icons/actions.png</file> - <file alias="COPYING">../../COPYING</file> + <file alias="COPYING">COPYING</file> + <file alias="icons/line.png">../resources/icons/line.png</file> + <file alias="icons/rect.png">../resources/icons/rect.png</file> + <file alias="icons/circle.png">../resources/icons/circle.png</file> + <file alias="icons/handline.png">../resources/icons/handline.png</file> + <file alias="icons/text.png">../resources/icons/text.png</file> + <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 @@ -11,10 +11,11 @@ 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")))); 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 @@ -16,5 +16,5 @@ 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(); diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.cpp b/Swift/QtUI/UserSearch/ContactListDelegate.cpp new file mode 100644 index 0000000..56c479b --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListDelegate.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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::ref contact = static_cast<Contact*>(index.internalPointer())->shared_from_this(); + 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..ef6383c --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListModel.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/UserSearch/ContactListModel.h> + +#include <QMimeData> + +#include <Swiften/Base/Path.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Elements/StatusShow.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtResourceHelper.h> + +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::ref>& list) { + emit layoutAboutToBeChanged(); + contacts_ = list; + emit layoutChanged(); +} + +const std::vector<Contact::ref>& ContactListModel::getList() const { + return contacts_; +} + +Contact::ref ContactListModel::getContact(const size_t i) const { + return contacts_[i]; +} + +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::ref& contact = contacts_[index.row()]; + if (role == Qt::EditRole) { + return P2QSTRING(contact->jid.toString()); + } + return dataForContact(contact, role); + } else { + return QVariant(); + } +} + +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, contacts_[row].get()) : 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::ref& 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::ref& contact) const { + return QIcon(statusShowTypeToIconPath(contact->statusType)); +} + +} diff --git a/Swift/QtUI/UserSearch/ContactListModel.h b/Swift/QtUI/UserSearch/ContactListModel.h new file mode 100644 index 0000000..e582ac4 --- /dev/null +++ b/Swift/QtUI/UserSearch/ContactListModel.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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::ref>& list); + const std::vector<Contact::ref>& getList() const; + Contact::ref getContact(const size_t i) 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; + 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::ref& contact, int role) const; + QIcon getPresenceIconForContact(const Contact::ref& contact) const; + + signals: + void onListChanged(std::vector<Contact::ref> list); + void onJIDsDropped(const std::vector<JID>& contact); + + private: + bool editable_; + std::vector<Contact::ref> contacts_; + }; + +} diff --git a/Swift/QtUI/UserSearch/QtContactListWidget.cpp b/Swift/QtUI/UserSearch/QtContactListWidget.cpp new file mode 100644 index 0000000..6504f3e --- /dev/null +++ b/Swift/QtUI/UserSearch/QtContactListWidget.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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::ref>)), this, SIGNAL(onListChanged(std::vector<Contact::ref>))); + connect(contactListModel_, SIGNAL(onJIDsDropped(std::vector<JID>)), this, SIGNAL(onJIDsAdded(std::vector<JID>))); + + setSelectionMode(QAbstractItemView::SingleSelection); + setSelectionBehavior(QAbstractItemView::SelectRows); + 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::ref>& list) { + contactListModel_->setList(list); +} + +std::vector<Contact::ref> QtContactListWidget::getList() const { + return contactListModel_->getList(); +} + +Contact::ref QtContactListWidget::getContact(const size_t i) { + return contactListModel_->getContact(i); +} + +void QtContactListWidget::setMaximumNoOfContactsToOne(bool limited) { + limited_ = limited; +} + +bool QtContactListWidget::isFull() const { + return limited_ && (getList().size() == 1); +} + +void QtContactListWidget::updateContacts(const std::vector<Contact::ref>& contactUpdates) { + std::vector<Contact::ref> contacts = contactListModel_->getList(); + foreach(const Contact::ref& 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::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..112f3ee --- /dev/null +++ b/Swift/QtUI/UserSearch/QtContactListWidget.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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::ref>& list); + std::vector<Contact::ref> getList() const; + Contact::ref getContact(const size_t i); + void setMaximumNoOfContactsToOne(bool limited); + bool isFull() const; + +public slots: + void updateContacts(const std::vector<Contact::ref>& contactUpdates); + +signals: + void onListChanged(std::vector<Contact::ref> list); + void onJIDsAdded(const std::vector<JID>& jids); + +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..57033d8 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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) { + 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_; +} + +Contact::ref QtSuggestingJIDInput::getContact() { + if (!!currentContact_) { + return currentContact_; + } + + if (!text().isEmpty()) { + JID jid(Q2PSTRING(text())); + if (jid.isValid()) { + Contact::ref manualContact = boost::make_shared<Contact>(); + manualContact->name = jid.toString(); + manualContact->jid = jid; + return manualContact; + } + } + return boost::shared_ptr<Contact>(); +} + +void QtSuggestingJIDInput::setSuggestions(const std::vector<Contact::ref>& suggestions) { + contactListModel_->setList(suggestions); + positionPopup(); + if (!suggestions.empty()) { + treeViewPopup_->setCurrentIndex(contactListModel_->index(0, 0)); + showPopup(); + } else { + currentContact_.reset(); + hidePopup(); + } +} + +void QtSuggestingJIDInput::clear() { + setText(""); + currentContact_.reset(); +} + +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_->getContact(index.row()); + if (currentContact_->jid.isValid()) { + setText(P2QSTRING(currentContact_->jid.toString())); + } else { + setText(P2QSTRING(currentContact_->name)); + } + hidePopup(); + clearFocus(); + } else { + currentContact_.reset(); + } + 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_->getContact(index.row()); + onUserSelected(currentContact_); + 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..23e7b94 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QLineEdit> +#include <QTreeView> +#include <Swiften/Base/boost_bsignals.h> +#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(); + + Contact::ref getContact(); + + void setSuggestions(const std::vector<Contact::ref>& suggestions); + + void clear(); + + boost::signal<void (const Contact::ref&)> onUserSelected; + + 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::ref currentContact_; +}; + +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.cpp index f4189e7..d8c70b6 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011 Remko Tronçon + * Copyright (c) 2011-2014 Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -8,4 +8,5 @@ #include <QVBoxLayout> +#include <QLabel> #include <boost/bind.hpp> diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp new file mode 100644 index 0000000..2c34aa6 --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h" + +#include <QMessageBox> +#include <QMimeData> +#include <QUrl> + +#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/QtUI/UserSearch/QtContactListWidget.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::ref>)), this, SLOT(emitCompletenessCheck())); + connect(jid_, SIGNAL(editingDone()), this, SLOT(handleEditingDone())); + + setAcceptDrops(true); +} + +bool QtUserSearchFirstMultiJIDPage::isComplete() const { + return !contactList_->getList().empty(); +} + +void QtUserSearchFirstMultiJIDPage::reset() { + jid_->clear(); + reason_->clear(); +} + +void QtUserSearchFirstMultiJIDPage::emitCompletenessCheck() { + emit completeChanged(); +} + +void QtUserSearchFirstMultiJIDPage::handleEditingDone() { + addContactButton_->setFocus(); +} + +void QtUserSearchFirstMultiJIDPage::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list") + || event->mimeData()->hasFormat("application/vnd.swift.contact-jid-muc")) { + if (!contactList_->isFull()) { + event->acceptProposedAction(); + } + } +} + +void QtUserSearchFirstMultiJIDPage::dropEvent(QDropEvent *event) { + if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list")) { + QByteArray dataBytes = event->mimeData()->data("application/vnd.swift.contact-jid-list"); + QDataStream dataStream(&dataBytes, QIODevice::ReadOnly); + std::vector<JID> jids; + while (!dataStream.atEnd()) { + QString jidString; + dataStream >> jidString; + jids.push_back(Q2PSTRING(jidString)); + } + onJIDsDropped(jids); + } else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-muc")) { + QMessageBox* messageBox = new QMessageBox(this); + messageBox->setText(tr("You can't invite a room to chat.")); + messageBox->setWindowTitle(tr("Error inviting room to chat")); + messageBox->show(); + } +} + +} diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h new file mode 100644 index 0000000..431dd3c --- /dev/null +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.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; + void reset(); + + signals: + void onJIDsDropped(std::vector<JID> jid); + + public slots: + void emitCompletenessCheck(); + void handleEditingDone(); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void dropEvent(QDropEvent *event); + + 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 @@ -9,10 +9,17 @@ #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 @@ -12,4 +12,6 @@ #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> +#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> + namespace Swift { class UserSearchModel; @@ -21,8 +23,10 @@ namespace Swift { 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 46a1277..24d401e 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui @@ -12,5 +12,5 @@ </property> <property name="windowTitle"> - <string></string> + <string/> </property> <property name="title"> @@ -24,5 +24,5 @@ <widget class="QLabel" name="howLabel_"> <property name="text"> - <string></string> + <string/> </property> </widget> @@ -35,19 +35,39 @@ <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> + <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> @@ -56,4 +76,7 @@ <string>I'd like to search another server:</string> </property> + <property name="autoExclusive"> + <bool>true</bool> + </property> </widget> </item> @@ -89,5 +112,5 @@ <widget class="QLabel" name="errorLabel_"> <property name="text"> - <string></string> + <string/> </property> <property name="alignment"> diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp index 2b11d02..babe115 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -11,58 +11,52 @@ #include <QWizardPage> #include <QMovie> +#include <boost/bind.hpp> #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), firstMultiJIDPage_(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); @@ -79,5 +73,14 @@ QtUserSearchWindow::~QtUserSearchWindow() { void QtUserSearchWindow::handleCurrentChanged(int page) { - if (page == 2 && lastPage_ == 1) { + searchNext_ = false; + if (firstMultiJIDPage_) { + firstMultiJIDPage_->reset(); + } + resultsPage_->emitCompletenessCheck(); + if (page == 1 && lastPage_ == 3) { + addSearchedJIDToList(getContact()); + setSecondPage(); + } + else if (page == 2 && lastPage_ == 1) { setError(""); /* next won't be called if JID is selected */ @@ -85,7 +88,20 @@ void QtUserSearchWindow::handleCurrentChanged(int page) { 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) { @@ -98,20 +114,60 @@ void QtUserSearchWindow::handleCurrentChanged(int page) { JID QtUserSearchWindow::getServerToSearch() { + 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(); - - if (type_ == AddContact) { + JID jid; + std::vector<JID> jids; + switch(type_) { + case AddContact: + jid = getContactJID(); eventStream_->send(boost::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups())); - } - else { - boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(jid)); + break; + case ChatToContact: + if (contactVector_.size() == 1) { + boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(contactVector_[0]->jid)); eventStream_->send(event); + break; + } + + foreach(Contact::ref contact, contactVector_) { + jids.push_back(contact->jid); + } + + eventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(jids, JID(), Q2PSTRING(firstMultiJIDPage_->reason_->text()))); + break; + case InviteToChat: + foreach(Contact::ref contact, contactVector_) { + jids.push_back(contact->jid); + } + eventStream_->send(boost::make_shared<InviteToMUCUIEvent>(roomJID_, jids, Q2PSTRING(firstMultiJIDPage_->reason_->text()))); + break; + } +} + +void QtUserSearchWindow::handleContactSuggestionRequested(const QString& text) { + std::string stdText = Q2PSTRING(text); + onContactSuggestionsRequested(stdText); +} + +void QtUserSearchWindow::addContact() { + if (!!firstMultiJIDPage_->jid_->getContact()) { + contactVector_.push_back(firstMultiJIDPage_->jid_->getContact()); + firstMultiJIDPage_->jid_->clear(); + } + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->emitCompletenessCheck(); + if (type_ == ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); } } int QtUserSearchWindow::nextId() const { + if (type_ == AddContact) { switch (currentId()) { case 1: return firstPage_->byJID_->isChecked() ? (type_ == AddContact ? 4 : -1) : 2; @@ -121,4 +177,13 @@ int QtUserSearchWindow::nextId() const { 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; + } + } } @@ -148,15 +213,16 @@ void QtUserSearchWindow::handleSearch() { if (fieldsPage_->getFormWidget()) { search->setForm(fieldsPage_->getFormWidget()->getCompletedForm()); + search->getForm()->clearEmptyTextFields(); } else { - if (fieldsPage_->nickInput_->isEnabled()) { + if (fieldsPage_->nickInput_->isEnabled() && !fieldsPage_->nickInput_->text().isEmpty()) { search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text())); } - if (fieldsPage_->firstInput_->isEnabled()) { + if (fieldsPage_->firstInput_->isEnabled() && !fieldsPage_->firstInput_->text().isEmpty()) { search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text())); } - if (fieldsPage_->lastInput_->isEnabled()) { + if (fieldsPage_->lastInput_->isEnabled() && !fieldsPage_->lastInput_->text().isEmpty()) { search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text())); } - if (fieldsPage_->emailInput_->isEnabled()) { + if (fieldsPage_->emailInput_->isEnabled() && !fieldsPage_->emailInput_->text().isEmpty()) { search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text())); } @@ -167,5 +233,13 @@ 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()); @@ -173,5 +247,5 @@ JID QtUserSearchWindow::getContactJID() const { jid = userItem->getJID(); } - } else { + } else if (dynamic_cast<QtFormResultItemModel*>(model_)) { int row = resultsPage_->results_->currentIndex().row(); @@ -179,10 +253,10 @@ JID QtUserSearchWindow::getContactJID() const { 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]; } } @@ -198,4 +272,19 @@ JID QtUserSearchWindow::getContactJID() const { } +Contact::ref QtUserSearchWindow::getContact() const { + return boost::make_shared<Contact>("", getContactJID(), StatusShow::None, ""); +} + +void QtUserSearchWindow::addSearchedJIDToList(const Contact::ref& contact) { + std::vector<JID> jids; + jids.push_back(contact->jid); + handleJIDsAdded(jids); + firstMultiJIDPage_->jid_->clear(); +} + +void QtUserSearchWindow::handleOnSearchedJIDSelected(const Contact::ref& contact) { + firstPage_->jid_->setText(P2QSTRING(contact->jid.toString())); +} + void QtUserSearchWindow::show() { clear(); @@ -204,4 +293,5 @@ void QtUserSearchWindow::show() { void QtUserSearchWindow::addSavedServices(const std::vector<JID>& services) { + if (type_ == AddContact) { firstPage_->service_->clear(); foreach (JID jid, services) { @@ -209,4 +299,11 @@ void QtUserSearchWindow::addSavedServices(const std::vector<JID>& services) { } firstPage_->service_->clearEditText(); + } else { + firstMultiJIDPage_->service_->clear(); + foreach (JID jid, services) { + firstMultiJIDPage_->service_->addItem(P2QSTRING(jid.toString())); + } + firstMultiJIDPage_->service_->clearEditText(); + } } @@ -221,5 +318,5 @@ void QtUserSearchWindow::setSearchFields(boost::shared_ptr<SearchPayload> fields } else { fieldsPage_->setFormWidget(NULL); - bool enabled[8] = {fields->getNick(), fields->getNick(), fields->getFirst(), fields->getFirst(), fields->getLast(), fields->getLast(), fields->getEMail(), fields->getEMail()}; + bool enabled[8] = {!!fields->getNick(), !!fields->getNick(), !!fields->getFirst(), !!fields->getFirst(), !!fields->getLast(), !!fields->getLast(), !!fields->getEMail(), !!fields->getEMail()}; QWidget* legacySearchWidgets[8] = {fieldsPage_->nickInputLabel_, fieldsPage_->nickInput_, fieldsPage_->firstInputLabel_, fieldsPage_->firstInput_, fieldsPage_->lastInputLabel_, fieldsPage_->lastInput_, fieldsPage_->emailInputLabel_, fieldsPage_->emailInput_}; for (int i = 0; i < 8; i++) { @@ -246,4 +343,95 @@ void QtUserSearchWindow::prepopulateJIDAndName(const JID& jid, const std::string } +void QtUserSearchWindow::setContactSuggestions(const std::vector<Contact::ref>& 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(boost::make_shared<Contact>("", jid, StatusShow::None, "")); + } + 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 (Contact::ref 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::ref>& contacts) { + if (type_ != AddContact) { + firstMultiJIDPage_->contactList_->updateContacts(contacts); + } +} + +void QtUserSearchWindow::addContacts(const std::vector<Contact::ref>& contacts) { + if (type_ != AddContact) { + /* prevent duplicate JIDs from appearing in the contact list */ + foreach (Contact::ref newContact, contacts) { + bool found = false; + foreach (Contact::ref oldContact, contactVector_) { + if (newContact->jid == oldContact->jid) { + found = true; + break; + } + } + if (!found) { + contactVector_.push_back(newContact); + } + } + if (!supportsImpromptu_ && contactVector_.size() > 1) { + contactVector_.resize(1); /* can't chat with more than one user */ + } + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->emitCompletenessCheck(); + if (type_ == ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? true : (contactVector_.size() < 1)); + } + } +} + +void QtUserSearchWindow::setCanSupplyDescription(bool allowed) { + firstMultiJIDPage_->label->setVisible(allowed); + firstMultiJIDPage_->reason_->setVisible(allowed); +} + +void QtUserSearchWindow::handleAddViaSearch() { + searchNext_ = true; + next(); +} + +void QtUserSearchWindow::handleListChanged(std::vector<Contact::ref> list) { + contactVector_ = list; + if (type_ == ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); + } +} + +void QtUserSearchWindow::handleJIDsAdded(std::vector<JID> jids) { + onJIDAddRequested(jids); +} + void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results) { UserSearchModel *newModel = new UserSearchModel(); @@ -255,4 +443,5 @@ void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results model_ = newModel; resultsPage_->setNoResults(model_->rowCount() == 0); + resultsPage_->emitCompletenessCheck(); } @@ -263,8 +452,13 @@ void QtUserSearchWindow::setResultsForm(Form::ref results) { 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); + resultsPage_->emitCompletenessCheck(); } @@ -273,4 +467,61 @@ void QtUserSearchWindow::setSelectedService(const JID& 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))); + firstPage_->jid_->onUserSelected.connect(boost::bind(&QtUserSearchWindow::handleOnSearchedJIDSelected, this, _1)); + 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))); + firstMultiJIDPage_->jid_->onUserSelected.connect(boost::bind(&QtUserSearchWindow::addSearchedJIDToList, this, _1)); + connect(firstMultiJIDPage_->addViaSearchButton_, SIGNAL(clicked()), this, SLOT(handleAddViaSearch())); + connect(firstMultiJIDPage_->contactList_, SIGNAL(onListChanged(std::vector<Contact::ref>)), this, SLOT(handleListChanged(std::vector<Contact::ref>))); + connect(firstMultiJIDPage_->contactList_, SIGNAL(onJIDsAdded(std::vector<JID>)), this, SLOT(handleJIDsAdded(std::vector<JID>))); + connect(firstMultiJIDPage_, SIGNAL(onJIDsDropped(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(); @@ -288,19 +539,26 @@ 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?")); - } - else { - howText = QString(tr("How would you like to find the user to chat to?")); - } 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); + } clearForm(); resultsPage_->results_->setModel(NULL); delete model_; model_ = NULL; - handleFirstPageRadioChange(); restart(); lastPage_ = 1; @@ -309,9 +567,18 @@ void QtUserSearchWindow::clear() { void QtUserSearchWindow::setError(const QString& error) { if (error.isEmpty()) { + if (type_ == AddContact) { firstPage_->errorLabel_->hide(); + } else { + firstMultiJIDPage_->errorLabel_->hide(); + } } else { + 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..255b8fe 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -19,13 +19,15 @@ namespace Swift { 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(); @@ -42,11 +44,32 @@ namespace Swift { 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::ref>& 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::ref> &contacts); + virtual void addContacts(const std::vector<Contact::ref>& contacts); + virtual void setCanSupplyDescription(bool allowed); 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::ref> list); + void handleJIDsAdded(std::vector<JID> jids); + + private: + void setFirstPage(QString title = ""); + void setSecondPage(); + void setThirdPage(); + private: void clearForm(); @@ -55,4 +78,7 @@ namespace Swift { void handleSearch(); JID getContactJID() const; + Contact::ref getContact() const; + void addSearchedJIDToList(const Contact::ref& contact); + void handleOnSearchedJIDSelected(const Contact::ref& contact); private: @@ -62,9 +88,15 @@ namespace Swift { UserSearchDelegate* delegate_; QtUserSearchFirstPage* firstPage_; + QtUserSearchFirstMultiJIDPage* firstMultiJIDPage_; QtUserSearchFieldsPage* fieldsPage_; QtUserSearchResultsPage* resultsPage_; QtUserSearchDetailsPage* detailsPage_; JID myServer_; + JID roomJID_; int lastPage_; + std::vector<Contact::ref> contactVector_; + SettingsProvider* settings_; + bool searchNext_; + bool supportsImpromptu_; }; } diff --git a/Swift/QtUI/Whiteboard/ColorWidget.cpp b/Swift/QtUI/Whiteboard/ColorWidget.cpp new file mode 100644 index 0000000..e96b760 --- /dev/null +++ b/Swift/QtUI/Whiteboard/ColorWidget.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + + +#include "ColorWidget.h" +#include <QPainter> +#include <QMouseEvent> + +namespace Swift { + ColorWidget::ColorWidget(QWidget* parent) : QWidget(parent) { + setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + } + + QSize ColorWidget::sizeHint() const { + return QSize(20, 20); + } + + void ColorWidget::setColor(QColor color) { + this->color = color; + update(); + } + + void ColorWidget::paintEvent(QPaintEvent* /*event*/) { + QPainter painter(this); + painter.fillRect(0, 0, 20, 20, color); + } + + void ColorWidget::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + emit clicked(); + } + } +} + diff --git a/Swift/QtUI/Whiteboard/ColorWidget.h b/Swift/QtUI/Whiteboard/ColorWidget.h new file mode 100644 index 0000000..ae1af0f --- /dev/null +++ b/Swift/QtUI/Whiteboard/ColorWidget.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QWidget> + +namespace Swift { + class ColorWidget : public QWidget { + Q_OBJECT + public: + ColorWidget(QWidget* parent = 0); + QSize sizeHint() const; + + public slots: + void setColor(QColor color); + + private: + QColor color; + + protected: + void paintEvent(QPaintEvent* /*event*/); + void mouseReleaseEvent(QMouseEvent* event); + + signals: + void clicked(); + + }; +} + diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.cpp b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp new file mode 100644 index 0000000..8821062 --- /dev/null +++ b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FreehandLineItem.h" + + +namespace Swift { + FreehandLineItem::FreehandLineItem(QGraphicsItem* parent) : QGraphicsItem(parent) { + } + + QRectF FreehandLineItem::boundingRect() const + { + return boundRect; + } + + void FreehandLineItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) + { + painter->setPen(pen_); + if (points_.size() > 0) { + QVector<QPointF>::const_iterator it = points_.begin(); + QPointF previous = *it; + ++it; + for (; it != points_.end(); ++it) { + painter->drawLine(previous, *it); + previous = *it; + } + } + } + + void FreehandLineItem::setStartPoint(QPointF point) + { + points_.clear(); + points_.append(point); + QRectF rect(point, point); + prepareGeometryChange(); + boundRect = rect; + } + + void FreehandLineItem::lineTo(QPointF point) + { + qreal x1, x2, y1, y2; + x1 = points_.last().x(); + x2 = point.x(); + y1 = points_.last().y(); + y2 = point.y(); + if (x1 > x2) { + qreal temp = x1; + x1 = x2; + x2 = temp; + } + if (y1 > y2) { + qreal temp = y1; + y1 = y2; + y2 = temp; + } + QRectF rect(x1-1, y1-1, x2+1-x1, y2+1-y1); + + points_.append(point); + + prepareGeometryChange(); + boundRect |= rect; + } + + bool FreehandLineItem::collidesWithPath(const QPainterPath& path, Qt::ItemSelectionMode /*mode*/) const + { + QVector<QPointF>::const_iterator it; + QSizeF size(1,1); + for (it = points_.begin(); it != points_.end(); ++it) { + if (path.intersects(QRectF(*it, size))) { + return true; + } + } + return false; + } + + void FreehandLineItem::setPen(const QPen& pen) + { + pen_ = pen; + update(boundRect); + } + + QPen FreehandLineItem::pen() const + { + return pen_; + } + + const QVector<QPointF>& FreehandLineItem::points() const { + return points_; + } + + int FreehandLineItem::type() const { + return Type; + } +} diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.h b/Swift/QtUI/Whiteboard/FreehandLineItem.h new file mode 100644 index 0000000..b1af3d1 --- /dev/null +++ b/Swift/QtUI/Whiteboard/FreehandLineItem.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QGraphicsItem> +#include <QPainter> +#include <iostream> + +namespace Swift { + class FreehandLineItem : public QGraphicsItem { + public: + enum {Type = UserType + 1}; + FreehandLineItem(QGraphicsItem* parent = 0); + QRectF boundingRect() const; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/ = 0); + void setStartPoint(QPointF point); + void lineTo(QPointF point); + bool collidesWithPath(const QPainterPath& path, Qt::ItemSelectionMode /*mode*/ = Qt::IntersectsItemShape) const; + void setPen(const QPen& pen); + QPen pen() const; + const QVector<QPointF>& points() const; + int type() const; + + private: + QPen pen_; + QVector<QPointF> points_; + QRectF boundRect; + }; +} diff --git a/Swift/QtUI/Whiteboard/GView.cpp b/Swift/QtUI/Whiteboard/GView.cpp new file mode 100644 index 0000000..d725cbb --- /dev/null +++ b/Swift/QtUI/Whiteboard/GView.cpp @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "GView.h" +#include <QtSwiftUtil.h> + +namespace Swift { + GView::GView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent), brush(QColor(Qt::white)), defaultBrush(QColor(Qt::white)) { + selectionRect = 0; + lastItem = 0; + zValue = 0; + } + + void GView::setLineWidth(int i) { + pen.setWidth(i); + if (selectionRect) { + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush); + lastItemChanged(item, items_.indexOf(item)+1, Update); + } else { + defaultPen.setWidth(i); + } + } + + void GView::setLineColor(QColor color) { + pen.setColor(color); + if (selectionRect) { + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush); + lastItemChanged(item, items_.indexOf(item)+1, Update); + } else { + defaultPen.setColor(color); + } + lineColorChanged(color); + } + + QColor GView::getLineColor() { + return pen.color(); + } + + void GView::setBrushColor(QColor color) { + brush.setColor(color); + if (selectionRect) { + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + changePenAndBrush(selectionRect->data(1).value<QGraphicsItem*>(), pen, brush); + lastItemChanged(item, items_.indexOf(item)+1, Update); + } else { + defaultBrush.setColor(color); + } + brushColorChanged(color); + } + + QColor GView::getBrushColor() { + return brush.color(); + } + + void GView::setMode(Mode mode) { + this->mode = mode; + lastItem = 0; + deselect(); + } + + void GView::addItem(QGraphicsItem* item, QString id, int pos) { + itemsMap_.insert(id, item); + if (pos > items_.size()) { + item->setZValue(zValue++); + scene()->addItem(item); + items_.append(item); + } else { + QGraphicsItem* temp = items_.at(pos-1); + item->setZValue(temp->zValue()); + scene()->addItem(item); + item->stackBefore(temp); + items_.insert(pos-1, item); + } + } + + void GView::clear() { + scene()->clear(); + items_.clear(); + itemsMap_.clear(); + lastItem = 0; + selectionRect = 0; + brush = QBrush(QColor(Qt::white)); + defaultBrush = QBrush(QColor(Qt::white)); + pen = QPen(); + pen.setWidth(1); + defaultPen = pen; + lineWidthChanged(1); + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + + QGraphicsItem* GView::getItem(QString id) { + return itemsMap_.value(id); + } + + void GView::deleteItem(QString id) { + deselect(id); + QGraphicsItem* item = itemsMap_.value(id); + items_.removeOne(item); + itemsMap_.remove(id); + scene()->removeItem(item); + delete item; + } + + QString GView::getNewID() { + return P2QSTRING(idGenerator.generateID()); + } + + void GView::mouseMoveEvent(QMouseEvent* event) + { + if (!mousePressed) { + return; + } + + if (mode == Line) { + QGraphicsLineItem* item = qgraphicsitem_cast<QGraphicsLineItem*>(lastItem); + if(item != 0) { + QLineF line = item->line(); + line.setP1(this->mapToScene(event->pos())); + item->setLine(line); + + } + } + else if (mode == Rect) { + QGraphicsRectItem* item = qgraphicsitem_cast<QGraphicsRectItem*>(lastItem); + if (item != 0) { + QPointF beginPoint = item->data(0).toPointF(); + QPointF newPoint = this->mapToScene(event->pos()); + QRectF rect = item->rect(); + if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) { + rect.setTopLeft(beginPoint); + rect.setBottomRight(newPoint); + } + else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) { + rect.setTopRight(beginPoint); + rect.setBottomLeft(newPoint); + } + else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) { + rect.setBottomLeft(beginPoint); + rect.setTopRight(newPoint); + } + else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) { + rect.setBottomRight(beginPoint); + rect.setTopLeft(newPoint); + } + item->setRect(rect); + } + } + else if (mode == Circle) { + QGraphicsEllipseItem* item = qgraphicsitem_cast<QGraphicsEllipseItem*>(lastItem); + QPainterPath path; + QPointF beginPoint = item->data(0).toPointF(); + QPointF newPoint = this->mapToScene(event->pos()); + QRectF rect = item->rect(); + if (beginPoint.x() <= newPoint.x() && beginPoint.y() <= newPoint.y()) { + rect.setTopLeft(beginPoint); + rect.setBottomRight(newPoint); + } + else if (beginPoint.x() > newPoint.x() && beginPoint.y() <= newPoint.y()) { + rect.setTopRight(beginPoint); + rect.setBottomLeft(newPoint); + } + else if (beginPoint.x() <= newPoint.x() && beginPoint.y() > newPoint.y()) { + rect.setBottomLeft(beginPoint); + rect.setTopRight(newPoint); + } + else if (beginPoint.x() > newPoint.x() && beginPoint.y() > newPoint.y()) { + rect.setBottomRight(beginPoint); + rect.setTopLeft(newPoint); + } + + item->setRect(rect); + } + else if (mode == HandLine) { + FreehandLineItem* item = qgraphicsitem_cast<FreehandLineItem*>(lastItem); + if (item != 0) { + QPointF newPoint = this->mapToScene(event->pos()); + item->lineTo(newPoint); + } + } + else if (mode == Polygon) { + QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(lastItem); + QPointF newPoint = this->mapToScene(event->pos()); + QPolygonF polygon = item->polygon(); + polygon.erase(polygon.end()-1); + polygon.append(newPoint); + item->setPolygon(polygon); + } + else if (mode == Select) { + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + if (item != 0) { + QPainterPath path; + QPointF beginPoint = selectionRect->data(0).toPointF(); + QPointF newPoint = this->mapToScene(event->pos()); + item->setPos(beginPoint + newPoint); + selectionRect->setPos(beginPoint + newPoint); + } + } + } + + void GView::mousePressEvent(QMouseEvent *event) + { + mousePressed = true; + deselect(); + if (mode == Line) { + QPointF point = this->mapToScene(event->pos()); + QGraphicsItem* item = scene()->addLine(point.x(), point.y(), point.x(), point.y(), pen); + QString id = getNewID(); + item->setZValue(10000000); + item->setData(100, id); + item->setData(101, items_.size()); + lastItem = item; + } + else if (mode == Rect) { + QPointF point = this->mapToScene(event->pos()); + QGraphicsRectItem* item = scene()->addRect(point.x(), point.y(), 0, 0, pen, brush); + QString id = getNewID(); + item->setZValue(10000000); + item->setData(0, point); + item->setData(100, id); + item->setData(101, items_.size()); + lastItem = item; + } + else if (mode == Rubber) { + QPointF point = this->mapToScene(event->pos()); + int w = pen.width(); + QRectF rect(point.x()-w, point.y()-w, w*2, w*2); + QList<QGraphicsItem*> list = scene()->items(rect); + if (!list.isEmpty()) + { + QGraphicsItem* item = scene()->items(rect).first(); + QString id = item->data(100).toString(); + int pos = items_.indexOf(item)+1; + itemDeleted(id, pos); + deleteItem(id); + } + } + else if (mode == Circle) { + QPointF point = this->mapToScene(event->pos()); + QGraphicsEllipseItem* item = scene()->addEllipse(point.x(), point.y(), 0, 0, pen, brush); + QString id = getNewID(); + item->setZValue(10000000); + item->setData(0, point); + item->setData(100, id); + item->setData(101, items_.size()); + lastItem = item; + } + else if (mode == HandLine) { + QPointF point = this->mapToScene(event->pos()); + FreehandLineItem* item = new FreehandLineItem; + QString id = getNewID(); + item->setPen(pen); + item->setStartPoint(point); + item->setZValue(10000000); + item->setData(100, id); + item->setData(101, items_.size()); + scene()->addItem(item); + lastItem = item; + } + else if (mode == Text) { + QPointF point = this->mapToScene(event->pos()); + QGraphicsTextItem* item = scene()->addText(""); + QString id = getNewID(); + item->setData(100, id); + item->setData(101, items_.size()); + item->setDefaultTextColor(pen.color()); + textDialog = new TextDialog(item, this); + connect(textDialog, SIGNAL(accepted(QGraphicsTextItem*)), this, SLOT(handleTextItemModified(QGraphicsTextItem*))); + textDialog->setAttribute(Qt::WA_DeleteOnClose); + textDialog->show(); + item->setPos(point); + lastItem = item; + } + else if (mode == Polygon) { + QPointF point = this->mapToScene(event->pos()); + QGraphicsPolygonItem* item = dynamic_cast<QGraphicsPolygonItem*>(lastItem); + if (item == 0) { + QPolygonF polygon; + polygon.append(point); + polygon.append(point); + item = scene()->addPolygon(polygon, pen, brush); + QString id = getNewID(); + item->setZValue(10000000); + item->setData(100, id); + item->setData(101, items_.size()); + lastItem = item; + } + else { + QPolygonF polygon; + polygon = item->polygon(); + polygon.append(point); + item->setPolygon(polygon); + } + } + else if (mode == Select) { + QPointF point = this->mapToScene(event->pos()); + int w = pen.width(); + if (w == 0) { + w = 1; + } + QRectF rect(point.x()-w, point.y()-w, w*2, w*2); + QList<QGraphicsItem*> list = scene()->items(rect); + if (!list.isEmpty()) { + QPen pen; + pen.setColor(QColor(Qt::gray)); + pen.setStyle(Qt::DashLine); + QGraphicsItem *item = scene()->items(rect).first(); + selectionRect = scene()->addRect(item->boundingRect(), pen); + selectionRect->setZValue(1000000); + selectionRect->setData(0, item->pos()-point); + selectionRect->setPos(item->pos()); + QVariant var(QVariant::UserType); + var.setValue(item); + selectionRect->setData(1, var); + setActualPenAndBrushFromItem(item); + } + } + } + + void GView::mouseReleaseEvent(QMouseEvent* /*event*/) + { + mousePressed = false; + QGraphicsPolygonItem* polygon = dynamic_cast<QGraphicsPolygonItem*>(lastItem); + if (polygon && polygon->polygon().size() >= 3) { + lastItemChanged(polygon, items_.indexOf(polygon)+1, Update); + } else if (lastItem) { + zValue++; + lastItem->setZValue(zValue++); + items_.append(lastItem); + itemsMap_.insert(lastItem->data(100).toString(), lastItem); + + lastItemChanged(lastItem, items_.size(), New); + } else if (selectionRect){ + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + lastItemChanged(item, items_.indexOf(item)+1, Update); + } + } + + + void GView::handleTextItemModified(QGraphicsTextItem* item) { + lastItemChanged(item, item->data(101).toInt(), Update); + } + + void GView::moveUpSelectedItem() + { + if (selectionRect) { + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + int pos = items_.indexOf(item); + if (pos < items_.size()-1) { + lastItemChanged(item, pos+1, MoveUp); + move(item, pos+2); + } + } + } + + void GView::moveDownSelectedItem() + { + if (selectionRect) { + QGraphicsItem* item = selectionRect->data(1).value<QGraphicsItem*>(); + int pos = items_.indexOf(item); + if (pos > 0) { + lastItemChanged(item, pos+1, MoveDown); + move(item, pos); + } + } + } + + void GView::move(QGraphicsItem* item, int npos) { + int pos = items_.indexOf(item); + QGraphicsItem* itemAfter = NULL; + if (npos-1 > pos) { + if (npos == items_.size()) { + item->setZValue(zValue++); + } else { + itemAfter = items_.at(npos); + } + + items_.insert(npos, item); + items_.removeAt(pos); + } else if (npos-1 < pos) { + itemAfter = items_.at(npos-1); + items_.insert(npos-1, item); + items_.removeAt(pos+1); + } + if (itemAfter) { + item->setZValue(itemAfter->zValue()); + item->stackBefore(itemAfter); + } + } + + void GView::changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush) { + QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item); + if (lineItem) { + lineItem->setPen(pen); + } + + FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item); + if (handLineItem) { + handLineItem->setPen(pen); + } + + QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item); + if (rectItem) { + rectItem->setPen(pen); + rectItem->setBrush(brush); + } + + QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item); + if (textItem) { + textItem->setDefaultTextColor(pen.color()); + } + + QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item); + if (polygonItem) { + polygonItem->setPen(pen); + polygonItem->setBrush(brush); + } + + QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item); + if (ellipseItem) { + ellipseItem->setPen(pen); + ellipseItem->setBrush(brush); + } + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + + void GView::setActualPenAndBrushFromItem(QGraphicsItem* item) { + QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item); + if (lineItem) { + pen = lineItem->pen(); + } + + FreehandLineItem* handLineItem = qgraphicsitem_cast<FreehandLineItem*>(item); + if (handLineItem) { + pen = handLineItem->pen(); + } + + QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item); + if (rectItem) { + pen = rectItem->pen(); + brush = rectItem->brush(); + } + + QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item); + if (textItem) { + pen.setColor(textItem->defaultTextColor()); + } + + QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item); + if (polygonItem) { + pen = polygonItem->pen(); + brush = polygonItem->brush(); + } + + QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item); + if (ellipseItem) { + pen = ellipseItem->pen(); + brush = ellipseItem->brush(); + } + lineWidthChanged(pen.width()); + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + + void GView::deselect() { + if (selectionRect != 0) { + pen = defaultPen; + brush = defaultBrush; + scene()->removeItem(selectionRect); + delete selectionRect; + selectionRect = 0; + lineWidthChanged(pen.width()); + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + } + + void GView::deselect(QString id) { + if (selectionRect != 0) { + QGraphicsItem* item = getItem(id); + if (item && selectionRect->data(1).value<QGraphicsItem*>() == item) { + pen = defaultPen; + brush = defaultBrush; + scene()->removeItem(selectionRect); + delete selectionRect; + selectionRect = 0; + lineWidthChanged(pen.width()); + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + } + } +} diff --git a/Swift/QtUI/Whiteboard/GView.h b/Swift/QtUI/Whiteboard/GView.h new file mode 100644 index 0000000..6a4fd2f --- /dev/null +++ b/Swift/QtUI/Whiteboard/GView.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QGraphicsView> +#include <QGraphicsLineItem> +#include <QMouseEvent> +#include <QPen> +#include <iostream> +#include <Swiften/Base/IDGenerator.h> + +#include "TextDialog.h" +#include "FreehandLineItem.h" + +namespace Swift { + class GView : public QGraphicsView { + Q_OBJECT + public: + enum Mode { Rubber, Line, Rect, Circle, HandLine, Text, Polygon, Select }; + enum Type { New, Update, MoveUp, MoveDown }; + GView(QGraphicsScene* scene, QWidget* parent = 0); + void setLineWidth(int i); + void setLineColor(QColor color); + QColor getLineColor(); + void setBrushColor(QColor color); + QColor getBrushColor(); + void setMode(Mode mode); + void mouseMoveEvent(QMouseEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* /*event*/); + void addItem(QGraphicsItem* item, QString id, int pos); + void clear(); + QGraphicsItem* getItem(QString id); + void deleteItem(QString id); + QString getNewID(); + void move(QGraphicsItem* item, int npos); + void deselect(QString id); + + public slots: + void moveUpSelectedItem(); + void moveDownSelectedItem(); + + private slots: + void handleTextItemModified(QGraphicsTextItem*); + private: + void changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush); + void setActualPenAndBrushFromItem(QGraphicsItem* item); + void deselect(); + + int zValue; + bool mousePressed; + QPen pen; + QBrush brush; + QPen defaultPen; + QBrush defaultBrush; + Mode mode; + QGraphicsItem* lastItem; + QGraphicsRectItem* selectionRect; + TextDialog* textDialog; + QMap<QString, QGraphicsItem*> itemsMap_; + QList<QGraphicsItem*> items_; + IDGenerator idGenerator; + + signals: + void lastItemChanged(QGraphicsItem* item, int pos, GView::Type type); + void itemDeleted(QString id, int pos); + void lineWidthChanged(int i); + void lineColorChanged(QColor color); + void brushColorChanged(QColor color); + }; +} diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp new file mode 100644 index 0000000..89de95e --- /dev/null +++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "QtWhiteboardWindow.h" + +#include <iostream> + +#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> +#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.h> +#include <Swiften/Elements/Whiteboard/WhiteboardUpdateOperation.h> +#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h> +#include <Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h> + +#include <QMessageBox> +#include <QLabel> + +namespace Swift { + QtWhiteboardWindow::QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession) : QWidget() { +#ifndef Q_OS_MAC + setWindowIcon(QIcon(":/logo-icon-16.png")); +#endif + layout = new QVBoxLayout(this); + hLayout = new QHBoxLayout; + sidebarLayout = new QVBoxLayout; + toolboxLayout = new QGridLayout; + + scene = new QGraphicsScene(this); + scene->setSceneRect(0, 0, 400, 400); + + graphicsView = new GView(scene, this); + graphicsView->setMode(GView::Line); + connect(graphicsView, SIGNAL(lastItemChanged(QGraphicsItem*, int, GView::Type)), this, SLOT(handleLastItemChanged(QGraphicsItem*, int, GView::Type))); + connect(graphicsView, SIGNAL(itemDeleted(QString, int)), this, SLOT(handleItemDeleted(QString, int))); + + widthBox = new QSpinBox(this); + connect(widthBox, SIGNAL(valueChanged(int)), this, SLOT(changeLineWidth(int))); + connect(graphicsView, SIGNAL(lineWidthChanged(int)), widthBox, SLOT(setValue(int))); + widthBox->setValue(1); + + moveUpButton = new QPushButton("Move Up", this); + connect(moveUpButton, SIGNAL(clicked()), graphicsView, SLOT(moveUpSelectedItem())); + + moveDownButton = new QPushButton("Move Down", this); + connect(moveDownButton, SIGNAL(clicked()), graphicsView, SLOT(moveDownSelectedItem())); + + strokeLayout = new QHBoxLayout; + strokeColor = new ColorWidget; + strokeLayout->addWidget(new QLabel("Stroke:")); + strokeLayout->addWidget(strokeColor); + connect(strokeColor, SIGNAL(clicked()), this, SLOT(showColorDialog())); + connect(graphicsView, SIGNAL(lineColorChanged(QColor)), strokeColor, SLOT(setColor(QColor))); + + fillLayout = new QHBoxLayout; + fillColor = new ColorWidget; + fillLayout->addWidget(new QLabel("Fill:")); + fillLayout->addWidget(fillColor); + connect(fillColor, SIGNAL(clicked()), this, SLOT(showBrushColorDialog())); + connect(graphicsView, SIGNAL(brushColorChanged(QColor)), fillColor, SLOT(setColor(QColor))); + + rubberButton = new QToolButton(this); + rubberButton->setIcon(QIcon(":/icons/eraser.png")); + rubberButton->setCheckable(true); + rubberButton->setAutoExclusive(true); + connect(rubberButton, SIGNAL(clicked()), this, SLOT(setRubberMode())); + + lineButton = new QToolButton(this); + lineButton->setIcon(QIcon(":/icons/line.png")); + lineButton->setCheckable(true); + lineButton->setAutoExclusive(true); + lineButton->setChecked(true); + connect(lineButton, SIGNAL(clicked()), this, SLOT(setLineMode())); + + rectButton = new QToolButton(this); + rectButton->setIcon(QIcon(":/icons/rect.png")); + rectButton->setCheckable(true); + rectButton->setAutoExclusive(true); + connect(rectButton, SIGNAL(clicked()), this, SLOT(setRectMode())); + + circleButton = new QToolButton(this); + circleButton->setIcon(QIcon(":/icons/circle.png")); + circleButton->setCheckable(true); + circleButton->setAutoExclusive(true); + connect(circleButton, SIGNAL(clicked()), this, SLOT(setCircleMode())); + + handLineButton = new QToolButton(this); + handLineButton->setIcon(QIcon(":/icons/handline.png")); + handLineButton->setCheckable(true); + handLineButton->setAutoExclusive(true); + connect(handLineButton, SIGNAL(clicked()), this, SLOT(setHandLineMode())); + + textButton = new QToolButton(this); + textButton->setIcon(QIcon(":/icons/text.png")); + textButton->setCheckable(true); + textButton->setAutoExclusive(true); + connect(textButton, SIGNAL(clicked()), this, SLOT(setTextMode())); + + polygonButton = new QToolButton(this); + polygonButton->setIcon(QIcon(":/icons/polygon.png")); + polygonButton->setCheckable(true); + polygonButton->setAutoExclusive(true); + connect(polygonButton, SIGNAL(clicked()), this, SLOT(setPolygonMode())); + + selectButton = new QToolButton(this); + selectButton->setIcon(QIcon(":/icons/cursor.png")); + selectButton->setCheckable(true); + selectButton->setAutoExclusive(true); + connect(selectButton, SIGNAL(clicked()), this, SLOT(setSelectMode())); + + toolboxLayout->addWidget(rubberButton, 0, 0); + toolboxLayout->addWidget(selectButton, 0, 1); + toolboxLayout->addWidget(lineButton, 0, 2); + toolboxLayout->addWidget(circleButton, 1, 0); + toolboxLayout->addWidget(handLineButton, 1, 1); + toolboxLayout->addWidget(rectButton, 1, 2); + toolboxLayout->addWidget(textButton, 2, 0); + toolboxLayout->addWidget(polygonButton, 2, 1); + + sidebarLayout->addLayout(toolboxLayout); + sidebarLayout->addSpacing(30); + sidebarLayout->addWidget(moveUpButton); + sidebarLayout->addWidget(moveDownButton); + sidebarLayout->addSpacing(40); + sidebarLayout->addWidget(widthBox); + sidebarLayout->addLayout(strokeLayout); + sidebarLayout->addLayout(fillLayout); + sidebarLayout->addStretch(); + hLayout->addWidget(graphicsView); + hLayout->addLayout(sidebarLayout); + layout->addLayout(hLayout); + this->setLayout(layout); + + setSession(whiteboardSession); + } + + void QtWhiteboardWindow::handleWhiteboardOperationReceive(const WhiteboardOperation::ref operation) { + WhiteboardInsertOperation::ref insertOp = boost::dynamic_pointer_cast<WhiteboardInsertOperation>(operation); + if (insertOp) { + WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::New); + insertOp->getElement()->accept(visitor); + } + + WhiteboardUpdateOperation::ref updateOp = boost::dynamic_pointer_cast<WhiteboardUpdateOperation>(operation); + if (updateOp) { + WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::Update); + updateOp->getElement()->accept(visitor); + if (updateOp->getPos() != updateOp->getNewPos()) { + graphicsView->move(graphicsView->getItem(P2QSTRING(updateOp->getElement()->getID())), updateOp->getNewPos()); + } + } + + WhiteboardDeleteOperation::ref deleteOp = boost::dynamic_pointer_cast<WhiteboardDeleteOperation>(operation); + if (deleteOp) { + graphicsView->deleteItem(P2QSTRING(deleteOp->getElementID())); + } + } + + void QtWhiteboardWindow::changeLineWidth(int i) + { + graphicsView->setLineWidth(i); + } + + void QtWhiteboardWindow::showColorDialog() + { + QColor color = QColorDialog::getColor(graphicsView->getLineColor(), 0, "Select pen color", QColorDialog::ShowAlphaChannel); + if(color.isValid()) + graphicsView->setLineColor(color); + } + + void QtWhiteboardWindow::showBrushColorDialog() + { + QColor color = QColorDialog::getColor(graphicsView->getBrushColor(), 0, "Select brush color", QColorDialog::ShowAlphaChannel); + if(color.isValid()) + graphicsView->setBrushColor(color); + } + + void QtWhiteboardWindow::setRubberMode() + { + graphicsView->setMode(GView::Rubber); + } + + void QtWhiteboardWindow::setLineMode() + { + graphicsView->setMode(GView::Line); + } + + void QtWhiteboardWindow::setRectMode() + { + graphicsView->setMode(GView::Rect); + } + + void QtWhiteboardWindow::setCircleMode() + { + graphicsView->setMode(GView::Circle); + } + + void QtWhiteboardWindow::setHandLineMode() + { + graphicsView->setMode(GView::HandLine); + } + + void QtWhiteboardWindow::setTextMode() + { + graphicsView->setMode(GView::Text); + } + + void QtWhiteboardWindow::setPolygonMode() + { + graphicsView->setMode(GView::Polygon); + } + + void QtWhiteboardWindow::setSelectMode() + { + graphicsView->setMode(GView::Select); + } + + void QtWhiteboardWindow::show() + { + QWidget::show(); + } + + void QtWhiteboardWindow::setSession(WhiteboardSession::ref session) { + graphicsView->clear(); + whiteboardSession_ = session; + whiteboardSession_->onOperationReceived.connect(boost::bind(&QtWhiteboardWindow::handleWhiteboardOperationReceive, this, _1)); + whiteboardSession_->onRequestAccepted.connect(boost::bind(&QWidget::show, this)); + whiteboardSession_->onSessionTerminated.connect(boost::bind(&QtWhiteboardWindow::handleSessionTerminate, this)); + } + + void QtWhiteboardWindow::activateWindow() { + QWidget::activateWindow(); + } + + void QtWhiteboardWindow::setName(const std::string& name) { + setWindowTitle(P2QSTRING(name)); + } + + void QtWhiteboardWindow::handleLastItemChanged(QGraphicsItem* item, int pos, GView::Type type) { + WhiteboardElement::ref el; + QGraphicsLineItem* lineItem = qgraphicsitem_cast<QGraphicsLineItem*>(item); + if (lineItem != 0) { + QLine line = lineItem->line().toLine(); + QColor color = lineItem->pen().color(); + WhiteboardLineElement::ref element = boost::make_shared<WhiteboardLineElement>(line.x1()+lineItem->pos().x(), line.y1()+lineItem->pos().y(), line.x2()+lineItem->pos().x(), line.y2()+lineItem->pos().y()); + element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha())); + element->setPenWidth(lineItem->pen().width()); + + element->setID(lineItem->data(100).toString().toStdString()); + el = element; + } + + FreehandLineItem* freehandLineItem = qgraphicsitem_cast<FreehandLineItem*>(item); + if (freehandLineItem != 0) { + WhiteboardFreehandPathElement::ref element = boost::make_shared<WhiteboardFreehandPathElement>(); + QColor color = freehandLineItem->pen().color(); + 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>( + 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())); + element->setPenWidth(freehandLineItem->pen().width()); + element->setPoints(points); + + element->setID(freehandLineItem->data(100).toString().toStdString()); + el = element; + } + + QGraphicsRectItem* rectItem = qgraphicsitem_cast<QGraphicsRectItem*>(item); + if (rectItem != 0) { + QRectF rect = rectItem->rect(); + WhiteboardRectElement::ref element = boost::make_shared<WhiteboardRectElement>(rect.x()+item->pos().x(), rect.y()+item->pos().y(), rect.width(), rect.height()); + QColor penColor = rectItem->pen().color(); + QColor brushColor = rectItem->brush().color(); + + element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha())); + element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha())); + element->setPenWidth(rectItem->pen().width()); + + element->setID(rectItem->data(100).toString().toStdString()); + el = element; + } + + QGraphicsTextItem* textItem = qgraphicsitem_cast<QGraphicsTextItem*>(item); + if (textItem != 0) { + QPointF point = textItem->pos(); + WhiteboardTextElement::ref element = boost::make_shared<WhiteboardTextElement>(point.x(), point.y()); + element->setText(textItem->toPlainText().toStdString()); + element->setSize(textItem->font().pointSize()); + QColor color = textItem->defaultTextColor(); + element->setColor(WhiteboardColor(color.red(), color.green(), color.blue(), color.alpha())); + + element->setID(textItem->data(100).toString().toStdString()); + el = element; + } + + QGraphicsPolygonItem* polygonItem = qgraphicsitem_cast<QGraphicsPolygonItem*>(item); + if (polygonItem) { + WhiteboardPolygonElement::ref element = boost::make_shared<WhiteboardPolygonElement>(); + QPolygonF polygon = polygonItem->polygon(); + 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>( + boost::numeric_cast<int>(it->x()+item->pos().x()), + boost::numeric_cast<int>(it->y()+item->pos().y()))); + } + + element->setPoints(points); + + QColor penColor = polygonItem->pen().color(); + QColor brushColor = polygonItem->brush().color(); + element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha())); + element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha())); + element->setPenWidth(polygonItem->pen().width()); + + element->setID(polygonItem->data(100).toString().toStdString()); + el = element; + } + + QGraphicsEllipseItem* ellipseItem = qgraphicsitem_cast<QGraphicsEllipseItem*>(item); + if (ellipseItem) { + QRectF rect = ellipseItem->rect(); + 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(); + QColor brushColor = ellipseItem->brush().color(); + element->setPenColor(WhiteboardColor(penColor.red(), penColor.green(), penColor.blue(), penColor.alpha())); + element->setBrushColor(WhiteboardColor(brushColor.red(), brushColor.green(), brushColor.blue(), brushColor.alpha())); + element->setPenWidth(ellipseItem->pen().width()); + + element->setID(ellipseItem->data(100).toString().toStdString()); + el = element; + } + + if (type == GView::New) { + WhiteboardInsertOperation::ref insertOp = boost::make_shared<WhiteboardInsertOperation>(); + insertOp->setPos(pos); + insertOp->setElement(el); + whiteboardSession_->sendOperation(insertOp); + } else { + WhiteboardUpdateOperation::ref updateOp = boost::make_shared<WhiteboardUpdateOperation>(); + updateOp->setPos(pos); + if (type == GView::Update) { + updateOp->setNewPos(pos); + } else if (type == GView::MoveUp) { + updateOp->setNewPos(pos+1); + } else if (type == GView::MoveDown) { + updateOp->setNewPos(pos-1); + } + updateOp->setElement(el); + whiteboardSession_->sendOperation(updateOp); + } + } + + void QtWhiteboardWindow::handleItemDeleted(QString id, int pos) { + WhiteboardDeleteOperation::ref deleteOp = boost::make_shared<WhiteboardDeleteOperation>(); + deleteOp->setElementID(Q2PSTRING(id)); + deleteOp->setPos(pos); + whiteboardSession_->sendOperation(deleteOp); + } + + void QtWhiteboardWindow::handleSessionTerminate() { + hide(); + } + + void QtWhiteboardWindow::closeEvent(QCloseEvent* event) { + QMessageBox box(this); + box.setText(tr("Closing window is equivalent closing the session. Are you sure you want to do this?")); + box.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + box.setIcon(QMessageBox::Question); + if (box.exec() == QMessageBox::Yes) { + whiteboardSession_->cancel(); + } else { + event->ignore(); + } + } +} diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h new file mode 100644 index 0000000..3957bb7 --- /dev/null +++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Whiteboard/WhiteboardSession.h> +#include <Swiften/Elements/Whiteboard/WhiteboardElement.h> + +#include <QWidget> +#include <QGraphicsView> +#include <QGraphicsScene> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QGridLayout> +#include <QPainter> +#include <QPushButton> +#include <QSpinBox> +#include <QColorDialog> +#include <QToolButton> +#include <QCloseEvent> + +#include "GView.h" +#include "ColorWidget.h" + +namespace Swift { + class QtWhiteboardWindow : public QWidget, public WhiteboardWindow + { + Q_OBJECT + public: + QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession); + void show(); + void setSession(WhiteboardSession::ref session); + void activateWindow(); + void setName(const std::string& name); + + private slots: + void changeLineWidth(int i); + void showColorDialog(); + void showBrushColorDialog(); + void setRubberMode(); + void setLineMode(); + void setRectMode(); + void setCircleMode(); + void setHandLineMode(); + void setTextMode(); + void setPolygonMode(); + void setSelectMode(); + void handleLastItemChanged(QGraphicsItem* item, int pos, GView::Type type); + void handleItemDeleted(QString id, int pos); + + private: + void handleSessionTerminate(); + void handleWhiteboardOperationReceive(const WhiteboardOperation::ref operation); + void closeEvent(QCloseEvent* event); + + private: + QGraphicsScene* scene; + GView* graphicsView; + QVBoxLayout* layout; + QVBoxLayout* sidebarLayout; + QHBoxLayout* hLayout; + QGridLayout* toolboxLayout; + QHBoxLayout* strokeLayout; + QHBoxLayout* fillLayout; + ColorWidget* strokeColor; + ColorWidget* fillColor; + QWidget* widget; + QPushButton* moveUpButton; + QPushButton* moveDownButton; + QSpinBox* widthBox; + QToolButton* rubberButton; + QToolButton* lineButton; + QToolButton* rectButton; + QToolButton* circleButton; + QToolButton* handLineButton; + QToolButton* textButton; + QToolButton* polygonButton; + QToolButton* selectButton; + + std::string lastOpID; + WhiteboardSession::ref whiteboardSession_; + }; +} diff --git a/Swift/QtUI/Whiteboard/TextDialog.cpp b/Swift/QtUI/Whiteboard/TextDialog.cpp new file mode 100644 index 0000000..021895a --- /dev/null +++ b/Swift/QtUI/Whiteboard/TextDialog.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "TextDialog.h" + +namespace Swift { + TextDialog::TextDialog(QGraphicsTextItem* item, QWidget* parent) : QDialog(parent) + { + this->item = item; + + layout = new QVBoxLayout(this); + hLayout = new QHBoxLayout; + + editor = new QLineEdit(this); + connect(editor, SIGNAL(textChanged(const QString&)), this, SLOT(changeItemText(const QString&))); + + fontSizeBox = new QSpinBox(this); + fontSizeBox->setMinimum(1); + connect(fontSizeBox, SIGNAL(valueChanged(int)), this, SLOT(changeItemFontSize(int))); + fontSizeBox->setValue(13); + + + buttonBox = new QDialogButtonBox(this); + buttonBox->setStandardButtons(QDialogButtonBox::Ok); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + + hLayout->addWidget(editor); + hLayout->addWidget(fontSizeBox); + layout->addLayout(hLayout); + layout->addWidget(buttonBox); + } + + void TextDialog::changeItemText(const QString &text) + { + item->setPlainText(text); + } + + void TextDialog::changeItemFontSize(int i) + { + QFont font = item->font(); + font.setPointSize(i); + item->setFont(font); + } + + void TextDialog::accept() { + emit accepted(item); + done(QDialog::Accepted); + } +} diff --git a/Swift/QtUI/Whiteboard/TextDialog.h b/Swift/QtUI/Whiteboard/TextDialog.h new file mode 100644 index 0000000..64a0fe2 --- /dev/null +++ b/Swift/QtUI/Whiteboard/TextDialog.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QDialog> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QDialogButtonBox> +#include <QLineEdit> +#include <QGraphicsTextItem> +#include <QSpinBox> + +#include <iostream> + +namespace Swift { + class TextDialog : public QDialog + { + Q_OBJECT + public: + TextDialog(QGraphicsTextItem* item, QWidget* parent = 0); + + private: + QGraphicsTextItem* item; + QLineEdit* editor; + QDialogButtonBox* buttonBox; + QVBoxLayout* layout; + QHBoxLayout* hLayout; + QSpinBox* fontSizeBox; + + signals: + void accepted(QGraphicsTextItem* item); + + private slots: + void accept(); + void changeItemText(const QString &text); + void changeItemFontSize(int i); + }; +} + diff --git a/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h new file mode 100644 index 0000000..74c6c1d --- /dev/null +++ b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2012 Mateusz Piękos + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h> +#include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h> +#include <Swift/QtUI/Whiteboard/GView.h> +#include <QtSwiftUtil.h> + +namespace Swift { + class WhiteboardElementDrawingVisitor : public WhiteboardElementVisitor { + public: + WhiteboardElementDrawingVisitor(GView* graphicsView, int pos, GView::Type type) : graphicsView_(graphicsView), pos_(pos), type_(type) {} + + void visit(WhiteboardLineElement& element) { + QGraphicsLineItem *item; + if (type_ == GView::New) { + item = new QGraphicsLineItem(element.x1(), element.y1(), element.x2(), element.y2()); + graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_); + } else { + item = qgraphicsitem_cast<QGraphicsLineItem*>(graphicsView_->getItem(P2QSTRING(element.getID()))); + QLineF line(element.x1(), element.y1(), element.x2(), element.y2()); + item->setLine(line); + item->setPos(0,0); + graphicsView_->deselect(P2QSTRING(element.getID())); + } + if (item) { + QPen pen; + WhiteboardColor color = element.getColor(); + pen.setColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha())); + pen.setWidth(element.getPenWidth()); + item->setPen(pen); + QString id = P2QSTRING(element.getID()); + item->setData(100, id); + } + } + + void visit(WhiteboardFreehandPathElement& element) { + FreehandLineItem *item; + if (type_ == GView::New) { + item = new FreehandLineItem; + } else { + item = qgraphicsitem_cast<FreehandLineItem*>(graphicsView_->getItem(P2QSTRING(element.getID()))); + item->setPos(0,0); + graphicsView_->deselect(P2QSTRING(element.getID())); + } + + if (item) { + QPen pen; + WhiteboardColor color = element.getColor(); + pen.setColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha())); + pen.setWidth(element.getPenWidth()); + item->setPen(pen); + + std::vector<std::pair<int, int> >::const_iterator it = element.getPoints().begin(); + item->setStartPoint(QPointF(it->first, it->second)); + for (++it; it != element.getPoints().end(); ++it) { + item->lineTo(QPointF(it->first, it->second)); + } + + QString id = P2QSTRING(element.getID()); + item->setData(100, id); + } + if (type_ == GView::New) { + graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_); + } + } + + void visit(WhiteboardRectElement& element) { + QGraphicsRectItem* item; + if (type_ == GView::New) { + item = new QGraphicsRectItem(element.getX(), element.getY(), element.getWidth(), element.getHeight()); + graphicsView_->addItem(item, P2QSTRING(element.getID()), pos_); + } else { + item = qgraphicsitem_cast<QGraphicsRectItem*>(graphicsView_->getItem(P2QSTRING(element.getID()))); + QRectF rect(element.getX(), element.getY(), element.getWidth(), element.getHeight()); + item->setRect(rect); + item->setPos(0,0); + graphicsView_->deselect(P2QSTRING(element.getID())); + } + + if (item) { + QPen pen; + QBrush brush(Qt::SolidPattern); + WhiteboardColor penColor = element.getPenColor(); + WhiteboardColor brushColor = element.getBrushColor(); + pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha())); + pen.setWidth(element.getPenWidth()); + brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha())); + item->setPen(pen); + item->setBrush(brush); + QString id = P2QSTRING(element.getID()); + item->setData(100, id); + } + } + + void visit(WhiteboardPolygonElement& element) { + QGraphicsPolygonItem* item = qgraphicsitem_cast<QGraphicsPolygonItem*>(graphicsView_->getItem(P2QSTRING(element.getID()))); + if (item == 0 && type_ == GView::New) { + item = new QGraphicsPolygonItem(); + QString id = P2QSTRING(element.getID()); + item->setData(100, id); + graphicsView_->addItem(item, id, pos_); + } + graphicsView_->deselect(P2QSTRING(element.getID())); + QPen pen; + QBrush brush(Qt::SolidPattern); + WhiteboardColor penColor = element.getPenColor(); + WhiteboardColor brushColor = element.getBrushColor(); + pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha())); + pen.setWidth(element.getPenWidth()); + brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha())); + item->setPen(pen); + item->setBrush(brush); + + item->setPos(0,0); + QPolygonF polygon; + std::vector<std::pair<int, int> >::const_iterator it = element.getPoints().begin(); + for (; it != element.getPoints().end(); ++it) { + polygon.append(QPointF(it->first, it->second)); + } + item->setPolygon(polygon); + } + + void visit(WhiteboardTextElement& element) { + QGraphicsTextItem* item; + QString id = P2QSTRING(element.getID()); + if (type_ == GView::New) { + item = new QGraphicsTextItem; + graphicsView_->addItem(item, id, pos_); + } else { + item = qgraphicsitem_cast<QGraphicsTextItem*>(graphicsView_->getItem(id)); + graphicsView_->deselect(P2QSTRING(element.getID())); + } + if (item) { + item->setPlainText(P2QSTRING(element.getText())); + item->setPos(QPointF(element.getX(), element.getY())); + QFont font = item->font(); + font.setPointSize(element.getSize()); + item->setFont(font); + WhiteboardColor color = element.getColor(); + item->setDefaultTextColor(QColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha())); + item->setData(100, id); + } + } + + void visit(WhiteboardEllipseElement& element) { + QRectF rect; + QGraphicsEllipseItem* item; + QString id = P2QSTRING(element.getID()); + rect.setTopLeft(QPointF(element.getCX()-element.getRX(), element.getCY()-element.getRY())); + rect.setBottomRight(QPointF(element.getCX()+element.getRX(), element.getCY()+element.getRY())); + if (type_ == GView::New) { + item = new QGraphicsEllipseItem(rect); + graphicsView_->addItem(item, id, pos_); + } else { + item = qgraphicsitem_cast<QGraphicsEllipseItem*>(graphicsView_->getItem(id)); + item->setRect(rect); + item->setPos(0,0); + graphicsView_->deselect(P2QSTRING(element.getID())); + } + QPen pen; + QBrush brush(Qt::SolidPattern); + WhiteboardColor penColor = element.getPenColor(); + WhiteboardColor brushColor = element.getBrushColor(); + pen.setColor(QColor(penColor.getRed(), penColor.getGreen(), penColor.getBlue(), penColor.getAlpha())); + pen.setWidth(element.getPenWidth()); + brush.setColor(QColor(brushColor.getRed(), brushColor.getGreen(), brushColor.getBlue(), brushColor.getAlpha())); + item->setPen(pen); + item->setBrush(brush); + item->setData(100, id); + } + + private: + GView* graphicsView_; + int pos_; + GView::Type type_; + }; +} diff --git a/Swift/QtUI/WinUIHelpers.cpp b/Swift/QtUI/WinUIHelpers.cpp new file mode 100644 index 0000000..161ff1d --- /dev/null +++ b/Swift/QtUI/WinUIHelpers.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 "WinUIHelpers.h" + +#include <windows.h> +#include <Wincrypt.h> +#include <cryptuiapi.h> +#pragma comment(lib, "cryptui.lib") + +#include <boost/shared_ptr.hpp> + +#include <Swiften/Base/foreach.h> + +namespace Swift { + +void WinUIHelpers::displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain) { + if (chain.empty()) { + return; + } + + // create certificate store to store the certificate chain in + HCERTSTORE chainStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL); + if (!chainStore) { + return; + } + + ByteArray certAsDER = chain[0]->toDER(); + boost::shared_ptr<const CERT_CONTEXT> certificate_chain; + { + PCCERT_CONTEXT certChain; + BOOL ok = CertAddCertificateContextToStore(chainStore, CertCreateCertificateContext(X509_ASN_ENCODING, vecptr(certAsDER), certAsDER.size()), CERT_STORE_ADD_ALWAYS, &certChain); + // maybe free the cert contex we created + if (!ok || !certChain) { + return; + } + certificate_chain.reset(certChain, CertFreeCertificateContext); + } + + for (size_t i = 1; i < chain.size(); ++i) { + ByteArray certAsDER = chain[i]->toDER(); + CertAddCertificateContextToStore(chainStore, CertCreateCertificateContext(X509_ASN_ENCODING, vecptr(certAsDER), certAsDER.size()), CERT_STORE_ADD_ALWAYS, NULL); + } + + CRYPTUI_VIEWCERTIFICATE_STRUCT viewDialogProperties = { 0 }; + viewDialogProperties.dwSize = sizeof(viewDialogProperties); + 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; + viewDialogProperties.rghStores = &chainStore; + BOOL properties_changed; + + // blocking call that shows modal certificate dialog + BOOL rv = ::CryptUIDlgViewCertificate(&viewDialogProperties, &properties_changed); +} + +} diff --git a/Swift/QtUI/WinUIHelpers.h b/Swift/QtUI/WinUIHelpers.h new file mode 100644 index 0000000..d34d236 --- /dev/null +++ b/Swift/QtUI/WinUIHelpers.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2012 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/TLS/Certificate.h> +#include <QWidget> + +namespace Swift { + +class WinUIHelpers { +public: + static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); +}; + +} + diff --git a/Swift/QtUI/main.cpp b/Swift/QtUI/main.cpp index fc3bf15..d734713 100644 --- a/Swift/QtUI/main.cpp +++ b/Swift/QtUI/main.cpp @@ -20,4 +20,7 @@ #include <Swift/Controllers/BuildVersion.h> #include <SwifTools/Application/PlatformApplicationPathProvider.h> +#include <SwifTools/CrashReporter.h> +#include <stdlib.h> +#include <Swiften/Base/Path.h> #include "QtSwift.h" @@ -28,21 +31,11 @@ int main(int argc, char* argv[]) { QApplication app(argc, argv); - QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); + Swift::PlatformApplicationPathProvider applicationPathProvider(SWIFT_APPLICATION_NAME); - // Translation - QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); - boost::filesystem::path someTranslationPath = Swift::PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME).getResourcePath("/translations/swift_en.qm"); - QTranslator qtTranslator; - if (!someTranslationPath.empty()) { -#if QT_VERSION >= 0x040800 - qtTranslator.load(QLocale::system(), QString(SWIFT_APPLICATION_NAME).toLower(), "_", someTranslationPath.parent_path().string().c_str()); -#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()); + Swift::CrashReporter crashReporter(applicationPathProvider.getDataDir() / "crashes"); + +#if QT_VERSION < 0x050000 + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); #endif - } - app.installTranslator(&qtTranslator); - QtTranslator swiftTranslator; - Swift::Translator::setInstance(&swiftTranslator); // Parse program options @@ -72,4 +65,29 @@ 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>()), P2QSTRING(Swift::pathToString(someTranslationPath.parent_path()))); + } + else { + 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(), P2QSTRING(Swift::pathToString(someTranslationPath))); +#endif + } + app.installTranslator(&qtTranslator); + QtTranslator swiftTranslator; + Swift::Translator::setInstance(&swiftTranslator); + + Swift::QtSwift swift(vm); int result = app.exec(); diff --git a/Swift/Translations/swift_ca.ts b/Swift/Translations/swift_ca.ts index 85d2c0c..a512f1f 100644 --- a/Swift/Translations/swift_ca.ts +++ b/Swift/Translations/swift_ca.ts @@ -6,35 +6,46 @@ <name></name> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="46"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> <source>Starting chat with %1% in chatroom %2%</source> <translation>Començant conversa amb %1% a la sala %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="49"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="62"/> <source>Starting chat with %1% - %2%</source> <translation>Començant conversa amb %1% - %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="119"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> + <source>This chat doesn't support delivery receipts.</source> + <translation>Aquesta conversa no és compatible amb confirmacions de lliurament.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="204"/> + <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> + <translation>Aquesta conversa potser no sigui compatible amb confirmacions de lliurament. Potser no rebis les confirmacions de lliurament dels missatges que enviïs.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="216"/> + <location filename="../Controllers/FileTransfer/FileTransferController.cpp" line="42"/> <source>me</source> <translation>Jo</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="160"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="324"/> <source>%1% has gone offline</source> <translation>%1% s'ha desconnectat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="164"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="328"/> <source>%1% has become available</source> <translation>%1% es troba disponible</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="166"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="330"/> <source>%1% has gone away</source> <translation>%1% s'ha absentat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="168"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="332"/> <source>%1% is now busy</source> <translatorcomment>TMPFIX genero: o/a? sinonimo? masculino?</translatorcomment> @@ -42,155 +53,159 @@ </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="56"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="74"/> <source>The day is now %1%</source> <translation>El día es ara %1%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="191"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <source>Couldn't send message: %1%</source> + <translation>No s'ha pogut enviar el missatge: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> <source>Error sending message</source> <translation>Error enviant missatge</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="197"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="275"/> <source>Bad request</source> <translation>Sol·licitud incorrecta</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="198"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="276"/> <source>Conflict</source> <translation>Conflicte</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="199"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="277"/> <source>This feature is not implemented</source> <translation>Aquesta característica no es troba implementada</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="200"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="278"/> <source>Forbidden</source> <translation>Prohibit</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="201"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="279"/> <source>Recipient can no longer be contacted</source> <translation>Ja no et pot contactar amb el destinatari</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="202"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="280"/> <source>Internal server error</source> <translation>Error intern del servidor</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="203"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="281"/> <source>Item not found</source> <translation>Element no trobat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="204"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="282"/> <source>JID Malformed</source> <translation>JID Malformat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="205"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="283"/> <source>Message was rejected</source> <translation>El missatge ha sigut rebutjat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="206"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="284"/> <source>Not allowed</source> <translation>No permès</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="207"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="285"/> <source>Not authorized</source> <translation>No autoritzat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="208"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="286"/> <source>Payment is required</source> <translation>Pagament requerit</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="287"/> <source>Recipient is unavailable</source> <translation>Destinatari no disponible</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="210"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="288"/> <source>Redirect</source> <translation>Redirecció</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="211"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="289"/> <source>Registration required</source> <translation>Registre requerit</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="212"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="290"/> <source>Recipient's server not found</source> <translation>No s'ha trobat el servidor del destinatari</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="213"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="291"/> <source>Remote server timeout</source> <translation>Temps d'espera del servidor remot esgotat</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="214"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="292"/> <source>The server is low on resources</source> <translation>El servidor te pocs recursos disponibles</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="215"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="293"/> <source>The service is unavailable</source> <translation>El servei no es troba disponible</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="216"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="294"/> <source>A subscription is required</source> <translation>Es requereix una subscripció</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="217"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="295"/> <source>Undefined condition</source> <translation>Condició no definida</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="218"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="296"/> <source>Unexpected request</source> <translation>Sol·licitud inesperada</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="114"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> <source>Room %1% is not responding. This operation may never complete.</source> - <translation>La sala %1% no respon. Es possible que aquesta operació no es completi mai.</translation> + <translation>La sala %1% no respon. És possible que aquesta operació no es completi mai.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="125"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="233"/> <source>Unable to enter this room</source> <translation>No es pot entrar a aquesta sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="131"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="239"/> <source>Unable to enter this room as %1%, retrying as %2%</source> <translation>No es pot entrar a aquesta sala com a %1%, provant de nou com a %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="135"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> <source>No nickname specified</source> <translation>No s'ha especificat un nick</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="139"/> <source>A password needed</source> - <translation>Es necessita contrasenya</translation> + <translation type="obsolete">Es necessita contrasenya</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="143"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="251"/> <source>Only members may enter</source> <translation>Només els membres poden entrar</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="147"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="255"/> <source>You are banned from the room</source> <translatorcomment>bloquejat?</translatorcomment> @@ -198,70 +213,75 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="151"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="259"/> <source>The room is full</source> <translation>La sala es troba plena</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="155"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="263"/> <source>The room does not exist</source> <translation>La sala no existeix</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="173"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="269"/> + <source>Couldn't join room: %1%.</source> + <translation>No s'ha pogut entrar a la sala: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="281"/> <source>You have entered room %1% as %2%.</source> <translation>Has entrat a la sala %1% com a %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="214"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="332"/> <source>%1% has entered the room as a %2%.</source> <translation>%1% ha entrat a la sala com a %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="217"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="335"/> <source>%1% has entered the room.</source> <translation>%1% ha entrat a la sala.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="384"/> <source>moderator</source> <translation>moderador</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="244"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="385"/> <source>participant</source> <translation>participant</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="245"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="386"/> <source>visitor</source> <translation>visitant</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="283"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="436"/> <source>The room subject is now: %1%</source> <translation>El tema de la sala és ara: %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="313"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="470"/> <source>%1% is now a %2%</source> <translation>%1% ara es un %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="319"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="486"/> <source>Moderators</source> <translation>Moderadors</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="320"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> <source>Participants</source> <translation>Participants</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="321"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="488"/> <source>Visitors</source> <translation>Visitants</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="322"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="489"/> <source>Occupants</source> <translatorcomment>TMPFIX, used where?</translatorcomment> @@ -269,172 +289,226 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="336"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> <source>Trying to enter room %1%</source> <translation>Intentant entrar a la sala %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="365"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="474"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="544"/> + <source>%1% has left the room%2%</source> + <translation>%1% ha sortit de la sala %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="548"/> + <source>You have been kicked out of the room</source> + <translation>Has sigut expulsat de la sala</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="549"/> + <source>You have been banned from the room</source> + <translation>Has sigut vetat de la sala</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="550"/> + <source>You are no longer a member of the room and have been removed</source> + <translation>Ja no ets un membre de la sala i has sigut expulsat</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="551"/> + <source>The room has been destroyed</source> + <translation>La sala ha estat destruïda</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="667"/> <source>%1% has left the room</source> <translation>%1% ha sortit de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="365"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="719"/> + <source>Room configuration failed: %1%.</source> + <translation>La configuració de la sala ha fallat: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="725"/> + <source>Occupant role change failed: %1%.</source> + <translation>El canvi de rol de l'ocupant ha fallat: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="553"/> <source>You have left the room</source> <translation>Has sortit de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="439"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="247"/> + <source>The correct room password is needed</source> + <translation>Es necessita la contrasenya de sala correcta</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="632"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="696"/> <source> and </source> <translation> i </translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="463"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="656"/> <source>%1% have entered the room</source> <translation>%1% han entrat a la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="466"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="659"/> <source>%1% has entered the room</source> <translation>%1% ha entrat a la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="471"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="664"/> <source>%1% have left the room</source> <translation>%1% han sortit de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="479"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="672"/> <source>%1% have entered then left the room</source> <translation>%1% han entrat i sortit de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="482"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="675"/> <source>%1% has entered then left the room</source> <translation>%1% ha entrat i sortit de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="680"/> <source>%1% have left then returned to the room</source> <translation>%1% han sortit i tornat a la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="490"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="683"/> <source>%1% has left then returned to the room</source> <translation>%1% ha sortit i tornat a la sala</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="51"/> + <location filename="../Controllers/EventNotifier.cpp" line="59"/> <source>%1% wants to add you to his/her contact list</source> <translation>%1% vol afegir-te a la seva llista de contactes</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="55"/> + <location filename="../Controllers/EventNotifier.cpp" line="63"/> <source>Error</source> <translation>Error</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="438"/> + <location filename="../Controllers/EventNotifier.cpp" line="67"/> + <source>%1% has invited you to enter the %2% room</source> + <translation>%1% t'ha convidat a entrar a la sala %2%</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="466"/> + <source>User address invalid. User address should be of the form 'alice@wonderland.lit'</source> + <translation>Adreça d'usuari no vàlida. L'adreça d'usuari ha de ser de la forma 'alicia@paismeravelles.lit'</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="568"/> <source>Unknown Error</source> <translation>Error Desconegut</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="439"/> + <location filename="../Controllers/MainController.cpp" line="569"/> <source>Unable to find server</source> <translation>No es pot trobar el servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="440"/> + <location filename="../Controllers/MainController.cpp" line="570"/> <source>Error connecting to server</source> <translation>Error connectant al servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="441"/> + <location filename="../Controllers/MainController.cpp" line="571"/> <source>Error while receiving server data</source> <translation>Error al rebre dades del servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="442"/> + <location filename="../Controllers/MainController.cpp" line="572"/> <source>Error while sending data to the server</source> <translation>Error enviant dades al servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="443"/> + <location filename="../Controllers/MainController.cpp" line="573"/> <source>Error parsing server data</source> <translation>Error analitzant dades del servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="444"/> + <location filename="../Controllers/MainController.cpp" line="574"/> <source>Login/password invalid</source> <translation>Usuari/contrasenya no vàlids</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="445"/> + <location filename="../Controllers/MainController.cpp" line="575"/> <source>Error while compressing stream</source> <translation>Error comprimint flux de dades</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="446"/> + <location filename="../Controllers/MainController.cpp" line="576"/> <source>Server verification failed</source> <translation>La verificació del servidor ha fallat</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="447"/> + <location filename="../Controllers/MainController.cpp" line="577"/> <source>Authentication mechanisms not supported</source> <translation>Mecanisme d'autenticació no soportat</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="448"/> + <location filename="../Controllers/MainController.cpp" line="578"/> <source>Unexpected response</source> <translation>Resposta inesperada</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="449"/> + <location filename="../Controllers/MainController.cpp" line="579"/> <source>Error binding resource</source> <translation>Error vinculant recurs</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="450"/> + <location filename="../Controllers/MainController.cpp" line="580"/> <source>Error starting session</source> <translation>Error iniciant sessió</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="451"/> + <location filename="../Controllers/MainController.cpp" line="581"/> <source>Stream error</source> <translation>Error de flux de dades</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="452"/> + <location filename="../Controllers/MainController.cpp" line="582"/> <source>Encryption error</source> <translation>Error d'encriptatge</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="453"/> + <location filename="../Controllers/MainController.cpp" line="583"/> <source>Error loading certificate (Invalid password?)</source> <translation>Error carregant certificat (Contrasenya no vàlida?)</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="454"/> + <location filename="../Controllers/MainController.cpp" line="584"/> <source>Certificate not authorized</source> <translation>Certificat no autoritzat</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="456"/> + <location filename="../Controllers/MainController.cpp" line="585"/> + <source>Certificate card removed</source> + <translation>Targeta de certificat eliminada</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="587"/> <source>Unknown certificate</source> <translation>Certificat desconegut</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="457"/> + <location filename="../Controllers/MainController.cpp" line="588"/> <source>Certificate has expired</source> <translation>El certificat ha caducat</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="458"/> + <location filename="../Controllers/MainController.cpp" line="589"/> <source>Certificate is not yet valid</source> <translation>El certificat encara no es vàlid</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="459"/> + <location filename="../Controllers/MainController.cpp" line="590"/> <source>Certificate is self-signed</source> <translatorcomment>TMPFIX, signatura personal??</translatorcomment> @@ -442,20 +516,20 @@ </message> <message> - <location filename="../Controllers/MainController.cpp" line="460"/> + <location filename="../Controllers/MainController.cpp" line="591"/> <source>Certificate has been rejected</source> <translation>El certificat ha sigut rebutjat</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="461"/> + <location filename="../Controllers/MainController.cpp" line="592"/> <source>Certificate is not trusted</source> <translation>El certificat no es de confiança</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="462"/> + <location filename="../Controllers/MainController.cpp" line="593"/> <source>Certificate cannot be used for encrypting your connection</source> <translation>El certificat no put set utilitzat per encriptar la teva connexió</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="463"/> + <location filename="../Controllers/MainController.cpp" line="594"/> <source>Certificate path length constraint exceeded</source> <translatorcomment>TMPFIX</translatorcomment> @@ -463,42 +537,62 @@ </message> <message> - <location filename="../Controllers/MainController.cpp" line="464"/> + <location filename="../Controllers/MainController.cpp" line="595"/> <source>Invalid certificate signature</source> <translation>Signatura de certificat no vàlida</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="465"/> + <location filename="../Controllers/MainController.cpp" line="596"/> <source>Invalid Certificate Authority</source> <translation>Entitat Certificadora no Vàlida</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="466"/> + <location filename="../Controllers/MainController.cpp" line="597"/> <source>Certificate does not match the host identity</source> <translation>El certificat no coincideix amb la identitat del servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="476"/> + <location filename="../Controllers/MainController.cpp" line="598"/> + <source>Certificate has been revoked</source> + <translation>El certificat ha estat revocat</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="599"/> + <source>Unable to determine certificate revocation state</source> + <translation>No es pot determinar l'estat de revocació del certificat</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="609"/> <source>Certificate error</source> <translation>Error de certificat</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="490"/> + <location filename="../Controllers/MainController.cpp" line="616"/> + <source>Re-enter credentials and retry</source> + <translation>Torna a introduir les credencials i prova de nou</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="629"/> + <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> + <translation>Desconnectat de %1%: %2%. Per tornar a connectar, desconnecta't i proporciona la teva contrasenya de nou.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="635"/> <source>Reconnect to %1% failed: %2%. Will retry in %3% seconds.</source> <translation>La reconnexió a %1% ha fallat: %2%. S'intentarà de nou d'aquí a %3% segons.</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="493"/> + <location filename="../Controllers/MainController.cpp" line="638"/> <source>Disconnected from %1%: %2%.</source> <translation>Desconnectat de %1%: %2%.</translation> </message> <message> - <location filename="../Controllers/Roster/RosterController.cpp" line="126"/> - <location filename="../Controllers/Roster/RosterController.cpp" line="152"/> - <location filename="../Controllers/Roster/RosterController.cpp" line="214"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="131"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="157"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="222"/> <source>Contacts</source> <translation>Contactes</translation> </message> <message> - <location filename="../Controllers/Roster/RosterController.cpp" line="251"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="263"/> <source>Server %1% rejected contact list change to item '%2%'</source> <translation>El servidor %1% ha rebutjat el canvi a l'element '%2%' de la llista de contactes</translation> @@ -531,4 +625,29 @@ <translation>Hi ha hagut un error publicant les dades del teu perfil</translation> </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="33"/> + <source>%1% (%2%)</source> + <translation>%1% (%2%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="38"/> + <source>%1% and %2% others (%3%)</source> + <translation>%1% i %2% més (%3%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="41"/> + <source>%1%, %2% (%3%)</source> + <translation>%1%, %2% (%3%)</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="63"/> + <source>TLS Client Certificate Selection</source> + <translation>Selecció de certificat de client TLS</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="64"/> + <source>Select a certificate to use for authentication</source> + <translation>Selecciona un certificat per utilitzar com a autenticació</translation> + </message> </context> <context> @@ -686,13 +805,110 @@ </message> <message> - <location filename="../QtUI/EventViewer/QtEvent.cpp" line="63"/> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="68"/> <source>%1 would like to add you to their contact list.</source> <translation>%1 vol afegir-te a la seva llista de contactes.</translation> </message> <message> - <location filename="../QtUI/EventViewer/QtEvent.cpp" line="66"/> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="71"/> <source>%1 would like to add you to their contact list, saying '%2'</source> <translation>%1 vol afegir-te a la seva llista de contactes, dient '%2'</translation> </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="81"/> + <source>%1 has invited you to enter the %2 room.</source> + <translation>%1 t'ha convidat a entrar a la sala %2.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="982"/> + <source>You've been invited to enter the %1 room.</source> + <translation>T'han convidat a entrar a la sala %1.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="984"/> + <source>Reason: %1</source> + <translation>Motiu: %1</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="987"/> + <source>This person may not have really sent this invitation!</source> + <translation>És possible que aquesta persona no hagi enviat realment aquesta invitació!</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="46"/> + <source>Direction</source> + <translatorcomment>Sentit?</translatorcomment> + <translation>Direcció</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="47"/> + <source>Other Party</source> + <translatorcomment>Sounds weird... "El teu contacte" o similar?</translatorcomment> + <translation type="unfinished">L'altra part</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="48"/> + <source>State</source> + <translation>Estat</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="49"/> + <source>Progress</source> + <translation>Progrés</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="50"/> + <source>Size</source> + <translation>Mida</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Incoming</source> + <translation>Entrant</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Outgoing</source> + <translation>Sortint</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="74"/> + <source>Waiting for start</source> + <translation>Esperant que comenci</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="76"/> + <source>Waiting for other side to accept</source> + <translation>Esperant a que l'altra banda accepti</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="78"/> + <source>Negotiating</source> + <translation>Negociant</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="80"/> + <source>Transferring</source> + <translation>Transferint</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="82"/> + <source>Finished</source> + <translation>Finalitzat</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="84"/> + <source>Failed</source> + <translation>Fallit</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="86"/> + <source>Canceled</source> + <translation>Cancel·lat</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="142"/> + <source>Connection Options</source> + <translation>Opcions de connexió</translation> + </message> </context> <context> @@ -880,5 +1096,5 @@ <location filename="../QtUI/QtStrings.h" line="66"/> <source>&Next</source> - <translation>&Seguent</translation> + <translation>&Següent</translation> </message> <message> @@ -918,41 +1134,90 @@ </context> <context> + <name>QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="14"/> + <source>Edit Affiliations</source> + <translation>Editar afiliacions</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="28"/> + <source>Affiliation:</source> + <translation>Afiliació:</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="36"/> + <source>Owner</source> + <translation>Propietari</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="41"/> + <source>Administrator</source> + <translation>Administrador</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="46"/> + <source>Member</source> + <translation>Membre</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="51"/> + <source>Outcast (Banned)</source> + <translation>Marginat (Vetat)</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="68"/> + <source>Add User</source> + <translation>Afegir usuari</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="75"/> + <source>Remove User</source> + <translation>Eliminar usuari</translation> + </message> +</context> +<context> <name>QtBookmarkDetailWindow</name> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="137"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="20"/> <source>Edit Bookmark Details</source> <translation>Editar Detalls de Marcador</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="138"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="40"/> <source>Bookmark Name:</source> <translation>Nom del Marcador:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="139"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="50"/> <source>Room Address:</source> <translation>Adreça de la sala:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="140"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="60"/> <source>Your Nickname:</source> <translation>El teu nick:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="141"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="70"/> <source>Room password:</source> <translation>Contrasenya de la sala:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="142"/> - <source>Join automatically</source> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="93"/> + <source>Enter automatically</source> <translation>Entrar automàticament</translation> </message> + <message> + <source>Join automatically</source> + <translation type="obsolete">Entrar automàticament</translation> + </message> +</context> +<context> + <name>QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.ui" line="14"/> + <source>Certificate Viewer</source> + <translation>Visualitzador de certificats</translation> + </message> </context> <context> @@ -972,4 +1237,126 @@ </context> <context> + <name>QtConnectionSettings</name> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="14"/> + <source>Connection Options</source> + <translation>Opcions de connexió</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="22"/> + <source>Connection Method:</source> + <translation>Mètode de connexió:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="36"/> + <source>Automatic</source> + <translation>Automàtic</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="41"/> + <source>Manual</source> + <translation>Manual</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="46"/> + <source>BOSH</source> + <translation>BOSH</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="99"/> + <source>Secure connection:</source> + <translation>Connexió segura:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="107"/> + <source>Never</source> + <translation>Mai</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="112"/> + <source>Encrypt when possible</source> + <translation>Encriptar quan sigui possible</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="117"/> + <source>Always encrypt</source> + <translation>Encriptar sempre</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="127"/> + <source>Allow Compression</source> + <translation>Permetre compressió</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="134"/> + <source>Allow sending password over insecure connection</source> + <translation>Permetre enviar contrasenya sobre connexió insegura</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="157"/> + <source>Manually select server</source> + <translation>Seleccionar servidor manualment</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="185"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="315"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="429"/> + <source>Hostname:</source> + <translation>Nom de servidor:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="208"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="338"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="452"/> + <source>Port:</source> + <translation>Port:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="230"/> + <source>Connection Proxy</source> + <translation>Proxy de connexió</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="238"/> + <source>Proxy type:</source> + <translation>Tipus de proxy:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="249"/> + <source>None</source> + <translation>Cap</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="254"/> + <source>Use system-configured proxy</source> + <translation>Utilitzar el proxy del sistema</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="259"/> + <source>SOCKS5</source> + <translation>SOCKS5</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="264"/> + <source>HTTP Connect</source> + <translation>HTTP Connect</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="287"/> + <source>Override system-configured proxy</source> + <translation>Substituir el proxy del sistema</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="382"/> + <source>BOSH URI:</source> + <translation>URI de BOSH:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="401"/> + <source>Manually select HTTP proxy</source> + <translation>Seleccionar proxy HTTP manualment</translation> + </message> +</context> +<context> <name>QtEventWindow</name> <message> @@ -979,33 +1366,69 @@ </context> <context> + <name>QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="14"/> + <source>History</source> + <translation>Historial</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="22"/> + <source>Search:</source> + <translation>Cercar:</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="42"/> + <source>Next</source> + <translation>Següent</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="52"/> + <source>Previous</source> + <translation>Anterior</translation> + </message> +</context> +<context> <name>QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="124"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="130"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="20"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="113"/> <source>Enter Room</source> <translation>Entrar a la sala</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="125"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="28"/> + <source>Room Address:</source> + <translation>Adreça de la sala:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="42"/> + <source>Your Nickname:</source> + <translation>El teu nick:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="59"/> + <source>Room Password:</source> + <translation>Contrasenya de la sala:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="71"/> + <source>Automatically configure newly created rooms</source> + <translation>Configurar automàticament sales de nova creació</translation> + </message> + <message> <source>Room:</source> - <translation>Sala:</translation> + <translation type="obsolete">Sala:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="126"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="35"/> <source>Search ...</source> <translation>Cercar ...</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="127"/> <source>Nickname:</source> - <translation>Nick:</translation> + <translation type="obsolete">Nick:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="129"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="106"/> <source>Enter automatically in future</source> <translation>Entrar automàticament en el futur</translation> @@ -1015,30 +1438,25 @@ <name>QtMUCSearchWindow</name> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="118"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="14"/> <source>Search Room</source> <translation>Cercar Sala</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="119"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="20"/> <source>Service:</source> <translation>Servei:</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="121"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="74"/> <source>Cancel</source> <translation>Cancel·lar</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="122"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="84"/> <source>OK</source> <translation>D'acord</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="123"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="96"/> <source>List rooms</source> <translation>Llistar sales</translation> @@ -1092,30 +1510,25 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>QtUserSearchFieldsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="119"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="27"/> <source>Nickname:</source> <translation>Nick:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="120"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="37"/> <source>First name:</source> <translation>Nom:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="47"/> <source>Last name:</source> <translation>Cognom:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="57"/> <source>E-Mail:</source> <translation>Correu electrònic:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="74"/> <source>Fetching search fields</source> <translation>Obtenint camps de cerca</translation> @@ -1125,30 +1538,25 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>QtUserSearchFirstPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="17"/> <source>Add a user</source> <translation>Afegir un usuari</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="20"/> <source>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</source> <translation>Afegir a un altre usuari a la teva llista de contactes. Si coneixes la seva adreça, pots introduir-la directament, o pots buscar a l'usuari.</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="35"/> <source>I know their address:</source> <translation>Conec la seva adreça:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="125"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="49"/> <source>I'd like to search my server</source> <translation>Vull buscar al meu servidor</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="126"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="73"/> <source>I'd like to search another server:</source> <translation>Vull buscar a un altre servidor:</translation> @@ -1168,4 +1576,12 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </context> <context> + <name>QtUserSearchResultsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui" line="27"/> + <source>No results.</source> + <translation>No hi ha resultats.</translation> + </message> +</context> +<context> <name>QtUserSearchWindow</name> <message> @@ -1201,6 +1617,5 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>QtUserSearchWizard</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchWizard.h" line="39"/> + <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui" line="14"/> <source>Find User</source> <translation>Buscar Usuari</translation> @@ -1210,9 +1625,19 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::ChatListModel</name> <message> - <location filename="../QtUI/ChatList/ChatListModel.cpp" line="15"/> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> <source>Bookmarked Rooms</source> <translatorcomment>TMPFIX?</translatorcomment> <translation>Sales en Marcadors</translation> </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="18"/> + <source>Recent Chats</source> + <translation>Converses recents</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="20"/> + <source>Opened Whiteboards</source> + <translation>Pissarres obertes</translation> + </message> </context> <context> @@ -1252,4 +1677,55 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </context> <context> + <name>Swift::QtAdHocCommandWindow</name> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="36"/> + <source>Cancel</source> + <translation>Cancel·lar</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="39"/> + <source>Back</source> + <translation>Retrocedir</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="42"/> + <source>Next</source> + <translation>Següent</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="45"/> + <source>Complete</source> + <translation>Complet</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="86"/> + <source>Error: %1</source> + <translation>Error: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="87"/> + <source>Warning: %1</source> + <translation>Avís: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="104"/> + <source>Error executing command</source> + <translation>Error executant l'ordre</translation> + </message> +</context> +<context> + <name>Swift::QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Add User</source> + <translation>Afegir usuari</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Added User's Address:</source> + <translation>Adreça de l'usuari afegit:</translation> + </message> +</context> +<context> <name>Swift::QtAvatarWidget</name> <message> @@ -1275,6 +1751,10 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <message> <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Image Files (*.png *.jpg *.jpeg *.gif)</source> + <translation>Fitxers d'imatge (*.png *.jpg *.jpeg *.gif)</translation> + </message> + <message> <source>Image Files (*.png *.jpg *.gif)</source> - <translation>Arxius d'imatge (*.png *.jpg *.gif)</translation> + <translation type="obsolete">Arxius d'imatge (*.png *.jpg *.gif)</translation> </message> <message> @@ -1307,57 +1787,352 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </context> <context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="95"/> + <source>General</source> + <translation>General</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="96"/> + <source>Valid From</source> + <translation>Vàlid des de</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="97"/> + <source>Valid To</source> + <translation>Vàlid fins a</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="98"/> + <source>Serial Number</source> + <translation>Número de sèrie</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="99"/> + <source>Version</source> + <translation>Versió</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="101"/> + <source>Subject</source> + <translation>Subjecte</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="102"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="124"/> + <source>Organization</source> + <translation>Organització</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="103"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="125"/> + <source>Common Name</source> + <translation>Nom comú</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="104"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="126"/> + <source>Locality</source> + <translation>Localitat</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="105"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="127"/> + <source>Organizational Unit</source> + <translation>Unitat organitzativa</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="106"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="128"/> + <source>Country</source> + <translation>País</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="107"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="129"/> + <source>State</source> + <translation>Estat</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="110"/> + <source>Alternate Subject Names</source> + <translation>Noms de subjecte alternatius</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="115"/> + <source>E-mail Address</source> + <translation>Adreça de correu electrònic</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="117"/> + <source>DNS Name</source> + <translation>Nom DNS</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="123"/> + <source>Issuer</source> + <translation>Emissor</translation> + </message> +</context> +<context> <name>Swift::QtChatListWindow</name> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="62"/> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="66"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="86"/> <source>Add New Bookmark</source> <translation>Afegir Nou Marcador</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="63"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="83"/> <source>Edit Bookmark</source> <translation>Editar Marcador</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="64"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> <source>Remove Bookmark</source> <translation>Eliminar Marcador</translation> </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="173"/> + <source>Clear recents</source> + <translation>Esborrar recents</translation> + </message> </context> <context> <name>Swift::QtChatView</name> <message> - <location filename="../QtUI/QtChatView.cpp" line="61"/> + <location filename="../QtUI/QtChatView.cpp" line="73"/> <source>Clear log</source> <translation>Esborrar text</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="62"/> + <location filename="../QtUI/QtChatView.cpp" line="74"/> <source>You are about to clear the contents of your chat log.</source> <translation>Estas a punt d'esborrar el contingut d'aquesta conversa.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="63"/> + <location filename="../QtUI/QtChatView.cpp" line="75"/> <source>Are you sure?</source> <translatorcomment>TMPFIX, genero?</translatorcomment> <translation>Estas segur?</translation> </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="219"/> + <source>%1 edited</source> + <translation>%1 editat</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="400"/> + <source>Waiting for other side to accept the transfer.</source> + <translation>Esperant a que l'altra banda accepti la transferència.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="401"/> + <location filename="../QtUI/QtChatView.cpp" line="406"/> + <location filename="../QtUI/QtChatView.cpp" line="417"/> + <source>Cancel</source> + <translation>Cancel·lar</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="405"/> + <source>Negotiating...</source> + <translation>Negociant...</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="420"/> + <source>Transfer has been canceled!</source> + <translation>La transferència ha estat cancel·lada!</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="424"/> + <source>Transfer completed successfully.</source> + <translation>La transferència s'ha completat amb èxit.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="427"/> + <source>Transfer failed.</source> + <translation>La transferència ha fallat.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="437"/> + <source>Started whiteboard chat</source> + <translation>S'ha començat una conversa de pissarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="438"/> + <source>Show whiteboard</source> + <translation>Mostrar pissarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="440"/> + <source>Whiteboard chat has been canceled</source> + <translation>La conversa de pissarra ha estat cancel·lada</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="442"/> + <source>Whiteboard chat request has been rejected</source> + <translation>La sol·licitud de conversa de pissarra ha estat rebutjada</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="451"/> + <source>Return to room</source> + <translation>Tornar a la sala</translation> + </message> </context> <context> <name>Swift::QtChatWindow</name> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="302"/> + <location filename="../QtUI/QtChatWindow.cpp" line="157"/> + <source>Correcting</source> + <translation>Corregint</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="269"/> + <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> + <translation>Aquesta conversa potser no sigui compatible amb la correcció de missatges. Si envies una correcció de tota manera, és possible que aparegui com un missatge duplicat</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="271"/> + <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> + <translation>Aquesta conversa no es compatible amb la correcció de missatges. Si envies una correcció de tota manera, es mostrarà com un missatge duplicat</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="546"/> <source>This message has not been received by your server yet.</source> <translation>Aquest missatge no ha sigut rebut pel teu servidor encara.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="304"/> + <location filename="../QtUI/QtChatWindow.cpp" line="553"/> <source>This message may not have been transmitted.</source> <translation>Es possible que aquest missatge no s'hagi transmès.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="324"/> + <location filename="../QtUI/QtChatWindow.cpp" line="562"/> + <source>The receipt for this message has been received.</source> + <translation>S'ha rebut la confirmació per aquest missatge.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="565"/> + <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> + <translatorcomment>Plural pain</translatorcomment> + <translation>Encara no s'ha rebut la confirmació per aquest missatge. És possible que els destinataris no l'hagin rebut.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="613"/> + <source>Send file</source> + <translation>Enviar fitxer</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="615"/> + <location filename="../QtUI/QtChatWindow.cpp" line="623"/> + <location filename="../QtUI/QtChatWindow.cpp" line="660"/> + <location filename="../QtUI/QtChatWindow.cpp" line="664"/> + <source>Cancel</source> + <translation>Cancel·lar</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="616"/> + <source>Set Description</source> + <translation>Establir descripció</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="617"/> + <source>Send</source> + <translation>Enviar</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="621"/> + <source>Receiving file</source> + <translation>Rebent fitxer</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="624"/> + <location filename="../QtUI/QtChatWindow.cpp" line="665"/> + <source>Accept</source> + <translation>Acceptar</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="659"/> + <source>Starting whiteboard chat</source> + <translation>Començant conversa de pissarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="663"/> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 vol començar una conversa de pissarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="700"/> + <source>File transfer description</source> + <translation>Descripció de la transferència de fitxer</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="701"/> + <source>Description:</source> + <translation>Descripció:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="715"/> + <source>Save File</source> + <translation>Guardar fitxer</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="908"/> + <source>Change subject…</source> + <translation>Canviar el tema...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="909"/> + <source>Configure room…</source> + <translation>Configurar sala...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="910"/> + <source>Edit affiliations…</source> + <translation>Editar afiliacions...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="911"/> + <source>Destroy room</source> + <translation>Destruir la sala</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="912"/> + <source>Invite person to this room…</source> + <translation>Convidar a una persona a aquesta sala...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="922"/> + <source>Change room subject</source> + <translation>Canviar el tema de la sala</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="922"/> + <source>New subject:</source> + <translation>Nou tema:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="940"/> + <source>Confirm room destruction</source> + <translation>Confirmar destrucció de la sala</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="941"/> + <source>Are you sure you want to destroy the room?</source> + <translation>Estàs segur de que vols destruir la sala?</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="942"/> + <source>This will destroy the room.</source> + <translation>Això destruirà la sala.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="993"/> + <source>Accept Invite</source> + <translation>Acceptar invitació</translation> + </message> + <message> <source>Couldn't send message: %1</source> - <translation>No s'ha pogut enviar el missatge: %1</translation> + <translation type="obsolete">No s'ha pogut enviar el missatge: %1</translation> </message> </context> @@ -1365,15 +2140,15 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtContactEditWidget</name> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="28"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="122"/> <source>Name:</source> <translation>Nom:</translation> </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="34"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="42"/> <source>Groups:</source> <translation>Grups:</translation> </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="56"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="65"/> <source>New Group:</source> <translation>Nou Grup:</translation> @@ -1383,30 +2158,30 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtContactEditWindow</name> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="26"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="28"/> <source>Edit contact</source> <translation>Editar contacte</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="41"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="43"/> <source>Remove contact</source> <translation>Eliminar contacte</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="44"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="46"/> <source>OK</source> <translation>D'acord</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="82"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="94"/> <source>Confirm contact deletion</source> <translation>Confirmar eliminació del contacte</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="83"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="95"/> <source>Are you sure you want to delete this contact?</source> <translation>Segur que vols eliminar aquest contacte?</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="84"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="96"/> <source>This will remove the contact '%1' from all groups they may be in.</source> <translation>Això eliminarà el contacte '%1' de tots els grups als que estigui.</translation> @@ -1416,5 +2191,5 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtEventWindow</name> <message> - <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="47"/> + <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="48"/> <source>Display Notice</source> <translation>Mostrar Avís</translation> @@ -1422,7 +2197,42 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </context> <context> + <name>Swift::QtFileTransferListWidget</name> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="39"/> + <source>Clear Finished Transfers</source> + <translation>Esborrar transferències finalitzades</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="44"/> + <source>File Transfer List</source> + <translation>Llista de transferència de fitxers</translation> + </message> +</context> +<context> + <name>Swift::QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.cpp" line="57"/> + <source>History</source> + <translation>Historial</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="25"/> + <source>Users to invite to this chat (one per line):</source> + <translatorcomment>conversa o sala?</translatorcomment> + <translation>Usuaris per convidar a aquesta conversa (un per línia):</translation> + </message> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="31"/> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>Si vols proporcionar un motiu per la invitació, el pots introduir aquí</translation> + </message> +</context> +<context> <name>Swift::QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.cpp" line="15"/> + <location filename="../QtUI/QtJoinMUCWindow.cpp" line="19"/> <source>someroom@rooms.example.com</source> <translation>algunasala@sales.exemple.com</translation> @@ -1432,53 +2242,53 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtLoginWindow</name> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="81"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="92"/> <source>User address:</source> <translation>Adreça d'usuari:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="86"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="87"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="98"/> <source>User address - looks like someuser@someserver.com</source> <translation>Adreça d'usuari - Semblant a usuari@algunservidor.com</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="91"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="102"/> <source>Example: alice@wonderland.lit</source> <translation>Exemple: alicia@paismeravelles.lit</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="108"/> <source>Password:</source> <translation>Contrasenya:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="118"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="119"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="129"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="130"/> <source>Click if you have a personal certificate used for login to the service.</source> <translation>Fes click si tens un certificat personal per connectar al servei.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="125"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="299"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="353"/> <source>Connect</source> <translation>Connectar</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="154"/> <source>Remember Password?</source> <translation>Recordar Contrasenya?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="138"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="156"/> <source>Login Automatically?</source> <translation>Connectar Automàticament?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="150"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="168"/> <source>&Swift</source> <translation>&Swift</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="152"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> <source>&General</source> <translatorcomment>TMPFIX, used where?</translatorcomment> @@ -1486,67 +2296,94 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="160"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="178"/> <source>&About %1</source> <translation>&Sobre %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="165"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="183"/> <source>&Show Debug Console</source> <translation>&Mostrar Consola de Depuració</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="169"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="188"/> + <source>Show &File Transfer Overview</source> + <translatorcomment>VERY long. Alternative "Mostrar llista de transferències"</translatorcomment> + <translation>Mostrar vista general de transferència de &fitxers</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="193"/> <source>&Play Sounds</source> <translation>&Reproduir Sons</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="175"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="199"/> <source>Display Pop-up &Notifications</source> <translation>Mostrar &Notificacions Emergents</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="190"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="211"/> <source>&Quit</source> <translation>&Sortir</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="205"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove profile</source> <translation>Eliminar perfil</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="205"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove the profile '%1'?</source> <translation>Eliminar el perfil '%1'?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="299"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="353"/> <source>Cancel</source> <translation>Cancel·lar</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="320"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="367"/> + <source>Confirm terms of use</source> + <translation>Confirmar condicions d'ús</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="410"/> <source>Select an authentication certificate</source> <translation>Selecciona un certificat d'autenticació</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="420"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="410"/> + <source>P12 files (*.cert *.p12 *.pfx);;All files (*.*)</source> + <translation>Fitxers P12 (*.cert *.p12 *.pfx);;Tots els fitxers (*.*)</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="523"/> <source>The certificate presented by the server is not valid.</source> <translation>El certificat presentat pel servidor no es vàlid.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="421"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="524"/> <source>Would you like to permanently trust this certificate? This must only be done if you know it is correct.</source> <translation>Vols confiar en aquest certificat de forma permanent? Això s'ha de fer només si saps que es correcte.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="423"/> <source>Subject: %1</source> - <translation>Subjecte: %1</translation> + <translation type="obsolete">Subjecte: %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="424"/> <source>SHA-1 Fingerprint: %1</source> - <translation>Empremta digital SHA-1: %1</translation> + <translation type="obsolete">Empremta digital SHA-1: %1</translation> + </message> +</context> +<context> + <name>Swift::QtMUCConfigurationWindow</name> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="34"/> + <source>Cancel</source> + <translation>Cancel·lar</translation> + </message> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="37"/> + <source>OK</source> + <translation>D'acord</translation> </message> </context> @@ -1563,16 +2400,16 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtMainWindow</name> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="64"/> + <location filename="../QtUI/QtMainWindow.cpp" line="79"/> <source>&Contacts</source> <translation>&Contactes</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="71"/> - <location filename="../QtUI/QtMainWindow.cpp" line="137"/> + <location filename="../QtUI/QtMainWindow.cpp" line="88"/> + <location filename="../QtUI/QtMainWindow.cpp" line="205"/> <source>&Notices</source> <translation>Av&isos</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="72"/> + <location filename="../QtUI/QtMainWindow.cpp" line="87"/> <source>C&hats</source> <translatorcomment>TMPFIX? Kev said "conversations" context</translatorcomment> @@ -1580,49 +2417,84 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="76"/> + <location filename="../QtUI/QtMainWindow.cpp" line="96"/> <source>&View</source> <translation>&Veure</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="78"/> + <location filename="../QtUI/QtMainWindow.cpp" line="98"/> <source>&Show offline contacts</source> <translation>&Mostrar contactes desconnectats</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="84"/> + <location filename="../QtUI/QtMainWindow.cpp" line="106"/> + <source>&Show Emoticons</source> + <translation>&Mostrar emoticones</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="120"/> <source>&Actions</source> <translation>&Accions</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>Editar &Perfil</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="122"/> + <source>Edit &Profile…</source> + <translation>Editar &Perfil…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <source>Enter &Room…</source> + <translation>Entrar a &Sala…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>Entrar a &Sala</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="129"/> + <source>&View History…</source> + <translation>&Veure historial...</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Afegir Contacte</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="133"/> + <source>&Add Contact…</source> + <translation>&Afegir Contacte…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Editar Contacte Sel·leccionat</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="136"/> + <source>&Edit Selected Contact…</source> + <translation>&Editar Contacte Sel·leccionat…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> - <translation>Començar &Conversa</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="140"/> + <source>Start &Chat…</source> + <translation>Començar &Conversa...</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="103"/> + <location filename="../QtUI/QtMainWindow.cpp" line="143"/> + <source>Run Server Command</source> + <translation>Executar ordre de servidor</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="146"/> <source>&Sign Out</source> <translation>&Desconnectar</translation> </message> <message> + <location filename="../QtUI/QtMainWindow.cpp" line="150"/> + <source>&Request Delivery Receipts</source> + <translation>&Demanar confirmació de lliurament</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="161"/> + <source>Collecting commands...</source> + <translation>Recopilant ordres...</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="216"/> + <source>&Chats</source> + <translation>C&onverses</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="339"/> + <source>No Available Commands</source> + <translation>No hi ha ordres disponibles</translation> + </message> + <message> <source>Notices</source> <translatorcomment>TMPFIX, used?</translatorcomment> @@ -1633,20 +2505,20 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtNameWidget</name> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="47"/> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> <source>Show Nickname</source> <translation>Mostrar Nick</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="47"/> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> <source>(No Nickname Set)</source> <translation>(Sense Nick Definit)</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="55"/> + <location filename="../QtUI/QtNameWidget.cpp" line="56"/> <source>Show Address</source> <translation>Mostrar Adreça</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="62"/> + <location filename="../QtUI/QtNameWidget.cpp" line="63"/> <source>Edit Profile</source> <translation>Editar Perfil</translation> @@ -1654,4 +2526,42 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </context> <context> + <name>Swift::QtOccupantListWidget</name> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="46"/> + <source>No actions for this user</source> + <translation>No hi ha accions per aquest usuari</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="55"/> + <source>Kick user</source> + <translation>Expulsar usuari</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="56"/> + <source>Kick and ban user</source> + <translation>Expulsar i vetar usuari</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="57"/> + <source>Make moderator</source> + <translation>Fer moderador</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="58"/> + <source>Make participant</source> + <translation>Fer participant</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="59"/> + <source>Remove voice</source> + <translation>Treure la veu</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="60"/> + <source>Add to contacts</source> + <translation>Afegir a contactes</translation> + </message> +</context> +<context> <name>Swift::QtProfileWindow</name> <message> @@ -1672,4 +2582,56 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </context> <context> + <name>Swift::QtRosterHeader</name> + <message> + <location filename="../QtUI/QtRosterHeader.cpp" line="59"/> + <source>Connection is secured</source> + <translation>La connexió és segura</translation> + </message> +</context> +<context> + <name>Swift::QtRosterWidget</name> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="58"/> + <source>Edit…</source> + <translation>Editar...</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="59"/> + <source>Remove</source> + <translation>Eliminar</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="63"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>Send File</source> + <translation>Enviar fitxer</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="69"/> + <source>Start Whiteboard Chat</source> + <translation>Començar conversa de pissarra</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>All Files (*);;</source> + <translation>Tots els fitxers (*);;</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="96"/> + <source>Rename</source> + <translation>Canviar el nom</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Rename group</source> + <translation>Canviar el nom al grup</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Enter a new name for group '%1':</source> + <translation>Introdueix un nom nou pel grup '%1':</translation> + </message> +</context> +<context> <name>Swift::QtStatusWidget</name> <message> @@ -1679,5 +2641,5 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </message> <message> - <location filename="../QtUI/QtStatusWidget.cpp" line="263"/> + <location filename="../QtUI/QtStatusWidget.cpp" line="261"/> <source>(No message)</source> <translation>(Sense missatge)</translation> @@ -1701,5 +2663,5 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <source>You have already replied to this request</source> <translatorcomment>TMPFIX, used where?</translatorcomment> - <translation>Ja has respost a aquesta petició</translation> + <translation>Ja has respost a aquesta sol·licitud</translation> </message> <message> @@ -1714,10 +2676,10 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </message> <message> - <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="32"/> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="33"/> <source>No</source> <translation>No</translation> </message> <message> - <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="34"/> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="35"/> <source>Defer</source> <translatorcomment>TMPFIX: deixar per despres?</translatorcomment> @@ -1728,27 +2690,22 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtTreeWidget</name> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="144"/> <source>Edit</source> - <translation>Editar</translation> + <translation type="obsolete">Editar</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="145"/> <source>Remove</source> - <translation>Eliminar</translation> + <translation type="obsolete">Eliminar</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="157"/> <source>Rename</source> - <translation>Renombrar</translation> + <translation type="obsolete">Renombrar</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="167"/> <source>Rename group</source> - <translation>Renombrar grup</translation> + <translation type="obsolete">Renombrar grup</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="167"/> <source>Enter a new name for group '%1':</source> - <translation>Introdueix un nom nou pel grup %1:</translation> + <translation type="obsolete">Introdueix un nom nou pel grup %1:</translation> </message> <message> @@ -1760,5 +2717,5 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtUserSearchDetailsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="17"/> + <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="22"/> <source>Please choose a name for the contact, and select the groups you want to add the contact to.</source> <translatorcomment>Somewhat free translation. TMPFIX?</translatorcomment> @@ -1787,35 +2744,35 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtUserSearchWindow</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="32"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> <source>Add Contact</source> <translation>Afegir Contacte</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="32"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> <source>Chat to User</source> <translation>Conversar amb Usuari</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="43"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="45"/> <source>alice@wonderland.lit</source> <translation>alicia@paismeravelles.lit</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="223"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> <source>How would you like to find the user to add?</source> <translation>Com vols buscar a l'usuari al que afegir?</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="226"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="299"/> <source>How would you like to find the user to chat to?</source> <translation>Com vols buscar a l'usuari amb el qual conversar?</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="251"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="326"/> <source>Error while searching</source> <translation>Error durant la cerca</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="257"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="332"/> <source>This server doesn't support searching for users.</source> <translation>Aquest servidor no suporta cerca d'usuaris.</translation> @@ -1825,8 +2782,26 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg <name>Swift::QtWebView</name> <message> - <location filename="../QtUI/QtWebView.cpp" line="61"/> + <location filename="../QtUI/QtWebView.cpp" line="66"/> <source>Clear</source> <translation>Esborrar text</translation> </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="67"/> + <source>Increase font size</source> + <translation>Augmentar la mida de la lletra</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="68"/> + <source>Decrease font size</source> + <translation>Reduir la mida de la lletra</translation> + </message> +</context> +<context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <location filename="../QtUI/Whiteboard/QtWhiteboardWindow.cpp" line="380"/> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>Tancar la finestra es equivalent a tancar la sessió. Estàs segur de que vols fer això?</translation> + </message> </context> <context> @@ -1853,10 +2828,10 @@ Si esculls ajornar aquesta elecció, se't preguntarà de nou la propera veg </message> <message> - <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="75"/> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="78"/> <source><!-- IN --></source> <translation><!-- ENTRANT --></translation> </message> <message> - <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="79"/> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="82"/> <source><!-- OUT --></source> <translation><!-- SORTINT --></translation> diff --git a/Swift/Translations/swift_cs.ts b/Swift/Translations/swift_cs.ts new file mode 100644 index 0000000..314cd60 --- /dev/null +++ b/Swift/Translations/swift_cs.ts @@ -0,0 +1,2867 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="cs_CZ"> +<defaultcodec>UTF-8</defaultcodec> +<context> + <name></name> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> + <source>Starting chat with %1% in chatroom %2%</source> + <translation>Začíná chat s %1% v místnosti %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="62"/> + <source>Starting chat with %1% - %2%</source> + <translation>Začíná chat s %1% - %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> + <source>This chat doesn't support delivery receipts.</source> + <translation>Tento chat nepodporuje doručenky.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="204"/> + <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> + <translation>Tento chat nemusí podporovat doručenky. Je možné, že k odeslaným zprávam nedostanete potvrzení o doručení.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="216"/> + <location filename="../Controllers/FileTransfer/FileTransferController.cpp" line="42"/> + <source>me</source> + <translation>já</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="324"/> + <source>%1% has gone offline</source> + <translation>%1% je odteď offline</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="328"/> + <source>%1% has become available</source> + <translation>%1% je odteď online</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="330"/> + <source>%1% has gone away</source> + <translation>%1% je odteď pryč</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="332"/> + <source>%1% is now busy</source> + <translation>%1% odteď nemá čas</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="74"/> + <source>The day is now %1%</source> + <translation>Nyní je %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <source>Couldn't send message: %1%</source> + <translation>Zprávu sa nepodařilo odeslat: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> + <source>Error sending message</source> + <translation>Chyba při posílání zprávy</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="275"/> + <source>Bad request</source> + <translation>Nesprávný požadavek</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="276"/> + <source>Conflict</source> + <translation>Konflikt</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="277"/> + <source>This feature is not implemented</source> + <translation>Tato vlastnost není implementovaná</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="278"/> + <source>Forbidden</source> + <translation>Zakázané</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="279"/> + <source>Recipient can no longer be contacted</source> + <translation>Příjemce už není možné kontaktovat</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="280"/> + <source>Internal server error</source> + <translation>Vnitřní chyba serveru</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="281"/> + <source>Item not found</source> + <translation>Položka nebyla nalezena</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="282"/> + <source>JID Malformed</source> + <translation>Nesprávný tvar JID</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="283"/> + <source>Message was rejected</source> + <translation>Zpráva byla odmítnuta</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="284"/> + <source>Not allowed</source> + <translation>Nepovolené</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="285"/> + <source>Not authorized</source> + <translation>Neautorizované</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="286"/> + <source>Payment is required</source> + <translation>Vyžadována platba</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="287"/> + <source>Recipient is unavailable</source> + <translation>Příjemce je nedostupný</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="288"/> + <source>Redirect</source> + <translation>Přesměrování</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="289"/> + <source>Registration required</source> + <translation>Vyžadována registrace</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="290"/> + <source>Recipient's server not found</source> + <translation>Server příjemce nenalezen</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="291"/> + <source>Remote server timeout</source> + <translatorcomment>správny význam?</translatorcomment> + <translation>Vypršel čas pro připojení ke vzdálenému serveru</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="292"/> + <source>The server is low on resources</source> + <translation>Server má nedostatek zdrojů</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="293"/> + <source>The service is unavailable</source> + <translation>Služba není dostupná</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="294"/> + <source>A subscription is required</source> + <translation>Vyžadováno přihlášení k odběru</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="295"/> + <source>Undefined condition</source> + <translation>Nedefinovaná podmínka</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="296"/> + <source>Unexpected request</source> + <translation>Neočekávaný požadavek</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> + <source>Room %1% is not responding. This operation may never complete.</source> + <translation>Místnost %1% neodpovídá. Tato operace nemusí nikdy skončit.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="233"/> + <source>Unable to enter this room</source> + <translation>Nepodařilo sa vstoupit do místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="239"/> + <source>Unable to enter this room as %1%, retrying as %2%</source> + <translation>Nepodařilo se vstoupit do místnosti jako %1%, znovu opakováno jako %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> + <source>No nickname specified</source> + <translation>Nezadána přezdívka</translation> + </message> + <message> + <source>A password needed</source> + <translation type="obsolete">Je vyžadováno heslo</translation> + </message> + <message> + <source>A password is needed</source> + <translation type="obsolete">Je vyžadováno heslo</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="251"/> + <source>Only members may enter</source> + <translation>Vstoupit mohou pouze členové</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="255"/> + <source>You are banned from the room</source> + <translation>Máte zakázáno vstoupit do místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="259"/> + <source>The room is full</source> + <translation>Místnost je plná</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="263"/> + <source>The room does not exist</source> + <translation>Místnost neexistuje</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="269"/> + <source>Couldn't join room: %1%.</source> + <translation>Nepodařilo se vstoupit do místnosti: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="281"/> + <source>You have entered room %1% as %2%.</source> + <translation>Vstoupil jste do místnosti %1% jako %2%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="332"/> + <source>%1% has entered the room as a %2%.</source> + <translation>%1% vstoupil do místnosti jako %2%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="335"/> + <source>%1% has entered the room.</source> + <translation>%1% vstoupil do místnosti.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="384"/> + <source>moderator</source> + <translation>moderátor</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="385"/> + <source>participant</source> + <translation>účastník</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="386"/> + <source>visitor</source> + <translation>návštěvník</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="436"/> + <source>The room subject is now: %1%</source> + <translation>Předmět místnosti je: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="470"/> + <source>%1% is now a %2%</source> + <translation>%1% je nyní %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="486"/> + <source>Moderators</source> + <translation>Moderátoři</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> + <source>Participants</source> + <translation>Účastníci</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="488"/> + <source>Visitors</source> + <translation>Návštěvníci</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="489"/> + <source>Occupants</source> + <translation>Ostatní uživatelé</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> + <source>Trying to enter room %1%</source> + <translation>Vstupování do místnonsti %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="544"/> + <source>%1% has left the room %2%</source> + <translation>%1% opustil místnost %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="548"/> + <source>You have been kicked out of the room</source> + <translation>Byl jste vyhozen z místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="549"/> + <source>You have been banned from the room</source> + <translation>Byl vám zakázán vstup do místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="550"/> + <source>You are no longer a member of the room and have been removed</source> + <translation>Už nejste členem místnosti a vaše členství bylo odebráno</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="551"/> + <source>The room has been destroyed</source> + <translation>Místnost byla odstraněna</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="667"/> + <source>%1% has left the room</source> + <translation>%1% opustil místnost</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="553"/> + <source>You have left the room</source> + <translation>Opustili jste místnost</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="247"/> + <source>The correct room password is needed</source> + <translation>Je požadováno heslo pro přístup do místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="632"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="696"/> + <source> and </source> + <translation> a </translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="656"/> + <source>%1% have entered the room</source> + <translation>%1% vstoupil do místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="659"/> + <source>%1% has entered the room</source> + <translation>%1% vstoupil do místnosti</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="664"/> + <source>%1% have left the room</source> + <translation>%1% opustil místnost</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="672"/> + <source>%1% have entered then left the room</source> + <translation>%1% vstoupil a potom opustil místnost</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="675"/> + <source>%1% has entered then left the room</source> + <translation>%1% vstoupil a potom opustil místnost</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="680"/> + <source>%1% have left then returned to the room</source> + <translation>%1% opustil místnost a potom se vrátil</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="683"/> + <source>%1% has left then returned to the room</source> + <translation>%1% opustil místnost a potom se vrátil</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="719"/> + <source>Room configuration failed: %1%.</source> + <translation>Nepodařilo se nastavit místnost: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="725"/> + <source>Occupant role change failed: %1%.</source> + <translation>Nepodařilo se změnit roli pro uživatele: %1%.</translation> + </message> + <message> + <location filename="../Controllers/EventNotifier.cpp" line="59"/> + <source>%1% wants to add you to his/her contact list</source> + <translation>%1% si vás chce přidat do seznamu kontaktů</translation> + </message> + <message> + <location filename="../Controllers/EventNotifier.cpp" line="63"/> + <source>Error</source> + <translation>Chyba</translation> + </message> + <message> + <location filename="../Controllers/EventNotifier.cpp" line="67"/> + <source>%1% has invited you to enter the %2% room</source> + <translation>%1% vás pozval do místnosti %2%</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="466"/> + <source>User address invalid. User address should be of the form 'alice@wonderland.lit'</source> + <translation>Adresa uživatele je neplatná. Adresa uživatele by měla být ve tvaru 'alice@wonderland.lit'</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="567"/> + <source>Unknown Error</source> + <translation>Neznámá chyba</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="568"/> + <source>Unable to find server</source> + <translation>Nepodařilo se najít server</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="569"/> + <source>Error connecting to server</source> + <translation>Chyba při připojení k serveru</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="570"/> + <source>Error while receiving server data</source> + <translation>Chyba při přijímání dat ze serveru</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="571"/> + <source>Error while sending data to the server</source> + <translation>Chyba při odesílání dat na server</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="572"/> + <source>Error parsing server data</source> + <translation>Chyba při zpracování dat ze serveru</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="573"/> + <source>Login/password invalid</source> + <translation>Přihlašovací jméno nebo heslo je neplatné</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="574"/> + <source>Error while compressing stream</source> + <translation>Chyba při kompresi dat</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="575"/> + <source>Server verification failed</source> + <translation>Ověření serveru selhalo</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="576"/> + <source>Authentication mechanisms not supported</source> + <translation>Ověřovací mechanismus není podporován</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="577"/> + <source>Unexpected response</source> + <translation>Neočekávaná odpověď</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="578"/> + <source>Error binding resource</source> + <translation>Chyba při napojení zdroje</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="579"/> + <source>Error starting session</source> + <translation>Chyba při zahajování sezení</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="580"/> + <source>Stream error</source> + <translation>Chyba dat</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="581"/> + <source>Encryption error</source> + <translation>Chyba šifrování</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="582"/> + <source>Error loading certificate (Invalid password?)</source> + <translation>Chyba při načítání certifikátu (nesprávné heslo?)</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="583"/> + <source>Certificate not authorized</source> + <translation>Neautorizovaný certifikát</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="584"/> + <source>Certificate card removed</source> + <translation>Karta s certifikátem odstraněna</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="586"/> + <source>Unknown certificate</source> + <translation>Neznámý certifikát</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="587"/> + <source>Certificate has expired</source> + <translation>Certifikát expiroval</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="588"/> + <source>Certificate is not yet valid</source> + <translation>Certifikát ještě není platný</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="589"/> + <source>Certificate is self-signed</source> + <translation>Certifikát je podepsaný sám sebou</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="590"/> + <source>Certificate has been rejected</source> + <translation>Certifikát byl odmítnut</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="591"/> + <source>Certificate is not trusted</source> + <translation>Certifikát není důvěryhodný</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="592"/> + <source>Certificate cannot be used for encrypting your connection</source> + <translation>Certifikát nemůže být použit pro šifrování spojení</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="593"/> + <source>Certificate path length constraint exceeded</source> + <translation>Překročena délka cesty certifikátu</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="594"/> + <source>Invalid certificate signature</source> + <translation>Neplatný podpis certifikátu</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="595"/> + <source>Invalid Certificate Authority</source> + <translation>Neplatná certifikační autorita</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="596"/> + <source>Certificate does not match the host identity</source> + <translation>Certifikát sa neshoduje s identitou hostitele</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="597"/> + <source>Certificate has been revoked</source> + <translation>Certifikát byl odvolán</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="598"/> + <source>Unable to determine certificate revocation state</source> + <translation>Nepodařilo se zjistit stav odvolání certifikátu</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="608"/> + <source>Certificate error</source> + <translation>Chyba certifikátu</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="615"/> + <source>Re-enter credentials and retry</source> + <translation>Zadejte znovu přihlašovací údaje a akci opakujte</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="628"/> + <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> + <translation>Odpojeno od %1%: %2%. Pro znovu připojení se odhlašte a zadejte znovu vaše heslo.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="634"/> + <source>Reconnect to %1% failed: %2%. Will retry in %3% seconds.</source> + <translation>Opětovné přihlášení k %1% selhalo: %2%. Další pokus za %3% sekund.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="637"/> + <source>Disconnected from %1%: %2%.</source> + <translation>Odpojeno od %1%: %2%.</translation> + </message> + <message> + <location filename="../Controllers/Roster/RosterController.cpp" line="131"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="157"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="222"/> + <source>Contacts</source> + <translation>Kontakty</translation> + </message> + <message> + <location filename="../Controllers/Roster/RosterController.cpp" line="263"/> + <source>Server %1% rejected contact list change to item '%2%'</source> + <translation>Server %1% odmítnul změnu položky '%2%' v seznamu kontaktů</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="15"/> + <location filename="../Controllers/StatusUtil.cpp" line="16"/> + <source>Available</source> + <translation>Jsem tu</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="17"/> + <location filename="../Controllers/StatusUtil.cpp" line="18"/> + <source>Away</source> + <translation>Jsem pryč</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="19"/> + <source>Busy</source> + <translation>Nemám čas</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="20"/> + <source>Offline</source> + <translation>Offline</translation> + </message> + <message> + <location filename="../Controllers/ProfileController.cpp" line="62"/> + <source>There was an error publishing your profile data</source> + <translation>Při zveřejňování údajů vašeho profilu nastala chyba</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="33"/> + <source>%1% (%2%)</source> + <translation>%1% (%2%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="38"/> + <source>%1% and %2% others (%3%)</source> + <translation>%1% a %2% ostatní (%3%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="41"/> + <source>%1%, %2% (%3%)</source> + <translation>%1%, %2% (%3%)</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="63"/> + <source>TLS Client Certificate Selection</source> + <translation>Výběr TLS klientského certifikátu</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="64"/> + <source>Select a certificate to use for authentication</source> + <translation>Vyberte certifikát, který chcete použít pro ověření</translation> + </message> +</context> +<context> + <name>CloseButton</name> + <message> + <location filename="../QtUI/QtStrings.h" line="17"/> + <source>Close Tab</source> + <translation>Zavřít kartu</translation> + </message> +</context> +<context> + <name>MAC_APPLICATION_MENU</name> + <message> + <location filename="../QtUI/QtStrings.h" line="79"/> + <source>Services</source> + <translation>Služby</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="80"/> + <source>Hide %1</source> + <translation>Skrýt %1</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="81"/> + <source>Hide Others</source> + <translation>Skrýt ostatní</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="82"/> + <source>Show All</source> + <translation>Zobrazit všechny</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="83"/> + <source>Preferences...</source> + <translation>Nastavení...</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="84"/> + <source>Quit %1</source> + <translation>Ukončit %1</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="85"/> + <source>About %1</source> + <translation>O programu %1</translation> + </message> +</context> +<context> + <name>QApplication</name> + <message> + <location filename="../QtUI/QtStrings.h" line="19"/> + <source>QT_LAYOUT_DIRECTION</source> + <comment>Translate this to LTR for left-to-right or RTL for right-to-left languages</comment> + <translation>LTR</translation> + </message> +</context> +<context> + <name>QDialogButtonBox</name> + <message> + <location filename="../QtUI/QtStrings.h" line="69"/> + <source>&Yes</source> + <translation>An&o</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="70"/> + <source>&No</source> + <translation>&Ne</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="71"/> + <source>&OK</source> + <translation>&Ok</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="72"/> + <source>OK</source> + <translation>Ok</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="73"/> + <source>&Cancel</source> + <translation>&Storno</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="74"/> + <source>Cancel</source> + <translation>Storno</translation> + </message> +</context> +<context> + <name>QLineEdit</name> + <message> + <location filename="../QtUI/QtStrings.h" line="21"/> + <source>Select All</source> + <translation>Vybrat vše</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="22"/> + <source>&Undo</source> + <translation>&Zpět</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="23"/> + <source>&Redo</source> + <translation>&Opakovat</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="24"/> + <source>Cu&t</source> + <translation>Vyjmou&t</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="25"/> + <source>&Copy</source> + <translation>&Kopírovat</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="26"/> + <source>&Paste</source> + <translation>&Vložit</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="27"/> + <source>Delete</source> + <translation>Smazat</translation> + </message> +</context> +<context> + <name>QMessageBox</name> + <message> + <location filename="../QtUI/QtStrings.h" line="76"/> + <source>Show Details...</source> + <translation>Zobrazit detaily...</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="77"/> + <source>Hide Details...</source> + <translation>Skrýt detaily...</translation> + </message> +</context> +<context> + <name>QObject</name> + <message> + <location filename="../QtUI/MUCSearch/MUCSearchEmptyItem.cpp" line="25"/> + <source>No rooms found</source> + <translation>Žádné místnosti nenalezeny</translation> + </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="68"/> + <source>%1 would like to add you to their contact list.</source> + <translation>%1 si vás chce přidat do seznamu kontaktů.</translation> + </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="71"/> + <source>%1 would like to add you to their contact list, saying '%2'</source> + <translation>%1 si vás chce přidat do seznamu kontaktů: '%2'</translation> + </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="81"/> + <source>%1 has invited you to enter the %2 room.</source> + <translation>%1 vás pozval do místnosti %2.</translation> + </message> + <message> + <source>Systray</source> + <translation type="obsolete">Oznamovací oblast</translation> + </message> + <message> + <source>No system tray</source> + <translation type="obsolete">Žádná oznamovací oblast</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="980"/> + <source>You've been invited to enter the %1 room.</source> + <translation>Dostali jste pozvánku do místnosti %1.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="982"/> + <source>Reason: %1</source> + <translation>Důvod: %1</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="985"/> + <source>This person may not have really sent this invitation!</source> + <translation>Tento uživatel nemusel doopravdy odeslat tuto pozvánku!</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="46"/> + <source>Direction</source> + <translation>Směr</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="47"/> + <source>Other Party</source> + <translation>Protistrana</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="48"/> + <source>State</source> + <translation>Stav</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="49"/> + <source>Progress</source> + <translation>Průběh</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="50"/> + <source>Size</source> + <translation>Velikost</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Incoming</source> + <translation>Přicházející</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Outgoing</source> + <translation>Odcházející</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="74"/> + <source>Waiting for start</source> + <translation>Čekání na spuštění</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="76"/> + <source>Waiting for other side to accept</source> + <translation>Čekání na přijetí protistranou</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="78"/> + <source>Negotiating</source> + <translation>Vyjednávání</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="80"/> + <source>Transferring</source> + <translation>Přenášení</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="82"/> + <source>Finished</source> + <translation>Dokončeno</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="84"/> + <source>Failed</source> + <translation>Chyba</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="86"/> + <source>Canceled</source> + <translation>Zrušeno</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="142"/> + <source>Connection Options</source> + <translation>Možnosti připojení</translation> + </message> +</context> +<context> + <name>QScrollBar</name> + <message> + <location filename="../QtUI/QtStrings.h" line="29"/> + <source>Scroll here</source> + <translation>Posunout sem</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="30"/> + <source>Top</source> + <translation>Nahoru</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="31"/> + <source>Bottom</source> + <translation>Dolů</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="32"/> + <source>Page up</source> + <translation>O stránku výše</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="33"/> + <source>Page down</source> + <translation>O stránku níže</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="34"/> + <source>Scroll up</source> + <translation>Posunout nahoru</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="35"/> + <source>Scroll down</source> + <translation>Posunout dolu</translation> + </message> +</context> +<context> + <name>QTextControl</name> + <message> + <location filename="../QtUI/QtStrings.h" line="37"/> + <source>Select All</source> + <translation>Vybrat vše</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="38"/> + <source>&Copy</source> + <translation>&Kopírovat</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="39"/> + <source>&Undo</source> + <translation>&Zpět</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="40"/> + <source>&Redo</source> + <translation>&Vpřed</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="41"/> + <source>Cu&t</source> + <translation>Vyjmou&t</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="42"/> + <source>&Paste</source> + <translation>&Vložit</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="43"/> + <source>Delete</source> + <translation>Smazat</translation> + </message> +</context> +<context> + <name>QWebPage</name> + <message> + <location filename="../QtUI/QtStrings.h" line="45"/> + <source>Copy Link</source> + <translation>Kopírovat odkaz</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="46"/> + <source>Copy</source> + <translation>Kopírovat</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="47"/> + <source>Copy Image</source> + <translation>Kopírovat obrázek</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="48"/> + <source>Scroll here</source> + <translation>Posunout sem</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="49"/> + <source>Top</source> + <translation>Nahoru</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="50"/> + <source>Bottom</source> + <translation>Dolů</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="51"/> + <source>Page up</source> + <translation>O stránku výše</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="52"/> + <source>Page down</source> + <translation>O stránku níže</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="53"/> + <source>Scroll up</source> + <translation>Posunout nahoru</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="54"/> + <source>Scroll down</source> + <translation>Posunout dolů</translation> + </message> +</context> +<context> + <name>QWizard</name> + <message> + <location filename="../QtUI/QtStrings.h" line="56"/> + <source>< &Back</source> + <translation>< &Zpět</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="57"/> + <source>&Finish</source> + <translation>&Dokončit</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="58"/> + <source>&Help</source> + <translation>&Nápověda</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="59"/> + <source>Go Back</source> + <translation>Přejít zpět</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="60"/> + <source>Continue</source> + <translation>Pokračovat</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="61"/> + <source>Commit</source> + <translation>Dokončit</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="62"/> + <source>Done</source> + <translation>Hotovo</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="63"/> + <source>Quit</source> + <translation>Ukončit</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="64"/> + <source>Help</source> + <translation>Nápověda</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="65"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="66"/> + <source>&Next</source> + <translation>D&alší</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="67"/> + <source>&Next ></source> + <translation>D&lší ></translation> + </message> +</context> +<context> + <name>QtAffiliationEditor</name> + <message> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="123"/> + <source>Dialog</source> + <translation>Dialog</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="14"/> + <source>Edit Affiliations</source> + <translation>Upravit členství</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="28"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="125"/> + <source>Affiliation:</source> + <translation>Členství:</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="36"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="128"/> + <source>Owner</source> + <translation>Majitel</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="41"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="129"/> + <source>Administrator</source> + <translation>Správce</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="46"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="130"/> + <source>Member</source> + <translation>Člen</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="51"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="131"/> + <source>Outcast (Banned)</source> + <translation>Vyvrhel (zakázaný)</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="68"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="133"/> + <source>Add User</source> + <translation>Přidat uživatele</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="75"/> + <location filename="../QtUI/ui_QtAffiliationEditor.h" line="134"/> + <source>Remove User</source> + <translation>Odstranit uživatele</translation> + </message> +</context> +<context> + <name>QtBookmarkDetailWindow</name> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="20"/> + <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="137"/> + <source>Edit Bookmark Details</source> + <translation>Upravit detail záložky</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="40"/> + <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="138"/> + <source>Bookmark Name:</source> + <translation>Název záložky:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="50"/> + <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="139"/> + <source>Room Address:</source> + <translation>Adresa místnosti:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="60"/> + <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="140"/> + <source>Your Nickname:</source> + <translation>Vaše přezdívka:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="70"/> + <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="141"/> + <source>Room password:</source> + <translation>Heslo pro místnost:</translation> + </message> + <message> + <source>Join automatically</source> + <translation type="obsolete">Vstoupit automaticky</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="93"/> + <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="142"/> + <source>Enter automatically</source> + <translation>Vstoupit automaticky</translation> + </message> +</context> +<context> + <name>QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.ui" line="14"/> + <location filename="../QtUI/ui_QtCertificateViewerDialog.h" line="99"/> + <source>Certificate Viewer</source> + <translation>Prohlížeč certifikátu</translation> + </message> +</context> +<context> + <name>QtConnectionSettings</name> + <message> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="383"/> + <source>Dialog</source> + <translation>Dialog</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="14"/> + <source>Connection Options</source> + <translation>Možnosti připojení</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="22"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="384"/> + <source>Connection Method:</source> + <translation>Způsob připojeni:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="36"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="387"/> + <source>Automatic</source> + <translation>Automaticky</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="41"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="388"/> + <source>Manual</source> + <translation>Manuálně</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="46"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="389"/> + <source>BOSH</source> + <translation>BOSH</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="99"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="391"/> + <source>Secure connection:</source> + <translation>Zabezpečení spojení:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="107"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="394"/> + <source>Never</source> + <translation>Bez zabezpečení</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="112"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="395"/> + <source>Encrypt when possible</source> + <translation>Šifrovat pokud je to možné</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="117"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="396"/> + <source>Always encrypt</source> + <translation>Vždy šifrovat</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="127"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="398"/> + <source>Allow Compression</source> + <translation>Povolit kompresi</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="134"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="399"/> + <source>Allow sending password over insecure connection</source> + <translation>Povolit odesílání hesla přes nezabezpeřené spojení</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="157"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="400"/> + <source>Manually select server</source> + <translation>Manuálně vybrat server</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="185"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="315"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="429"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="401"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="413"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="417"/> + <source>Hostname:</source> + <translation>Název serveru:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="208"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="338"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="452"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="402"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="414"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="418"/> + <source>Port:</source> + <translation>Port:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="230"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="403"/> + <source>Connection Proxy</source> + <translation>Proxy server</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="238"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="404"/> + <source>Proxy type:</source> + <translation>Typ proxy serveru:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="249"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="407"/> + <source>None</source> + <translation>Žádný</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="254"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="408"/> + <source>Use system-configured proxy</source> + <translation>Podle nastavení systému</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="259"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="409"/> + <source>SOCKS5</source> + <translation>SOCKS5</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="264"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="410"/> + <source>HTTP Connect</source> + <translation>HTTP Connect</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="287"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="412"/> + <source>Override system-configured proxy</source> + <translation>Přepsat systémové nastavení serveru</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="382"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="415"/> + <source>BOSH URI:</source> + <translation>BOSH URI:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="401"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="416"/> + <source>Manually select HTTP proxy</source> + <translation>Ručně zadat adresu HTTP proxy</translation> + </message> +</context> +<context> + <name>QtHistoryWindow</name> + <message> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="133"/> + <source>Form</source> + <translation>Formulář</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="14"/> + <source>History</source> + <translation>Historie</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="22"/> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="134"/> + <source>Search:</source> + <translation>Hledat:</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="42"/> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="135"/> + <source>Next</source> + <translation>Další</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="52"/> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="136"/> + <source>Previous</source> + <translation>Zpět</translation> + </message> +</context> +<context> + <name>QtJoinMUCWindow</name> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="20"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="113"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="142"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="150"/> + <source>Enter Room</source> + <translation>Vstoupit do místnosti</translation> + </message> + <message> + <source>Room:</source> + <translation type="obsolete">Místnost:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="35"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="144"/> + <source>Search ...</source> + <translation>Hledat ...</translation> + </message> + <message> + <source>Nickname:</source> + <translation type="obsolete">Přezdívka:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="106"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="149"/> + <source>Enter automatically in future</source> + <translation>Příště vstoupit automaticky</translation> + </message> + <message> + <source>Password:</source> + <translation type="obsolete">Heslo:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="71"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="148"/> + <source>Automatically configure newly created rooms</source> + <translation>Nové místnosti nastavit automaticky</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="28"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="143"/> + <source>Room Address:</source> + <translation>Adresa místnosti:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="42"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="145"/> + <source>Your Nickname:</source> + <translation>Vaša přezdívka:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="59"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="147"/> + <source>Room Password:</source> + <translation>Heslo místnosti:</translation> + </message> +</context> +<context> + <name>QtMUCSearchWindow</name> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="14"/> + <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="118"/> + <source>Search Room</source> + <translation>Vyhledat místnost</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="20"/> + <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="119"/> + <source>Service:</source> + <translation>Služba:</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="74"/> + <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="121"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="84"/> + <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="122"/> + <source>OK</source> + <translation>Ok</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="96"/> + <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="123"/> + <source>List rooms</source> + <translation>Seznam místností</translation> + </message> +</context> +<context> + <name>QtUserSearchFieldsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="27"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="131"/> + <source>Nickname:</source> + <translation>Přezdívka:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="37"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="132"/> + <source>First name:</source> + <translation>Jméno:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="47"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="133"/> + <source>Last name:</source> + <translation>Příjmení:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="57"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="134"/> + <source>E-Mail:</source> + <translation>E-mail:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="74"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="136"/> + <source>Fetching search fields</source> + <translation>Probíhá načítání polí pro vyhledávání</translation> + </message> +</context> +<context> + <name>QtUserSearchFirstPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="17"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="132"/> + <source>Add a user</source> + <translation>Přidat uživatele</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="20"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="133"/> + <source>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</source> + <translation>Přidat dalšího uživatele do seznamu kontaktů. Pokud znáte jeho adresu, můžete ho zadat přímo nebo jej můžete vyhledat.</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="35"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="135"/> + <source>I know their address:</source> + <translation>Znám jejich adresu:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="49"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="136"/> + <source>I'd like to search my server</source> + <translation>Prohledat můj server</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="73"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="137"/> + <source>I'd like to search another server:</source> + <translation>Prohledat jiný server:</translation> + </message> +</context> +<context> + <name>QtUserSearchResultsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui" line="27"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchResultsPage.h" line="59"/> + <source>No results.</source> + <translation>Žádné výsledky.</translation> + </message> +</context> +<context> + <name>QtUserSearchWizard</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui" line="14"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchWizard.h" line="39"/> + <source>Find User</source> + <translation>Hledat uživatele</translation> + </message> +</context> +<context> + <name>Swift::ChatListModel</name> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> + <source>Bookmarked Rooms</source> + <translation>Zapamatované místnosti</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="18"/> + <source>Recent Chats</source> + <translation>Poslední zprávy</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="20"/> + <source>Opened Whiteboards</source> + <translation>Otevřené whiteboard chat sezení</translation> + </message> +</context> +<context> + <name>Swift::FileTransferController</name> + <message> + <source>me</source> + <translation type="obsolete">já</translation> + </message> +</context> +<context> + <name>Swift::QtAboutWidget</name> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="23"/> + <source>About %1</source> + <translation>O programu %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="40"/> + <source>Version %1</source> + <translation>Verze %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="43"/> + <source>Built with Qt %1</source> + <translation>Sestavené pomocí Qt %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="44"/> + <source>Running with Qt %1</source> + <translation>Beží na Qt %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="50"/> + <source>Using the English translation by +%1</source> + <translation>Do češtiny přeložil +%1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="54"/> + <source>View License</source> + <translation>Zobrazit licenci</translation> + </message> +</context> +<context> + <name>Swift::QtAdHocCommandWindow</name> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="36"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="39"/> + <source>Back</source> + <translation>Zpět</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="42"/> + <source>Next</source> + <translation>Další</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="45"/> + <source>Complete</source> + <translation>Hotovo</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="86"/> + <source>Error: %1</source> + <translation>Chyba: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="87"/> + <source>Warning: %1</source> + <translation>Upozornění: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="104"/> + <source>Error executing command</source> + <translation>Chyba při vykonávání příkazu</translation> + </message> +</context> +<context> + <name>Swift::QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Add User</source> + <translation>Přidat uživatele</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Added User's Address:</source> + <translation>Přidána adresa uživatele:</translation> + </message> +</context> +<context> + <name>Swift::QtAvatarWidget</name> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="61"/> + <source>No picture</source> + <translation>Bez obrázku</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="73"/> + <source>Select picture ...</source> + <translation>Vybrat obrázek ...</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="76"/> + <source>Clear picture</source> + <translation>Vymazat obrázek</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Select picture</source> + <translation>Vybrat obrázek</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Image Files (*.png *.jpg *.jpeg *.gif)</source> + <translation>Obrázkové soubory (*.png *.jpg *.jpeg *.gif)</translation> + </message> + <message> + <source>Image Files (*.png *.jpg *.gif)</source> + <translation type="obsolete">Obrázkové soubory (*.png *.jpg *.gif)</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="95"/> + <source>Error</source> + <translation>Chyba</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="95"/> + <source>The selected picture is in an unrecognized format</source> + <translation>Nepodařilo se rozpoznat formát vybraného obrázku</translation> + </message> +</context> +<context> + <name>Swift::QtBookmarkDetailWindow</name> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.cpp" line="31"/> + <source>Bookmark not valid</source> + <translation>Neplatná záložka</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.cpp" line="31"/> + <source>You must specify a valid room address (e.g. someroom@rooms.example.com).</source> + <translation>Musíte uvést platnou adresu místnosti (například someroom@rooms.example.com).</translation> + </message> + <message> + <source>You must specify a valid room address (e.g. myroom@chats.example.com).</source> + <translation type="obsolete">Musíte uvést platnou adresu místnosti (například myroom@chats.example.com).</translation> + </message> +</context> +<context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="95"/> + <source>General</source> + <translation>Obecné</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="96"/> + <source>Valid From</source> + <translation>Platné od</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="97"/> + <source>Valid To</source> + <translation>Platné do</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="98"/> + <source>Serial Number</source> + <translation>Sériové číslo</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="99"/> + <source>Version</source> + <translation>Verze</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="101"/> + <source>Subject</source> + <translation>Předmět</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="102"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="124"/> + <source>Organization</source> + <translation>Organizace</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="103"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="125"/> + <source>Common Name</source> + <translation>Běžný název</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="104"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="126"/> + <source>Locality</source> + <translation>Lokalita</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="105"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="127"/> + <source>Organizational Unit</source> + <translation>Organizační jednotka</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="106"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="128"/> + <source>Country</source> + <translation>Stát</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="107"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="129"/> + <source>State</source> + <translation>Stav</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="110"/> + <source>Alternate Subject Names</source> + <translation>Altenativní názvy subjektu</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="115"/> + <source>E-mail Address</source> + <translation>E-mailová adresa</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="117"/> + <source>DNS Name</source> + <translation>DNS název</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="123"/> + <source>Issuer</source> + <translation>Vydavatel</translation> + </message> +</context> +<context> + <name>Swift::QtChatListWindow</name> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="86"/> + <source>Add New Bookmark</source> + <translation>Přidat novou záložku</translation> + </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="83"/> + <source>Edit Bookmark</source> + <translation>Upravit záložku</translation> + </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> + <source>Remove Bookmark</source> + <translation>Odstranit záložku</translation> + </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="173"/> + <source>Clear recents</source> + <translation>Vymazat nedávné</translation> + </message> +</context> +<context> + <name>Swift::QtChatView</name> + <message> + <location filename="../QtUI/QtChatView.cpp" line="73"/> + <source>Clear log</source> + <translation>Vymazat log</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="74"/> + <source>You are about to clear the contents of your chat log.</source> + <translation>Chystáte se smazat historii zpráv.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="75"/> + <source>Are you sure?</source> + <translation>Jste si jist?</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="219"/> + <source>%1 edited</source> + <translation>%1 upraveno</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="400"/> + <source>Waiting for other side to accept the transfer.</source> + <translation>Čekání na příjetí přenosu druhou stranou.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="401"/> + <location filename="../QtUI/QtChatView.cpp" line="406"/> + <location filename="../QtUI/QtChatView.cpp" line="417"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="405"/> + <source>Negotiating...</source> + <translation>Spojování...</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="420"/> + <source>Transfer has been canceled!</source> + <translation>Přenost byl přerušen!</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="424"/> + <source>Transfer completed successfully.</source> + <translation>Přenos byl úspěšně dokončen.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="427"/> + <source>Transfer failed.</source> + <translation>Přenos selhal.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="437"/> + <source>Started whiteboard chat</source> + <translation>Spuštěn whiteboard chat</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="438"/> + <source>Show whiteboard</source> + <translation>Zobrazit whiteboard</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="440"/> + <source>Whiteboard chat has been canceled</source> + <translation>Whiteboard chat byl přerušen</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="442"/> + <source>Whiteboard chat request has been rejected</source> + <translation>Požadavek na whiteboard chat byl odmítnut</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="451"/> + <source>Return to room</source> + <translation>Vrátit se do místnosti</translation> + </message> +</context> +<context> + <name>Swift::QtChatWindow</name> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="546"/> + <source>This message has not been received by your server yet.</source> + <translation>Tato zpráva zatím nebyla přijata vaším serverem.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="553"/> + <source>This message may not have been transmitted.</source> + <translation>Táto zpráva ještě nemusela být přenesena.</translation> + </message> + <message> + <source>Couldn't send message: %1</source> + <translation type="obsolete">Zpráu se nepodařilo odeslat: %1</translation> + </message> + <message> + <source>Actions</source> + <translation type="obsolete">Akce</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="157"/> + <source>Correcting</source> + <translation>Opravování</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="269"/> + <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> + <translation>Tento chat nemusí podporovat opravu zpráv. Pokud pošlete opravu, může se zpráva zobrazit jako duplicitní</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="271"/> + <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> + <translation>Tento chat nemusí podporovat opravu zpráv. Pokud pošlete opravu, může se zpráva zobrazit jako duplicitní</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="562"/> + <source>The receipt for this message has been received.</source> + <translation>Pro tuto zprávu byla přijata doručenka.</translation> + </message> + <message> + <source>The receipt for this message has not yet been received. The receipient(s) might not have received this message.</source> + <translation type="obsolete">Doručenka pro tuto zprávu zatím nedorazila. Příjemce nemusel tuto zprávu přijmout.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="565"/> + <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> + <translation>Doručenka pro tuto zprávu zatím nedorazila. Příjemce nemusel tuto zprávu přijmout.</translation> + </message> + <message> + <source>Send file</source> + <translation type="obsolete">Odeslat soubor</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="613"/> + <source>Send file</source> + <translation>Odeslat soubor</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="615"/> + <location filename="../QtUI/QtChatWindow.cpp" line="623"/> + <location filename="../QtUI/QtChatWindow.cpp" line="660"/> + <location filename="../QtUI/QtChatWindow.cpp" line="664"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="616"/> + <source>Set Description</source> + <translation>Nastavit popis</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="617"/> + <source>Send</source> + <translation>Odoslat</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="621"/> + <source>Receiving file</source> + <translation>Přijímání souboru</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="624"/> + <location filename="../QtUI/QtChatWindow.cpp" line="665"/> + <source>Accept</source> + <translation>Přijmout</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="659"/> + <source>Starting whiteboard chat</source> + <translation>Zahajuje se whiteboard chat</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="663"/> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 chce zahájit whiteboard chat</translation> + </message> + <message> + <source> would like to start whiteboard chat</source> + <translation type="obsolete"> chce zahájit whiteboard chat</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="700"/> + <source>File transfer description</source> + <translation>Popis přeneseného souboru</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="701"/> + <source>Description:</source> + <translation>Popis:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="715"/> + <source>Save File</source> + <translation>Uložit soubor</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="906"/> + <source>Change subject…</source> + <translation>Změnit předmět…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="907"/> + <source>Configure room…</source> + <translation>Nastavit místnost…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="908"/> + <source>Edit affiliations…</source> + <translation>Upravit členství…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="910"/> + <source>Invite person to this room…</source> + <translation>Pozvat uživatele do této místnosti…</translation> + </message> + <message> + <source>Change subject</source> + <translation type="obsolete">Zmenit předmět</translation> + </message> + <message> + <source>Configure room</source> + <translation type="obsolete">Nastavit místnost</translation> + </message> + <message> + <source>Edit affiliations</source> + <translation type="obsolete">Upravit členství</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="909"/> + <source>Destroy room</source> + <translation>Zrušit místnost</translation> + </message> + <message> + <source>Invite person to this room</source> + <translation type="obsolete">Pozvat uživatele do této místnosti</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="920"/> + <source>Change room subject</source> + <translation>Změnit předmět místnosti</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="920"/> + <source>New subject:</source> + <translation>Nový předmět:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="938"/> + <source>Confirm room destruction</source> + <translation>Potvrdit zrušení místnosti</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="939"/> + <source>Are you sure you want to destroy the room?</source> + <translation>Opravdu chcete zrušit tuto místnost?</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="940"/> + <source>This will destroy the room.</source> + <translation>Tímto místnost zrušíte.</translation> + </message> + <message> + <source>Enter person's address</source> + <translation type="obsolete">Zadajte adresu uživatele</translation> + </message> + <message> + <source>Address:</source> + <translation type="obsolete">Adresa:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="991"/> + <source>Accept Invite</source> + <translation>Přijmout pozvánku</translation> + </message> +</context> +<context> + <name>Swift::QtContactEditWidget</name> + <message> + <location filename="../QtUI/QtContactEditWidget.cpp" line="122"/> + <source>Name:</source> + <translation>Jméno:</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWidget.cpp" line="42"/> + <source>Groups:</source> + <translation>Skupiny:</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWidget.cpp" line="65"/> + <source>New Group:</source> + <translation>Nová skupina:</translation> + </message> +</context> +<context> + <name>Swift::QtContactEditWindow</name> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="28"/> + <source>Edit contact</source> + <translation>Upravit kontakt</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="43"/> + <source>Remove contact</source> + <translation>Odstranit kontakt</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="46"/> + <source>OK</source> + <translation>Ok</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="94"/> + <source>Confirm contact deletion</source> + <translation>Potvrzení odstranění kontaktu</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="95"/> + <source>Are you sure you want to delete this contact?</source> + <translation>Opravdu chcete tento kontakt odstranit?</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="96"/> + <source>This will remove the contact '%1' from all groups they may be in.</source> + <translation>Kontakt '%1' bude odstraněný ze všech jeho skupin.</translation> + </message> +</context> +<context> + <name>Swift::QtEventWindow</name> + <message> + <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="48"/> + <source>Display Notice</source> + <translation>Zobrazit oznámení</translation> + </message> +</context> +<context> + <name>Swift::QtFileTransferListWidget</name> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="39"/> + <source>Clear Finished Transfers</source> + <translation>Vymazat dokončené přenosy</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="44"/> + <source>File Transfer List</source> + <translation>Seznam přenesených souborů</translation> + </message> +</context> +<context> + <name>Swift::QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.cpp" line="57"/> + <source>History</source> + <translation>Historie</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="25"/> + <source>Users to invite to this chat (one per line):</source> + <translation>Seznam uživatelů, které chcete pozvat do tohoto sezení (jeden na řádek):</translation> + </message> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="31"/> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>Pokud chcete zadat důvod pro odeslání pozvánky, napište ho sem</translation> + </message> +</context> +<context> + <name>Swift::QtJoinMUCWindow</name> + <message> + <location filename="../QtUI/QtJoinMUCWindow.cpp" line="19"/> + <source>someroom@rooms.example.com</source> + <translation>someroom@rooms.example.com</translation> + </message> +</context> +<context> + <name>Swift::QtLoginWindow</name> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="92"/> + <source>User address:</source> + <translation>Adresa uživatele:</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="98"/> + <source>User address - looks like someuser@someserver.com</source> + <translation>Adresa uživatele - vypadá jako someuser@someserver.com</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="102"/> + <source>Example: alice@wonderland.lit</source> + <translation>Příklad: alice@wonderland.lit</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="108"/> + <source>Password:</source> + <translation>Heslo:</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="129"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="130"/> + <source>Click if you have a personal certificate used for login to the service.</source> + <translation>Klikněte pokud máte certifikát pro přihlášení ke službě.</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="355"/> + <source>Connect</source> + <translation>Přihlásit</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="154"/> + <source>Remember Password?</source> + <translation>Zapamatovat heslo?</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="156"/> + <source>Login Automatically?</source> + <translation>Přihlásit automaticky?</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="168"/> + <source>&Swift</source> + <translation>&Swift</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> + <source>&General</source> + <translation>&Obecné</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="178"/> + <source>&About %1</source> + <translation>O &programu %1</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="183"/> + <source>&Show Debug Console</source> + <translation>&Zobrazit ladící konzoli</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="193"/> + <source>&Play Sounds</source> + <translation>&Přehrávat zvuky</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="199"/> + <source>Display Pop-up &Notifications</source> + <translation>Zobrazovat &oznámení</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="211"/> + <source>&Quit</source> + <translation>U&končit</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> + <source>Remove profile</source> + <translation>Odstranit profil</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> + <source>Remove the profile '%1'?</source> + <translation>Odstranit profil '%1'?</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="355"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="369"/> + <source>Confirm terms of use</source> + <translation>Potvrzení podmínek užití</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="412"/> + <source>Select an authentication certificate</source> + <translation>Vyberte certifikát pro ověření</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="412"/> + <source>P12 files (*.cert *.p12 *.pfx);;All files (*.*)</source> + <translation>Soubory P12 (*.cert *.p12 *.pfx);;Všechny soubory (*.*)</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="525"/> + <source>The certificate presented by the server is not valid.</source> + <translation>Certifikát, který odeslal server je neplatný.</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="526"/> + <source>Would you like to permanently trust this certificate? This must only be done if you know it is correct.</source> + <translation>Chcete natrvalo důvěřovat tomuto certifikátu? Toto musí být uděláno pouze pokud víte, že je to správně.</translation> + </message> + <message> + <source>Subject: %1</source> + <translation type="obsolete">Předmět: %1</translation> + </message> + <message> + <source>SHA-1 Fingerprint: %1</source> + <translation type="obsolete">Otisk SHA-1: %1</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="188"/> + <source>Show &File Transfer Overview</source> + <translation>Zobrazit přehled přenosu &souborů</translation> + </message> +</context> +<context> + <name>Swift::QtMUCConfigurationWindow</name> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="34"/> + <source>Cancel</source> + <translation>Zrušit</translation> + </message> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="37"/> + <source>OK</source> + <translation>Ok</translation> + </message> +</context> +<context> + <name>Swift::QtMUCSearchWindow</name> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.cpp" line="49"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.cpp" line="51"/> + <source>Searching</source> + <translation>Hledání</translation> + </message> +</context> +<context> + <name>Swift::QtMainWindow</name> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="79"/> + <source>&Contacts</source> + <translation>&Kontakty</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="88"/> + <location filename="../QtUI/QtMainWindow.cpp" line="205"/> + <source>&Notices</source> + <translation>&Oznámení</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="87"/> + <source>C&hats</source> + <translation>&Zprávy</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="96"/> + <source>&View</source> + <translation>&Zobrazit</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="98"/> + <source>&Show offline contacts</source> + <translation>Zobrazit &offline kontakty</translation> + </message> + <message> + <source>&Compact Roster</source> + <translation type="obsolete">&Kompaktní seznam kontaktů</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="106"/> + <source>&Show Emoticons</source> + <translation>&Zobrazit smajlíky</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="120"/> + <source>&Actions</source> + <translation>&Akce</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="122"/> + <source>Edit &Profile…</source> + <translation>Upravit &profil</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <source>Enter &Room…</source> + <translation>Vztoupit do &místnosti…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="129"/> + <source>&View History…</source> + <translation>&Zobrazit historii…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="133"/> + <source>&Add Contact…</source> + <translation>Přidat &kontakt…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="136"/> + <source>&Edit Selected Contact…</source> + <translation>&Upravit vybraný kontakt…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="140"/> + <source>Start &Chat…</source> + <translation>Zahájit &chat…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="146"/> + <source>&Sign Out</source> + <translation>&Odhlásit se</translation> + </message> + <message> + <source>Notices</source> + <translation type="obsolete">&Oznámení</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="143"/> + <source>Run Server Command</source> + <translation>Spustit příkaz na serveru</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="150"/> + <source>&Request Delivery Receipts</source> + <translation>Vyžadovat &doručenky</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="161"/> + <source>Collecting commands...</source> + <translation>Získávání příkazů…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="216"/> + <source>&Chats</source> + <translation>&Zprávy</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="339"/> + <source>No Available Commands</source> + <translation>Není dostupný žádný příkaz</translation> + </message> +</context> +<context> + <name>Swift::QtNameWidget</name> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> + <source>Show Nickname</source> + <translation>Zobrazit přezdívku</translation> + </message> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> + <source>(No Nickname Set)</source> + <translation>(Žádná přezdívka)</translation> + </message> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="56"/> + <source>Show Address</source> + <translation>Zobrazit adresu</translation> + </message> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="63"/> + <source>Edit Profile</source> + <translation>Upravit profil</translation> + </message> +</context> +<context> + <name>Swift::QtOccupantListWidget</name> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="46"/> + <source>No actions for this user</source> + <translation>Pro tohoto uživatele nejsou dostupné žádné akce</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="55"/> + <source>Kick user</source> + <translation>Vykopnout uživatele</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="56"/> + <source>Kick and ban user</source> + <translation>Vykopnout uživatele a zakázat mu přístup</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="57"/> + <source>Make moderator</source> + <translation>Nastavit jako moderátora</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="58"/> + <source>Make participant</source> + <translation>Nastavit jako účastníka</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="59"/> + <source>Remove voice</source> + <translation>Odstranit hlas</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="60"/> + <source>Add to contacts</source> + <translation>Přidat do kontaktů</translation> + </message> + <message> + <source>Add contact</source> + <translation type="obsolete">Přidat kontakt</translation> + </message> +</context> +<context> + <name>Swift::QtProfileWindow</name> + <message> + <location filename="../QtUI/QtProfileWindow.cpp" line="24"/> + <source>Edit Profile</source> + <translation>Upravit profil</translation> + </message> + <message> + <location filename="../QtUI/QtProfileWindow.cpp" line="43"/> + <source>Nickname:</source> + <translation>Přezdívka:</translation> + </message> + <message> + <location filename="../QtUI/QtProfileWindow.cpp" line="67"/> + <source>Save</source> + <translation>Uložit</translation> + </message> +</context> +<context> + <name>Swift::QtRosterHeader</name> + <message> + <location filename="../QtUI/QtRosterHeader.cpp" line="59"/> + <source>Connection is secured</source> + <translation>Spojení je zabezpečené</translation> + </message> +</context> +<context> + <name>Swift::QtRosterWidget</name> + <message> + <source>Edit</source> + <translation type="obsolete">Upravit</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="58"/> + <source>Edit…</source> + <translation>Upravit…</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="59"/> + <source>Remove</source> + <translation>Odstranit</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="63"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>Send File</source> + <translation>Odoslat soubor</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="69"/> + <source>Start Whiteboard Chat</source> + <translation>Zahájit whiteboard chat</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>All Files (*);;</source> + <translation>Všechny soubory (*);;</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="96"/> + <source>Rename</source> + <translation>Přejmenovat</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Rename group</source> + <translation>Přejmenovat skupinu</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Enter a new name for group '%1':</source> + <translation>Zadajte nové jméno pro skupinu '%1':</translation> + </message> +</context> +<context> + <name>Swift::QtStatusWidget</name> + <message> + <location filename="../QtUI/QtStatusWidget.cpp" line="231"/> + <source>Connecting</source> + <translation>Připojování</translation> + </message> + <message> + <location filename="../QtUI/QtStatusWidget.cpp" line="261"/> + <source>(No message)</source> + <translation>(Bez zprávy)</translation> + </message> +</context> +<context> + <name>Swift::QtSubscriptionRequestWindow</name> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="24"/> + <source>You have already replied to this request</source> + <translation>Na tuto žádost jste už reagovali</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="25"/> + <source>OK</source> + <translation>Ok</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="30"/> + <source>Yes</source> + <translation>Ano</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="33"/> + <source>No</source> + <translation>Ne</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="35"/> + <source>Defer</source> + <translation>Odložit</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="18"/> + <source>%1 would like to add you to their contact list. + Would you like to add them to your contact list and share your status when you're online? + +If you choose to defer this choice, you will be asked again when you next login.</source> + <translation>%1 si vás chce přidat do seznamu kontaktů. + Chcete si ho přidat do vašeho seznamu kontaktů a když budete připojeni, sdílet s ním stav? + +Když se rozhodnete odložit výběr, při dalším přihlášení budete znovu vyzváni.</translation> + </message> +</context> +<context> + <name>Swift::QtSwift</name> + <message> + <source>Confirm terms of use</source> + <translation type="obsolete">Potvrzení podmínek užití</translation> + </message> + <message> + <source>Do you agree to the terms of use?</source> + <translation type="obsolete">Souhlasíte s podmínkami užití?</translation> + </message> +</context> +<context> + <name>Swift::QtTreeWidget</name> + <message> + <source>Edit</source> + <translation type="obsolete">Upravit</translation> + </message> + <message> + <source>Remove</source> + <translation type="obsolete">Odstranit</translation> + </message> + <message> + <source>Rename</source> + <translation type="obsolete">Přejmenovat</translation> + </message> + <message> + <source>Rename group</source> + <translation type="obsolete">Přejmenovat skupinu</translation> + </message> + <message> + <source>Enter a new name for group '%1':</source> + <translation type="obsolete">Zadejte nové jméno pro skupinu '%1':</translation> + </message> + <message> + <source>New name for %1</source> + <translation type="obsolete">Nové jméno pro %1</translation> + </message> +</context> +<context> + <name>Swift::QtUserSearchDetailsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="22"/> + <source>Please choose a name for the contact, and select the groups you want to add the contact to.</source> + <translation>Zvolte jméno pro kontakt a vyberte skupiny, do kterých ho chcete vložit.</translation> + </message> +</context> +<context> + <name>Swift::QtUserSearchFirstPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> + <source>%1. If you know their address you can enter it directly, or you can search for them.</source> + <translation>%1. Pokud znáte jeho adresu, můžete jí zadat přímo, nebo ho můžete vyhledat.</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> + <source>Add another user to your contact list</source> + <translation>Přidat uživatele do vašeho seznamu kontaktů</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> + <source>Chat to another user</source> + <translation>Chat s dalším uživatelem</translation> + </message> +</context> +<context> + <name>Swift::QtUserSearchWindow</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> + <source>Add Contact</source> + <translation>Přidat kontakt</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> + <source>Chat to User</source> + <translation>Chat s uživatelem</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="45"/> + <source>alice@wonderland.lit</source> + <translation>alice@wonderland.lit</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> + <source>How would you like to find the user to add?</source> + <translation>Jak chcete vyhledat uživatele pro přidání?</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="299"/> + <source>How would you like to find the user to chat to?</source> + <translation>Jak chcete vyhledat uživatele pro chat?</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="326"/> + <source>Error while searching</source> + <translation>Chyba při hledání</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="332"/> + <source>This server doesn't support searching for users.</source> + <translation>Tento server nepodporuje vyhledávání uživatelů.</translation> + </message> +</context> +<context> + <name>Swift::QtWebView</name> + <message> + <location filename="../QtUI/QtWebView.cpp" line="66"/> + <source>Clear</source> + <translation>Vymazat</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="67"/> + <source>Increase font size</source> + <translation>Zvětšit písmo</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="68"/> + <source>Decrease font size</source> + <translation>Změnšit písmo</translation> + </message> +</context> +<context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <location filename="../QtUI/Whiteboard/QtWhiteboardWindow.cpp" line="380"/> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>Zavřením okna ukončíte chat. Chcete opravdu pokračovat?</translation> + </message> +</context> +<context> + <name>Swift::QtXMLConsoleWidget</name> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="22"/> + <source>Console</source> + <translation>Konzole</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="40"/> + <source>Trace input/output</source> + <translation>Sledovat vstup a výstup</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="46"/> + <source>Clear</source> + <translation>Vymazat</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="50"/> + <source>Debug Console</source> + <translation>Ladící konzole</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="78"/> + <source><!-- IN --></source> + <translation><!-- IN --></translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="82"/> + <source><!-- OUT --></source> + <translation><!-- OUT --></translation> + </message> +</context> +<context> + <name>TRANSLATION_INFO</name> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="49"/> + <location filename="../QtUI/QtAboutWidget.cpp" line="50"/> + <source>TRANSLATION_AUTHOR</source> + <translation>Roman Štefko</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="52"/> + <source>TRANSLATION_LICENSE</source> + <comment>This string contains the license under which this translation is licensed. We ask you to license the translation under the BSD license. Please read http://www.opensource.org/licenses/bsd-license.php, and if you agree to release your translation under this license, use the following (untranslated) text: 'This translation is licensed under the BSD License. See http://www.opensource.org/licenses/bsd-license.php'</comment> + <translation>This translation is licensed under the BSD License. See http://www.opensource.org/licenses/bsd-license.php</translation> + </message> + <message> + <source>TRANSLATION_LICENSE</source> + <comment>Should be the following (untranslated) text: 'This translation is licensed under the BSD License. See http://www.opensource.org/licenses/bsd-license.php'</comment> + <translation type="obsolete">This translation is licensed under the BSD License. See http://www.opensource.org/licenses/bsd-license.php</translation> + </message> +</context> +</TS> diff --git a/Swift/Translations/swift_de.ts b/Swift/Translations/swift_de.ts index 6183099..0fee815 100644 --- a/Swift/Translations/swift_de.ts +++ b/Swift/Translations/swift_de.ts @@ -1533,25 +1533,25 @@ <message> <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>Edit &Profile</source> - <translation>&Profil editieren</translation> + <source>Edit &Profile…</source> + <translation>&Profil editieren…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="98"/> - <source>Enter &Room</source> - <translation>Chat&raum betreten</translation> + <source>Enter &Room…</source> + <translation>Chat&raum betreten…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="101"/> - <source>&Add Contact</source> - <translation>Kont&akt hinzufügen</translation> + <source>&Add Contact…</source> + <translation>Kont&akt hinzufügen…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="104"/> - <source>&Edit Selected Contact</source> - <translation>Ausgewählten Kontakt &editieren</translation> + <source>&Edit Selected Contact…</source> + <translation>Ausgewählten Kontakt &editieren…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="108"/> - <source>Start &Chat</source> + <source>Start &Chat…</source> <translation>Gesprä&ch beginnen</translation> </message> diff --git a/Swift/Translations/swift_es.ts b/Swift/Translations/swift_es.ts index 2231ecf..384c411 100644 --- a/Swift/Translations/swift_es.ts +++ b/Swift/Translations/swift_es.ts @@ -6,35 +6,46 @@ <name></name> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="46"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> <source>Starting chat with %1% in chatroom %2%</source> <translation>Comenzando conversación con %1% en la sala %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="49"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="62"/> <source>Starting chat with %1% - %2%</source> <translation>Comenzando conversación con %1% - %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="119"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> + <source>This chat doesn't support delivery receipts.</source> + <translation>Esta conversación no es compatible con confirmaciones de entrega.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="204"/> + <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> + <translation>Esta conversación quizá no sea compatible con confirmaciones de entrega. Tal vez no recibas las confirmaciones de entrega de los mensajes que envies.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="216"/> + <location filename="../Controllers/FileTransfer/FileTransferController.cpp" line="42"/> <source>me</source> <translation>Yo</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="160"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="324"/> <source>%1% has gone offline</source> <translation>%1% se ha desconectado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="164"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="328"/> <source>%1% has become available</source> <translation>%1% se encuentra disponible</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="166"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="330"/> <source>%1% has gone away</source> <translation>%1% se ha ausentado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="168"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="332"/> <source>%1% is now busy</source> <translatorcomment>TMPFIX genero: o/a? sinonimo? masculino?</translatorcomment> @@ -42,155 +53,159 @@ </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="56"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="74"/> <source>The day is now %1%</source> <translation>El día es ahora %1%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="191"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <source>Couldn't send message: %1%</source> + <translation>No se ha podido enviar el mensaje: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> <source>Error sending message</source> <translation>Error enviando mensaje</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="197"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="275"/> <source>Bad request</source> <translation>Solicitud incorrecta</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="198"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="276"/> <source>Conflict</source> <translation>Conflicto</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="199"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="277"/> <source>This feature is not implemented</source> <translation>Esta característica no está implementada</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="200"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="278"/> <source>Forbidden</source> <translation>Prohibido</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="201"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="279"/> <source>Recipient can no longer be contacted</source> <translation>El destinatario ya no puede ser contactado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="202"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="280"/> <source>Internal server error</source> <translation>Error interno del servidor</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="203"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="281"/> <source>Item not found</source> <translation>Elemento no encontrado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="204"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="282"/> <source>JID Malformed</source> <translation>JID Malformado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="205"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="283"/> <source>Message was rejected</source> <translation>El mensaje ha sido rechazado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="206"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="284"/> <source>Not allowed</source> <translation>No permitido</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="207"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="285"/> <source>Not authorized</source> <translation>No autorizado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="208"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="286"/> <source>Payment is required</source> <translation>Pago requerido</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="287"/> <source>Recipient is unavailable</source> <translation>Destinatario no disponible</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="210"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="288"/> <source>Redirect</source> <translation>Redirección</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="211"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="289"/> <source>Registration required</source> <translation>Registro requerido</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="212"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="290"/> <source>Recipient's server not found</source> <translation>No se ha encontrado el servidor del destinatario</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="213"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="291"/> <source>Remote server timeout</source> <translation>Tiempo de espera del servidor remoto agotado</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="214"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="292"/> <source>The server is low on resources</source> <translation>El servidor tiene pocos recursos disponibles</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="215"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="293"/> <source>The service is unavailable</source> <translation>El servicio no está disponible</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="216"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="294"/> <source>A subscription is required</source> <translation>Se requiere una suscripción</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="217"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="295"/> <source>Undefined condition</source> <translation>Condición no definida</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="218"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="296"/> <source>Unexpected request</source> <translation>Solicitud inesperada</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="114"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> <source>Room %1% is not responding. This operation may never complete.</source> <translation>La sala %1% no responde. Es posible que esta operación no se complete nunca.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="125"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="233"/> <source>Unable to enter this room</source> <translation>No se puede entrar en esta sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="131"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="239"/> <source>Unable to enter this room as %1%, retrying as %2%</source> <translation>No se puede entrar en esta sala como %1%, reintentando como %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="135"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> <source>No nickname specified</source> <translation>No se ha especificado nick</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="139"/> <source>A password needed</source> - <translation>Se necesita contraseña</translation> + <translation type="obsolete">Se necesita contraseña</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="143"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="251"/> <source>Only members may enter</source> <translation>Solo los miembros pueden entrar</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="147"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="255"/> <source>You are banned from the room</source> <translatorcomment>bloqueado?</translatorcomment> @@ -198,70 +213,75 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="151"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="259"/> <source>The room is full</source> - <translation>La sala esta llena</translation> + <translation>La sala está llena</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="155"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="263"/> <source>The room does not exist</source> <translation>La sala no existe</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="173"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="269"/> + <source>Couldn't join room: %1%.</source> + <translation>No se ha podido entrar a la sala: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="281"/> <source>You have entered room %1% as %2%.</source> <translation>Has entrado a la sala %1% como %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="214"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="332"/> <source>%1% has entered the room as a %2%.</source> <translation>%1% ha entrado en la sala como %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="217"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="335"/> <source>%1% has entered the room.</source> <translation>%1% ha entrado a la sala.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="384"/> <source>moderator</source> <translation>moderador</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="244"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="385"/> <source>participant</source> <translation>participante</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="245"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="386"/> <source>visitor</source> <translation>visitante</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="283"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="436"/> <source>The room subject is now: %1%</source> <translation>El tema de la sala es ahora: %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="313"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="470"/> <source>%1% is now a %2%</source> <translation>%1% ahora es un %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="319"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="486"/> <source>Moderators</source> <translation>Moderadores</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="320"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> <source>Participants</source> <translation>Participantes</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="321"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="488"/> <source>Visitors</source> <translation>Visitantes</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="322"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="489"/> <source>Occupants</source> <translatorcomment>TMPFIX, used where?</translatorcomment> @@ -269,172 +289,226 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="336"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> <source>Trying to enter room %1%</source> <translation>Intentando entrar a la sala %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="365"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="474"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="544"/> + <source>%1% has left the room%2%</source> + <translation>%1% ha salido de la sala %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="548"/> + <source>You have been kicked out of the room</source> + <translation>Has sido expulsado de la sala</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="549"/> + <source>You have been banned from the room</source> + <translation>Has sido vetado de la sala</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="550"/> + <source>You are no longer a member of the room and have been removed</source> + <translation>Ya no eres un miembro de la sala y has sido expulsado</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="551"/> + <source>The room has been destroyed</source> + <translation>La sala ha sido destruida</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="667"/> <source>%1% has left the room</source> <translation>%1% ha salido de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="365"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="719"/> + <source>Room configuration failed: %1%.</source> + <translation>La configuración de la sala ha fallado: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="725"/> + <source>Occupant role change failed: %1%.</source> + <translation>El cambio de rol del ocupante ha fallado: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="553"/> <source>You have left the room</source> <translation>Has salido de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="439"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="247"/> + <source>The correct room password is needed</source> + <translation>Se necesita la contraseña de sala correcta</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="632"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="696"/> <source> and </source> <translation> y </translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="463"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="656"/> <source>%1% have entered the room</source> <translation>%1% han entrado a la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="466"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="659"/> <source>%1% has entered the room</source> <translation>%1% ha entrado a la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="471"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="664"/> <source>%1% have left the room</source> <translation>%1% han salido de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="479"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="672"/> <source>%1% have entered then left the room</source> <translation>%1% han entrado y salido de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="482"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="675"/> <source>%1% has entered then left the room</source> <translation>%1% ha entrado y salido de la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="680"/> <source>%1% have left then returned to the room</source> <translation>%1% han salido y vuelto a la sala</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="490"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="683"/> <source>%1% has left then returned to the room</source> <translation>%1% ha salido y vuelto a la sala</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="51"/> + <location filename="../Controllers/EventNotifier.cpp" line="59"/> <source>%1% wants to add you to his/her contact list</source> <translation>%1% quiere añadirte a su lista de contactos</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="55"/> + <location filename="../Controllers/EventNotifier.cpp" line="63"/> <source>Error</source> <translation>Error</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="438"/> + <location filename="../Controllers/EventNotifier.cpp" line="67"/> + <source>%1% has invited you to enter the %2% room</source> + <translation>%1% te ha invitado a entrar a la sala %2%</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="466"/> + <source>User address invalid. User address should be of the form 'alice@wonderland.lit'</source> + <translation>Dirección de usuario no válida. La dirección de usuario ha de ser de la forma 'alicia@paismaravillas.lit'</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="568"/> <source>Unknown Error</source> <translation>Error Desconocido</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="439"/> + <location filename="../Controllers/MainController.cpp" line="569"/> <source>Unable to find server</source> <translation>No se puede encontrar el servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="440"/> + <location filename="../Controllers/MainController.cpp" line="570"/> <source>Error connecting to server</source> <translation>Error conectando al servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="441"/> + <location filename="../Controllers/MainController.cpp" line="571"/> <source>Error while receiving server data</source> <translation>Error al recibir datos del servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="442"/> + <location filename="../Controllers/MainController.cpp" line="572"/> <source>Error while sending data to the server</source> <translation>Error al enviar datos al servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="443"/> + <location filename="../Controllers/MainController.cpp" line="573"/> <source>Error parsing server data</source> <translation>Error analizando datos del servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="444"/> + <location filename="../Controllers/MainController.cpp" line="574"/> <source>Login/password invalid</source> <translation>Usuario/contraseña no válidos</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="445"/> + <location filename="../Controllers/MainController.cpp" line="575"/> <source>Error while compressing stream</source> <translation>Error comprimiendo flujo de datos</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="446"/> + <location filename="../Controllers/MainController.cpp" line="576"/> <source>Server verification failed</source> <translation>La verificación del servidor ha fallado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="447"/> + <location filename="../Controllers/MainController.cpp" line="577"/> <source>Authentication mechanisms not supported</source> <translation>Mecanismo de autenticación no soportado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="448"/> + <location filename="../Controllers/MainController.cpp" line="578"/> <source>Unexpected response</source> <translation>Respuesta inesperada</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="449"/> + <location filename="../Controllers/MainController.cpp" line="579"/> <source>Error binding resource</source> <translation>Error vinculando recurso</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="450"/> + <location filename="../Controllers/MainController.cpp" line="580"/> <source>Error starting session</source> <translation>Error iniciando sesión</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="451"/> + <location filename="../Controllers/MainController.cpp" line="581"/> <source>Stream error</source> <translation>Error de flujo de datos</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="452"/> + <location filename="../Controllers/MainController.cpp" line="582"/> <source>Encryption error</source> <translation>Error de cifrado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="453"/> + <location filename="../Controllers/MainController.cpp" line="583"/> <source>Error loading certificate (Invalid password?)</source> <translation>Error cargando certificado (¿Contraseña no válida?)</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="454"/> + <location filename="../Controllers/MainController.cpp" line="584"/> <source>Certificate not authorized</source> <translation>Certificado no autorizado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="456"/> + <location filename="../Controllers/MainController.cpp" line="585"/> + <source>Certificate card removed</source> + <translation>Tarjeta de certificado eliminada</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="587"/> <source>Unknown certificate</source> <translation>Certificado desconocido</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="457"/> + <location filename="../Controllers/MainController.cpp" line="588"/> <source>Certificate has expired</source> <translation>El certificado ha caducado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="458"/> + <location filename="../Controllers/MainController.cpp" line="589"/> <source>Certificate is not yet valid</source> <translation>El certificado aún no es válido</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="459"/> + <location filename="../Controllers/MainController.cpp" line="590"/> <source>Certificate is self-signed</source> <translatorcomment>TMPFIX, firma personal?</translatorcomment> @@ -442,20 +516,20 @@ </message> <message> - <location filename="../Controllers/MainController.cpp" line="460"/> + <location filename="../Controllers/MainController.cpp" line="591"/> <source>Certificate has been rejected</source> <translation>El certificado ha sido rechazado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="461"/> + <location filename="../Controllers/MainController.cpp" line="592"/> <source>Certificate is not trusted</source> <translation>El certificado no es de confianza</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="462"/> + <location filename="../Controllers/MainController.cpp" line="593"/> <source>Certificate cannot be used for encrypting your connection</source> <translation>El certificado no puede usarse para cifrar tu conexión</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="463"/> + <location filename="../Controllers/MainController.cpp" line="594"/> <source>Certificate path length constraint exceeded</source> <translatorcomment>TMPFIX</translatorcomment> @@ -463,42 +537,62 @@ </message> <message> - <location filename="../Controllers/MainController.cpp" line="464"/> + <location filename="../Controllers/MainController.cpp" line="595"/> <source>Invalid certificate signature</source> <translation>Firma de certificado no válida</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="465"/> + <location filename="../Controllers/MainController.cpp" line="596"/> <source>Invalid Certificate Authority</source> <translation>Entidad Certificadora no Válida</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="466"/> + <location filename="../Controllers/MainController.cpp" line="597"/> <source>Certificate does not match the host identity</source> <translation>El certificado no coincide con la identidad del servidor</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="476"/> + <location filename="../Controllers/MainController.cpp" line="598"/> + <source>Certificate has been revoked</source> + <translation>El certificado ha sido revocado</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="599"/> + <source>Unable to determine certificate revocation state</source> + <translation>No se puede determinar el estado de revocación del certificado</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="609"/> <source>Certificate error</source> <translation>Error de certificado</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="490"/> + <location filename="../Controllers/MainController.cpp" line="616"/> + <source>Re-enter credentials and retry</source> + <translation>Vuelve a introducir las credenciales y prueba de nuevo</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="629"/> + <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> + <translation>Desconectado de %1%: %2%. Para reconectar, desconéctate y proporciona tu contraseña de nuevo.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="635"/> <source>Reconnect to %1% failed: %2%. Will retry in %3% seconds.</source> <translation>La reconexión a %1% ha fallado: %2%. Se intentará de nuevo en %3% segundos.</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="493"/> + <location filename="../Controllers/MainController.cpp" line="638"/> <source>Disconnected from %1%: %2%.</source> <translation>Desconectado de %1%: %2%.</translation> </message> <message> - <location filename="../Controllers/Roster/RosterController.cpp" line="126"/> - <location filename="../Controllers/Roster/RosterController.cpp" line="152"/> - <location filename="../Controllers/Roster/RosterController.cpp" line="214"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="131"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="157"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="222"/> <source>Contacts</source> <translation>Contactos</translation> </message> <message> - <location filename="../Controllers/Roster/RosterController.cpp" line="251"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="263"/> <source>Server %1% rejected contact list change to item '%2%'</source> <translation>El servidor %1% ha rechazado el cambio al elemento '%2%' de la lista de contactos</translation> @@ -531,4 +625,29 @@ <translation>Ha habido un error publicando los datos de tu perfil</translation> </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="33"/> + <source>%1% (%2%)</source> + <translation>%1% (%2%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="38"/> + <source>%1% and %2% others (%3%)</source> + <translation>%1% y %2% más (%3%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="41"/> + <source>%1%, %2% (%3%)</source> + <translation>%1%, %2% (%3%)</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="63"/> + <source>TLS Client Certificate Selection</source> + <translation>Selección de certificado de cliente TLS</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="64"/> + <source>Select a certificate to use for authentication</source> + <translation>Selecciona un certificado para usar como autenticación</translation> + </message> </context> <context> @@ -686,13 +805,110 @@ </message> <message> - <location filename="../QtUI/EventViewer/QtEvent.cpp" line="63"/> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="68"/> <source>%1 would like to add you to their contact list.</source> <translation>%1 quiere añadirte a su lista de contactos.</translation> </message> <message> - <location filename="../QtUI/EventViewer/QtEvent.cpp" line="66"/> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="71"/> <source>%1 would like to add you to their contact list, saying '%2'</source> <translation>%1 quiere añadirte a su lista de contactos, diciendo '%2'</translation> </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="81"/> + <source>%1 has invited you to enter the %2 room.</source> + <translation>%1 te ha invitado a entrar a la sala %2.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="982"/> + <source>You've been invited to enter the %1 room.</source> + <translation>Te han invitado a entrar a la sala %1.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="984"/> + <source>Reason: %1</source> + <translation>Motivo: %1</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="987"/> + <source>This person may not have really sent this invitation!</source> + <translation>¡Es posible que esta persona no haya enviado realmente esta invitación!</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="46"/> + <source>Direction</source> + <translatorcomment>Sentido?</translatorcomment> + <translation>Dirección</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="47"/> + <source>Other Party</source> + <translatorcomment>Sounds weird... "Tu contacto" o similar?</translatorcomment> + <translation type="unfinished">La otra parte</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="48"/> + <source>State</source> + <translation>Estado</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="49"/> + <source>Progress</source> + <translation>Progreso</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="50"/> + <source>Size</source> + <translation>Tamaño</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Incoming</source> + <translation>Entrante</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Outgoing</source> + <translation>Saliente</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="74"/> + <source>Waiting for start</source> + <translation>Esperando que comience</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="76"/> + <source>Waiting for other side to accept</source> + <translation>Esperando a que el otro lado acepte</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="78"/> + <source>Negotiating</source> + <translation>Negociando</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="80"/> + <source>Transferring</source> + <translation>Transfiriendo</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="82"/> + <source>Finished</source> + <translation>Finalizado</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="84"/> + <source>Failed</source> + <translation>Fallido</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="86"/> + <source>Canceled</source> + <translation>Cancelado</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="142"/> + <source>Connection Options</source> + <translation>Opciones de conexión</translation> + </message> </context> <context> @@ -918,41 +1134,90 @@ </context> <context> + <name>QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="14"/> + <source>Edit Affiliations</source> + <translation>Editar afiliaciones</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="28"/> + <source>Affiliation:</source> + <translation>Afiliación:</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="36"/> + <source>Owner</source> + <translation>Propietario</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="41"/> + <source>Administrator</source> + <translation>Administrador</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="46"/> + <source>Member</source> + <translation>Miembro</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="51"/> + <source>Outcast (Banned)</source> + <translation>Marginado (Vetado)</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="68"/> + <source>Add User</source> + <translation>Añadir usuario</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="75"/> + <source>Remove User</source> + <translation>Eliminar usuario</translation> + </message> +</context> +<context> <name>QtBookmarkDetailWindow</name> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="137"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="20"/> <source>Edit Bookmark Details</source> <translation>Editar Detalles de Marcador</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="138"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="40"/> <source>Bookmark Name:</source> <translation>Nombre del Marcador:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="139"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="50"/> <source>Room Address:</source> <translation>Dirección de la sala:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="140"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="60"/> <source>Your Nickname:</source> <translation>Tu nick:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="141"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="70"/> <source>Room password:</source> <translation>Contraseña de la sala:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="142"/> - <source>Join automatically</source> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="93"/> + <source>Enter automatically</source> <translation>Entrar automáticamente</translation> </message> + <message> + <source>Join automatically</source> + <translation type="obsolete">Entrar automáticamente</translation> + </message> +</context> +<context> + <name>QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.ui" line="14"/> + <source>Certificate Viewer</source> + <translation>Visor de certificados</translation> + </message> </context> <context> @@ -972,4 +1237,126 @@ </context> <context> + <name>QtConnectionSettings</name> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="14"/> + <source>Connection Options</source> + <translation>Opciones de conexión</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="22"/> + <source>Connection Method:</source> + <translation>Método de conexión:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="36"/> + <source>Automatic</source> + <translation>Automático</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="41"/> + <source>Manual</source> + <translation>Manual</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="46"/> + <source>BOSH</source> + <translation>BOSH</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="99"/> + <source>Secure connection:</source> + <translation>Conexión segura:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="107"/> + <source>Never</source> + <translation>Nunca</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="112"/> + <source>Encrypt when possible</source> + <translation>Cifrar cuando sea posible</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="117"/> + <source>Always encrypt</source> + <translation>Cifrar siempre</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="127"/> + <source>Allow Compression</source> + <translation>Permitir compresión</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="134"/> + <source>Allow sending password over insecure connection</source> + <translation>Permitir enviar contraseña sobre conexión insegura</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="157"/> + <source>Manually select server</source> + <translation>Seleccionar servidor manualmente</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="185"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="315"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="429"/> + <source>Hostname:</source> + <translation>Nombre de servidor:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="208"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="338"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="452"/> + <source>Port:</source> + <translation>Puerto:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="230"/> + <source>Connection Proxy</source> + <translation>Proxy de conexión</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="238"/> + <source>Proxy type:</source> + <translation>Tipo de proxy:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="249"/> + <source>None</source> + <translation>Ninguno</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="254"/> + <source>Use system-configured proxy</source> + <translation>Usar el proxy del sistema</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="259"/> + <source>SOCKS5</source> + <translation>SOCKS5</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="264"/> + <source>HTTP Connect</source> + <translation>HTTP Connect</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="287"/> + <source>Override system-configured proxy</source> + <translation>Reemplazar el proxy del sistema</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="382"/> + <source>BOSH URI:</source> + <translation>URI de BOSH:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="401"/> + <source>Manually select HTTP proxy</source> + <translation>Seleccionar proxy HTTP manualmente</translation> + </message> +</context> +<context> <name>QtEventWindow</name> <message> @@ -979,33 +1366,69 @@ </context> <context> + <name>QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="14"/> + <source>History</source> + <translation>Historial</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="22"/> + <source>Search:</source> + <translation>Buscar:</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="42"/> + <source>Next</source> + <translation>Siguiente</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="52"/> + <source>Previous</source> + <translation>Anterior</translation> + </message> +</context> +<context> <name>QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="124"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="130"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="20"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="113"/> <source>Enter Room</source> <translation>Entrar a la sala</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="125"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="28"/> + <source>Room Address:</source> + <translation>Dirección de la sala:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="42"/> + <source>Your Nickname:</source> + <translation>Tu nick:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="59"/> + <source>Room Password:</source> + <translation>Contraseña de la sala:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="71"/> + <source>Automatically configure newly created rooms</source> + <translation>Configurar automáticamente salas de nueva creación</translation> + </message> + <message> <source>Room:</source> - <translation>Sala:</translation> + <translation type="obsolete">Sala:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="126"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="35"/> <source>Search ...</source> <translation>Buscar ...</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="127"/> <source>Nickname:</source> - <translation>Nick:</translation> + <translation type="obsolete">Nick:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="129"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="106"/> <source>Enter automatically in future</source> <translation>Entrar automáticamente en el futuro</translation> @@ -1015,30 +1438,25 @@ <name>QtMUCSearchWindow</name> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="118"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="14"/> <source>Search Room</source> <translation>Buscar Sala</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="119"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="20"/> <source>Service:</source> <translation>Servicio:</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="121"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="74"/> <source>Cancel</source> <translation>Cancelar</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="122"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="84"/> <source>OK</source> <translation>Aceptar</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="123"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="96"/> <source>List rooms</source> <translation>Listar salas</translation> @@ -1092,30 +1510,25 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>QtUserSearchFieldsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="119"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="27"/> <source>Nickname:</source> <translation>Nick:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="120"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="37"/> <source>First name:</source> <translation>Nombre:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="47"/> <source>Last name:</source> <translation>Apellido:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="57"/> <source>E-Mail:</source> <translation>Correo electrónico:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="74"/> <source>Fetching search fields</source> <translation>Obteniendo campos de búsqueda</translation> @@ -1125,30 +1538,25 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>QtUserSearchFirstPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="17"/> <source>Add a user</source> <translation>Añadir un usuario</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="20"/> <source>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</source> <translation>Añadir a otro usuario a tu lista de contactos. Si conoces su dirección, puedes introducirla directamente, o puedes buscar al usuario.</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="35"/> <source>I know their address:</source> <translation>Conozco su dirección:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="125"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="49"/> <source>I'd like to search my server</source> <translation>Quiero buscar en mi servidor</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="126"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="73"/> <source>I'd like to search another server:</source> <translation>Quiero buscar en otro servidor:</translation> @@ -1168,4 +1576,12 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </context> <context> + <name>QtUserSearchResultsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui" line="27"/> + <source>No results.</source> + <translation>No hay resultados.</translation> + </message> +</context> +<context> <name>QtUserSearchWindow</name> <message> @@ -1201,6 +1617,5 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>QtUserSearchWizard</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchWizard.h" line="39"/> + <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui" line="14"/> <source>Find User</source> <translation>Buscar Usuario</translation> @@ -1210,9 +1625,19 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::ChatListModel</name> <message> - <location filename="../QtUI/ChatList/ChatListModel.cpp" line="15"/> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> <source>Bookmarked Rooms</source> <translatorcomment>TMPFIX? "de marcadores"?</translatorcomment> <translation>Salas en Marcadores</translation> </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="18"/> + <source>Recent Chats</source> + <translation>Conversaciones recientes</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="20"/> + <source>Opened Whiteboards</source> + <translation>Pizarras abiertas</translation> + </message> </context> <context> @@ -1252,4 +1677,55 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </context> <context> + <name>Swift::QtAdHocCommandWindow</name> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="36"/> + <source>Cancel</source> + <translation>Cancelar</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="39"/> + <source>Back</source> + <translation>Retroceder</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="42"/> + <source>Next</source> + <translation>Siguiente</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="45"/> + <source>Complete</source> + <translation>Completado</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="86"/> + <source>Error: %1</source> + <translation>Error: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="87"/> + <source>Warning: %1</source> + <translation>Advertencia: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="104"/> + <source>Error executing command</source> + <translation>Error ejecutando el comando</translation> + </message> +</context> +<context> + <name>Swift::QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Add User</source> + <translation>Añadir usuario</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Added User's Address:</source> + <translation>Dirección del usuario añadido:</translation> + </message> +</context> +<context> <name>Swift::QtAvatarWidget</name> <message> @@ -1275,6 +1751,10 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <message> <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Image Files (*.png *.jpg *.jpeg *.gif)</source> + <translation>Archivos de imagen (*.png *.jpg *.jpeg *.gif)</translation> + </message> + <message> <source>Image Files (*.png *.jpg *.gif)</source> - <translation>Archivos de imagen (*.png *.jpg *.gif)</translation> + <translation type="obsolete">Archivos de imagen (*.png *.jpg *.gif)</translation> </message> <message> @@ -1307,57 +1787,352 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </context> <context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="95"/> + <source>General</source> + <translation>General</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="96"/> + <source>Valid From</source> + <translation>Válido desde</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="97"/> + <source>Valid To</source> + <translation>Válido hasta</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="98"/> + <source>Serial Number</source> + <translation>Número de serie</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="99"/> + <source>Version</source> + <translation>Versión</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="101"/> + <source>Subject</source> + <translation>Sujeto</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="102"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="124"/> + <source>Organization</source> + <translation>Organización</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="103"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="125"/> + <source>Common Name</source> + <translation>Nombre común</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="104"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="126"/> + <source>Locality</source> + <translation>Localidad</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="105"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="127"/> + <source>Organizational Unit</source> + <translation>Unidad organizativa</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="106"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="128"/> + <source>Country</source> + <translation>País</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="107"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="129"/> + <source>State</source> + <translation>Estado</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="110"/> + <source>Alternate Subject Names</source> + <translation>Nombres de sujeto alternativos</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="115"/> + <source>E-mail Address</source> + <translation>Dirección de correo electrónico</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="117"/> + <source>DNS Name</source> + <translation>Nombre DNS</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="123"/> + <source>Issuer</source> + <translation>Emisor</translation> + </message> +</context> +<context> <name>Swift::QtChatListWindow</name> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="62"/> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="66"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="86"/> <source>Add New Bookmark</source> <translation>Añadir Nuevo Marcador</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="63"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="83"/> <source>Edit Bookmark</source> <translation>Editar Marcador</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="64"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> <source>Remove Bookmark</source> <translation>Eliminar Marcador</translation> </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="173"/> + <source>Clear recents</source> + <translation>Borrar recientes</translation> + </message> </context> <context> <name>Swift::QtChatView</name> <message> - <location filename="../QtUI/QtChatView.cpp" line="61"/> + <location filename="../QtUI/QtChatView.cpp" line="73"/> <source>Clear log</source> <translation>Borrar texto</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="62"/> + <location filename="../QtUI/QtChatView.cpp" line="74"/> <source>You are about to clear the contents of your chat log.</source> <translation>Estás a punto de borrar el contenido de esta conversación.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="63"/> + <location filename="../QtUI/QtChatView.cpp" line="75"/> <source>Are you sure?</source> <translatorcomment>TMPFIX, genero?</translatorcomment> <translation>¿Estás seguro?</translation> </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="219"/> + <source>%1 edited</source> + <translation>%1 editado</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="400"/> + <source>Waiting for other side to accept the transfer.</source> + <translation>Esperando a que el otro lado acepte la transferencia.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="401"/> + <location filename="../QtUI/QtChatView.cpp" line="406"/> + <location filename="../QtUI/QtChatView.cpp" line="417"/> + <source>Cancel</source> + <translation>Cancelar</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="405"/> + <source>Negotiating...</source> + <translation>Negociando...</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="420"/> + <source>Transfer has been canceled!</source> + <translation>¡La transferencia ha sido cancelada!</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="424"/> + <source>Transfer completed successfully.</source> + <translation>La transferencia se ha completado con éxito.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="427"/> + <source>Transfer failed.</source> + <translation>La transferencia ha fallado.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="437"/> + <source>Started whiteboard chat</source> + <translation>Se ha comenzado una conversación de pizarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="438"/> + <source>Show whiteboard</source> + <translation>Mostrar pizarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="440"/> + <source>Whiteboard chat has been canceled</source> + <translation>La conversación de pizarra ha sido cancelada</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="442"/> + <source>Whiteboard chat request has been rejected</source> + <translation>La solicitud de conversación de pizarra ha sido rechazada</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="451"/> + <source>Return to room</source> + <translation>Volver a la sala</translation> + </message> </context> <context> <name>Swift::QtChatWindow</name> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="302"/> + <location filename="../QtUI/QtChatWindow.cpp" line="157"/> + <source>Correcting</source> + <translation>Corrigiendo</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="269"/> + <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> + <translation>Esta conversación quizá no sea compatible con la corrección de mensajes. Si envías una corrección de todos modos, es posbile que aparezca como un mensaje duplicado</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="271"/> + <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> + <translation>Esta conversación no es compatible con la corrección de mensajes. Si envías una corrección de todos modos, aparecerá como un mensaje duplicado</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="546"/> <source>This message has not been received by your server yet.</source> <translation>Este mensaje no ha sido recibido por tu servidor todavía.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="304"/> + <location filename="../QtUI/QtChatWindow.cpp" line="553"/> <source>This message may not have been transmitted.</source> <translation>Es posible que este mensaje no se haya transmitido.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="324"/> + <location filename="../QtUI/QtChatWindow.cpp" line="562"/> + <source>The receipt for this message has been received.</source> + <translation>Se ha recibido la confirmación para este mensaje.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="565"/> + <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> + <translatorcomment>Plural pain</translatorcomment> + <translation>Aún no se ha recibido la confirmación para este mensaje. Es posible que los destinatarios no lo hayan recibido.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="613"/> + <source>Send file</source> + <translation>Enviar archivo</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="615"/> + <location filename="../QtUI/QtChatWindow.cpp" line="623"/> + <location filename="../QtUI/QtChatWindow.cpp" line="660"/> + <location filename="../QtUI/QtChatWindow.cpp" line="664"/> + <source>Cancel</source> + <translation>Cancelar</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="616"/> + <source>Set Description</source> + <translation>Establecer descripción</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="617"/> + <source>Send</source> + <translation>Enviar</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="621"/> + <source>Receiving file</source> + <translation>Recibiendo archivo</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="624"/> + <location filename="../QtUI/QtChatWindow.cpp" line="665"/> + <source>Accept</source> + <translation>Aceptar</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="659"/> + <source>Starting whiteboard chat</source> + <translation>Comenzando conversación de pizarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="663"/> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 quiere comenzar una conversación de pizarra</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="700"/> + <source>File transfer description</source> + <translation>Descripción de la transferencia de archivo</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="701"/> + <source>Description:</source> + <translation>Descripción:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="715"/> + <source>Save File</source> + <translation>Guardar archivo</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="908"/> + <source>Change subject…</source> + <translation>Cambiar el tema...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="909"/> + <source>Configure room…</source> + <translation>Configurar sala...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="910"/> + <source>Edit affiliations…</source> + <translation>Editar afiliaciones...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="911"/> + <source>Destroy room</source> + <translation>Destruir la sala</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="912"/> + <source>Invite person to this room…</source> + <translation>Invitar a una persona a esta sala...</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="922"/> + <source>Change room subject</source> + <translation>Cambiar el tema de la sala</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="922"/> + <source>New subject:</source> + <translation>Nuevo tema:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="940"/> + <source>Confirm room destruction</source> + <translation>Confirmar destrucción de la sala</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="941"/> + <source>Are you sure you want to destroy the room?</source> + <translation>¿Estás seguro de que quieres destruir la sala?</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="942"/> + <source>This will destroy the room.</source> + <translation>Esto destruirá la sala.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="993"/> + <source>Accept Invite</source> + <translation>Aceptar invitación</translation> + </message> + <message> <source>Couldn't send message: %1</source> - <translation>No se ha podido enviar el mensaje: %1</translation> + <translation type="obsolete">No se ha podido enviar el mensaje: %1</translation> </message> </context> @@ -1365,15 +2140,15 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtContactEditWidget</name> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="28"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="122"/> <source>Name:</source> <translation>Nombre:</translation> </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="34"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="42"/> <source>Groups:</source> <translation>Grupos:</translation> </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="56"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="65"/> <source>New Group:</source> <translation>Nuevo Grupo:</translation> @@ -1383,30 +2158,30 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtContactEditWindow</name> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="26"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="28"/> <source>Edit contact</source> <translation>Editar contacto</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="41"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="43"/> <source>Remove contact</source> <translation>Eliminar contacto</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="44"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="46"/> <source>OK</source> <translation>Aceptar</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="82"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="94"/> <source>Confirm contact deletion</source> <translation>Confirmar eliminación del contacto</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="83"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="95"/> <source>Are you sure you want to delete this contact?</source> <translation>¿Seguro que quieres eliminar este contacto?</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="84"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="96"/> <source>This will remove the contact '%1' from all groups they may be in.</source> <translation>Esto eliminará el contacto '%1' de todos los grupos en los que esté.</translation> @@ -1416,5 +2191,5 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtEventWindow</name> <message> - <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="47"/> + <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="48"/> <source>Display Notice</source> <translation>Mostrar Aviso</translation> @@ -1422,7 +2197,42 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </context> <context> + <name>Swift::QtFileTransferListWidget</name> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="39"/> + <source>Clear Finished Transfers</source> + <translation>Borrar transferencias finalizadas</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="44"/> + <source>File Transfer List</source> + <translation>Lista de transferencia de archivos</translation> + </message> +</context> +<context> + <name>Swift::QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.cpp" line="57"/> + <source>History</source> + <translation>Historial</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="25"/> + <source>Users to invite to this chat (one per line):</source> + <translatorcomment>conversación o sala?</translatorcomment> + <translation>Usuarios a invitar a esta conversación (uno por línea):</translation> + </message> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="31"/> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>Si quieres proporcionar un motivo para la invitación, introdúcelo aquí</translation> + </message> +</context> +<context> <name>Swift::QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.cpp" line="15"/> + <location filename="../QtUI/QtJoinMUCWindow.cpp" line="19"/> <source>someroom@rooms.example.com</source> <translation>algunasala@salas.ejemplo.com</translation> @@ -1432,53 +2242,53 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtLoginWindow</name> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="81"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="92"/> <source>User address:</source> <translation>Dirección de usuario:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="86"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="87"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="98"/> <source>User address - looks like someuser@someserver.com</source> <translation>Dirección de usuario - Similar a usuario@algunservidor.com</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="91"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="102"/> <source>Example: alice@wonderland.lit</source> <translation>Ejemplo: alicia@paismaravillas.lit</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="108"/> <source>Password:</source> <translation>Contraseña:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="118"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="119"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="129"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="130"/> <source>Click if you have a personal certificate used for login to the service.</source> <translation>Haz click si tienes un certificado personal para conectar al servicio.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="125"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="299"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="353"/> <source>Connect</source> <translation>Conectar</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="154"/> <source>Remember Password?</source> <translation>¿Recordar Contraseña?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="138"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="156"/> <source>Login Automatically?</source> <translation>¿Conectar Automáticamente?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="150"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="168"/> <source>&Swift</source> <translation>&Swift</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="152"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> <source>&General</source> <translatorcomment>TMPFIX, used where?</translatorcomment> @@ -1486,67 +2296,94 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="160"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="178"/> <source>&About %1</source> <translation>&Acerca de %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="165"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="183"/> <source>&Show Debug Console</source> <translation>&Mostrar Consola de Depuración</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="169"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="188"/> + <source>Show &File Transfer Overview</source> + <translatorcomment>VERY long. Alternative "Mostrar lista de transferencias"</translatorcomment> + <translation>Mostrar vista general de transferencia de &archivos</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="193"/> <source>&Play Sounds</source> <translation>&Reproducir Sonidos</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="175"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="199"/> <source>Display Pop-up &Notifications</source> <translation>Mostrar &Notificaciones Emergentes</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="190"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="211"/> <source>&Quit</source> <translation>&Salir</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="205"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove profile</source> <translation>Eliminar perfil</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="205"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove the profile '%1'?</source> <translation>¿Eliminar el perfil '%1'?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="299"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="353"/> <source>Cancel</source> <translation>Cancelar</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="320"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="367"/> + <source>Confirm terms of use</source> + <translation>Confirmar condiciones de uso</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="410"/> <source>Select an authentication certificate</source> <translation>Selecciona un certificado de autenticación</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="420"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="410"/> + <source>P12 files (*.cert *.p12 *.pfx);;All files (*.*)</source> + <translation>Archivos P12 (*.cert *.p12 *.pfx);;Todos los archivos (*.*)</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="523"/> <source>The certificate presented by the server is not valid.</source> <translation>El certificado presentado por el servidor no es válido.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="421"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="524"/> <source>Would you like to permanently trust this certificate? This must only be done if you know it is correct.</source> <translation>¿Quieres confiar en este certificado de forma permanente? Esto solo debe hacerse si sabes que es correcto.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="423"/> <source>Subject: %1</source> - <translation>Sujeto: %1</translation> + <translation type="obsolete">Sujeto: %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="424"/> <source>SHA-1 Fingerprint: %1</source> - <translation>Huella digital SHA-1: %1</translation> + <translation type="obsolete">Huella digital SHA-1: %1</translation> + </message> +</context> +<context> + <name>Swift::QtMUCConfigurationWindow</name> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="34"/> + <source>Cancel</source> + <translation>Cancelar</translation> + </message> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="37"/> + <source>OK</source> + <translation>Aceptar</translation> </message> </context> @@ -1563,16 +2400,16 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtMainWindow</name> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="64"/> + <location filename="../QtUI/QtMainWindow.cpp" line="79"/> <source>&Contacts</source> <translation>&Contactos</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="71"/> - <location filename="../QtUI/QtMainWindow.cpp" line="137"/> + <location filename="../QtUI/QtMainWindow.cpp" line="88"/> + <location filename="../QtUI/QtMainWindow.cpp" line="205"/> <source>&Notices</source> <translation>Av&isos</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="72"/> + <location filename="../QtUI/QtMainWindow.cpp" line="87"/> <source>C&hats</source> <translatorcomment>TMPFIX? Kev said "conversations" context</translatorcomment> @@ -1580,49 +2417,84 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="76"/> + <location filename="../QtUI/QtMainWindow.cpp" line="96"/> <source>&View</source> <translation>&Ver</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="78"/> + <location filename="../QtUI/QtMainWindow.cpp" line="98"/> <source>&Show offline contacts</source> <translation>&Mostrar contactos desconectados</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="84"/> + <location filename="../QtUI/QtMainWindow.cpp" line="106"/> + <source>&Show Emoticons</source> + <translation>&Mostrar emoticonos</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="120"/> <source>&Actions</source> <translation>&Acciones</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>Editar &Perfil</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="122"/> + <source>Edit &Profile…</source> + <translation>Editar &Perfil…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <source>Enter &Room…</source> + <translation>Entrar a &Sala…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>Entrar a &Sala</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="129"/> + <source>&View History…</source> + <translation>&Ver historial...</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Añadir Contacto</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="133"/> + <source>&Add Contact…</source> + <translation>&Añadir Contacto…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Editar Contacto Seleccionado</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="136"/> + <source>&Edit Selected Contact…</source> + <translation>&Editar Contacto Seleccionado…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> - <translation>Comenzar &Conversación</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="140"/> + <source>Start &Chat…</source> + <translation>Comenzar &Conversación...</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="103"/> + <location filename="../QtUI/QtMainWindow.cpp" line="143"/> + <source>Run Server Command</source> + <translation>Ejecutar comando de servidor</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="146"/> <source>&Sign Out</source> <translation>&Desconectar</translation> </message> <message> + <location filename="../QtUI/QtMainWindow.cpp" line="150"/> + <source>&Request Delivery Receipts</source> + <translation>&Pedir confirmación de entrega</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="161"/> + <source>Collecting commands...</source> + <translation>Recopilando comandos...</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="216"/> + <source>&Chats</source> + <translation>C&onversaciones</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="339"/> + <source>No Available Commands</source> + <translation>No hay comandos disponibles</translation> + </message> + <message> <source>Notices</source> <translatorcomment>TMPFIX, used?</translatorcomment> @@ -1633,20 +2505,20 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtNameWidget</name> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="47"/> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> <source>Show Nickname</source> <translation>Mostrar Nick</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="47"/> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> <source>(No Nickname Set)</source> <translation>(Sin Nick Definido)</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="55"/> + <location filename="../QtUI/QtNameWidget.cpp" line="56"/> <source>Show Address</source> <translation>Mostrar Dirección</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="62"/> + <location filename="../QtUI/QtNameWidget.cpp" line="63"/> <source>Edit Profile</source> <translation>Editar Perfil</translation> @@ -1654,4 +2526,42 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </context> <context> + <name>Swift::QtOccupantListWidget</name> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="46"/> + <source>No actions for this user</source> + <translation>No hay acciones para este usuario</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="55"/> + <source>Kick user</source> + <translation>Expulsar usuario</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="56"/> + <source>Kick and ban user</source> + <translation>Expulsar y vetar usuario</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="57"/> + <source>Make moderator</source> + <translation>Hacer moderador</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="58"/> + <source>Make participant</source> + <translation>Hacer participante</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="59"/> + <source>Remove voice</source> + <translation>Quitar la voz</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="60"/> + <source>Add to contacts</source> + <translation>Añadir a contactos</translation> + </message> +</context> +<context> <name>Swift::QtProfileWindow</name> <message> @@ -1672,4 +2582,56 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </context> <context> + <name>Swift::QtRosterHeader</name> + <message> + <location filename="../QtUI/QtRosterHeader.cpp" line="59"/> + <source>Connection is secured</source> + <translation>La conexión es segura</translation> + </message> +</context> +<context> + <name>Swift::QtRosterWidget</name> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="58"/> + <source>Edit…</source> + <translation>Editar...</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="59"/> + <source>Remove</source> + <translation>Eliminar</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="63"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>Send File</source> + <translation>Enviar archivo</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="69"/> + <source>Start Whiteboard Chat</source> + <translation>Comenzar conversación de pizarra</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>All Files (*);;</source> + <translation>Todos los archivos (*);;</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="96"/> + <source>Rename</source> + <translation>Renombrar</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Rename group</source> + <translation>Renombrar grupo</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Enter a new name for group '%1':</source> + <translation>Introduce un nombre nuevo para el grupo '%1':</translation> + </message> +</context> +<context> <name>Swift::QtStatusWidget</name> <message> @@ -1679,5 +2641,5 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </message> <message> - <location filename="../QtUI/QtStatusWidget.cpp" line="263"/> + <location filename="../QtUI/QtStatusWidget.cpp" line="261"/> <source>(No message)</source> <translation>(Sin mensaje)</translation> @@ -1701,5 +2663,5 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <source>You have already replied to this request</source> <translatorcomment>TMPFIX, used where?</translatorcomment> - <translation>Ya has respondido a esta petición</translation> + <translation>Ya has respondido a esta solicitud</translation> </message> <message> @@ -1714,10 +2676,10 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </message> <message> - <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="32"/> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="33"/> <source>No</source> <translation>No</translation> </message> <message> - <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="34"/> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="35"/> <source>Defer</source> <translation>Posponer</translation> @@ -1727,27 +2689,22 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtTreeWidget</name> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="144"/> <source>Edit</source> - <translation>Editar</translation> + <translation type="obsolete">Editar</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="145"/> <source>Remove</source> - <translation>Eliminar</translation> + <translation type="obsolete">Eliminar</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="157"/> <source>Rename</source> - <translation>Renombrar</translation> + <translation type="obsolete">Renombrar</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="167"/> <source>Rename group</source> - <translation>Renombrar grupo</translation> + <translation type="obsolete">Renombrar grupo</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="167"/> <source>Enter a new name for group '%1':</source> - <translation>Introduce un nombre nuevo para el grupo %1:</translation> + <translation type="obsolete">Introduce un nombre nuevo para el grupo %1:</translation> </message> <message> @@ -1759,5 +2716,5 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtUserSearchDetailsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="17"/> + <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="22"/> <source>Please choose a name for the contact, and select the groups you want to add the contact to.</source> <translatorcomment>Somewhat free translation. TMPFIX?</translatorcomment> @@ -1786,35 +2743,35 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtUserSearchWindow</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="32"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> <source>Add Contact</source> <translation>Añadir Contacto</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="32"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> <source>Chat to User</source> <translation>Conversar con Usuario</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="43"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="45"/> <source>alice@wonderland.lit</source> <translation>alicia@paismaravillas.lit</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="223"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> <source>How would you like to find the user to add?</source> <translation>¿Cómo quieres buscar al usuario a añadir?</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="226"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="299"/> <source>How would you like to find the user to chat to?</source> <translation>¿Cómo quieres buscar al usuario con el que conversar?</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="251"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="326"/> <source>Error while searching</source> <translation>Error durante la búsqueda</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="257"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="332"/> <source>This server doesn't support searching for users.</source> <translation>Este servidor no soporta búsqueda de usuarios.</translation> @@ -1824,8 +2781,26 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q <name>Swift::QtWebView</name> <message> - <location filename="../QtUI/QtWebView.cpp" line="61"/> + <location filename="../QtUI/QtWebView.cpp" line="66"/> <source>Clear</source> <translation>Borrar texto</translation> </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="67"/> + <source>Increase font size</source> + <translation>Incrementar el tamaño de la letra</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="68"/> + <source>Decrease font size</source> + <translation>Reducir el tamaño de la letra</translation> + </message> +</context> +<context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <location filename="../QtUI/Whiteboard/QtWhiteboardWindow.cpp" line="380"/> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>Cerrar la ventana es equivalente a cerrar la sesión. ¿Estás seguro de que quieres hacer esto?</translation> + </message> </context> <context> @@ -1852,10 +2827,10 @@ Si escoges posponer esta elección, se te preguntará de nuevo la próxima vez q </message> <message> - <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="75"/> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="78"/> <source><!-- IN --></source> <translation><!-- ENTRANTE --></translation> </message> <message> - <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="79"/> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="82"/> <source><!-- OUT --></source> <translation><!-- SALIENTE --></translation> diff --git a/Swift/Translations/swift_fr.ts b/Swift/Translations/swift_fr.ts index 44bb980..b95c0d1 100644 --- a/Swift/Translations/swift_fr.ts +++ b/Swift/Translations/swift_fr.ts @@ -1438,25 +1438,25 @@ <message> <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>Editer le &Profil</translation> + <source>Edit &Profile…</source> + <translation>Editer le &Profil…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>Joindre un &Salon</translation> + <source>Enter &Room…</source> + <translation>Joindre un &Salon…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Ajouter un Contact</translation> + <source>&Add Contact…</source> + <translation>&Ajouter un Contact…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Editer le Contact Sélectionné</translation> + <source>&Edit Selected Contact…</source> + <translation>&Editer le Contact Sélectionné…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> + <source>Start &Chat…</source> <translation>Démarrer une &Discussion</translation> </message> diff --git a/Swift/Translations/swift_gl.ts b/Swift/Translations/swift_gl.ts index 00540a3..c189a1d 100644 --- a/Swift/Translations/swift_gl.ts +++ b/Swift/Translations/swift_gl.ts @@ -1442,25 +1442,25 @@ <message> <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>Editar &perfil</translation> + <source>Edit &Profile…</source> + <translation>Editar &perfil…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>Entrar á &sala</translation> + <source>Enter &Room…</source> + <translation>Entrar á &sala…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Engadir contacto</translation> + <source>&Add Contact…</source> + <translation>&Engadir contacto…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Editar contacto seleccionado</translation> + <source>&Edit Selected Contact…</source> + <translation>&Editar contacto seleccionado…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> + <source>Start &Chat…</source> <translation>Comezar &conversa</translation> </message> diff --git a/Swift/Translations/swift_he.ts b/Swift/Translations/swift_he.ts new file mode 100644 index 0000000..4a810f8 --- /dev/null +++ b/Swift/Translations/swift_he.ts @@ -0,0 +1,2717 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.0" language="he_IL"> +<defaultcodec>UTF-8</defaultcodec> +<context> + <name></name> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> + <source>Starting chat with %1% in chatroom %2%</source> + <translation>מתחיל כעת שיחה עם %1% בתוך חדר שיחה %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="62"/> + <source>Starting chat with %1% - %2%</source> + <translation>מתחיל כעת שיחה עם %1% - %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> + <source>This chat doesn't support delivery receipts.</source> + <translation>שיחה זו לא תומכת קבלות משלוח.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="204"/> + <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> + <translation>ייתכן כי שיחה זו לא תומכת בקבלות משלוח. אתה עשוי שלא לקבל קבלות משלוח עבור ההודעות אשר נשלחות מן הקצה שלך.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="216"/> + <location filename="../Controllers/FileTransfer/FileTransferController.cpp" line="42"/> + <source>me</source> + <translation>אני</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="324"/> + <source>%1% has gone offline</source> + <translation>%1% במצב לא מקוון כעת</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="328"/> + <source>%1% has become available</source> + <translation>%1% במצב זמין כעת</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="330"/> + <source>%1% has gone away</source> + <translation>%1% במצב נעדר כעת</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="332"/> + <source>%1% is now busy</source> + <translation>%1% עסוק/ה כעת</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="74"/> + <source>The day is now %1%</source> + <translation>היום כעת הוא %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <source>Couldn't send message: %1%</source> + <translation>לא היתה אפשרות לשלוח הודעה: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> + <source>Error sending message</source> + <translation>שגיאה בשליחת הודעה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="275"/> + <source>Bad request</source> + <translation>בקשה רעה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="276"/> + <source>Conflict</source> + <translation>התנגשות</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="277"/> + <source>This feature is not implemented</source> + <translation>תכונה זו אינה מיושמת</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="278"/> + <source>Forbidden</source> + <translation>אסור</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="279"/> + <source>Recipient can no longer be contacted</source> + <translation>אין אפשרות לתקשר עוד עם נמען</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="280"/> + <source>Internal server error</source> + <translation>שגיאת שרת פנימית</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="281"/> + <source>Item not found</source> + <translation>פריט לא נמצא</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="282"/> + <source>JID Malformed</source> + <translation>כתובת JID פגומה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="283"/> + <source>Message was rejected</source> + <translation>הודעה נדחתה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="284"/> + <source>Not allowed</source> + <translation>לא מותר</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="285"/> + <source>Not authorized</source> + <translation>לא מורשה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="286"/> + <source>Payment is required</source> + <translation>נדרש תשלום</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="287"/> + <source>Recipient is unavailable</source> + <translation>נמען אינו זמין</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="288"/> + <source>Redirect</source> + <translation>הכוונה חוזרת</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="289"/> + <source>Registration required</source> + <translation>נדרשת הרשמה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="290"/> + <source>Recipient's server not found</source> + <translation>שרת נמען לא נמצא</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="291"/> + <source>Remote server timeout</source> + <translation>תם זמן שרת מרוחק</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="292"/> + <source>The server is low on resources</source> + <translatorcomment>רדוד</translatorcomment> + <translation>השרת הינו דל במשאבים</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="293"/> + <source>The service is unavailable</source> + <translation>השירות אינו זמין</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="294"/> + <source>A subscription is required</source> + <translatorcomment>נדרש מינוי</translatorcomment> + <translation>נדרשת הרשמה</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="295"/> + <source>Undefined condition</source> + <translatorcomment>תנאי</translatorcomment> + <translation>מצב לא מוגדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="296"/> + <source>Unexpected request</source> + <translation>בקשה לא צפויה</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> + <source>Room %1% is not responding. This operation may never complete.</source> + <translation>חדר %1% אינו מגיב. פעולה זו עשויה שלא להסתיים.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="233"/> + <source>Unable to enter this room</source> + <translation>אין אפשרות להיכנס אל חדר זה</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="239"/> + <source>Unable to enter this room as %1%, retrying as %2%</source> + <translation>אין אפשרות להיכנס אל חדר זה תחת השם %1%, מנסה כעת להיכנס בתור %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> + <source>No nickname specified</source> + <translation>לא צוין שם כינוי</translation> + </message> + <message> + <source>A password needed</source> + <translation type="obsolete">נדרשת מילת מעבר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="251"/> + <source>Only members may enter</source> + <translation>רק חברים מורשים להיכנס</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="255"/> + <source>You are banned from the room</source> + <translation>נאסרת מן חדר זה</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="259"/> + <source>The room is full</source> + <translation>החדר מלא</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="263"/> + <source>The room does not exist</source> + <translation>החדר לא קיים</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="269"/> + <source>Couldn't join room: %1%.</source> + <translation>לא היה ניתן להצטרף אל חדר: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="281"/> + <source>You have entered room %1% as %2%.</source> + <translation>נכנסת אל החדר %1% בכינוי %2%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="332"/> + <source>%1% has entered the room as a %2%.</source> + <translation>%1% נכנס/ה אל החדר בתפקיד %2%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="335"/> + <source>%1% has entered the room.</source> + <translation>%1% נכנס/ה אל החדר.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="384"/> + <source>moderator</source> + <translation>אחראי</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="385"/> + <source>participant</source> + <translation>משתתף</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="386"/> + <source>visitor</source> + <translation>מבקר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="436"/> + <source>The room subject is now: %1%</source> + <translation>נושא החדר כעת הוא: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="470"/> + <source>%1% is now a %2%</source> + <translation>%1% מצוי/ה כעת במצב %2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="486"/> + <source>Moderators</source> + <translation>אחראים</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> + <source>Participants</source> + <translation>משתתפים</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="488"/> + <source>Visitors</source> + <translation>מבקרים</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="489"/> + <source>Occupants</source> + <translation>נוכחים</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> + <source>Trying to enter room %1%</source> + <translation>מנסה כעת להיכנס אל חדר %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="544"/> + <source>%1% has left the room%2%</source> + <translation>%1% עזב/ה את החדר%2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="548"/> + <source>You have been kicked out of the room</source> + <translation>נבעטת החוצה מן החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="549"/> + <source>You have been banned from the room</source> + <translation>נאסרת מן החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="550"/> + <source>You are no longer a member of the room and have been removed</source> + <translation>אינך עוד במעמד של חבר בחדר זה והוסרת</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="551"/> + <source>The room has been destroyed</source> + <translation>החדר הוחרב</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="667"/> + <source>%1% has left the room</source> + <translation>%1% עזב/ה את החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="719"/> + <source>Room configuration failed: %1%.</source> + <translation>תצורת חדר נכשלה: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="725"/> + <source>Occupant role change failed: %1%.</source> + <translation>שינוי תפקיד נוכח נכשל: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="553"/> + <source>You have left the room</source> + <translation>עזבת את החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="247"/> + <source>The correct room password is needed</source> + <translation>סיסמת חדר מדויקת הינה נחוצה</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="632"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="696"/> + <source> and </source> + <translation> וגם </translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="656"/> + <source>%1% have entered the room</source> + <translation>%1% נכנסו אל החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="659"/> + <source>%1% has entered the room</source> + <translation>%1% נכנס/ה אל החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="664"/> + <source>%1% have left the room</source> + <translation>%1% עזבו את החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="672"/> + <source>%1% have entered then left the room</source> + <translation>%1% נכנסו ואז עזבו את החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="675"/> + <source>%1% has entered then left the room</source> + <translation>%1% נכנס/ה ואז עזב/ה את החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="680"/> + <source>%1% have left then returned to the room</source> + <translation>%1% עזבו ואז חזרו אל החדר</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="683"/> + <source>%1% has left then returned to the room</source> + <translation>%1% עזב/ה ואז חזר/ה אל החדר</translation> + </message> + <message> + <location filename="../Controllers/EventNotifier.cpp" line="59"/> + <source>%1% wants to add you to his/her contact list</source> + <translation>%1% רוצה להוסיף אותך אל רשימת הקשרים שלו/שלה</translation> + </message> + <message> + <location filename="../Controllers/EventNotifier.cpp" line="63"/> + <source>Error</source> + <translation>שגיאה</translation> + </message> + <message> + <location filename="../Controllers/EventNotifier.cpp" line="67"/> + <source>%1% has invited you to enter the %2% room</source> + <translation>%1% הזמינך להיכנס אל החדר %2%</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="466"/> + <source>User address invalid. User address should be of the form 'alice@wonderland.lit'</source> + <translation>כתובת משתמש שגויה. על כתובת משתמש להיות בצורה זו 'alice@wonderland.lit'</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="567"/> + <source>Unknown Error</source> + <translation>שגיאה לא מוכרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="568"/> + <source>Unable to find server</source> + <translation>אין אפשרות למצוא שרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="569"/> + <source>Error connecting to server</source> + <translation>שגיאה בעת התחברות אל שרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="570"/> + <source>Error while receiving server data</source> + <translation>שגיאה במהלך קבלת נתוני שרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="571"/> + <source>Error while sending data to the server</source> + <translation>שגיאה במהלך שליחת נתונים אל השרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="572"/> + <source>Error parsing server data</source> + <translation>שגיאה בניתוח נתוני שרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="573"/> + <source>Login/password invalid</source> + <translatorcomment>credentials</translatorcomment> + <translation>התחברות/סיסמה שגויים</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="574"/> + <source>Error while compressing stream</source> + <translation>שגיאה במהלך דחיסת זרם</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="575"/> + <source>Server verification failed</source> + <translation>אימות שרת נכשל</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="576"/> + <source>Authentication mechanisms not supported</source> + <translation>מנגנון אימות לא נתמך</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="577"/> + <source>Unexpected response</source> + <translation>מענה לא צפוי</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="578"/> + <source>Error binding resource</source> + <translation>שגיאה בכריכת משאב</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="579"/> + <source>Error starting session</source> + <translation>שגיאה בהתחלת סשן</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="580"/> + <source>Stream error</source> + <translation>שגיאת זרם</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="581"/> + <source>Encryption error</source> + <translation>שגיאת הצפנה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="582"/> + <source>Error loading certificate (Invalid password?)</source> + <translation>שגיאה בטעינת תעודה (סיסמה שגויה?)</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="583"/> + <source>Certificate not authorized</source> + <translation>תעודה לא מורשית</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="584"/> + <source>Certificate card removed</source> + <translation>כרטיס תעודה הוסר</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="586"/> + <source>Unknown certificate</source> + <translation>תעודה לא מוכרת</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="587"/> + <source>Certificate has expired</source> + <translation>תעודה פקעה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="588"/> + <source>Certificate is not yet valid</source> + <translation>תעודה אינה תקפה עדיין</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="589"/> + <source>Certificate is self-signed</source> + <translation>התעודה הינה חתומה באופן עצמי</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="590"/> + <source>Certificate has been rejected</source> + <translation>תעודה נדחתה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="591"/> + <source>Certificate is not trusted</source> + <translation>התעודה אינה מהימנה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="592"/> + <source>Certificate cannot be used for encrypting your connection</source> + <translation>תעודה לא שמישה עבור הצפנת החיבור שלך</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="593"/> + <source>Certificate path length constraint exceeded</source> + <translation>הגבלת אורך נתיב תעודה נחצתה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="594"/> + <source>Invalid certificate signature</source> + <translation>חתימת תעודה שגויה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="595"/> + <source>Invalid Certificate Authority</source> + <translation>רשות תעודה שגויה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="596"/> + <source>Certificate does not match the host identity</source> + <translation>תעודה לא תואמת את זהות המארח</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="597"/> + <source>Certificate has been revoked</source> + <translatorcomment>נשללה</translatorcomment> + <translation>תעודה נפסלה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="598"/> + <source>Unable to determine certificate revocation state</source> + <translation>אין אפשרות לקבוע מצב פסילות של תעודה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="608"/> + <source>Certificate error</source> + <translation>שגיאת תעודה</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="615"/> + <source>Re-enter credentials and retry</source> + <translation>הזן נתוני התחברות מחדש ונסה שוב</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="628"/> + <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> + <translation>מנותק מן %1%: %2%. כדי להתחבר שוב, התנתק וספק את הסיסמה שלך פעם נוספת.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="634"/> + <source>Reconnect to %1% failed: %2%. Will retry in %3% seconds.</source> + <translation>מתחבר מחדש כעת אל %1% נכשל: %2%. ניסיון נוסף יערך בעוד %3% שניות.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="637"/> + <source>Disconnected from %1%: %2%.</source> + <translation>מנותק מן %1%: %2%.</translation> + </message> + <message> + <location filename="../Controllers/Roster/RosterController.cpp" line="131"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="157"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="222"/> + <source>Contacts</source> + <translation>קשרים</translation> + </message> + <message> + <location filename="../Controllers/Roster/RosterController.cpp" line="263"/> + <source>Server %1% rejected contact list change to item '%2%'</source> + <translation>שרת %1% דחה שינוי רשימת קשרים לפריט '%2%'</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="15"/> + <location filename="../Controllers/StatusUtil.cpp" line="16"/> + <source>Available</source> + <translation>זמין</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="17"/> + <location filename="../Controllers/StatusUtil.cpp" line="18"/> + <source>Away</source> + <translation>נעדר</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="19"/> + <source>Busy</source> + <translation>עסוק</translation> + </message> + <message> + <location filename="../Controllers/StatusUtil.cpp" line="20"/> + <source>Offline</source> + <translation>לא מקוון</translation> + </message> + <message> + <location filename="../Controllers/ProfileController.cpp" line="62"/> + <source>There was an error publishing your profile data</source> + <translation>אירעה שגיאה בפרסום נתוני הדיוקן שלך</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="33"/> + <source>%1% (%2%)</source> + <translation></translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="38"/> + <source>%1% and %2% others (%3%)</source> + <translation>%1% וגם %2% אחרים (%3%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="41"/> + <source>%1%, %2% (%3%)</source> + <translation></translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="63"/> + <source>TLS Client Certificate Selection</source> + <translation>מבחר תעודת לקוח TLS</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="64"/> + <source>Select a certificate to use for authentication</source> + <translation>בחר תעודה לשימוש עבור אימות</translation> + </message> +</context> +<context> + <name>CloseButton</name> + <message> + <location filename="../QtUI/QtStrings.h" line="17"/> + <source>Close Tab</source> + <translation>סגור כרטיסייה</translation> + </message> +</context> +<context> + <name>MAC_APPLICATION_MENU</name> + <message> + <location filename="../QtUI/QtStrings.h" line="79"/> + <source>Services</source> + <translation>שירותים</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="80"/> + <source>Hide %1</source> + <translation>הסתר %1</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="81"/> + <source>Hide Others</source> + <translation>הסתר אחרים</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="82"/> + <source>Show All</source> + <translation>הצג הכל</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="83"/> + <source>Preferences...</source> + <translation>העדפות...</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="84"/> + <source>Quit %1</source> + <translation>צא מתוך %1</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="85"/> + <source>About %1</source> + <translation>אודות %1</translation> + </message> +</context> +<context> + <name>QApplication</name> + <message> + <location filename="../QtUI/QtStrings.h" line="19"/> + <source>QT_LAYOUT_DIRECTION</source> + <comment>Translate this to LTR for left-to-right or RTL for right-to-left languages</comment> + <translation>RTL</translation> + </message> +</context> +<context> + <name>QDialogButtonBox</name> + <message> + <location filename="../QtUI/QtStrings.h" line="69"/> + <source>&Yes</source> + <translation>&כן</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="70"/> + <source>&No</source> + <translation>&לא</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="71"/> + <source>&OK</source> + <translation>&אישור</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="72"/> + <source>OK</source> + <translation>אישור</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="73"/> + <source>&Cancel</source> + <translation>&ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="74"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> +</context> +<context> + <name>QLineEdit</name> + <message> + <location filename="../QtUI/QtStrings.h" line="21"/> + <source>Select All</source> + <translation>בחר הכל</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="22"/> + <source>&Undo</source> + <translation>&בטל</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="23"/> + <source>&Redo</source> + <translation>בצע &שוב</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="24"/> + <source>Cu&t</source> + <translation>&גזור</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="25"/> + <source>&Copy</source> + <translation>הע&תק</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="26"/> + <source>&Paste</source> + <translation>ה&דבק</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="27"/> + <source>Delete</source> + <translation>מחק</translation> + </message> +</context> +<context> + <name>QMessageBox</name> + <message> + <location filename="../QtUI/QtStrings.h" line="76"/> + <source>Show Details...</source> + <translation>הצגת פרטים...</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="77"/> + <source>Hide Details...</source> + <translation>הסתרת פרטים...</translation> + </message> +</context> +<context> + <name>QObject</name> + <message> + <location filename="../QtUI/MUCSearch/MUCSearchEmptyItem.cpp" line="25"/> + <source>No rooms found</source> + <translation>לא נמצאו חדרים</translation> + </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="68"/> + <source>%1 would like to add you to their contact list.</source> + <translation>%1 רוצה להוסיפך אל רשימת הקשרים שלהם.</translation> + </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="71"/> + <source>%1 would like to add you to their contact list, saying '%2'</source> + <translation>%1 רוצה להוסיפך אל רשימת הקשרים שלהם, באומרם '%2'</translation> + </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="81"/> + <source>%1 has invited you to enter the %2 room.</source> + <translation>%1 הזמינך להיכנס אל החדר %2.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="980"/> + <source>You've been invited to enter the %1 room.</source> + <translation>הוזמנת להיכנס אל החדר %1.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="982"/> + <source>Reason: %1</source> + <translation>סיבה: %1</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="985"/> + <source>This person may not have really sent this invitation!</source> + <translation>אפשרי כי אישיות זו לא באמת שלחה את הזמנה זו!</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="46"/> + <source>Direction</source> + <translation>כיוון</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="47"/> + <source>Other Party</source> + <translation>צד אחר</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="48"/> + <source>State</source> + <translation>מצב</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="49"/> + <source>Progress</source> + <translation>התקדמות</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="50"/> + <source>Size</source> + <translation>גודל</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Incoming</source> + <translation>נכנסת</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Outgoing</source> + <translation>יוצאת</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="74"/> + <source>Waiting for start</source> + <translation>ממתין כעת להתחלה</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="76"/> + <source>Waiting for other side to accept</source> + <translation>ממתין כעת לצד השני לקבל</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="78"/> + <source>Negotiating</source> + <translation>מסדיר כעת</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="80"/> + <source>Transferring</source> + <translation>מעביר כעת</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="82"/> + <source>Finished</source> + <translation>הוגמרה</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="84"/> + <source>Failed</source> + <translation>נכשלה</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="86"/> + <source>Canceled</source> + <translation>בוטלה</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="142"/> + <source>Connection Options</source> + <translation>אפשרויות חיבור</translation> + </message> +</context> +<context> + <name>QScrollBar</name> + <message> + <location filename="../QtUI/QtStrings.h" line="29"/> + <source>Scroll here</source> + <translation>גלול לכאן</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="30"/> + <source>Top</source> + <translation>למעלה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="31"/> + <source>Bottom</source> + <translation>למטה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="32"/> + <source>Page up</source> + <translation>עמוד מעלה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="33"/> + <source>Page down</source> + <translation>עמוד מטה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="34"/> + <source>Scroll up</source> + <translation>גלול מעלה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="35"/> + <source>Scroll down</source> + <translation>גלול מטה</translation> + </message> +</context> +<context> + <name>QTextControl</name> + <message> + <location filename="../QtUI/QtStrings.h" line="37"/> + <source>Select All</source> + <translation>בחר הכל</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="38"/> + <source>&Copy</source> + <translation>הע&תק</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="39"/> + <source>&Undo</source> + <translation>&בטל</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="40"/> + <source>&Redo</source> + <translation>בצע &שוב</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="41"/> + <source>Cu&t</source> + <translation>&גזור</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="42"/> + <source>&Paste</source> + <translation>ה&דבק</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="43"/> + <source>Delete</source> + <translation>מחק</translation> + </message> +</context> +<context> + <name>QWebPage</name> + <message> + <location filename="../QtUI/QtStrings.h" line="45"/> + <source>Copy Link</source> + <translation>העתק קישור</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="46"/> + <source>Copy</source> + <translation>העתק</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="47"/> + <source>Copy Image</source> + <translation>העתק תמונה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="48"/> + <source>Scroll here</source> + <translation>גלול לכאן</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="49"/> + <source>Top</source> + <translation>למעלה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="50"/> + <source>Bottom</source> + <translation>למטה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="51"/> + <source>Page up</source> + <translation>עמוד מעלה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="52"/> + <source>Page down</source> + <translation>עמוד מטה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="53"/> + <source>Scroll up</source> + <translation>גלול מעלה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="54"/> + <source>Scroll down</source> + <translation>גלול מטה</translation> + </message> +</context> +<context> + <name>QWizard</name> + <message> + <location filename="../QtUI/QtStrings.h" line="56"/> + <source>< &Back</source> + <translation>< &אחורה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="57"/> + <source>&Finish</source> + <translation>&סיום</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="58"/> + <source>&Help</source> + <translation>&עזרה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="59"/> + <source>Go Back</source> + <translation>חזור</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="60"/> + <source>Continue</source> + <translation>המשך</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="61"/> + <source>Commit</source> + <translation>בצע</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="62"/> + <source>Done</source> + <translation>סיים</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="63"/> + <source>Quit</source> + <translation>יציאה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="64"/> + <source>Help</source> + <translation>עזרה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="65"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="66"/> + <source>&Next</source> + <translation>&קדימה</translation> + </message> + <message> + <location filename="../QtUI/QtStrings.h" line="67"/> + <source>&Next ></source> + <translation>&קדימה ></translation> + </message> +</context> +<context> + <name>QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="14"/> + <source>Edit Affiliations</source> + <translation>עריכת שיוכים</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="28"/> + <source>Affiliation:</source> + <translation>שיוך:</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="36"/> + <source>Owner</source> + <translation>בעלים</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="41"/> + <source>Administrator</source> + <translation>מנהל</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="46"/> + <source>Member</source> + <translation>חבר</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="51"/> + <source>Outcast (Banned)</source> + <translation>מוחרם (נאסר)</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="68"/> + <source>Add User</source> + <translation>הוספת משתמש</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="75"/> + <source>Remove User</source> + <translation>הסרת משתמש</translation> + </message> +</context> +<context> + <name>QtBookmarkDetailWindow</name> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="20"/> + <source>Edit Bookmark Details</source> + <translation>עריכת פרטי סימנייה</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="40"/> + <source>Bookmark Name:</source> + <translation>שם סימנייה:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="50"/> + <source>Room Address:</source> + <translation>כתובת חדר:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="60"/> + <source>Your Nickname:</source> + <translation>שם כינוי:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="70"/> + <source>Room password:</source> + <translation>סיסמת חדר:</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="93"/> + <source>Enter automatically</source> + <translation>כנס אוטומטית</translation> + </message> + <message> + <source>Join automatically</source> + <translation type="obsolete">הצטרף אוטומטית</translation> + </message> +</context> +<context> + <name>QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.ui" line="14"/> + <source>Certificate Viewer</source> + <translation>מציג תעודה</translation> + </message> +</context> +<context> + <name>QtConnectionSettings</name> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="14"/> + <source>Connection Options</source> + <translation>אפשרויות חיבור</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="22"/> + <source>Connection Method:</source> + <translation>שיטת חיבור:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="36"/> + <source>Automatic</source> + <translation>אטומטי</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="41"/> + <source>Manual</source> + <translation>ידנית</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="46"/> + <source>BOSH</source> + <translation></translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="99"/> + <source>Secure connection:</source> + <translation>חיבור מאובטח:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="107"/> + <source>Never</source> + <translation>כלל לא</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="112"/> + <source>Encrypt when possible</source> + <translatorcomment>הצפן במידת האפשר</translatorcomment> + <translation>הצפן כאשר אפשר</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="117"/> + <source>Always encrypt</source> + <translation>הצפן תמיד</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="127"/> + <source>Allow Compression</source> + <translation>התר דחיסה</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="134"/> + <source>Allow sending password over insecure connection</source> + <translation>התר שליחת סיסמה על פני חיבור לא מאובטח</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="157"/> + <source>Manually select server</source> + <translation>בחר שרת באופן ידני</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="185"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="315"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="429"/> + <source>Hostname:</source> + <translation>שם מארח:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="208"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="338"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="452"/> + <source>Port:</source> + <translation>פורט:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="230"/> + <source>Connection Proxy</source> + <translation>חיבור פרוקסי</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="238"/> + <source>Proxy type:</source> + <translation>טיפוס פרוקסי:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="249"/> + <source>None</source> + <translation>ללא</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="254"/> + <source>Use system-configured proxy</source> + <translation>השתמש בהגדרות פרוקסי מערכת</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="259"/> + <source>SOCKS5</source> + <translation></translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="264"/> + <source>HTTP Connect</source> + <translation></translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="287"/> + <source>Override system-configured proxy</source> + <translation>עקוף הגדרות פרוקסי מערכת</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="382"/> + <source>BOSH URI:</source> + <translation>כתובת BOSH:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="401"/> + <source>Manually select HTTP proxy</source> + <translatorcomment>באופן ידני</translatorcomment> + <translation>בחר ידנית פרוקסי HTTP</translation> + </message> +</context> +<context> + <name>QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="14"/> + <source>History</source> + <translation>היסטוריה</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="22"/> + <source>Search:</source> + <translation>חיפוש:</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="42"/> + <source>Next</source> + <translation>קדימה</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="52"/> + <source>Previous</source> + <translation>אחורה</translation> + </message> +</context> +<context> + <name>QtJoinMUCWindow</name> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="20"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="113"/> + <source>Enter Room</source> + <translation>כניסה אל חדר</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="28"/> + <source>Room Address:</source> + <translation>כתובת חדר:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="42"/> + <source>Your Nickname:</source> + <translation>שם כינוי:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="59"/> + <source>Room Password:</source> + <translation>סיסמת חדר:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="71"/> + <source>Automatically configure newly created rooms</source> + <translation>הגדר אוטומטית חדרים חדשים אשר נוצרים</translation> + </message> + <message> + <source>Room:</source> + <translation type="obsolete">חדר:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="35"/> + <source>Search ...</source> + <translation>חיפוש ...</translation> + </message> + <message> + <source>Nickname:</source> + <translation type="obsolete">שם כינוי:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="106"/> + <source>Enter automatically in future</source> + <translation>כנס אוטומטית בעתיד</translation> + </message> +</context> +<context> + <name>QtMUCSearchWindow</name> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="14"/> + <source>Search Room</source> + <translation>חיפוש חדר</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="20"/> + <source>Service:</source> + <translation>שירות:</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="74"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="84"/> + <source>OK</source> + <translation>אישור</translation> + </message> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="96"/> + <source>List rooms</source> + <translatorcomment>מנה חדרים</translatorcomment> + <translation>רשימת חדרים</translation> + </message> +</context> +<context> + <name>QtUserSearchFieldsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="27"/> + <source>Nickname:</source> + <translation>שם כינוי:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="37"/> + <source>First name:</source> + <translation>שם פרטי:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="47"/> + <source>Last name:</source> + <translation>שם משפחה:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="57"/> + <source>E-Mail:</source> + <translation>דוא״ל:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="74"/> + <source>Fetching search fields</source> + <translation>מאחזר כעת שדות חיפוש</translation> + </message> +</context> +<context> + <name>QtUserSearchFirstPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="17"/> + <source>Add a user</source> + <translation>הוספת משתמש</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="20"/> + <source>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</source> + <translation>הוסף משתמש אחר אל רשימת הקשרים שלך. אם כתובתם ידועה לך באפשרותך להוסיפה ישירות, לחלופין באפשרותך לחפש עבורם.</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="35"/> + <source>I know their address:</source> + <translation>כתובתם ידועה לי:</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="49"/> + <source>I'd like to search my server</source> + <translation>ברצוני לחפש בשרת שלי</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="73"/> + <source>I'd like to search another server:</source> + <translation>ברצוני לחפש בשרת אחר:</translation> + </message> +</context> +<context> + <name>QtUserSearchResultsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui" line="27"/> + <source>No results.</source> + <translation>אין תוצאות.</translation> + </message> +</context> +<context> + <name>QtUserSearchWizard</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui" line="14"/> + <source>Find User</source> + <translation>מציאת משתמש</translation> + </message> +</context> +<context> + <name>Swift::ChatListModel</name> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> + <source>Bookmarked Rooms</source> + <translation>חדרים מסומנים</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="18"/> + <source>Recent Chats</source> + <translation>שיחות אחרונות</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="20"/> + <source>Opened Whiteboards</source> + <translation>לוחות לבנים פתוחים</translation> + </message> +</context> +<context> + <name>Swift::QtAboutWidget</name> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="23"/> + <source>About %1</source> + <translation>אודות %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="40"/> + <source>Version %1</source> + <translation>גרסא %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="43"/> + <source>Built with Qt %1</source> + <translation>הודר בעזרת Qt %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="44"/> + <source>Running with Qt %1</source> + <translation>מורץ בעזרת Qt %1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="50"/> + <source>Using the English translation by +%1</source> + <translation>ממשק זה תורגם לשפה העברית באדיבות +%1</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="54"/> + <source>View License</source> + <translation>הצג רישיון</translation> + </message> +</context> +<context> + <name>Swift::QtAdHocCommandWindow</name> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="36"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="39"/> + <source>Back</source> + <translation>אחורה</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="42"/> + <source>Next</source> + <translation>קדימה</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="45"/> + <source>Complete</source> + <translation>השלם</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="86"/> + <source>Error: %1</source> + <translation>שגיאה: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="87"/> + <source>Warning: %1</source> + <translation>אזהרה: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="104"/> + <source>Error executing command</source> + <translation>שגיאה בהרצת פקודה</translation> + </message> +</context> +<context> + <name>Swift::QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Add User</source> + <translation>הוספת משתמש</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Added User's Address:</source> + <translation>כתובת משתמש שהוספה:</translation> + </message> +</context> +<context> + <name>Swift::QtAvatarWidget</name> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="61"/> + <source>No picture</source> + <translation>אין תמונה</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="73"/> + <source>Select picture ...</source> + <translation>בחירת תמונה ...</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="76"/> + <source>Clear picture</source> + <translation>טהר תמונה</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Select picture</source> + <translation>בחר תמונה</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Image Files (*.png *.jpg *.jpeg *.gif)</source> + <translation>קבצי תמונה (*.png *.jpg *.jpeg *.gif)</translation> + </message> + <message> + <source>Image Files (*.png *.jpg *.gif)</source> + <translation type="obsolete">קבצי תמונה (*.png *.jpg *.gif)</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="95"/> + <source>Error</source> + <translation>שגיאה</translation> + </message> + <message> + <location filename="../QtUI/QtAvatarWidget.cpp" line="95"/> + <source>The selected picture is in an unrecognized format</source> + <translatorcomment>התמונה הנבחרת מצויה בתוך פורמט לא מוכר</translatorcomment> + <translation>הפורמט של התמונה הנבחרת אינו מוכר</translation> + </message> +</context> +<context> + <name>Swift::QtBookmarkDetailWindow</name> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.cpp" line="31"/> + <source>Bookmark not valid</source> + <translation>סימנייה לא תקפה</translation> + </message> + <message> + <location filename="../QtUI/QtBookmarkDetailWindow.cpp" line="31"/> + <source>You must specify a valid room address (e.g. someroom@rooms.example.com).</source> + <translation>עליך לציין כתובת חדר תקפה (למשל myroom@chats.example.com).</translation> + </message> +</context> +<context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="95"/> + <source>General</source> + <translation>כללי</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="96"/> + <source>Valid From</source> + <translation>בתוקף מן</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="97"/> + <source>Valid To</source> + <translation>בתוקף עד</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="98"/> + <source>Serial Number</source> + <translation>מספר סידורי</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="99"/> + <source>Version</source> + <translation>גרסא</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="101"/> + <source>Subject</source> + <translation>נושא</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="102"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="124"/> + <source>Organization</source> + <translation>ארגון</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="103"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="125"/> + <source>Common Name</source> + <translation>שם כללי</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="104"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="126"/> + <source>Locality</source> + <translation>מקומיות</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="105"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="127"/> + <source>Organizational Unit</source> + <translation>יחידה ארגונית</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="106"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="128"/> + <source>Country</source> + <translation>ארץ</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="107"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="129"/> + <source>State</source> + <translation>מחוז</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="110"/> + <source>Alternate Subject Names</source> + <translation>שמות נושא חילופיים</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="115"/> + <source>E-mail Address</source> + <translation>כתובת דוא״ל</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="117"/> + <source>DNS Name</source> + <translation>שם DNS</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="123"/> + <source>Issuer</source> + <translation>מנפיק</translation> + </message> +</context> +<context> + <name>Swift::QtChatListWindow</name> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="86"/> + <source>Add New Bookmark</source> + <translation>הוסף סימנייה חדשה</translation> + </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="83"/> + <source>Edit Bookmark</source> + <translation>ערוך סימנייה</translation> + </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> + <source>Remove Bookmark</source> + <translation>הסר סימנייה</translation> + </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="173"/> + <source>Clear recents</source> + <translation>טהר אחרונות</translation> + </message> +</context> +<context> + <name>Swift::QtChatView</name> + <message> + <location filename="../QtUI/QtChatView.cpp" line="73"/> + <source>Clear log</source> + <translation>טהר רשומת יומן</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="74"/> + <source>You are about to clear the contents of your chat log.</source> + <translation>בחרת לטהר את התכנים של יומן השיחה שלך.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="75"/> + <source>Are you sure?</source> + <translation>האם דעתך גמורה?</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="219"/> + <source>%1 edited</source> + <translatorcomment>ערוכה</translatorcomment> + <translation type="unfinished">%1 ערוך</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="400"/> + <source>Waiting for other side to accept the transfer.</source> + <translation>ממתין כעת לקצה האחר לקבל את ההעברה.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="401"/> + <location filename="../QtUI/QtChatView.cpp" line="406"/> + <location filename="../QtUI/QtChatView.cpp" line="417"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="405"/> + <source>Negotiating...</source> + <translation>מסדיר כעת...</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="420"/> + <source>Transfer has been canceled!</source> + <translation>העברה בוטלה!</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="424"/> + <source>Transfer completed successfully.</source> + <translation>העברה הושלמה בהצלחה.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="427"/> + <source>Transfer failed.</source> + <translation>העברה נכשלה.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="437"/> + <source>Started whiteboard chat</source> + <translation>שיחת לוח לבן הותחלה</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="438"/> + <source>Show whiteboard</source> + <translation>הצג לוח לבן</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="440"/> + <source>Whiteboard chat has been canceled</source> + <translation>שיחת לוח לבן בוטלה</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="442"/> + <source>Whiteboard chat request has been rejected</source> + <translation>בקשה לשיחת לוח לבן נדחתה</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="451"/> + <source>Return to room</source> + <translation>חזור אל חדר</translation> + </message> +</context> +<context> + <name>Swift::QtChatWindow</name> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="157"/> + <source>Correcting</source> + <translation>תיקון</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="269"/> + <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> + <translatorcomment>שיחה זו עשויה ש</translatorcomment> + <translation>אפשרי כי שיחה זו לא תומכת בתיקון הודעות. אם תשלח תיקון בכל זאת, זה עשוי להופיע בתור הודעה כפולה</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="271"/> + <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> + <translation>שיחה זו לא תומכת בתיקון הודעות. אם תשלח תיקון בכל זאת, זו תופיע בתור הודעה כפולה</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="546"/> + <source>This message has not been received by your server yet.</source> + <translation>הודעה זו טרם התקבלה על ידי השרת שלך.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="553"/> + <source>This message may not have been transmitted.</source> + <translation>אפשרי כי הודעה זו לא שודרה.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="562"/> + <source>The receipt for this message has been received.</source> + <translation>הקבלה עבור הודעה זו התקבלה.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="565"/> + <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> + <translation>הקבלה עבור הודעה זו טרם התקבלה. ייתכן כי הקצה המרוחק לא קיבל את הודעה זו.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="613"/> + <source>Send file</source> + <translation>שלח קובץ</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="615"/> + <location filename="../QtUI/QtChatWindow.cpp" line="623"/> + <location filename="../QtUI/QtChatWindow.cpp" line="660"/> + <location filename="../QtUI/QtChatWindow.cpp" line="664"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="616"/> + <source>Set Description</source> + <translation>הגדר תיאור</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="617"/> + <source>Send</source> + <translation>שלח</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="621"/> + <source>Receiving file</source> + <translation>מקבל כעת קובץ</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="624"/> + <location filename="../QtUI/QtChatWindow.cpp" line="665"/> + <source>Accept</source> + <translatorcomment>הסכם</translatorcomment> + <translation>קבל</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="659"/> + <source>Starting whiteboard chat</source> + <translation>מתחיל כעת שיחת לוח לבן</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="663"/> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 רוצה להתחיל שיחת לוח לבן</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="700"/> + <source>File transfer description</source> + <translation>תיאור העברת קובץ</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="701"/> + <source>Description:</source> + <translation>תיאור:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="715"/> + <source>Save File</source> + <translation>שמור קובץ</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="906"/> + <source>Change subject…</source> + <translation>שינוי נושא…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="907"/> + <source>Configure room…</source> + <translation>הגדרת חדר…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="908"/> + <source>Edit affiliations…</source> + <translation>עריכת שיוכים…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="909"/> + <source>Destroy room</source> + <translation>החרב חדר</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="910"/> + <source>Invite person to this room…</source> + <translation>הזמנת אישיות אל חדר זה…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="920"/> + <source>Change room subject</source> + <translation>שינוי נושא חדר</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="920"/> + <source>New subject:</source> + <translation>נושא חדש:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="938"/> + <source>Confirm room destruction</source> + <translation>אמת החרבת חדר</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="939"/> + <source>Are you sure you want to destroy the room?</source> + <translation>האם אתה בטוח כי ברצונך להרוס את חדר זה?</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="940"/> + <source>This will destroy the room.</source> + <translation>פעולה זו תחריב את החדר.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="991"/> + <source>Accept Invite</source> + <translation>הסכם להזמנה</translation> + </message> + <message> + <source>Couldn't send message: %1</source> + <translation type="obsolete">לא היתה אפשרות לשלוח הודעה: %1</translation> + </message> +</context> +<context> + <name>Swift::QtContactEditWidget</name> + <message> + <location filename="../QtUI/QtContactEditWidget.cpp" line="122"/> + <source>Name:</source> + <translation>שם:</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWidget.cpp" line="42"/> + <source>Groups:</source> + <translation>קבוצות:</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWidget.cpp" line="65"/> + <source>New Group:</source> + <translation>קבוצה חדשה:</translation> + </message> +</context> +<context> + <name>Swift::QtContactEditWindow</name> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="28"/> + <source>Edit contact</source> + <translation>עריכת קשר</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="43"/> + <source>Remove contact</source> + <translation>הסרת קשר</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="46"/> + <source>OK</source> + <translation>אישור</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="94"/> + <source>Confirm contact deletion</source> + <translation>ודא מחיקת קשר</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="95"/> + <source>Are you sure you want to delete this contact?</source> + <translation>האם אתה בטוח כי ברצונך למחוק את קשר זה?</translation> + </message> + <message> + <location filename="../QtUI/QtContactEditWindow.cpp" line="96"/> + <source>This will remove the contact '%1' from all groups they may be in.</source> + <translation>פעולה זו תסיר את הקשר '%1' מן כל הקבוצות בהן הם עשויים להיות.</translation> + </message> +</context> +<context> + <name>Swift::QtEventWindow</name> + <message> + <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="48"/> + <source>Display Notice</source> + <translation>הצג התראה</translation> + </message> +</context> +<context> + <name>Swift::QtFileTransferListWidget</name> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="39"/> + <source>Clear Finished Transfers</source> + <translation>טהר העברות גמורות</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="44"/> + <source>File Transfer List</source> + <translation>רשימת העברת קובץ</translation> + </message> +</context> +<context> + <name>Swift::QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.cpp" line="57"/> + <source>History</source> + <translation>היסטוריה</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="25"/> + <source>Users to invite to this chat (one per line):</source> + <translation>משתמשים להזמנה אל שיחה זו (אחד בכל שורה):</translation> + </message> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="31"/> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>אם ברצונך לספק סיבה עבור ההזמנה, הזן אותה כאן</translation> + </message> +</context> +<context> + <name>Swift::QtJoinMUCWindow</name> + <message> + <location filename="../QtUI/QtJoinMUCWindow.cpp" line="19"/> + <source>someroom@rooms.example.com</source> + <translation></translation> + </message> +</context> +<context> + <name>Swift::QtLoginWindow</name> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="92"/> + <source>User address:</source> + <translation>כתובת משתמש:</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="98"/> + <source>User address - looks like someuser@someserver.com</source> + <translation>כתובת משתמש - נראית כמו someuser@someserver.com</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="102"/> + <source>Example: alice@wonderland.lit</source> + <translation>דוגמא: alice@wonderland.lit</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="108"/> + <source>Password:</source> + <translation>סיסמה:</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="129"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="130"/> + <source>Click if you have a personal certificate used for login to the service.</source> + <translation>ברשותי תעודה אישית לשימוש עבור התחברותי אל השירות.</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="355"/> + <source>Connect</source> + <translation>התחבר</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="154"/> + <source>Remember Password?</source> + <translation>זכור סיסמה?</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="156"/> + <source>Login Automatically?</source> + <translation>התחבר אוטומטית?</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="168"/> + <source>&Swift</source> + <translation></translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> + <source>&General</source> + <translation>&כללי</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="178"/> + <source>&About %1</source> + <translation>&אודות %1</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="183"/> + <source>&Show Debug Console</source> + <translation>&הצג מסוף ניפוי שגיאות</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="188"/> + <source>Show &File Transfer Overview</source> + <translation>הצג סקירת העברת ק&ובץ</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="193"/> + <source>&Play Sounds</source> + <translation>&נגן צלילים</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="199"/> + <source>Display Pop-up &Notifications</source> + <translation>הצג התראות &קופצות</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="211"/> + <source>&Quit</source> + <translation>י&ציאה</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> + <source>Remove profile</source> + <translation>הסר דיוקן</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> + <source>Remove the profile '%1'?</source> + <translation>להסיר את הדיוקן '%1'?</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="355"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="369"/> + <source>Confirm terms of use</source> + <translation>אשר תנאי שימוש</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="412"/> + <source>Select an authentication certificate</source> + <translation>בחר תעודת אימות</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="412"/> + <source>P12 files (*.cert *.p12 *.pfx);;All files (*.*)</source> + <translation>קבצי P12 (*.cert *.p12 *.pfx);;כל הקבצים (*.*)</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="525"/> + <source>The certificate presented by the server is not valid.</source> + <translation>התעודה אשר מוצגת על ידי השרת אינה תקפה.</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="526"/> + <source>Would you like to permanently trust this certificate? This must only be done if you know it is correct.</source> + <translatorcomment>you know for certain it</translatorcomment> + <translation>האם ברצונך לסמוך על תעודה זו לצמיתות? על פעולה זו להיעשות אך ורק אם ידוע לך בוודאות כי תעודה זו הינה מדויקת.</translation> + </message> + <message> + <source>Subject: %1</source> + <translation type="obsolete">נושא: %1</translation> + </message> + <message> + <source>SHA-1 Fingerprint: %1</source> + <translation type="obsolete">טביעת אצבע SHA-1: %1</translation> + </message> +</context> +<context> + <name>Swift::QtMUCConfigurationWindow</name> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="34"/> + <source>Cancel</source> + <translation>ביטול</translation> + </message> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="37"/> + <source>OK</source> + <translation>אישור</translation> + </message> +</context> +<context> + <name>Swift::QtMUCSearchWindow</name> + <message> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.cpp" line="49"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.cpp" line="51"/> + <source>Searching</source> + <translation>חיפוש</translation> + </message> +</context> +<context> + <name>Swift::QtMainWindow</name> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="79"/> + <source>&Contacts</source> + <translation>&קשרים</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="88"/> + <location filename="../QtUI/QtMainWindow.cpp" line="205"/> + <source>&Notices</source> + <translation>ה&תראות</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="87"/> + <source>C&hats</source> + <translation>&שיחות</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="96"/> + <source>&View</source> + <translation>&תצוגה</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="98"/> + <source>&Show offline contacts</source> + <translation>הצג קשרים &לא מקוונים</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="106"/> + <source>&Show Emoticons</source> + <translation>הצג &רגשונים</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="120"/> + <source>&Actions</source> + <translation>&פעולות</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="122"/> + <source>Edit &Profile…</source> + <translation>עריכת &דיוקן…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <source>Enter &Room…</source> + <translation>כניסה אל &חדר…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="129"/> + <source>&View History…</source> + <translation>&הצגת היסטוריה…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="133"/> + <source>&Add Contact…</source> + <translation>הוספת &קשר…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="136"/> + <source>&Edit Selected Contact…</source> + <translation>&עריכת קשר נבחר…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="140"/> + <source>Start &Chat…</source> + <translation>התחלת &שיחה…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="143"/> + <source>Run Server Command</source> + <translation>הרץ פקודת שרת</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="150"/> + <source>&Request Delivery Receipts</source> + <translatorcomment>משלוח</translatorcomment> + <translation>&בקש קבלות מסירה</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="161"/> + <source>Collecting commands...</source> + <translation>אוסף כעת פקודות...</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="216"/> + <source>&Chats</source> + <translation>&שיחות</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="339"/> + <source>No Available Commands</source> + <translation>אין פקודות זמינות</translation> + </message> + <message> + <source>Edit &Profile</source> + <translation type="obsolete">ערוך &דיוקן</translation> + </message> + <message> + <source>Enter &Room</source> + <translation type="obsolete">כנס אל &חדר</translation> + </message> + <message> + <source>&Add Contact</source> + <translation type="obsolete">&הוסף קשר</translation> + </message> + <message> + <source>&Edit Selected Contact</source> + <translation type="obsolete">&ערוך קשר נוכחי</translation> + </message> + <message> + <source>Start &Chat</source> + <translation type="obsolete">התחל &שיחה</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="146"/> + <source>&Sign Out</source> + <translation>&התנתק</translation> + </message> +</context> +<context> + <name>Swift::QtNameWidget</name> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> + <source>Show Nickname</source> + <translation>הצג שם כינוי</translation> + </message> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> + <source>(No Nickname Set)</source> + <translation>(לא נקבע שם כינוי)</translation> + </message> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="56"/> + <source>Show Address</source> + <translation>הצג כתובת</translation> + </message> + <message> + <location filename="../QtUI/QtNameWidget.cpp" line="63"/> + <source>Edit Profile</source> + <translation>ערוך דיוקן</translation> + </message> +</context> +<context> + <name>Swift::QtOccupantListWidget</name> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="46"/> + <source>No actions for this user</source> + <translatorcomment>לא קיימות</translatorcomment> + <translation>אין פעולות עבור משתמש זה</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="55"/> + <source>Kick user</source> + <translation>בעט משתמש</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="56"/> + <source>Kick and ban user</source> + <translation>בעט ואסור משתמש</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="57"/> + <source>Make moderator</source> + <translation>הפוך לאחראי</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="58"/> + <source>Make participant</source> + <translation>הפוך למשתתף</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="59"/> + <source>Remove voice</source> + <translation>שלול ביטוי</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="60"/> + <source>Add to contacts</source> + <translation>הוסף אל קשרים</translation> + </message> +</context> +<context> + <name>Swift::QtProfileWindow</name> + <message> + <location filename="../QtUI/QtProfileWindow.cpp" line="24"/> + <source>Edit Profile</source> + <translation>ערוך דיוקן</translation> + </message> + <message> + <location filename="../QtUI/QtProfileWindow.cpp" line="43"/> + <source>Nickname:</source> + <translation>שם כינוי:</translation> + </message> + <message> + <location filename="../QtUI/QtProfileWindow.cpp" line="67"/> + <source>Save</source> + <translation>שמור</translation> + </message> +</context> +<context> + <name>Swift::QtRosterHeader</name> + <message> + <location filename="../QtUI/QtRosterHeader.cpp" line="59"/> + <source>Connection is secured</source> + <translation>חיבור הינו מאובטח</translation> + </message> +</context> +<context> + <name>Swift::QtRosterWidget</name> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="58"/> + <source>Edit…</source> + <translation>עריכה…</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="59"/> + <source>Remove</source> + <translation>הסר</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="63"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>Send File</source> + <translation>שליחת קובץ</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="69"/> + <source>Start Whiteboard Chat</source> + <translation>התחל שיחת לוח לבן</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>All Files (*);;</source> + <translation>כל הקבצים (*);;</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="96"/> + <source>Rename</source> + <translation>שינוי שם</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Rename group</source> + <translation>שינוי שם קבוצה</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Enter a new name for group '%1':</source> + <translation>הזן שם חדש עבור קבוצה '%1':</translation> + </message> +</context> +<context> + <name>Swift::QtStatusWidget</name> + <message> + <location filename="../QtUI/QtStatusWidget.cpp" line="231"/> + <source>Connecting</source> + <translation>מתחבר כעת</translation> + </message> + <message> + <location filename="../QtUI/QtStatusWidget.cpp" line="261"/> + <source>(No message)</source> + <translation>(אין הודעה)</translation> + </message> +</context> +<context> + <name>Swift::QtSubscriptionRequestWindow</name> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="18"/> + <source>%1 would like to add you to their contact list. + Would you like to add them to your contact list and share your status when you're online? + +If you choose to defer this choice, you will be asked again when you next login.</source> + <translation>%1 רוצה להוסיפך אל רשימת הקשרים שלהם. + האם ברצונך להוסיפם אל רשימת הקשרים שלך ולשתף את מצב החיבור שלך כאשר מצב זה הינו מקוון? + +במידה ותיבחר האפשרות לדחות, אני אתשאל אותך שוב בהתחברותך הבאה.</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="24"/> + <source>You have already replied to this request</source> + <translation>כבר השבת לבקשה זו</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="25"/> + <source>OK</source> + <translation>אישור</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="30"/> + <source>Yes</source> + <translation>כן</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="33"/> + <source>No</source> + <translation>לא</translation> + </message> + <message> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="35"/> + <source>Defer</source> + <translation>עכב</translation> + </message> +</context> +<context> + <name>Swift::QtTreeWidget</name> + <message> + <source>Edit</source> + <translation type="obsolete">עריכה</translation> + </message> + <message> + <source>Remove</source> + <translation type="obsolete">הסרה</translation> + </message> + <message> + <source>Rename</source> + <translation type="obsolete">שינוי שם</translation> + </message> + <message> + <source>Rename group</source> + <translation type="obsolete">שינוי שם קבוצה</translation> + </message> + <message> + <source>Enter a new name for group '%1':</source> + <translation type="obsolete">הזן שם חדש עבור הקבוצה '%1':</translation> + </message> +</context> +<context> + <name>Swift::QtUserSearchDetailsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="22"/> + <source>Please choose a name for the contact, and select the groups you want to add the contact to.</source> + <translation>אנא בחר שם עבור הקשר, ובחר את הקבוצות אליהן ברצונך להוסיף את הקשר.</translation> + </message> +</context> +<context> + <name>Swift::QtUserSearchFirstPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> + <source>%1. If you know their address you can enter it directly, or you can search for them.</source> + <translation>%1. אם כתובתם ידועה לך באפשרותך להזינה ישירות, לחלופין באפשרותך לחפש עבורם.</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> + <source>Add another user to your contact list</source> + <translation>הוסף משתמש אחר אל רשימת הקשרים שלך</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> + <source>Chat to another user</source> + <translation>שוחח עם משתמש אחר</translation> + </message> +</context> +<context> + <name>Swift::QtUserSearchWindow</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> + <source>Add Contact</source> + <translation>הוספת קשר</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> + <source>Chat to User</source> + <translatorcomment>שיח</translatorcomment> + <translation>שוחח עם משתמש</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="45"/> + <source>alice@wonderland.lit</source> + <translation></translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> + <source>How would you like to find the user to add?</source> + <translation>כיצד ברצונך למצוא את המשתמש להוספה?</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="299"/> + <source>How would you like to find the user to chat to?</source> + <translation>כיצד ברצונך למצוא את המשתמש לשיחה?</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="326"/> + <source>Error while searching</source> + <translation>שגיאה במהלך חיפוש</translation> + </message> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="332"/> + <source>This server doesn't support searching for users.</source> + <translation>שרת זה לא תומך בחיפוש עבור משתמשים.</translation> + </message> +</context> +<context> + <name>Swift::QtWebView</name> + <message> + <location filename="../QtUI/QtWebView.cpp" line="66"/> + <source>Clear</source> + <translation>טהר</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="67"/> + <source>Increase font size</source> + <translation>הגדל מידת גופן</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="68"/> + <source>Decrease font size</source> + <translation>הקטן מידת גופן</translation> + </message> +</context> +<context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <location filename="../QtUI/Whiteboard/QtWhiteboardWindow.cpp" line="380"/> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>סגירת חלון הינה שקולה לסגירת סשן זה. האם אתה בטוח כי ברצונך לעשות זאת?</translation> + </message> +</context> +<context> + <name>Swift::QtXMLConsoleWidget</name> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="22"/> + <source>Console</source> + <translation>מסוף</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="40"/> + <source>Trace input/output</source> + <translation>חקור קלט/פלט</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="46"/> + <source>Clear</source> + <translation>טהר</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="50"/> + <source>Debug Console</source> + <translation>מסוף ניפוי שגיאות</translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="78"/> + <source><!-- IN --></source> + <translation><!-- נכנסת --></translation> + </message> + <message> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="82"/> + <source><!-- OUT --></source> + <translation><!-- יוצאת --></translation> + </message> +</context> +<context> + <name>TRANSLATION_INFO</name> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="49"/> + <location filename="../QtUI/QtAboutWidget.cpp" line="50"/> + <source>TRANSLATION_AUTHOR</source> + <translation>Isratine Citizen</translation> + </message> + <message> + <location filename="../QtUI/QtAboutWidget.cpp" line="52"/> + <source>TRANSLATION_LICENSE</source> + <comment>This string contains the license under which this translation is licensed. We ask you to license the translation under the BSD license. Please read http://www.opensource.org/licenses/bsd-license.php, and if you agree to release your translation under this license, use the following (untranslated) text: 'This translation is licensed under the BSD License. See http://www.opensource.org/licenses/bsd-license.php'</comment> + <translatorcomment>'תרגום זה הינו רשוי תחת הרשיון BSD. למידע נוסף ראה http://www.opensource.org/licenses/bsd-license.php'</translatorcomment> + <translation>'This translation is licensed under the BSD License. See http://www.opensource.org/licenses/bsd-license.php'</translation> + </message> +</context> +</TS> diff --git a/Swift/Translations/swift_hu.ts b/Swift/Translations/swift_hu.ts index 30f98ec..54167e2 100644 --- a/Swift/Translations/swift_hu.ts +++ b/Swift/Translations/swift_hu.ts @@ -1137,21 +1137,21 @@ </message> <message> - <source>Edit &Profile</source> - <translation>&Profil szerkesztése</translation> + <source>Edit &Profile…</source> + <translation>&Profil szerkesztése…</translation> </message> <message> - <source>Enter &Room</source> - <translation>Belépés &szobába</translation> + <source>Enter &Room…</source> + <translation>Belépés &szobába…</translation> </message> <message> - <source>&Add Contact</source> - <translation>&Partner felvétele</translation> + <source>&Add Contact…</source> + <translation>&Partner felvétele…</translation> </message> <message> - <source>&Edit Selected Contact</source> - <translation>&Kiválasztott partner szerkesztése</translation> + <source>&Edit Selected Contact…</source> + <translation>&Kiválasztott partner szerkesztése…</translation> </message> <message> - <source>Start &Chat</source> + <source>Start &Chat…</source> <translation>Bes&zélgetés kezdeményezése</translation> </message> diff --git a/Swift/Translations/swift_nl.ts b/Swift/Translations/swift_nl.ts index 7e8bfbd..20a03ef 100644 --- a/Swift/Translations/swift_nl.ts +++ b/Swift/Translations/swift_nl.ts @@ -139,5 +139,5 @@ <message> <source>A password needed</source> - <translation>Wachtwoord vereist</translation> + <translation type="obsolete">Wachtwoord vereist</translation> </message> <message> @@ -159,5 +159,5 @@ <message> <source>Unable to enter this room as %1%, retrying as %2%</source> - <translation>Kan deze kamer niet betreden als %1%; als %2% opniew aan het proberen</translation> + <translation>Kan deze kamer niet betreden als %1%; als %2% opnieuw aan het proberen</translation> </message> <message> @@ -433,4 +433,84 @@ <translation>Gebruikersadres ongeldig. Gebruikersadres moet van de vorm 'alice@wonderland.lit' zijn</translation> </message> + <message> + <source>This chat doesn't support delivery receipts.</source> + <translation>Deze conversatie ondersteunt geen ontvangstbevestigingen.</translation> + </message> + <message> + <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> + <translation>Deze conversatie ondersteunt mogelijks geen ontvangstbevestigingen.</translation> + </message> + <message> + <source>Couldn't send message: %1%</source> + <translation>Kon boodschap niet verzenden: %1%</translation> + </message> + <message> + <source>The correct room password is needed</source> + <translation>Het correcte wachtwoord voor de kamer is vereist</translation> + </message> + <message> + <source>Couldn't join room: %1%.</source> + <translation>Kon kamer niet betreden: %1%.</translation> + </message> + <message> + <source>%1% has left the room%2%</source> + <translation>%1% heeft de kamer %2% verlaten</translation> + </message> + <message> + <source>You have been kicked out of the room</source> + <translation>U bent uit de kamer geschopt</translation> + </message> + <message> + <source>You have been banned from the room</source> + <translation>U bent uit de kamer verbannen</translation> + </message> + <message> + <source>You are no longer a member of the room and have been removed</source> + <translation>U bent niet langer een lid van de kamer, en werd uit de kamer verwijderd</translation> + </message> + <message> + <source>The room has been destroyed</source> + <translation>De kamer werd vernietigd</translation> + </message> + <message> + <source>Room configuration failed: %1%.</source> + <translation>Configuratie van kamer gefaald: %1%.</translation> + </message> + <message> + <source>Occupant role change failed: %1%.</source> + <translation>Deelnemersrol veranderd: %1%.</translation> + </message> + <message> + <source>%1% has invited you to enter the %2% room</source> + <translation>%1% heeft U uitgenodigd om de kamer %2% te betreden</translation> + </message> + <message> + <source>Certificate card removed</source> + <translation>Certificaatskaart verwijderd</translation> + </message> + <message> + <source>Certificate has been revoked</source> + <translation>Certificaat werd ingetrokken</translation> + </message> + <message> + <source>Unable to determine certificate revocation state</source> + <translation>Kan ingetrokken toestand van certificaat niet conttroleren </translation> + </message> + <message> + <source>Re-enter credentials and retry</source> + <translation>Voer gebruikersinformatie terug in en probeer opnieuw</translation> + </message> + <message> + <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> + <translation>Verbinding met %1% verbroken: %2%. Om opnieuw te verbinden, meld terug af en voer het wachtwoord opnieuw in.</translation> + </message> + <message> + <source>TLS Client Certificate Selection</source> + <translation>Keuze TLS Klantcertificaat </translation> + </message> + <message> + <source>Select a certificate to use for authentication</source> + <translation>Kies een certificaat om te gebruiken voor authenticatie</translation> + </message> </context> <context> @@ -523,5 +603,5 @@ <message> <source>&Redo</source> - <translation>&Opniew</translation> + <translation>&Opnieuw</translation> </message> <message> @@ -563,4 +643,80 @@ <translation>%1 wil u aan zijn contactenlijst toevoegen, met als boodschap '%2'</translation> </message> + <message> + <source>%1 has invited you to enter the %2 room.</source> + <translation>%1 heeft U uitgenodigd om de kamer %2 te betreden.</translation> + </message> + <message> + <source>You've been invited to enter the %1 room.</source> + <translation>U bent uitgenodigd om de kamer %1 te betreden.</translation> + </message> + <message> + <source>Reason: %1</source> + <translation>Reden: %1</translation> + </message> + <message> + <source>This person may not have really sent this invitation!</source> + <translation>Deze persoon heeft mogelijks deze uitnodiging niet echt verstuurd!</translation> + </message> + <message> + <source>Direction</source> + <translation>Richting</translation> + </message> + <message> + <source>Other Party</source> + <translation>Andere Partij</translation> + </message> + <message> + <source>State</source> + <translation>Toestand</translation> + </message> + <message> + <source>Progress</source> + <translation>Voortgang</translation> + </message> + <message> + <source>Size</source> + <translation>Grootte</translation> + </message> + <message> + <source>Incoming</source> + <translation>Inkomend</translation> + </message> + <message> + <source>Outgoing</source> + <translation>Uitgaand</translation> + </message> + <message> + <source>Waiting for start</source> + <translation>Aan het wachten om te starten</translation> + </message> + <message> + <source>Waiting for other side to accept</source> + <translation>Aan het wachten op de andere kant om te aanvaarden</translation> + </message> + <message> + <source>Negotiating</source> + <translation>Aan het onderhandelen</translation> + </message> + <message> + <source>Transferring</source> + <translation>Aan het overbrengen</translation> + </message> + <message> + <source>Finished</source> + <translation>Voltooid</translation> + </message> + <message> + <source>Failed</source> + <translation>Gefaald</translation> + </message> + <message> + <source>Canceled</source> + <translation>Geannuleerd</translation> + </message> + <message> + <source>Connection Options</source> + <translation>Verbindingsopties</translation> + </message> </context> <context> @@ -611,5 +767,5 @@ <message> <source>&Redo</source> - <translation>&Opniew</translation> + <translation>&Opnieuw</translation> </message> <message> @@ -721,4 +877,39 @@ </context> <context> + <name>QtAffiliationEditor</name> + <message> + <source>Affiliation:</source> + <translation>Lidmaatschap:</translation> + </message> + <message> + <source>Owner</source> + <translation>Eigenaar</translation> + </message> + <message> + <source>Administrator</source> + <translation>Beheerder</translation> + </message> + <message> + <source>Member</source> + <translation>Lid</translation> + </message> + <message> + <source>Outcast (Banned)</source> + <translation>Verstoteling</translation> + </message> + <message> + <source>Add User</source> + <translation>Voeg gebruiker toe</translation> + </message> + <message> + <source>Remove User</source> + <translation>Verwijder gebruiker</translation> + </message> + <message> + <source>Edit Affiliations</source> + <translation>Editeer Lidmaatschappen</translation> + </message> +</context> +<context> <name>QtBookmarkDetailWindow</name> <message> @@ -740,5 +931,5 @@ <message> <source>Join automatically</source> - <translation>Automatisch betreden</translation> + <translation type="obsolete">Automatisch betreden</translation> </message> <message> @@ -746,4 +937,129 @@ <translation>Adres kamer:</translation> </message> + <message> + <source>Enter automatically</source> + <translation>Automatisch betreden</translation> + </message> +</context> +<context> + <name>QtCertificateViewerDialog</name> + <message> + <source>Certificate Viewer</source> + <translation>Certificaat</translation> + </message> +</context> +<context> + <name>QtConnectionSettings</name> + <message> + <source>Connection Method:</source> + <translation>Verbindingsmethode:</translation> + </message> + <message> + <source>Automatic</source> + <translation>Automatisch</translation> + </message> + <message> + <source>Manual</source> + <translation>Manueel</translation> + </message> + <message> + <source>BOSH</source> + <translation>BOSH</translation> + </message> + <message> + <source>Secure connection:</source> + <translation>Beveilig verbinding:</translation> + </message> + <message> + <source>Never</source> + <translation>Nooit</translation> + </message> + <message> + <source>Encrypt when possible</source> + <translation>Beveilig wanneer mogelijk</translation> + </message> + <message> + <source>Always encrypt</source> + <translation>Altijd beveiligen</translation> + </message> + <message> + <source>Allow Compression</source> + <translation>Comprimeer wanneer mogelijk</translation> + </message> + <message> + <source>Allow sending password over insecure connection</source> + <translation>Sta toe om paswoord over onbeveiligde verbinding te sturen</translation> + </message> + <message> + <source>Manually select server</source> + <translation>Manuele serverselectie</translation> + </message> + <message> + <source>Hostname:</source> + <translation>Hostnaam:</translation> + </message> + <message> + <source>Port:</source> + <translation>Poort:</translation> + </message> + <message> + <source>Connection Proxy</source> + <translation>Verbindingsproxy</translation> + </message> + <message> + <source>Proxy type:</source> + <translation>Proxy type:</translation> + </message> + <message> + <source>None</source> + <translation>Geen</translation> + </message> + <message> + <source>Use system-configured proxy</source> + <translation>Gebruik systeemgeconfigureerde proxy</translation> + </message> + <message> + <source>SOCKS5</source> + <translation>SOCKS5</translation> + </message> + <message> + <source>HTTP Connect</source> + <translation>HTTP Connect</translation> + </message> + <message> + <source>Override system-configured proxy</source> + <translation>Override systemgeconfigureerde proxy</translation> + </message> + <message> + <source>BOSH URI:</source> + <translation>BOSH URI:</translation> + </message> + <message> + <source>Manually select HTTP proxy</source> + <translation>Selecteer HTTP proxy manueel</translation> + </message> + <message> + <source>Connection Options</source> + <translation>Verbindingsopties</translation> + </message> +</context> +<context> + <name>QtHistoryWindow</name> + <message> + <source>Search:</source> + <translation>Zoek:</translation> + </message> + <message> + <source>Next</source> + <translation>Volgende</translation> + </message> + <message> + <source>Previous</source> + <translation>Vorige</translation> + </message> + <message> + <source>History</source> + <translation>Geschiedenis</translation> + </message> </context> <context> @@ -751,5 +1067,5 @@ <message> <source>Room:</source> - <translation>Kamer:</translation> + <translation type="obsolete">Kamer:</translation> </message> <message> @@ -759,5 +1075,5 @@ <message> <source>Nickname:</source> - <translation>Roepnaam:</translation> + <translation type="obsolete">Roepnaam:</translation> </message> <message> @@ -769,4 +1085,20 @@ <translation>Automatisch betreden</translation> </message> + <message> + <source>Room Address:</source> + <translation>Adres kamer:</translation> + </message> + <message> + <source>Your Nickname:</source> + <translation>Uw roepnaam:</translation> + </message> + <message> + <source>Room Password:</source> + <translation>Wachtwoord kamer:</translation> + </message> + <message> + <source>Automatically configure newly created rooms</source> + <translation>Configureer nieuwe kamer automatisch</translation> + </message> </context> <context> @@ -794,4 +1126,15 @@ </context> <context> + <name>QtProfileWindow</name> + <message> + <source>Edit Profile</source> + <translation>Bewerk profiel</translation> + </message> + <message> + <source>Save</source> + <translation>Bewaar</translation> + </message> +</context> +<context> <name>QtUserSearchFieldsPage</name> <message> @@ -840,4 +1183,11 @@ </context> <context> + <name>QtUserSearchResultsPage</name> + <message> + <source>No results.</source> + <translation>Geen resultaten.</translation> + </message> +</context> +<context> <name>QtUserSearchWizard</name> <message> @@ -847,4 +1197,50 @@ </context> <context> + <name>QtVCardPhotoAndNameFields</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Formatted Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Nickname</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Prefix</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Given Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Middle Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Last Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Suffix</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>QtVCardWidget</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Add Field</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>Swift::ChatListModel</name> <message> @@ -856,4 +1252,8 @@ <translation>Recente conversaties</translation> </message> + <message> + <source>Opened Whiteboards</source> + <translation>Geopenede tekentafels</translation> + </message> </context> <context> @@ -918,4 +1318,15 @@ </context> <context> + <name>Swift::QtAffiliationEditor</name> + <message> + <source>Add User</source> + <translation>Voeg gebruiker toe</translation> + </message> + <message> + <source>Added User's Address:</source> + <translation>Adres toegevoegde gebruiker:</translation> + </message> +</context> +<context> <name>Swift::QtAvatarWidget</name> <message> @@ -938,5 +1349,5 @@ afbeelding</translation> <message> <source>Image Files (*.png *.jpg *.gif)</source> - <translation>Afbeeldingen (*.png *.jpg *.gif)</translation> + <translation type="obsolete">Afbeeldingen (*.png *.jpg *.gif)</translation> </message> <message> @@ -948,4 +1359,8 @@ afbeelding</translation> <translation>Het formaat van de geselecteerde afbeelding werd niet herkend</translation> </message> + <message> + <source>Image Files (*.png *.jpg *.jpeg *.gif)</source> + <translation>Beeldbestanden (*.png *.jpg *.jpeg *.gif)</translation> + </message> </context> <context> @@ -961,4 +1376,71 @@ afbeelding</translation> </context> <context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <source>General</source> + <translation>Algemeen</translation> + </message> + <message> + <source>Valid From</source> + <translation>Geldig van</translation> + </message> + <message> + <source>Valid To</source> + <translation>Geldig tot</translation> + </message> + <message> + <source>Serial Number</source> + <translation>Serienummer</translation> + </message> + <message> + <source>Version</source> + <translation>Versie</translation> + </message> + <message> + <source>Subject</source> + <translation>Onderwerp</translation> + </message> + <message> + <source>Organization</source> + <translation>Organisatie</translation> + </message> + <message> + <source>Common Name</source> + <translation>Naam</translation> + </message> + <message> + <source>Locality</source> + <translation>Localiteit</translation> + </message> + <message> + <source>Organizational Unit</source> + <translation>Organisatie-eenheid</translation> + </message> + <message> + <source>Country</source> + <translation>Land</translation> + </message> + <message> + <source>State</source> + <translation>Staat</translation> + </message> + <message> + <source>Alternate Subject Names</source> + <translation>Alternatieve onderwerpsnaam</translation> + </message> + <message> + <source>E-mail Address</source> + <translation>E-mail adres</translation> + </message> + <message> + <source>DNS Name</source> + <translation>DNS naam</translation> + </message> + <message> + <source>Issuer</source> + <translation>Verstrekker</translation> + </message> +</context> +<context> <name>Swift::QtChatListWindow</name> <message> @@ -974,4 +1456,8 @@ afbeelding</translation> <translation>Verwijder bladwijzer</translation> </message> + <message> + <source>Clear recents</source> + <translation>Wis recente conversaties</translation> + </message> </context> <context> @@ -989,4 +1475,52 @@ afbeelding</translation> <translation>Bent u zeker?</translation> </message> + <message> + <source>%1 edited</source> + <translation>%1 geëditeerd</translation> + </message> + <message> + <source>Waiting for other side to accept the transfer.</source> + <translation>Aan het wachten op de andere kant om te aanvaarden.</translation> + </message> + <message> + <source>Cancel</source> + <translation>Annuleren</translation> + </message> + <message> + <source>Negotiating...</source> + <translation>Aan het onderhandelen...</translation> + </message> + <message> + <source>Transfer has been canceled!</source> + <translation>Overdracht geannuleerd!</translation> + </message> + <message> + <source>Transfer completed successfully.</source> + <translation>Overdracht succesvol beëindigd.</translation> + </message> + <message> + <source>Transfer failed.</source> + <translation>Overdracht gefaald.</translation> + </message> + <message> + <source>Started whiteboard chat</source> + <translation>Tekentafel gestart</translation> + </message> + <message> + <source>Show whiteboard</source> + <translation>Toon tekentafel</translation> + </message> + <message> + <source>Whiteboard chat has been canceled</source> + <translation>Tekentafel werd geannuleerd</translation> + </message> + <message> + <source>Whiteboard chat request has been rejected</source> + <translation>Tekentafeverzoekl werd verworpen</translation> + </message> + <message> + <source>Return to room</source> + <translation>Keer terug naar kamer</translation> + </message> </context> <context> @@ -1002,5 +1536,113 @@ afbeelding</translation> <message> <source>Couldn't send message: %1</source> - <translation>Kon boodschap niet verzenden: %1</translation> + <translation type="obsolete">Kon boodschap niet verzenden: %1</translation> + </message> + <message> + <source>Correcting</source> + <translation>Corrigeren</translation> + </message> + <message> + <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> + <translation>Deze conversatie ondersteunt mogelijks geen verbeteringen. Verbeteringen kunnen als duplicate boodschappen aankomen</translation> + </message> + <message> + <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> + <translation>Deze conversatie ondersteunt geen verbeteringen. Verbeteringen zullen als duplicate boodschappen aankomen</translation> + </message> + <message> + <source>The receipt for this message has been received.</source> + <translation>Bevestiging ontvangen.</translation> + </message> + <message> + <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> + <translation>Bevestiging nog niet ontvangen. De ontvanger kan mogelijks dit bericht nog niet ontvangen hebben.</translation> + </message> + <message> + <source>Send file</source> + <translation>Verzend bestand</translation> + </message> + <message> + <source>Cancel</source> + <translation>Annuleren</translation> + </message> + <message> + <source>Set Description</source> + <translation>Verander beschrijving</translation> + </message> + <message> + <source>Send</source> + <translation>Verzenden</translation> + </message> + <message> + <source>Receiving file</source> + <translation>Bestand aan het ontvangen</translation> + </message> + <message> + <source>Accept</source> + <translation>Accepteren</translation> + </message> + <message> + <source>Starting whiteboard chat</source> + <translation>Tekentafel aan het starten</translation> + </message> + <message> + <source>File transfer description</source> + <translation>Beschrijving bestand</translation> + </message> + <message> + <source>Description:</source> + <translation>Beschrijving:</translation> + </message> + <message> + <source>Save File</source> + <translation>Bestand opslaan</translation> + </message> + <message> + <source>Change subject…</source> + <translation>Verander onderwerp...</translation> + </message> + <message> + <source>Configure room…</source> + <translation>Configureer kamer...</translation> + </message> + <message> + <source>Edit affiliations…</source> + <translation>Verander lidmaatschap...</translation> + </message> + <message> + <source>Destroy room</source> + <translation>Vernietig kamer</translation> + </message> + <message> + <source>Invite person to this room…</source> + <translation>Nodig persoon uit voor deze kamer...</translation> + </message> + <message> + <source>Change room subject</source> + <translation>Verander onderwerp</translation> + </message> + <message> + <source>New subject:</source> + <translation>Niew onderwerp:</translation> + </message> + <message> + <source>Confirm room destruction</source> + <translation>Bevestig vernietiging kamer</translation> + </message> + <message> + <source>Are you sure you want to destroy the room?</source> + <translation>Bent U zeker dat U deze kamer wil vernietigen?</translation> + </message> + <message> + <source>This will destroy the room.</source> + <translation>Dit zal de kamer vernietigeng.</translation> + </message> + <message> + <source>Accept Invite</source> + <translation>Accepteer uitnodiging</translation> + </message> + <message> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 wil een tekentafel starten</translation> </message> </context> @@ -1055,4 +1697,33 @@ afbeelding</translation> </context> <context> + <name>Swift::QtFileTransferListWidget</name> + <message> + <source>Clear Finished Transfers</source> + <translation>Wis voltooide overdrachten</translation> + </message> + <message> + <source>File Transfer List</source> + <translation>Lijst Bestandsoverdrachten</translation> + </message> +</context> +<context> + <name>Swift::QtHistoryWindow</name> + <message> + <source>History</source> + <translation>Geschiedenis</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <source>Users to invite to this chat (one per line):</source> + <translation>Uit te nodigen gebruikers (één per lijn):</translation> + </message> + <message> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>Uitnodigingsreden (Optioneel)</translation> + </message> +</context> +<context> <name>Swift::QtJoinMUCWindow</name> <message> @@ -1149,9 +1820,28 @@ afbeelding</translation> <message> <source>Subject: %1</source> - <translation>Onderwerp: %1</translation> + <translation type="obsolete">Onderwerp: %1</translation> </message> <message> <source>SHA-1 Fingerprint: %1</source> - <translation>SHA-1 vingerafdruk: %1</translation> + <translation type="obsolete">SHA-1 vingerafdruk: %1</translation> + </message> + <message> + <source>Show &File Transfer Overview</source> + <translation>Toon overzicht &Bestandsoverdrachten</translation> + </message> + <message> + <source>Confirm terms of use</source> + <translation>Bevestig gebruikersovereenkomst</translation> + </message> +</context> +<context> + <name>Swift::QtMUCConfigurationWindow</name> + <message> + <source>Cancel</source> + <translation>Annuleren</translation> + </message> + <message> + <source>OK</source> + <translation>OK</translation> </message> </context> @@ -1190,14 +1880,14 @@ afbeelding</translation> </message> <message> - <source>&Add Contact</source> - <translation>Contact &toevoegen</translation> + <source>&Add Contact…</source> + <translation>Contact &toevoegen…</translation> </message> <message> - <source>&Edit Selected Contact</source> - <translation>Geselecteerde contact &bewerken</translation> + <source>&Edit Selected Contact…</source> + <translation>Geselecteerde contact &bewerken…</translation> </message> <message> - <source>Start &Chat</source> - <translation>&Conversatie starten</translation> + <source>Start &Chat…</source> + <translation>&Conversatie starten...</translation> </message> <message> @@ -1206,10 +1896,10 @@ afbeelding</translation> </message> <message> - <source>Edit &Profile</source> - <translation>Bewerk &profiel</translation> + <source>Edit &Profile…</source> + <translation>Bewerk &profiel…</translation> </message> <message> - <source>Enter &Room</source> - <translation>&Kamer betreden</translation> + <source>Enter &Room…</source> + <translation>&Kamer betreden…</translation> </message> <message> @@ -1225,4 +1915,20 @@ afbeelding</translation> <translation>Geen beschikbare opdrachten</translation> </message> + <message> + <source>&Show Emoticons</source> + <translation>&Toon emoticons</translation> + </message> + <message> + <source>&View History…</source> + <translation>Toon &Geschiedenis...</translation> + </message> + <message> + <source>&Request Delivery Receipts</source> + <translation>Vraag &ontvangstbevestigingen</translation> + </message> + <message> + <source>&Chats</source> + <translation>&Conversaties</translation> + </message> </context> <context> @@ -1246,4 +1952,35 @@ afbeelding</translation> </context> <context> + <name>Swift::QtOccupantListWidget</name> + <message> + <source>No actions for this user</source> + <translation>Geen acties voor deze gebruiker</translation> + </message> + <message> + <source>Kick user</source> + <translation>Schop gebruiker</translation> + </message> + <message> + <source>Kick and ban user</source> + <translation>Schop en verban gebruiker</translation> + </message> + <message> + <source>Make moderator</source> + <translation>Maak moderator</translation> + </message> + <message> + <source>Make participant</source> + <translation>Maak deelnemer</translation> + </message> + <message> + <source>Remove voice</source> + <translation>Verwijder stem</translation> + </message> + <message> + <source>Add to contacts</source> + <translation>Voeg toe aan contacten</translation> + </message> +</context> +<context> <name>Swift::QtProfileWindow</name> <message> @@ -1261,4 +1998,46 @@ afbeelding</translation> </context> <context> + <name>Swift::QtRosterHeader</name> + <message> + <source>Connection is secured</source> + <translation>Verbinding is beveiligd</translation> + </message> +</context> +<context> + <name>Swift::QtRosterWidget</name> + <message> + <source>Edit…</source> + <translation>Bewerk...</translation> + </message> + <message> + <source>Remove</source> + <translation>Verwijder</translation> + </message> + <message> + <source>Send File</source> + <translation>Verzend bestand</translation> + </message> + <message> + <source>Start Whiteboard Chat</source> + <translation>Start tekentafel</translation> + </message> + <message> + <source>All Files (*);;</source> + <translation>Alle bestanden (*);;</translation> + </message> + <message> + <source>Rename</source> + <translation>Naam wijzigen</translation> + </message> + <message> + <source>Rename group</source> + <translation>Groepsnaam wijzigen</translation> + </message> + <message> + <source>Enter a new name for group '%1':</source> + <translation>Geef een nieuwe naam voor groep '%1':</translation> + </message> +</context> +<context> <name>Swift::QtStatusWidget</name> <message> @@ -1308,21 +2087,21 @@ Als u deze keuze uitstelt, zal deze vraag opnieuw gesteld worden wanneer u zich <message> <source>Edit</source> - <translation>Bewerk</translation> + <translation type="obsolete">Bewerk</translation> </message> <message> <source>Remove</source> - <translation>Verwijder</translation> + <translation type="obsolete">Verwijder</translation> </message> <message> <source>Rename</source> - <translation>Naam wijzigen</translation> + <translation type="obsolete">Naam wijzigen</translation> </message> <message> <source>Rename group</source> - <translation>Groepsnaam wijzigen</translation> + <translation type="obsolete">Groepsnaam wijzigen</translation> </message> <message> <source>Enter a new name for group '%1':</source> - <translation>Geef een niewe naam voor groep '%1':</translation> + <translation type="obsolete">Geef een nieuwe naam voor groep '%1':</translation> </message> </context> @@ -1381,4 +2160,309 @@ Als u deze keuze uitstelt, zal deze vraag opnieuw gesteld worden wanneer u zich </context> <context> + <name>Swift::QtVCardAddressField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Street</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Region</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Country</source> + <translation type="unfinished">Land</translation> + </message> + <message> + <source>Postal Code</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>City</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Address Extension</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>PO Box</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Preferred</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Home</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Work</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Postal</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Parcel</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Domestic Delivery</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>International Delivery</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Delivery Type</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardAddressLabelField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Address Label</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Preferred</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Home</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Work</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Postal</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Parcel</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Domestic Delivery</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>International Delivery</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Delivery Type</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardBirthdayField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Birthday</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>dd.MM.yyyy</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardDescriptionField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Description</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardEMailField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Type:</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Internet</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>X.400</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>E-Mail</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Preferred</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Home</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Work</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardJIDField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>JID</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardOrganisationField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Organisation Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Remove Unit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Add Unit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Organisation Units</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardRoleField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Role</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardTelephoneField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Telephone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Preferred</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Home</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Work</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Voice</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cell</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pager</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>ISDN</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Video</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Voice Messaging Service</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Fax</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Modem</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Personal Communication Service</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Bulletin Board System</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardTitleField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Title</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Swift::QtVCardURLField</name> + <message> + <source>Form</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>URL</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> <name>Swift::QtWebView</name> <message> @@ -1386,4 +2470,19 @@ Als u deze keuze uitstelt, zal deze vraag opnieuw gesteld worden wanneer u zich <translation>Wissen</translation> </message> + <message> + <source>Increase font size</source> + <translation>Vergroot lettertype</translation> + </message> + <message> + <source>Decrease font size</source> + <translation>Verklein lettertype</translation> + </message> +</context> +<context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>Het venster sluiten zal deze sessie beëindigen. Bent U zeker dat U dit wil doen?</translation> + </message> </context> <context> diff --git a/Swift/Translations/swift_pl.ts b/Swift/Translations/swift_pl.ts index f471f4a..cc461a1 100644 --- a/Swift/Translations/swift_pl.ts +++ b/Swift/Translations/swift_pl.ts @@ -1438,25 +1438,25 @@ <message> <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>Edytuj &profil</translation> + <source>Edit &Profile…</source> + <translation>Edytuj &profil…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>&Wejdź do pokoju</translation> + <source>Enter &Room…</source> + <translation>&Wejdź do pokoju…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Dodaj kontakt</translation> + <source>&Add Contact…</source> + <translation>&Dodaj kontakt…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Edytuj wybrany kontakt</translation> + <source>&Edit Selected Contact…</source> + <translation>&Edytuj wybrany kontakt…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> + <source>Start &Chat…</source> <translation>&Rozpocznij rozmowę</translation> </message> diff --git a/Swift/Translations/swift_ru.ts b/Swift/Translations/swift_ru.ts index c1a9a6c..56069a5 100644 --- a/Swift/Translations/swift_ru.ts +++ b/Swift/Translations/swift_ru.ts @@ -6,70 +6,86 @@ <name></name> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="46"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> <source>Starting chat with %1% in chatroom %2%</source> <translation>Начат чат с %1% в комнате %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="49"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="62"/> <source>Starting chat with %1% - %2%</source> <translation>Начат чат с %1% - %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="119"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> + <source>This chat doesn't support delivery receipts.</source> + <translation>Этот чат не поддерживает отчёты о доставке.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="204"/> + <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> + <translation>Этот чат не поддерживает отчёты о доставке. Вы не будете получать уведомления о доставке отправленных Вами сообщений.</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatController.cpp" line="216"/> + <location filename="../Controllers/FileTransfer/FileTransferController.cpp" line="42"/> <source>me</source> <translation>я</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="160"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="324"/> <source>%1% has gone offline</source> <translation>%1% теперь отключён</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="164"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="328"/> <source>%1% has become available</source> <translation>%1% снова доступен</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="166"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="330"/> <source>%1% has gone away</source> <translation>%1% теперь 'отсутствую'</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="168"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="332"/> <source>%1% is now busy</source> <translation>%1% теперь 'не беспокоить'</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="56"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="74"/> <source>The day is now %1%</source> <translation>Сегодня %1%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="191"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <source>Couldn't send message: %1%</source> + <translation>Ошибка отправки сообщения: %1%</translation> + </message> + <message> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> <source>Error sending message</source> <translation>Ошибка отправки сообщения</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="197"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="275"/> <source>Bad request</source> <translation>Неверный запрос</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="198"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="276"/> <source>Conflict</source> <translation>Конфликт</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="199"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="277"/> <source>This feature is not implemented</source> <translation>Эта функция не реализована</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="200"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="278"/> <source>Forbidden</source> <translation>Запрещено</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="201"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="279"/> <source>Recipient can no longer be contacted</source> <translatorcomment>wtf</translatorcomment> @@ -77,190 +93,194 @@ </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="202"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="280"/> <source>Internal server error</source> <translation>Внутренняя ошибка сервера</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="203"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="281"/> <source>Item not found</source> <translation>Элемент не найден</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="204"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="282"/> <source>JID Malformed</source> <translation>Некорректный JID</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="205"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="283"/> <source>Message was rejected</source> <translation>Сообщение отклонено</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="206"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="284"/> <source>Not allowed</source> <translation>Не разрешено</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="207"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="285"/> <source>Not authorized</source> <translation>Не авторизован</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="208"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="286"/> <source>Payment is required</source> <translation>Требуется оплата</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="287"/> <source>Recipient is unavailable</source> <translation>Получатель недоступен</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="210"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="288"/> <source>Redirect</source> <translation>Перенаправление</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="211"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="289"/> <source>Registration required</source> <translation>Требуется регистрация</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="212"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="290"/> <source>Recipient's server not found</source> <translation>Сервер получателя не найден</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="213"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="291"/> <source>Remote server timeout</source> <translation>Таймаут сервера</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="214"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="292"/> <source>The server is low on resources</source> <translation>Серверу не хватает ресурсов</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="215"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="293"/> <source>The service is unavailable</source> <translation>Сервис недоступен</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="216"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="294"/> <source>A subscription is required</source> <translation>Требуется подписка</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="217"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="295"/> <source>Undefined condition</source> <translation>Неопределённое условие</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="218"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="296"/> <source>Unexpected request</source> <translation>Неожиданный запрос</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="114"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> <source>Room %1% is not responding. This operation may never complete.</source> <translation>Комната %1% не отвечает. Эта операция может не завершиться.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="125"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="233"/> <source>Unable to enter this room</source> <translation>Не удалось войти в комнату</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="131"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="239"/> <source>Unable to enter this room as %1%, retrying as %2%</source> <translation>Не удалось войти в комнату как %1%, попытка войти как %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="135"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> <source>No nickname specified</source> <translation>Ник не указан</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="139"/> <source>A password needed</source> - <translation>Нужен пароль</translation> + <translation type="obsolete">Нужен пароль</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="143"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="251"/> <source>Only members may enter</source> <translation>Вход только для зарегистрированных участников</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="147"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="255"/> <source>You are banned from the room</source> <translation>Вы забанены в этой комнате</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="151"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="259"/> <source>The room is full</source> <translation>Комната полная</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="155"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="263"/> <source>The room does not exist</source> <translation>Комната не существует</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="173"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="269"/> + <source>Couldn't join room: %1%.</source> + <translation>Ошибка входа в комнату: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="281"/> <source>You have entered room %1% as %2%.</source> <translation>Вы вошли в комнату %1% как %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="214"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="332"/> <source>%1% has entered the room as a %2%.</source> <translation>%1% входит как %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="217"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="335"/> <source>%1% has entered the room.</source> <translation>%1% входит.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="384"/> <source>moderator</source> <translation>модератор</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="244"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="385"/> <source>participant</source> <translation>участник</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="245"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="386"/> <source>visitor</source> <translation>гость</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="283"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="436"/> <source>The room subject is now: %1%</source> <translation>Тема конференции: %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="313"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="470"/> <source>%1% is now a %2%</source> <translation>%1% теперь %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="319"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="486"/> <source>Moderators</source> <translation>Модераторы</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="320"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> <source>Participants</source> <translation>Участники</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="321"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="488"/> <source>Visitors</source> <translation>Гости</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="322"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="489"/> <source>Occupants</source> <translatorcomment>wtf</translatorcomment> @@ -268,234 +288,308 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="336"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> <source>Trying to enter room %1%</source> <translation>Попытка войти в комнату %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="365"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="474"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="544"/> + <source>%1% has left the room%2%</source> + <translation>%1% вышел из комнаты%2%</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="548"/> + <source>You have been kicked out of the room</source> + <translation>Вас выгнали из комнаты</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="549"/> + <source>You have been banned from the room</source> + <translation>Вас забанили в этой комнате</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="550"/> + <source>You are no longer a member of the room and have been removed</source> + <translation>Вы больше не зарегистрированный пользователь комнаты и были удалены</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="551"/> + <source>The room has been destroyed</source> + <translation>Комната была уничтожена</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="667"/> <source>%1% has left the room</source> <translation>%1% вышел</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="365"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="719"/> + <source>Room configuration failed: %1%.</source> + <translation>Ошибка настройки комнаты: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="725"/> + <source>Occupant role change failed: %1%.</source> + <translation>Ошибка изменения роли: %1%.</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="553"/> <source>You have left the room</source> <translation>Вы вышли из комнаты</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="439"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="247"/> + <source>The correct room password is needed</source> + <translation>Необходим правильный пароль к комнате</translation> + </message> + <message> + <location filename="../Controllers/Chat/MUCController.cpp" line="632"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="696"/> <source> and </source> <translation> и </translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="463"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="656"/> <source>%1% have entered the room</source> <translation>%1% вошли в комнату</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="466"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="659"/> <source>%1% has entered the room</source> <translation>%1% вошёл в комнату</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="471"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="664"/> <source>%1% have left the room</source> <translation>%1% вышли из комнаты</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="479"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="672"/> <source>%1% have entered then left the room</source> <translation>%1% вошли и вышли</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="482"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="675"/> <source>%1% has entered then left the room</source> <translation>%1% вошёл и вышел</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="680"/> <source>%1% have left then returned to the room</source> <translation>%1% вышли, затем вернулись</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="490"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="683"/> <source>%1% has left then returned to the room</source> <translation>%1% вышел, затем вернулся</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="51"/> + <location filename="../Controllers/EventNotifier.cpp" line="59"/> <source>%1% wants to add you to his/her contact list</source> <translation>%1% хочет добавить Вас в свой список контактов</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="55"/> + <location filename="../Controllers/EventNotifier.cpp" line="63"/> <source>Error</source> <translation>Ошибка</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="438"/> + <location filename="../Controllers/EventNotifier.cpp" line="67"/> + <source>%1% has invited you to enter the %2% room</source> + <translation>%1 приглашает Вас войти в конференцию %2</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="466"/> + <source>User address invalid. User address should be of the form 'alice@wonderland.lit'</source> + <translation>Адрес пользователя недействителен. Он должен иметь вид"vasya@pup.kin"</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="568"/> <source>Unknown Error</source> <translation>Неизвестная ошибка</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="439"/> + <location filename="../Controllers/MainController.cpp" line="569"/> <source>Unable to find server</source> <translation>Не удаётся найти сервер</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="440"/> + <location filename="../Controllers/MainController.cpp" line="570"/> <source>Error connecting to server</source> <translation>Ошибка подключения к серверу</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="441"/> + <location filename="../Controllers/MainController.cpp" line="571"/> <source>Error while receiving server data</source> <translation>Ошибка получения данных</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="442"/> + <location filename="../Controllers/MainController.cpp" line="572"/> <source>Error while sending data to the server</source> <translation>Ошибка отправки данных на сервер</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="443"/> + <location filename="../Controllers/MainController.cpp" line="573"/> <source>Error parsing server data</source> <translation>Ошибка обработки данных от сервера</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="444"/> + <location filename="../Controllers/MainController.cpp" line="574"/> <source>Login/password invalid</source> <translation>Неверный логин/пароль</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="445"/> + <location filename="../Controllers/MainController.cpp" line="575"/> <source>Error while compressing stream</source> <translation>Ошибка сжатия потока</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="446"/> + <location filename="../Controllers/MainController.cpp" line="576"/> <source>Server verification failed</source> <translation>Проверка сервера не удалась</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="447"/> + <location filename="../Controllers/MainController.cpp" line="577"/> <source>Authentication mechanisms not supported</source> <translation>Механизм авторизации не поддерживается</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="448"/> + <location filename="../Controllers/MainController.cpp" line="578"/> <source>Unexpected response</source> <translation>Неожиданный ответ</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="449"/> + <location filename="../Controllers/MainController.cpp" line="579"/> <source>Error binding resource</source> <translation>Ошибка назначения ресурса</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="450"/> + <location filename="../Controllers/MainController.cpp" line="580"/> <source>Error starting session</source> <translation>Ошибка при запуске сессии</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="451"/> + <location filename="../Controllers/MainController.cpp" line="581"/> <source>Stream error</source> <translation>Ошибка потока</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="452"/> + <location filename="../Controllers/MainController.cpp" line="582"/> <source>Encryption error</source> <translation>Ошибка шифрования</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="453"/> + <location filename="../Controllers/MainController.cpp" line="583"/> <source>Error loading certificate (Invalid password?)</source> <translation>Ошибка загрузки сертификата (неверный пароль?)</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="454"/> + <location filename="../Controllers/MainController.cpp" line="584"/> <source>Certificate not authorized</source> <translation>Сертификат не авторизован</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="456"/> + <location filename="../Controllers/MainController.cpp" line="585"/> + <source>Certificate card removed</source> + <translation>Сертификат удалён</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="587"/> <source>Unknown certificate</source> <translation>Неизвестный сертификат</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="457"/> + <location filename="../Controllers/MainController.cpp" line="588"/> <source>Certificate has expired</source> <translation>Срок действия сертификата истек</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="458"/> + <location filename="../Controllers/MainController.cpp" line="589"/> <source>Certificate is not yet valid</source> <translation>Сертификат ещё не действителен</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="459"/> + <location filename="../Controllers/MainController.cpp" line="590"/> <source>Certificate is self-signed</source> <translation>Сертификат самоподписанный</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="460"/> + <location filename="../Controllers/MainController.cpp" line="591"/> <source>Certificate has been rejected</source> <translation>Сертификат отклонён</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="461"/> + <location filename="../Controllers/MainController.cpp" line="592"/> <source>Certificate is not trusted</source> <translation>Сертификат не является доверенным</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="462"/> + <location filename="../Controllers/MainController.cpp" line="593"/> <source>Certificate cannot be used for encrypting your connection</source> <translation>Сертификат не может быть использован для шифрования Вашего соединения</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="463"/> + <location filename="../Controllers/MainController.cpp" line="594"/> <source>Certificate path length constraint exceeded</source> <translation>Превышена длина пути сертификата</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="464"/> + <location filename="../Controllers/MainController.cpp" line="595"/> <source>Invalid certificate signature</source> <translation>Подпись сертификата недействительна</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="465"/> + <location filename="../Controllers/MainController.cpp" line="596"/> <source>Invalid Certificate Authority</source> <translation>Центр сертификации недействителен</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="466"/> + <location filename="../Controllers/MainController.cpp" line="597"/> <source>Certificate does not match the host identity</source> <translation>Сертификат не соответствует серверу</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="476"/> + <location filename="../Controllers/MainController.cpp" line="598"/> + <source>Certificate has been revoked</source> + <translation>Сертификат отозван</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="599"/> + <source>Unable to determine certificate revocation state</source> + <translation>Невозможно проверить состояние отзыва сертификата</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="609"/> <source>Certificate error</source> <translation>Ошибка сертификата</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="490"/> + <location filename="../Controllers/MainController.cpp" line="616"/> + <source>Re-enter credentials and retry</source> + <translation>Повторно введите учётные данные и повторите</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="629"/> + <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> + <translation>Разорвано соединение с %1%: %2%. Чтобы восстановить связь, отключитесь и введите пароль ещё раз.</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="635"/> <source>Reconnect to %1% failed: %2%. Will retry in %3% seconds.</source> <translation>Переподключение к %1% не удалось: %2%. Повтор через %3% секунд.</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="493"/> + <location filename="../Controllers/MainController.cpp" line="638"/> <source>Disconnected from %1%: %2%.</source> <translation>Отключение от %1%: %2%.</translation> </message> <message> - <location filename="../Controllers/Roster/RosterController.cpp" line="126"/> - <location filename="../Controllers/Roster/RosterController.cpp" line="152"/> - <location filename="../Controllers/Roster/RosterController.cpp" line="214"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="131"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="157"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="222"/> <source>Contacts</source> <translation>Контакты</translation> </message> <message> - <location filename="../Controllers/Roster/RosterController.cpp" line="251"/> + <location filename="../Controllers/Roster/RosterController.cpp" line="263"/> <source>Server %1% rejected contact list change to item '%2%'</source> <translation>Сервер %1% отклонил изменение списка контактов с элементом '%2%'</translation> @@ -528,4 +622,30 @@ <translation>Ошибка публикации данных Вашего профиля</translation> </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="33"/> + <source>%1% (%2%)</source> + <translation>%1% (%2%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="38"/> + <source>%1% and %2% others (%3%)</source> + <translatorcomment>xzxz</translatorcomment> + <translation>%1% и %2% другие (%3%)</translation> + </message> + <message> + <location filename="../Controllers/ChatMessageSummarizer.cpp" line="41"/> + <source>%1%, %2% (%3%)</source> + <translation>%1%, %2% (%3%)</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="63"/> + <source>TLS Client Certificate Selection</source> + <translation>Выбор клиентского сертификата TLS</translation> + </message> + <message> + <location filename="../QtUI/CAPICertificateSelector.cpp" line="64"/> + <source>Select a certificate to use for authentication</source> + <translation>Выбрать сертификат для аутентификации</translation> + </message> </context> <context> @@ -676,13 +796,110 @@ </message> <message> - <location filename="../QtUI/EventViewer/QtEvent.cpp" line="63"/> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="68"/> <source>%1 would like to add you to their contact list.</source> <translation>%1 хочет добавить Вас в свой список контактов.</translation> </message> <message> - <location filename="../QtUI/EventViewer/QtEvent.cpp" line="66"/> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="71"/> <source>%1 would like to add you to their contact list, saying '%2'</source> <translation>%1 хочет добавить Вас в свой список контактов, говоря '%2'</translation> </message> + <message> + <location filename="../QtUI/EventViewer/QtEvent.cpp" line="81"/> + <source>%1 has invited you to enter the %2 room.</source> + <translation>%1 приглашает Вас войти в конференцию %2.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="982"/> + <source>You've been invited to enter the %1 room.</source> + <translation>Вас пригласили войти в конференцию %1.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="984"/> + <source>Reason: %1</source> + <translation>Причина: %1</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="987"/> + <source>This person may not have really sent this invitation!</source> + <translation>Этот человек, возможно, не посылал это приглашение!</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="46"/> + <source>Direction</source> + <translatorcomment>xzxz</translatorcomment> + <translation>Направление</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="47"/> + <source>Other Party</source> + <translation>Другая сторона</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="48"/> + <source>State</source> + <translation>Регион</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="49"/> + <source>Progress</source> + <translation>Прогресс</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="50"/> + <source>Size</source> + <translation>Размер</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Incoming</source> + <translation>Входящий</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="65"/> + <source>Outgoing</source> + <translation>Исходящий</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="74"/> + <source>Waiting for start</source> + <translation>Ожидание старта</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="76"/> + <source>Waiting for other side to accept</source> + <translation>Ожидание принятия на другой стороне</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="78"/> + <source>Negotiating</source> + <translation>Переговоры</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="80"/> + <source>Transferring</source> + <translation>Передача</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="82"/> + <source>Finished</source> + <translatorcomment>xzxz</translatorcomment> + <translation>Завершено</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="84"/> + <source>Failed</source> + <translation>Не удалось</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListItemModel.cpp" line="86"/> + <source>Canceled</source> + <translation>Отменено</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="142"/> + <source>Connection Options</source> + <translation>Параметры подключения</translation> + </message> </context> <context> @@ -944,72 +1161,280 @@ </context> <context> + <name>QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="14"/> + <source>Edit Affiliations</source> + <translation>Редактирование рангов</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="28"/> + <source>Affiliation:</source> + <translation>Ранг:</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="36"/> + <source>Owner</source> + <translation>Владелец</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="41"/> + <source>Administrator</source> + <translation>Администратор</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="46"/> + <source>Member</source> + <translation>Зарегистрированный пользователь</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="51"/> + <source>Outcast (Banned)</source> + <translation>Заблокированный</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="68"/> + <source>Add User</source> + <translation>Добавить пользователя</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="75"/> + <source>Remove User</source> + <translation>Удалить пользователя</translation> + </message> +</context> +<context> <name>QtBookmarkDetailWindow</name> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="137"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="20"/> <source>Edit Bookmark Details</source> <translation>Редактирование закладки</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="138"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="40"/> <source>Bookmark Name:</source> <translation>Название закладки:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="139"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="50"/> <source>Room Address:</source> <translation>Адрес комнаты:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="140"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="60"/> <source>Your Nickname:</source> <translation>Ваш ник:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="141"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="70"/> <source>Room password:</source> <translation>Пароль комнаты:</translation> </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> - <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="142"/> - <source>Join automatically</source> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="93"/> + <source>Enter automatically</source> <translation>Входить автоматически</translation> </message> + <message> + <source>Join automatically</source> + <translation type="obsolete">Входить автоматически</translation> + </message> +</context> +<context> + <name>QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.ui" line="14"/> + <source>Certificate Viewer</source> + <translation>Просмотр сертификата</translation> + </message> +</context> +<context> + <name>QtConnectionSettings</name> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="14"/> + <source>Connection Options</source> + <translation>Параметры подключения</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="22"/> + <source>Connection Method:</source> + <translation>Метод подключения:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="36"/> + <source>Automatic</source> + <translation>Автоматически</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="41"/> + <source>Manual</source> + <translation>Вручную</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="46"/> + <source>BOSH</source> + <translation>BOSH</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="99"/> + <source>Secure connection:</source> + <translation>Безопасное подключение:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="107"/> + <source>Never</source> + <translation>Никогда</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="112"/> + <source>Encrypt when possible</source> + <translation>Шифровать соединение если возможно</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="117"/> + <source>Always encrypt</source> + <translation>Всегда шифровать</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="127"/> + <source>Allow Compression</source> + <translation>Разрешить сжатие</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="134"/> + <source>Allow sending password over insecure connection</source> + <translation>Разрешить отправку пароля через незащищённое соединение</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="157"/> + <source>Manually select server</source> + <translation>Вручную выбрать сервер</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="185"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="315"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="429"/> + <source>Hostname:</source> + <translation>Сервер:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="208"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="338"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="452"/> + <source>Port:</source> + <translation>Порт:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="230"/> + <source>Connection Proxy</source> + <translation>Прокси для подключения</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="238"/> + <source>Proxy type:</source> + <translation>Тип прокси:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="249"/> + <source>None</source> + <translation>Отсутствует</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="254"/> + <source>Use system-configured proxy</source> + <translation>Использовать системные настройки прокси</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="259"/> + <source>SOCKS5</source> + <translation>SOCKS5</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="264"/> + <source>HTTP Connect</source> + <translation>HTTP Connect</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="287"/> + <source>Override system-configured proxy</source> + <translation>Перезаписать системные настройки прокси</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="382"/> + <source>BOSH URI:</source> + <translation>BOSH URI:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="401"/> + <source>Manually select HTTP proxy</source> + <translation>Вручную выбрать HTTP прокси</translation> + </message> +</context> +<context> + <name>QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="14"/> + <source>History</source> + <translation>История</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="22"/> + <source>Search:</source> + <translation>Поиск:</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="42"/> + <source>Next</source> + <translatorcomment>xzxz</translatorcomment> + <translation>Следующий</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="52"/> + <source>Previous</source> + <translation>Предыдущий</translation> + </message> </context> <context> <name>QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="124"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="130"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="20"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="113"/> <source>Enter Room</source> <translation>Войти в комнату</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="125"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="28"/> + <source>Room Address:</source> + <translation>Адрес комнаты:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="42"/> + <source>Your Nickname:</source> + <translation>Ваш ник:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="59"/> + <source>Room Password:</source> + <translation>Пароль комнаты:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="71"/> + <source>Automatically configure newly created rooms</source> + <translation>Автоматически откывать окно конфигурации комнаты при её создании</translation> + </message> + <message> <source>Room:</source> - <translation>Комната:</translation> + <translation type="obsolete">Комната:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="126"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="35"/> <source>Search ...</source> <translation>Поиск ...</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="127"/> <source>Nickname:</source> - <translation>Ник:</translation> + <translation type="obsolete">Ник:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="129"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="106"/> <source>Enter automatically in future</source> <translation>Входить автоматически в будущем</translation> @@ -1019,30 +1444,25 @@ <name>QtMUCSearchWindow</name> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="118"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="14"/> <source>Search Room</source> <translation>Поик комнаты</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="119"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="20"/> <source>Service:</source> <translation>Сервис:</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="121"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="74"/> <source>Cancel</source> <translation>Отмена</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="122"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="84"/> <source>OK</source> <translation>ОК</translation> </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> - <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="123"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="96"/> <source>List rooms</source> <translation>Список комнат</translation> @@ -1052,30 +1472,25 @@ <name>QtUserSearchFieldsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="119"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="27"/> <source>Nickname:</source> <translation>Ник:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="120"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="37"/> <source>First name:</source> <translation>Имя:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="47"/> <source>Last name:</source> <translation>Фамилия:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="57"/> <source>E-Mail:</source> <translation>E-Mail:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="74"/> <source>Fetching search fields</source> <translation>Получение полей поиска</translation> @@ -1085,30 +1500,25 @@ <name>QtUserSearchFirstPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="17"/> <source>Add a user</source> <translation>Добавить пользователя</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="20"/> <source>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</source> <translation>Добавить пользователя в список контактов. Вы можете ввести его адрес или воспользоваться поиском.</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="35"/> <source>I know their address:</source> <translation>Я знаю его адрес:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="125"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="49"/> <source>I'd like to search my server</source> <translation>Я хочу искать на моём сервере</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="126"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="73"/> <source>I'd like to search another server:</source> <translation>Я хочу искать на другом сервере:</translation> @@ -1116,8 +1526,15 @@ </context> <context> + <name>QtUserSearchResultsPage</name> + <message> + <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui" line="27"/> + <source>No results.</source> + <translation>Нет результатов.</translation> + </message> +</context> +<context> <name>QtUserSearchWizard</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchWizard.h" line="39"/> + <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui" line="14"/> <source>Find User</source> <translation>Поиск пользователя</translation> @@ -1127,8 +1544,18 @@ <name>Swift::ChatListModel</name> <message> - <location filename="../QtUI/ChatList/ChatListModel.cpp" line="15"/> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> <source>Bookmarked Rooms</source> <translation>Закладки комнат</translation> </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="18"/> + <source>Recent Chats</source> + <translation>Последние чаты</translation> + </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="20"/> + <source>Opened Whiteboards</source> + <translation>Открытые доски</translation> + </message> </context> <context> @@ -1168,4 +1595,57 @@ </context> <context> + <name>Swift::QtAdHocCommandWindow</name> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="36"/> + <source>Cancel</source> + <translation>Отмена</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="39"/> + <source>Back</source> + <translation>Назад</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="42"/> + <source>Next</source> + <translatorcomment>xzxz следующий</translatorcomment> + <translation>Далее</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="45"/> + <source>Complete</source> + <translatorcomment>xzxz ejabberd buggy ad-hoc</translatorcomment> + <translation>Выполнено</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="86"/> + <source>Error: %1</source> + <translation>Ошибка: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="87"/> + <source>Warning: %1</source> + <translation>Предупреждение: %1</translation> + </message> + <message> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="104"/> + <source>Error executing command</source> + <translation>Ошибка выполнения команды</translation> + </message> +</context> +<context> + <name>Swift::QtAffiliationEditor</name> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Add User</source> + <translation>Добавить пользователя</translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> + <source>Added User's Address:</source> + <translation>Введите адрес пользователя:</translation> + </message> +</context> +<context> <name>Swift::QtAvatarWidget</name> <message> @@ -1191,6 +1671,10 @@ <message> <location filename="../QtUI/QtAvatarWidget.cpp" line="81"/> + <source>Image Files (*.png *.jpg *.jpeg *.gif)</source> + <translation>Изображения (*.png *.jpg *.jpeg *.gif)</translation> + </message> + <message> <source>Image Files (*.png *.jpg *.gif)</source> - <translation>Изображения (*.png *.jpg *.gif)</translation> + <translation type="obsolete">Изображения (*.png *.jpg *.gif)</translation> </message> <message> @@ -1219,56 +1703,350 @@ </context> <context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="95"/> + <source>General</source> + <translation>Общее</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="96"/> + <source>Valid From</source> + <translation>Действует с</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="97"/> + <source>Valid To</source> + <translation>Действителен до</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="98"/> + <source>Serial Number</source> + <translation>Серийный номер</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="99"/> + <source>Version</source> + <translation>Версия</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="101"/> + <source>Subject</source> + <translation>Тема</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="102"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="124"/> + <source>Organization</source> + <translation>Организация</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="103"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="125"/> + <source>Common Name</source> + <translation>Полное имя</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="104"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="126"/> + <source>Locality</source> + <translation>Местонахождение</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="105"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="127"/> + <source>Organizational Unit</source> + <translation>Подразделение</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="106"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="128"/> + <source>Country</source> + <translation>Страна</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="107"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="129"/> + <source>State</source> + <translation>Регион</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="110"/> + <source>Alternate Subject Names</source> + <translation>Альтернативные имена субъекта</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="115"/> + <source>E-mail Address</source> + <translation>E-Mail адрес</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="117"/> + <source>DNS Name</source> + <translation>DNS имя</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="123"/> + <source>Issuer</source> + <translation>Издатель</translation> + </message> +</context> +<context> <name>Swift::QtChatListWindow</name> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="62"/> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="66"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="86"/> <source>Add New Bookmark</source> <translation>Добавить закладку</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="63"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="83"/> <source>Edit Bookmark</source> <translation>Редактировать закладку</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="64"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> <source>Remove Bookmark</source> <translation>Удалить закладку</translation> </message> + <message> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="173"/> + <source>Clear recents</source> + <translation>Удалить последние</translation> + </message> </context> <context> <name>Swift::QtChatView</name> <message> - <location filename="../QtUI/QtChatView.cpp" line="61"/> + <location filename="../QtUI/QtChatView.cpp" line="73"/> <source>Clear log</source> <translation>Очистить лог</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="62"/> + <location filename="../QtUI/QtChatView.cpp" line="74"/> <source>You are about to clear the contents of your chat log.</source> <translation>Вы собираетесь очистить содержимое Вашего чата.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="63"/> + <location filename="../QtUI/QtChatView.cpp" line="75"/> <source>Are you sure?</source> <translation>Вы уверены?</translation> </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="219"/> + <source>%1 edited</source> + <translation>%1 отредактировано</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="400"/> + <source>Waiting for other side to accept the transfer.</source> + <translation>Ожидание принятия на другой стороне.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="401"/> + <location filename="../QtUI/QtChatView.cpp" line="406"/> + <location filename="../QtUI/QtChatView.cpp" line="417"/> + <source>Cancel</source> + <translation>Отмена</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="405"/> + <source>Negotiating...</source> + <translation>Переговоры...</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="420"/> + <source>Transfer has been canceled!</source> + <translation>Передача была отменена!</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="424"/> + <source>Transfer completed successfully.</source> + <translation>Передача успешно завершена.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="427"/> + <source>Transfer failed.</source> + <translation>Передача не удалась.</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="437"/> + <source>Started whiteboard chat</source> + <translation>Открыта доска для рисования</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="438"/> + <source>Show whiteboard</source> + <translation>Показать доску для рисования</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="440"/> + <source>Whiteboard chat has been canceled</source> + <translation>Доска для рисования была отменена</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="442"/> + <source>Whiteboard chat request has been rejected</source> + <translation>Запрос порисовать был отклонён</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="451"/> + <source>Return to room</source> + <translation>Вернуться в комнату</translation> + </message> </context> <context> <name>Swift::QtChatWindow</name> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="302"/> + <location filename="../QtUI/QtChatWindow.cpp" line="157"/> + <source>Correcting</source> + <translation>Исправление</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="269"/> + <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> + <translation>Этот чат, возможно, не поддерживает исправление сообщений. Если Вы отправляете исправление, это может выглядеть как дубликат сообщения</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="271"/> + <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> + <translation>Этот чат не поддерживает исправление сообщений. Если Вы отправляете исправление, это будет выглядеть как дубликат сообщения</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="546"/> <source>This message has not been received by your server yet.</source> <translation>Это сообщение не может быть получено.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="304"/> + <location filename="../QtUI/QtChatWindow.cpp" line="553"/> <source>This message may not have been transmitted.</source> <translation>Это сообщение не может быть передано.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="324"/> + <location filename="../QtUI/QtChatWindow.cpp" line="562"/> + <source>The receipt for this message has been received.</source> + <translation>Отчёт о доставке этого сообщения был получен.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="565"/> + <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> + <translation>Отчёт о доставке этого сообщения пока не получен. Получатель, возможно, не получил это сообщение.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="613"/> + <source>Send file</source> + <translation>Отправить файл</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="615"/> + <location filename="../QtUI/QtChatWindow.cpp" line="623"/> + <location filename="../QtUI/QtChatWindow.cpp" line="660"/> + <location filename="../QtUI/QtChatWindow.cpp" line="664"/> + <source>Cancel</source> + <translation>Отмена</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="616"/> + <source>Set Description</source> + <translation>Установка описания</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="617"/> + <source>Send</source> + <translation>Отправка</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="621"/> + <source>Receiving file</source> + <translation>Получить файл</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="624"/> + <location filename="../QtUI/QtChatWindow.cpp" line="665"/> + <source>Accept</source> + <translation>Принять</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="659"/> + <source>Starting whiteboard chat</source> + <translation>Открывается доска для рисования</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="663"/> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 хочет порисовать</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="700"/> + <source>File transfer description</source> + <translation>Описание передачи файлов</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="701"/> + <source>Description:</source> + <translation>Описание:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="715"/> + <source>Save File</source> + <translation>Сохранить файл</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="908"/> + <source>Change subject…</source> + <translation>Изменить тему…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="909"/> + <source>Configure room…</source> + <translation>Настроить комнату…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="910"/> + <source>Edit affiliations…</source> + <translation>Редактировать ранги…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="911"/> + <source>Destroy room</source> + <translation>Уничтожить комнату</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="912"/> + <source>Invite person to this room…</source> + <translation>Пригласить в эту комнату…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="922"/> + <source>Change room subject</source> + <translation>Изменение темы конференции</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="922"/> + <source>New subject:</source> + <translation>Новая тема:</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="940"/> + <source>Confirm room destruction</source> + <translation>Подтверждение удаления комнаты</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="941"/> + <source>Are you sure you want to destroy the room?</source> + <translation>Вы уверены, что хотите удалить комнату?</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="942"/> + <source>This will destroy the room.</source> + <translation>Комната будет уничтожена.</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="993"/> + <source>Accept Invite</source> + <translation>Принять приглашение</translation> + </message> + <message> <source>Couldn't send message: %1</source> - <translation>Ошибка отправки: %1</translation> + <translation type="obsolete">Ошибка отправки: %1</translation> </message> </context> @@ -1276,15 +2054,15 @@ <name>Swift::QtContactEditWidget</name> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="28"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="122"/> <source>Name:</source> <translation>Имя:</translation> </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="34"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="42"/> <source>Groups:</source> <translation>Группы:</translation> </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="56"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="65"/> <source>New Group:</source> <translation>Новая группа:</translation> @@ -1294,30 +2072,30 @@ <name>Swift::QtContactEditWindow</name> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="26"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="28"/> <source>Edit contact</source> <translation>Редактировать контакт</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="41"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="43"/> <source>Remove contact</source> <translation>Удалить контакт</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="44"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="46"/> <source>OK</source> <translation>ОК</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="82"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="94"/> <source>Confirm contact deletion</source> <translation>Подтверждение удаления</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="83"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="95"/> <source>Are you sure you want to delete this contact?</source> <translation>Вы уверены, что хотите удалить этот контакт?</translation> </message> <message> - <location filename="../QtUI/QtContactEditWindow.cpp" line="84"/> + <location filename="../QtUI/QtContactEditWindow.cpp" line="96"/> <source>This will remove the contact '%1' from all groups they may be in.</source> <translation>Это позволит удалить контакт '%1' из всех групп, где он может быть</translation> @@ -1327,7 +2105,41 @@ <name>Swift::QtEventWindow</name> <message> - <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="47"/> + <location filename="../QtUI/EventViewer/QtEventWindow.cpp" line="48"/> <source>Display Notice</source> - <translation>Показать уведомления</translation> + <translation>Показать уведомление</translation> + </message> +</context> +<context> + <name>Swift::QtFileTransferListWidget</name> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="39"/> + <source>Clear Finished Transfers</source> + <translation>Очистить завершённые передачи</translation> + </message> + <message> + <location filename="../QtUI/QtFileTransferListWidget.cpp" line="44"/> + <source>File Transfer List</source> + <translation>Список передачи файлов</translation> + </message> +</context> +<context> + <name>Swift::QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.cpp" line="57"/> + <source>History</source> + <translation>История</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="25"/> + <source>Users to invite to this chat (one per line):</source> + <translation>Пользователи для приглашения в этот чат (один на строку):</translation> + </message> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="31"/> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>Если Вы хотите указать причину для приглашения, введите её здесь</translation> </message> </context> @@ -1335,5 +2147,5 @@ <name>Swift::QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.cpp" line="15"/> + <location filename="../QtUI/QtJoinMUCWindow.cpp" line="19"/> <source>someroom@rooms.example.com</source> <translation>someroom@rooms.example.com</translation> @@ -1343,120 +2155,151 @@ <name>Swift::QtLoginWindow</name> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="81"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="92"/> <source>User address:</source> <translation>Адрес пользователя:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="86"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="87"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="98"/> <source>User address - looks like someuser@someserver.com</source> <translation>Например, вася@jabber.ru/стриж</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="91"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="102"/> <source>Example: alice@wonderland.lit</source> <translation>Например, ivan@jabber.ru</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="108"/> <source>Password:</source> <translation>Пароль:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="118"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="119"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="129"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="130"/> <source>Click if you have a personal certificate used for login to the service.</source> <translation>Нажмите, если у Вас есть личный сертификат, используемый для входа.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="125"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="299"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="353"/> <source>Connect</source> <translation>Подключиться</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="154"/> <source>Remember Password?</source> - <translation>Запомнить пароль?</translation> + <translation>Запомнить пароль</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="138"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="156"/> <source>Login Automatically?</source> - <translation>Подключаться автоматически?</translation> + <translation>Подключаться автоматически</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="150"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="168"/> <source>&Swift</source> <translation>&Swift</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="152"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> <source>&General</source> <translation>&Общие</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="160"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="178"/> <source>&About %1</source> <translation>&О %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="165"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="183"/> <source>&Show Debug Console</source> <translation>&Показать консоль отладки</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="169"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="188"/> + <source>Show &File Transfer Overview</source> + <translation>Показать окно передачи &файлов</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="193"/> <source>&Play Sounds</source> - <translation>&Играть звуки</translation> + <translation>&Воспроизводить звуки</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="175"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="199"/> <source>Display Pop-up &Notifications</source> <translation>Показывать всплывающие &уведомления</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="190"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="211"/> <source>&Quit</source> - <translation>&Выход</translation> + <translation>В&ыход</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="205"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove profile</source> <translation>Удалить профиль</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="205"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove the profile '%1'?</source> <translation>Удалить профиль '%1'?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="299"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="353"/> <source>Cancel</source> <translation>Отмена</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="320"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="367"/> + <source>Confirm terms of use</source> + <translation>Подтверждение условий использования</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="410"/> <source>Select an authentication certificate</source> <translation>Выберите сертификат проверки подлинности</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="420"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="410"/> + <source>P12 files (*.cert *.p12 *.pfx);;All files (*.*)</source> + <translation>P12 файлы (*.cert *.p12 *.pfx);;Все файлы (*.*)</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="523"/> <source>The certificate presented by the server is not valid.</source> <translation>Сертификат, предоставленный сервером, является недопустимым.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="421"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="524"/> <source>Would you like to permanently trust this certificate? This must only be done if you know it is correct.</source> <translation>Хотели бы Вы доверять этому сертификату?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="423"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="526"/> + <source>Show Certificate</source> + <translation>Просмотр сертификата</translation> + </message> + <message> <source>Subject: %1</source> - <translation>Тема: %1</translation> + <translation type="obsolete">Тема: %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="424"/> <source>SHA-1 Fingerprint: %1</source> - <translation>Отпечаток SHA-1: %1</translation> + <translation type="obsolete">Отпечаток SHA-1: %1</translation> + </message> +</context> +<context> + <name>Swift::QtMUCConfigurationWindow</name> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="34"/> + <source>Cancel</source> + <translation>Отмена</translation> + </message> + <message> + <location filename="../QtUI/QtMUCConfigurationWindow.cpp" line="37"/> + <source>OK</source> + <translation>ОК</translation> </message> </context> @@ -1473,84 +2316,119 @@ <name>Swift::QtMainWindow</name> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="64"/> + <location filename="../QtUI/QtMainWindow.cpp" line="79"/> <source>&Contacts</source> <translation>&Контакты</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="71"/> - <location filename="../QtUI/QtMainWindow.cpp" line="137"/> + <location filename="../QtUI/QtMainWindow.cpp" line="88"/> + <location filename="../QtUI/QtMainWindow.cpp" line="205"/> <source>&Notices</source> <translation>&Уведомления</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="72"/> + <location filename="../QtUI/QtMainWindow.cpp" line="87"/> <source>C&hats</source> - <translation>&Чаты</translation> + <translation>Ч&аты</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="76"/> + <location filename="../QtUI/QtMainWindow.cpp" line="96"/> <source>&View</source> <translation>&Вид</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="78"/> + <location filename="../QtUI/QtMainWindow.cpp" line="98"/> <source>&Show offline contacts</source> <translation>&Показывать отключённых</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="84"/> + <location filename="../QtUI/QtMainWindow.cpp" line="106"/> + <source>&Show Emoticons</source> + <translation>Показывать &смайлы</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="120"/> <source>&Actions</source> <translation>&Действия</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>Редактировать &профиль</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="122"/> + <source>Edit &Profile…</source> + <translation>Редактировать &профиль…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>&Войти в комнату</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <source>Enter &Room…</source> + <translation>&Войти в комнату…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Добавить контакт</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="129"/> + <source>&View History…</source> + <translation>Просмотр &истории…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Редактировать выделенный контакт</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="133"/> + <source>&Add Contact…</source> + <translation>До&бавить контакт…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> - <translation>Начать &чат</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="136"/> + <source>&Edit Selected Contact…</source> + <translation>&Редактировать выделенный контакт…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="103"/> + <location filename="../QtUI/QtMainWindow.cpp" line="140"/> + <source>Start &Chat…</source> + <translation>Нач&ать чат…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="143"/> + <source>Run Server Command</source> + <translation>Выполнить серверную команду</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="146"/> <source>&Sign Out</source> <translation>&Отключиться</translation> </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="150"/> + <source>&Request Delivery Receipts</source> + <translation>&Запрашивать уведомления о доставке</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="161"/> + <source>Collecting commands...</source> + <translation>Сбор команд...</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="216"/> + <source>&Chats</source> + <translation>&Чаты</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="339"/> + <source>No Available Commands</source> + <translation>Нет доступных комманд</translation> + </message> </context> <context> <name>Swift::QtNameWidget</name> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="47"/> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> <source>Show Nickname</source> <translation>Показывать ник</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="47"/> + <location filename="../QtUI/QtNameWidget.cpp" line="48"/> <source>(No Nickname Set)</source> <translation>(ник не установлен)</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="55"/> + <location filename="../QtUI/QtNameWidget.cpp" line="56"/> <source>Show Address</source> <translation>Показывать адрес</translation> </message> <message> - <location filename="../QtUI/QtNameWidget.cpp" line="62"/> + <location filename="../QtUI/QtNameWidget.cpp" line="63"/> <source>Edit Profile</source> <translation>Редактировать профиль</translation> @@ -1558,4 +2436,42 @@ </context> <context> + <name>Swift::QtOccupantListWidget</name> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="46"/> + <source>No actions for this user</source> + <translation>Нет действий для данного пользователя</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="55"/> + <source>Kick user</source> + <translation>Выгнать</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="56"/> + <source>Kick and ban user</source> + <translation>Выгнать и забанить</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="57"/> + <source>Make moderator</source> + <translation>Сделать модератором</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="58"/> + <source>Make participant</source> + <translation>Сделать участником</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="59"/> + <source>Remove voice</source> + <translation>Лишить голоса</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="60"/> + <source>Add to contacts</source> + <translation>Добавить в контакты</translation> + </message> +</context> +<context> <name>Swift::QtProfileWindow</name> <message> @@ -1576,4 +2492,56 @@ </context> <context> + <name>Swift::QtRosterHeader</name> + <message> + <location filename="../QtUI/QtRosterHeader.cpp" line="59"/> + <source>Connection is secured</source> + <translation>Подключение защищено</translation> + </message> +</context> +<context> + <name>Swift::QtRosterWidget</name> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="58"/> + <source>Edit…</source> + <translation>Редактировать…</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="59"/> + <source>Remove</source> + <translation>Удалить</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="63"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>Send File</source> + <translation>Отправить файл</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="69"/> + <source>Start Whiteboard Chat</source> + <translation>Открыть доску для рисования</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> + <source>All Files (*);;</source> + <translation>Все файлы (*);;</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="96"/> + <source>Rename</source> + <translation>Переименовать</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Rename group</source> + <translation>Переименовать группу</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> + <source>Enter a new name for group '%1':</source> + <translation>Введите новое название группы '%1':</translation> + </message> +</context> +<context> <name>Swift::QtStatusWidget</name> <message> @@ -1583,5 +2551,5 @@ </message> <message> - <location filename="../QtUI/QtStatusWidget.cpp" line="263"/> + <location filename="../QtUI/QtStatusWidget.cpp" line="261"/> <source>(No message)</source> <translation>(нет сообщения)</translation> @@ -1597,5 +2565,5 @@ If you choose to defer this choice, you will be asked again when you next login.</source> <translation>%1 хочет добавить Вас в свой список контактов. -Вы хотите добавить его в список контактов и обмениваться статусами, когда вы в сети? +Вы хотите добавить его в список контактов и обмениваться статусами, когда Вы в сети? Если Вы решили отложить выбор, то запрос придёт снова при следующем подключении.</translation> @@ -1617,10 +2585,10 @@ If you choose to defer this choice, you will be asked again when you next login. </message> <message> - <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="32"/> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="33"/> <source>No</source> <translation>Нет</translation> </message> <message> - <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="34"/> + <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="35"/> <source>Defer</source> <translation>Отложить</translation> @@ -1630,27 +2598,22 @@ If you choose to defer this choice, you will be asked again when you next login. <name>Swift::QtTreeWidget</name> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="144"/> <source>Edit</source> - <translation>Редактировать</translation> + <translation type="obsolete">Редактировать</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="145"/> <source>Remove</source> - <translation>Удалить</translation> + <translation type="obsolete">Удалить</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="157"/> <source>Rename</source> - <translation>Переименовать</translation> + <translation type="obsolete">Переименовать</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="167"/> <source>Rename group</source> - <translation>Переименовать группу</translation> + <translation type="obsolete">Переименовать группу</translation> </message> <message> - <location filename="../QtUI/Roster/QtTreeWidget.cpp" line="167"/> <source>Enter a new name for group '%1':</source> - <translation>Введите новое название группы '%1':</translation> + <translation type="obsolete">Введите новое название группы '%1':</translation> </message> </context> @@ -1658,5 +2621,5 @@ If you choose to defer this choice, you will be asked again when you next login. <name>Swift::QtUserSearchDetailsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="17"/> + <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="22"/> <source>Please choose a name for the contact, and select the groups you want to add the contact to.</source> <translation>Пожалуйста, выберите имя контакта и выберите группы, в которые Вы хотите добавить контакт.</translation> @@ -1684,35 +2647,35 @@ If you choose to defer this choice, you will be asked again when you next login. <name>Swift::QtUserSearchWindow</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="32"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> <source>Add Contact</source> <translation>Добавить контакт</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="32"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="35"/> <source>Chat to User</source> <translation>Чат с пользователем</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="43"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="45"/> <source>alice@wonderland.lit</source> <translation>alice@wonderland.lit</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="223"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> <source>How would you like to find the user to add?</source> <translation>Вы хотите найти пользователя чтобы его добавить?</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="226"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="299"/> <source>How would you like to find the user to chat to?</source> <translation>Вы хотите найти пользователя чтобы начать с ним чат?</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="251"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="326"/> <source>Error while searching</source> <translation>Ошибка поиска</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="257"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="332"/> <source>This server doesn't support searching for users.</source> <translation>Этот сервер не поддерживает поиск пользователей.</translation> @@ -1722,8 +2685,26 @@ If you choose to defer this choice, you will be asked again when you next login. <name>Swift::QtWebView</name> <message> - <location filename="../QtUI/QtWebView.cpp" line="61"/> + <location filename="../QtUI/QtWebView.cpp" line="66"/> <source>Clear</source> <translation>Очистить</translation> </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="67"/> + <source>Increase font size</source> + <translation>Увеличить размер шрифта</translation> + </message> + <message> + <location filename="../QtUI/QtWebView.cpp" line="68"/> + <source>Decrease font size</source> + <translation>Уменьшить размер шрифта</translation> + </message> +</context> +<context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <location filename="../QtUI/Whiteboard/QtWhiteboardWindow.cpp" line="380"/> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>Закрытие окна эквивалентно закрытию сессии. Вы уверены, что хотите это сделать?</translation> + </message> </context> <context> @@ -1750,10 +2731,10 @@ If you choose to defer this choice, you will be asked again when you next login. </message> <message> - <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="75"/> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="78"/> <source><!-- IN --></source> <translation><!-- IN --></translation> </message> <message> - <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="79"/> + <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="82"/> <source><!-- OUT --></source> <translation><!-- OUT --></translation> diff --git a/Swift/Translations/swift_sk.ts b/Swift/Translations/swift_sk.ts index bdd199b..e99a638 100644 --- a/Swift/Translations/swift_sk.ts +++ b/Swift/Translations/swift_sk.ts @@ -6,25 +6,25 @@ <name></name> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="56"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> <source>Starting chat with %1% in chatroom %2%</source> <translation>Začína sa rozhovor s %1% v miestnosti %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="59"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="62"/> <source>Starting chat with %1% - %2%</source> <translation>Začína sa rozhovor s %1% - %2%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="188"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> <source>This chat doesn't support delivery receipts.</source> <translation>Tento rozhovor nepodporuje doručenky.</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="190"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="204"/> <source>This chat may not support delivery receipts. You might not receive delivery receipts for the messages you sent.</source> <translation>Tento rozhovor nemusí podporovať doručenky. Je možné, že k odoslaným správam nedostanete potvrdenie o doručení.</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="202"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="216"/> <location filename="../Controllers/FileTransfer/FileTransferController.cpp" line="42"/> <source>me</source> @@ -32,120 +32,120 @@ </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="290"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="324"/> <source>%1% has gone offline</source> <translation>%1% je odteraz offline</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="294"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="328"/> <source>%1% has become available</source> <translation>%1% je odteraz tu</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="296"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="330"/> <source>%1% has gone away</source> <translation>%1% je odteraz preč</translation> </message> <message> - <location filename="../Controllers/Chat/ChatController.cpp" line="298"/> + <location filename="../Controllers/Chat/ChatController.cpp" line="332"/> <source>%1% is now busy</source> <translation>%1% teraz nemá čas</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="69"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="74"/> <source>The day is now %1%</source> <translation>Teraz je %1%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="188"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="209"/> <source>Couldn't send message: %1%</source> <translation>Správu sa nepodarilo odoslať: %1%</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="246"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> <source>Error sending message</source> <translation>Chyba pri posielaní správy</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="252"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="275"/> <source>Bad request</source> <translation>Nesprávna požiadavka</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="253"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="276"/> <source>Conflict</source> <translation>Konflikt</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="254"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="277"/> <source>This feature is not implemented</source> <translation>Táto vlastnosť nie je implementovaná</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="255"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="278"/> <source>Forbidden</source> <translation>Zakázané</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="256"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="279"/> <source>Recipient can no longer be contacted</source> <translation>Príjemcu už nie je možné kontaktovať</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="257"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="280"/> <source>Internal server error</source> <translation>Vnútorná chyba serveru</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="258"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="281"/> <source>Item not found</source> <translation>Položka nenájdená</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="259"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="282"/> <source>JID Malformed</source> <translation>Nesprávne utvorené JID</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="260"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="283"/> <source>Message was rejected</source> <translation>Správa bola odmietnutá</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="261"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="284"/> <source>Not allowed</source> <translation>Nepovolené</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="262"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="285"/> <source>Not authorized</source> <translation>Neautorizované</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="263"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="286"/> <source>Payment is required</source> <translation>Vyžadovaná platba</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="264"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="287"/> <source>Recipient is unavailable</source> <translation>Príjemca je nedostupný</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="265"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="288"/> <source>Redirect</source> <translation>Presmerovanie</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="266"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="289"/> <source>Registration required</source> <translation>Vyžadovaná registrácia</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="267"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="290"/> <source>Recipient's server not found</source> <translation>Server príjemcu nenájdený</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="268"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="291"/> <source>Remote server timeout</source> <translatorcomment>správny význam?</translatorcomment> @@ -153,45 +153,45 @@ </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="269"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="292"/> <source>The server is low on resources</source> <translation>Server má nedostatok zdrojov</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="270"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="293"/> <source>The service is unavailable</source> <translation>Služba nie je dostupná</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="271"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="294"/> <source>A subscription is required</source> <translation>Vyžadované prihlásenie k odberu</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="272"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="295"/> <source>Undefined condition</source> <translation>Nedefinovaná podmienka</translation> </message> <message> - <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="273"/> + <location filename="../Controllers/Chat/ChatControllerBase.cpp" line="296"/> <source>Unexpected request</source> <translation>Neočakávaná požiadavka</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="205"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> <source>Room %1% is not responding. This operation may never complete.</source> <translation>Miestnosť %1% neodpovedá. Táto operácia nemusí nikdy skončiť.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="216"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="233"/> <source>Unable to enter this room</source> <translation>Nepodarilo sa vstúpiť do miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="222"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="239"/> <source>Unable to enter this room as %1%, retrying as %2%</source> <translation>Nepodarilo sa vstúpiť do miestnosti ako %1%, znovu opakované ako %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="226"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="243"/> <source>No nickname specified</source> <translation>Nezadaná prezývka</translation> @@ -206,85 +206,85 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="234"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="251"/> <source>Only members may enter</source> <translation>Vstúpiť môžu iba členovia</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="238"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="255"/> <source>You are banned from the room</source> <translation>Máte zakázané vstúpiť do miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="242"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="259"/> <source>The room is full</source> <translation>Miestosť je plná</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="246"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="263"/> <source>The room does not exist</source> <translation>Miestosť neexistuje</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="252"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="269"/> <source>Couldn't join room: %1%.</source> <translation>Nepodarilo sa vstúpiť do miestnosti: %1%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="264"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="281"/> <source>You have entered room %1% as %2%.</source> <translation>Vstúpili ste do miestnosti %1% ako %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="310"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="332"/> <source>%1% has entered the room as a %2%.</source> - <translation>%1% vstúpil/-a do miestnosti ako %2%.</translation> + <translation>%1% vstúpil do miestnosti ako %2%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="313"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="335"/> <source>%1% has entered the room.</source> - <translation>%1% vstúpil/-a do miestnosti.</translation> + <translation>%1% vstúpil do miestnosti.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="362"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="384"/> <source>moderator</source> <translation>moderátor</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="363"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="385"/> <source>participant</source> <translation>účastník</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="364"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="386"/> <source>visitor</source> <translation>návštevník</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="412"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="436"/> <source>The room subject is now: %1%</source> <translation>Predmet miestnosti je: %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="445"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="470"/> <source>%1% is now a %2%</source> <translation>%1% je teraz %2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="461"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="486"/> <source>Moderators</source> <translation>Moderátori</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="462"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="487"/> <source>Participants</source> <translation>Účastníci</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="463"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="488"/> <source>Visitors</source> <translation>Návštevníci</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="464"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="489"/> <source>Occupants</source> <translatorcomment>lepšie synonymum k NoRole?</translatorcomment> @@ -292,291 +292,296 @@ </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="478"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="503"/> <source>Trying to enter room %1%</source> <translation>Pokúšam sa vstúpiť do miestnosti %1%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="519"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="544"/> <source>%1% has left the room%2%</source> - <translation>%1% opustil/-a miestnosť%2%</translation> + <translation>%1% opustil miestnosť%2%</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="523"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="548"/> <source>You have been kicked out of the room</source> <translation>Boli ste vyhodení z miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="524"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="549"/> <source>You have been banned from the room</source> <translation>Bol vám zakázaný prístup do miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="525"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="550"/> <source>You are no longer a member of the room and have been removed</source> <translation>Už nieste členom miestnosti a vaše členstvo vám bol odobrané</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="526"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="551"/> <source>The room has been destroyed</source> <translation>Miestnosť bola odstránená</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="642"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="667"/> <source>%1% has left the room</source> <translation>%1% opustil/-a miestnosť</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="528"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="553"/> <source>You have left the room</source> <translation>Opustili ste miestnosť</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="230"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="247"/> <source>The correct room password is needed</source> <translation>Je potrebné správne heslo miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="607"/> - <location filename="../Controllers/Chat/MUCController.cpp" line="671"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="632"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="696"/> <source> and </source> <translation> a </translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="631"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="656"/> <source>%1% have entered the room</source> <translation>%1% vstúpili do miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="634"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="659"/> <source>%1% has entered the room</source> <translation>%1% vstúpil/-a do miestnosti</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="639"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="664"/> <source>%1% have left the room</source> <translation>%1% opustili miestnosť</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="647"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="672"/> <source>%1% have entered then left the room</source> <translation>%1% vstúpili a potom opustili miestnosť</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="650"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="675"/> <source>%1% has entered then left the room</source> <translation>%1% vstúpil/-a a potom opustil/a miestnosť</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="655"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="680"/> <source>%1% have left then returned to the room</source> <translation>%1% opustili miestnosť a potom sa vrátili</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="658"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="683"/> <source>%1% has left then returned to the room</source> <translation>%1% opustil/-a miestnosť a potom sa vrátil/-a</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="694"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="719"/> <source>Room configuration failed: %1%.</source> <translation>Nepodarilo sa nastaviť miestnosť: %1%.</translation> </message> <message> - <location filename="../Controllers/Chat/MUCController.cpp" line="700"/> + <location filename="../Controllers/Chat/MUCController.cpp" line="725"/> <source>Occupant role change failed: %1%.</source> <translation>Nepodarila sa zmeniť rola pre návštevníka: %1%.</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="53"/> + <location filename="../Controllers/EventNotifier.cpp" line="59"/> <source>%1% wants to add you to his/her contact list</source> <translation>%1% si vás chce pridať do zoznamu kontaktov</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="57"/> + <location filename="../Controllers/EventNotifier.cpp" line="63"/> <source>Error</source> <translation>Chyba</translation> </message> <message> - <location filename="../Controllers/EventNotifier.cpp" line="61"/> + <location filename="../Controllers/EventNotifier.cpp" line="67"/> <source>%1% has invited you to enter the %2% room</source> <translation>%1% vás pozval/-a vstúpiť do miestnosti %2%</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="423"/> + <location filename="../Controllers/MainController.cpp" line="466"/> <source>User address invalid. User address should be of the form 'alice@wonderland.lit'</source> - <translation>Používateľská adresa je neplatná. Používateľská adresa by mala byť v tvare „alica@krajina-zazrakov.lit.“</translation> + <translation>Adresa osoby je neplatná. Adresa osoby by mala byť v tvare „alica@krajina-zazrakov.lit.“</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="523"/> + <location filename="../Controllers/MainController.cpp" line="567"/> <source>Unknown Error</source> <translation>Neznáma chyba</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="524"/> + <location filename="../Controllers/MainController.cpp" line="568"/> <source>Unable to find server</source> <translation>Nepodarilo sa nájsť server</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="525"/> + <location filename="../Controllers/MainController.cpp" line="569"/> <source>Error connecting to server</source> <translation>Chyba pri pripojení k serveru</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="526"/> + <location filename="../Controllers/MainController.cpp" line="570"/> <source>Error while receiving server data</source> <translation>Chyba pri prijímaní dát zo serveru</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="527"/> + <location filename="../Controllers/MainController.cpp" line="571"/> <source>Error while sending data to the server</source> <translation>Chyba pri posielaní dát na server</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="528"/> + <location filename="../Controllers/MainController.cpp" line="572"/> <source>Error parsing server data</source> <translation>Chyba pri spracovaní dát zo serveru</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="529"/> + <location filename="../Controllers/MainController.cpp" line="573"/> <source>Login/password invalid</source> - <translation>Prihlasovacie meno, či heslo je neplatné</translation> + <translation>Prihlasovacie meno alebo heslo nie je platné</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="530"/> + <location filename="../Controllers/MainController.cpp" line="574"/> <source>Error while compressing stream</source> <translation>Chyba pri komprimovaní prúdu</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="531"/> + <location filename="../Controllers/MainController.cpp" line="575"/> <source>Server verification failed</source> <translation>Zlyhalo overenie serveru</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="532"/> + <location filename="../Controllers/MainController.cpp" line="576"/> <source>Authentication mechanisms not supported</source> - <translation>Autentizačné mechanizmy niesú podporované</translation> + <translation>Overovacie mechanizmy nie sú podporované</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="533"/> + <location filename="../Controllers/MainController.cpp" line="577"/> <source>Unexpected response</source> <translation>Neočakávaná odpoveď</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="534"/> + <location filename="../Controllers/MainController.cpp" line="578"/> <source>Error binding resource</source> <translation>Chyba pri spájaní zdroju</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="535"/> + <location filename="../Controllers/MainController.cpp" line="579"/> <source>Error starting session</source> <translation>Chyba pri spúštaní sedenia</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="536"/> + <location filename="../Controllers/MainController.cpp" line="580"/> <source>Stream error</source> <translation>Chyba prúdu</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="537"/> + <location filename="../Controllers/MainController.cpp" line="581"/> <source>Encryption error</source> <translation>Chyba šifrovania</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="538"/> + <location filename="../Controllers/MainController.cpp" line="582"/> <source>Error loading certificate (Invalid password?)</source> <translation>Chyba pri načítaní certifikátu (nesprávne heslo?)</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="539"/> + <location filename="../Controllers/MainController.cpp" line="583"/> <source>Certificate not authorized</source> <translation>Neautorizovaný certifikát</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="540"/> + <location filename="../Controllers/MainController.cpp" line="584"/> <source>Certificate card removed</source> <translation>Karta so certifikátom odstránená</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="542"/> + <location filename="../Controllers/MainController.cpp" line="586"/> <source>Unknown certificate</source> <translation>Neznámy certifikát</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="543"/> + <location filename="../Controllers/MainController.cpp" line="587"/> <source>Certificate has expired</source> <translation>Certifikát expiroval</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="544"/> + <location filename="../Controllers/MainController.cpp" line="588"/> <source>Certificate is not yet valid</source> <translation>Certifikát zatiaľ nie je platný</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="545"/> + <location filename="../Controllers/MainController.cpp" line="589"/> <source>Certificate is self-signed</source> <translation>Certifikát je podpísaný samým sebou</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="546"/> + <location filename="../Controllers/MainController.cpp" line="590"/> <source>Certificate has been rejected</source> <translation>Certifikát bol odmietnutý</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="547"/> + <location filename="../Controllers/MainController.cpp" line="591"/> <source>Certificate is not trusted</source> - <translation>Certifikát nie je dôvernyhodný</translation> + <translation>Certifikát nie je dôveryhodný</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="548"/> + <location filename="../Controllers/MainController.cpp" line="592"/> <source>Certificate cannot be used for encrypting your connection</source> <translation>Certifikát nemôže byť použitý pre šifrovanie vášho spojenia</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="549"/> + <location filename="../Controllers/MainController.cpp" line="593"/> <source>Certificate path length constraint exceeded</source> <translation>Prekročená dĺžka cesty certifikátu</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="550"/> + <location filename="../Controllers/MainController.cpp" line="594"/> <source>Invalid certificate signature</source> <translation>Neplatný podpis certifikátu</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="551"/> + <location filename="../Controllers/MainController.cpp" line="595"/> <source>Invalid Certificate Authority</source> <translation>Neplatná certifikačná autorita</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="552"/> + <location filename="../Controllers/MainController.cpp" line="596"/> <source>Certificate does not match the host identity</source> <translation>Certifikát sa nezhoduje s identitou hostiteľa</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="553"/> + <location filename="../Controllers/MainController.cpp" line="597"/> <source>Certificate has been revoked</source> <translation>Certifikát bol odvolaný</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="554"/> + <location filename="../Controllers/MainController.cpp" line="598"/> <source>Unable to determine certificate revocation state</source> <translation>Nepodarilo sa zistiť stav odvolania certifikátu</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="564"/> + <location filename="../Controllers/MainController.cpp" line="608"/> <source>Certificate error</source> <translation>Chyba certifikátu</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="578"/> + <location filename="../Controllers/MainController.cpp" line="615"/> + <source>Re-enter credentials and retry</source> + <translation>Znovu zadajte prístupové údaje a zopakujte akciu</translation> + </message> + <message> + <location filename="../Controllers/MainController.cpp" line="628"/> <source>Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.</source> - <translation>Odpojené od %1%: %2. Pre znovu pripojenie sa odhláste a zadajte znovu svoje heslo.</translation> + <translation>Odpojené od %1%: %2%. Pre znovu pripojenie sa odhláste a zadajte znovu svoje heslo.</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="584"/> + <location filename="../Controllers/MainController.cpp" line="634"/> <source>Reconnect to %1% failed: %2%. Will retry in %3% seconds.</source> - <translation>Znovupripojenie ku %1% zlyhalo: %2%. Ďalší pokus o %3% sekúnd.</translation> + <translation>Opätovné pripojenie ku %1% zlyhalo: %2%. Ďalší pokus o %3% sekúnd.</translation> </message> <message> - <location filename="../Controllers/MainController.cpp" line="587"/> + <location filename="../Controllers/MainController.cpp" line="637"/> <source>Disconnected from %1%: %2%.</source> <translation>Odpojené od %1%: %2%.</translation> @@ -592,5 +597,5 @@ <location filename="../Controllers/Roster/RosterController.cpp" line="263"/> <source>Server %1% rejected contact list change to item '%2%'</source> - <translation>Server %1% odmietol zmenu položky „%2%“ zoznamu kontaktov</translation> + <translation>Server %1% odmietol zmenu položky „%2%“ v zozname kontaktov</translation> </message> <message> @@ -619,5 +624,5 @@ <location filename="../Controllers/ProfileController.cpp" line="62"/> <source>There was an error publishing your profile data</source> - <translation>Vyskytla sa chyba pri zverejnení údajov vášho profilu</translation> + <translation>Pri zverejnení údajov vášho profilu nastala chyba</translation> </message> <message> @@ -644,5 +649,5 @@ <location filename="../QtUI/CAPICertificateSelector.cpp" line="64"/> <source>Select a certificate to use for authentication</source> - <translation>Vyberte certifikát, ktorý má byť použitý pre overenie</translation> + <translation>Vyberte certifikát, ktorý chcete použiť pre overenie</translation> </message> </context> @@ -791,5 +796,5 @@ <location filename="../QtUI/MUCSearch/MUCSearchEmptyItem.cpp" line="25"/> <source>No rooms found</source> - <translation>Žiadne miestnosti neboli nájdené</translation> + <translation>Nenašli sa žiadne miestnosti</translation> </message> <message> @@ -806,5 +811,5 @@ <location filename="../QtUI/EventViewer/QtEvent.cpp" line="81"/> <source>%1 has invited you to enter the %2 room.</source> - <translation>%1% vás pozval/-a vstúpiť do miestnosti %2%.</translation> + <translation>%1 vás pozval vstúpiť do miestnosti %2.</translation> </message> <message> @@ -817,15 +822,15 @@ </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="857"/> + <location filename="../QtUI/QtChatWindow.cpp" line="980"/> <source>You've been invited to enter the %1 room.</source> - <translation>Boli ste pozvaní vstúpiť do miestnosti %1.</translation> + <translation>Dostali ste pozvánku na vstup do miestnosti %1.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="859"/> + <location filename="../QtUI/QtChatWindow.cpp" line="982"/> <source>Reason: %1</source> <translation>Dôvod: %1</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="862"/> + <location filename="../QtUI/QtChatWindow.cpp" line="985"/> <source>This person may not have really sent this invitation!</source> <translation>Táto osoba nemusela skutočne poslať túto pozvánku!</translation> @@ -901,4 +906,9 @@ <translation>Zrušené</translation> </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="142"/> + <source>Connection Options</source> + <translation>Možnosti pripojenia</translation> + </message> </context> <context> @@ -1087,10 +1097,10 @@ <location filename="../QtUI/QtStrings.h" line="66"/> <source>&Next</source> - <translation>&Pokračovať</translation> + <translation>Ď&alej</translation> </message> <message> <location filename="../QtUI/QtStrings.h" line="67"/> <source>&Next ></source> - <translation>&Pokračovať ></translation> + <translation>Ď&alej ></translation> </message> </context> @@ -1098,5 +1108,4 @@ <name>QtAffiliationEditor</name> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="123"/> <source>Dialog</source> @@ -1104,5 +1113,10 @@ </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="14"/> + <source>Edit Affiliations</source> + <translation>Upraviť členstvo </translation> + </message> + <message> + <location filename="../QtUI/QtAffiliationEditor.ui" line="28"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="125"/> <source>Affiliation:</source> @@ -1110,5 +1124,5 @@ </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="36"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="128"/> <source>Owner</source> @@ -1116,5 +1130,5 @@ </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="41"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="129"/> <source>Administrator</source> @@ -1122,5 +1136,5 @@ </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="46"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="130"/> <source>Member</source> @@ -1128,5 +1142,5 @@ </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="51"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="131"/> <source>Outcast (Banned)</source> @@ -1134,14 +1148,14 @@ </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="68"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="133"/> <source>Add User</source> - <translation>Pridať používateľa</translation> + <translation>Pridať osobu</translation> </message> <message> - <location filename="../QtUI/QtAffiliationEditor.ui"/> + <location filename="../QtUI/QtAffiliationEditor.ui" line="75"/> <location filename="../QtUI/ui_QtAffiliationEditor.h" line="134"/> <source>Remove User</source> - <translation>Odstrániť používateľa</translation> + <translation>Odstrániť osobu</translation> </message> </context> @@ -1149,5 +1163,5 @@ <name>QtBookmarkDetailWindow</name> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="20"/> <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="137"/> <source>Edit Bookmark Details</source> @@ -1155,5 +1169,5 @@ </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="40"/> <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="138"/> <source>Bookmark Name:</source> @@ -1161,5 +1175,5 @@ </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="50"/> <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="139"/> <source>Room Address:</source> @@ -1167,5 +1181,5 @@ </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="60"/> <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="140"/> <source>Your Nickname:</source> @@ -1173,5 +1187,5 @@ </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="70"/> <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="141"/> <source>Room password:</source> @@ -1183,5 +1197,5 @@ </message> <message> - <location filename="../QtUI/QtBookmarkDetailWindow.ui"/> + <location filename="../QtUI/QtBookmarkDetailWindow.ui" line="93"/> <location filename="../QtUI/ui_QtBookmarkDetailWindow.h" line="142"/> <source>Enter automatically</source> @@ -1190,7 +1204,201 @@ </context> <context> + <name>QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.ui" line="14"/> + <location filename="../QtUI/ui_QtCertificateViewerDialog.h" line="99"/> + <source>Certificate Viewer</source> + <translation>Prehliadač certifikátu</translation> + </message> +</context> +<context> + <name>QtConnectionSettings</name> + <message> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="383"/> + <source>Dialog</source> + <translation>Dialóg</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="14"/> + <source>Connection Options</source> + <translation>Možnosti pripojenia</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="22"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="384"/> + <source>Connection Method:</source> + <translation>Spôsob pripojenia:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="36"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="387"/> + <source>Automatic</source> + <translation>Automatické</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="41"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="388"/> + <source>Manual</source> + <translation>Manuálne</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="46"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="389"/> + <source>BOSH</source> + <translation>BOSH</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="99"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="391"/> + <source>Secure connection:</source> + <translation>Zabezpečenie spojenia:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="107"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="394"/> + <source>Never</source> + <translation>Bez zabezpečenia</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="112"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="395"/> + <source>Encrypt when possible</source> + <translation>Ak je možné, šifrovať</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="117"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="396"/> + <source>Always encrypt</source> + <translation>Vždy šifrovať</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="127"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="398"/> + <source>Allow Compression</source> + <translation>Povoliť kompresiu</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="134"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="399"/> + <source>Allow sending password over insecure connection</source> + <translation>Povoliť odosielanie hesla cez nezabezpečené spojenie</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="157"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="400"/> + <source>Manually select server</source> + <translation>Manuálny výber servera</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="185"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="315"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="429"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="401"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="413"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="417"/> + <source>Hostname:</source> + <translation>Názov hostiteľa:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="208"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="338"/> + <location filename="../QtUI/QtConnectionSettings.ui" line="452"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="402"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="414"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="418"/> + <source>Port:</source> + <translation>Port:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="230"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="403"/> + <source>Connection Proxy</source> + <translation>Proxy server pre spojenie</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="238"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="404"/> + <source>Proxy type:</source> + <translation>Typ proxy servera:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="249"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="407"/> + <source>None</source> + <translation>Žiadny</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="254"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="408"/> + <source>Use system-configured proxy</source> + <translation>Podľa nastavenia systému</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="259"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="409"/> + <source>SOCKS5</source> + <translation>SOCKS5</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="264"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="410"/> + <source>HTTP Connect</source> + <translation>HTTP Connect</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="287"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="412"/> + <source>Override system-configured proxy</source> + <translation>Prepísať systémové nastavenia proxy servera</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="382"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="415"/> + <source>BOSH URI:</source> + <translation>BOSH URI:</translation> + </message> + <message> + <location filename="../QtUI/QtConnectionSettings.ui" line="401"/> + <location filename="../QtUI/ui_QtConnectionSettings.h" line="416"/> + <source>Manually select HTTP proxy</source> + <translation>Ručné zadanie HTTP proxy servera </translation> + </message> +</context> +<context> + <name>QtHistoryWindow</name> + <message> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="133"/> + <source>Form</source> + <translation>Formulár</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="14"/> + <source>History</source> + <translation>História</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="22"/> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="134"/> + <source>Search:</source> + <translation>Vyhľadať:</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="42"/> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="135"/> + <source>Next</source> + <translation>Ďalej</translation> + </message> + <message> + <location filename="../QtUI/QtHistoryWindow.ui" line="52"/> + <location filename="../QtUI/ui_QtHistoryWindow.h" line="136"/> + <source>Previous</source> + <translation>Naspäť</translation> + </message> +</context> +<context> <name>QtJoinMUCWindow</name> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="20"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="113"/> <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="142"/> <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="150"/> @@ -1199,11 +1407,9 @@ </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="143"/> <source>Room:</source> - <translation>Miestnosť:</translation> + <translation type="obsolete">Miestnosť:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="35"/> <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="144"/> <source>Search ...</source> @@ -1211,11 +1417,9 @@ </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="145"/> <source>Nickname:</source> - <translation>Prezývka:</translation> + <translation type="obsolete">Prezývka:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="106"/> <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="149"/> <source>Enter automatically in future</source> @@ -1223,20 +1427,36 @@ </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> - <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="147"/> <source>Password:</source> - <translation>Heslo:</translation> + <translation type="obsolete">Heslo:</translation> </message> <message> - <location filename="../QtUI/QtJoinMUCWindow.ui"/> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="71"/> <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="148"/> <source>Automatically configure newly created rooms</source> <translation>Nové miestnosti nastaviť automaticky</translation> </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="28"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="143"/> + <source>Room Address:</source> + <translation>Adresa miestnosti:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="42"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="145"/> + <source>Your Nickname:</source> + <translation>Vaša prezývka:</translation> + </message> + <message> + <location filename="../QtUI/QtJoinMUCWindow.ui" line="59"/> + <location filename="../QtUI/ui_QtJoinMUCWindow.h" line="147"/> + <source>Room Password:</source> + <translation>Heslo miestnosti:</translation> + </message> </context> <context> <name>QtMUCSearchWindow</name> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="14"/> <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="118"/> <source>Search Room</source> @@ -1244,5 +1464,5 @@ </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="20"/> <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="119"/> <source>Service:</source> @@ -1250,5 +1470,5 @@ </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="74"/> <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="121"/> <source>Cancel</source> @@ -1256,5 +1476,5 @@ </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="84"/> <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="122"/> <source>OK</source> @@ -1262,5 +1482,5 @@ </message> <message> - <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui"/> + <location filename="../QtUI/MUCSearch/QtMUCSearchWindow.ui" line="96"/> <location filename="../QtUI/MUCSearch/ui_QtMUCSearchWindow.h" line="123"/> <source>List rooms</source> @@ -1271,5 +1491,5 @@ <name>QtUserSearchFieldsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="27"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="131"/> <source>Nickname:</source> @@ -1277,5 +1497,5 @@ </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="37"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="132"/> <source>First name:</source> @@ -1283,5 +1503,5 @@ </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="47"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="133"/> <source>Last name:</source> @@ -1289,5 +1509,5 @@ </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="57"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="134"/> <source>E-Mail:</source> @@ -1295,5 +1515,5 @@ </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchFieldsPage.ui" line="74"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchFieldsPage.h" line="136"/> <source>Fetching search fields</source> @@ -1304,12 +1524,12 @@ <name>QtUserSearchFirstPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="121"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="17"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="132"/> <source>Add a user</source> <translation>Pridať osobu</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="122"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="20"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="133"/> <source>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</source> <translatorcomment>inak pomenované ako inde</translatorcomment> @@ -1317,20 +1537,20 @@ </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="124"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="35"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="135"/> <source>I know their address:</source> - <translation>Viem jej/jeho adresu:</translation> + <translation>Viem jej adresu:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="125"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="49"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="136"/> <source>I'd like to search my server</source> - <translation>Chcem prehľadať môj server</translation> + <translation>Prehľadať môj server</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui"/> - <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="126"/> + <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.ui" line="73"/> + <location filename="../QtUI/UserSearch/ui_QtUserSearchFirstPage.h" line="137"/> <source>I'd like to search another server:</source> - <translation>Chcem prehľadať iný server:</translation> + <translation>Prehľadať iný server:</translation> </message> </context> @@ -1338,5 +1558,5 @@ <name>QtUserSearchResultsPage</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchResultsPage.ui" line="27"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchResultsPage.h" line="59"/> <source>No results.</source> @@ -1347,5 +1567,5 @@ <name>QtUserSearchWizard</name> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui"/> + <location filename="../QtUI/UserSearch/QtUserSearchWizard.ui" line="14"/> <location filename="../QtUI/UserSearch/ui_QtUserSearchWizard.h" line="39"/> <source>Find User</source> @@ -1356,5 +1576,5 @@ <name>Swift::ChatListModel</name> <message> - <location filename="../QtUI/ChatList/ChatListModel.cpp" line="16"/> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> <source>Bookmarked Rooms</source> <translatorcomment>kostrbatý preklad</translatorcomment> @@ -1362,8 +1582,13 @@ </message> <message> - <location filename="../QtUI/ChatList/ChatListModel.cpp" line="17"/> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="18"/> <source>Recent Chats</source> <translation>Nedávne rozhovory</translation> </message> + <message> + <location filename="../QtUI/ChatList/ChatListModel.cpp" line="20"/> + <source>Opened Whiteboards</source> + <translation>Otvorené tabule</translation> + </message> </context> <context> @@ -1412,35 +1637,35 @@ <name>Swift::QtAdHocCommandWindow</name> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="37"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="36"/> <source>Cancel</source> <translation>Zrušiť</translation> </message> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="40"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="39"/> <source>Back</source> <translation>Naspäť</translation> </message> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="43"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="42"/> <source>Next</source> <translation>Ďalej</translation> </message> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="46"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="45"/> <source>Complete</source> <translation>Hotovo</translation> </message> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="92"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="86"/> <source>Error: %1</source> <translation>Chyba: %1</translation> </message> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="93"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="87"/> <source>Warning: %1</source> <translation>Upozornenie: %1</translation> </message> <message> - <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="106"/> + <location filename="../QtUI/QtAdHocCommandWindow.cpp" line="104"/> <source>Error executing command</source> <translation>Chyba pri vykonávaní príkazu</translation> @@ -1452,10 +1677,10 @@ <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> <source>Add User</source> - <translation>Pridať používateľa</translation> + <translation>Pridať osobu</translation> </message> <message> <location filename="../QtUI/QtAffiliationEditor.cpp" line="48"/> <source>Added User's Address:</source> - <translation>Používateľova adresa:</translation> + <translation>Pridaná adresa osoby:</translation> </message> </context> @@ -1520,23 +1745,112 @@ </context> <context> + <name>Swift::QtCertificateViewerDialog</name> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="95"/> + <source>General</source> + <translation>Všeobecné</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="96"/> + <source>Valid From</source> + <translation>Platné od</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="97"/> + <source>Valid To</source> + <translation>Platné do</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="98"/> + <source>Serial Number</source> + <translation>Sériové číslo</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="99"/> + <source>Version</source> + <translation>Verzia</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="101"/> + <source>Subject</source> + <translation>Subjekt</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="102"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="124"/> + <source>Organization</source> + <translation>Organizácia</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="103"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="125"/> + <source>Common Name</source> + <translation>Bežný názov</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="104"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="126"/> + <source>Locality</source> + <translation>Lokalita</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="105"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="127"/> + <source>Organizational Unit</source> + <translation>Organizačná jednotka</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="106"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="128"/> + <source>Country</source> + <translation>Štát</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="107"/> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="129"/> + <source>State</source> + <translation>Stav</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="110"/> + <source>Alternate Subject Names</source> + <translation>Alternatívne mená subjektu </translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="115"/> + <source>E-mail Address</source> + <translation>E-mailová adresa</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="117"/> + <source>DNS Name</source> + <translation>Meno DNS</translation> + </message> + <message> + <location filename="../QtUI/QtCertificateViewerDialog.cpp" line="123"/> + <source>Issuer</source> + <translation>Vydavateľ</translation> + </message> +</context> +<context> <name>Swift::QtChatListWindow</name> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="80"/> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="86"/> <source>Add New Bookmark</source> <translation>Pridať novú záložku</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="81"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="83"/> <source>Edit Bookmark</source> <translation>Upraviť záložku</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="82"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="84"/> <source>Remove Bookmark</source> <translation>Odstrániť záložku</translation> </message> <message> - <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="158"/> + <location filename="../QtUI/ChatList/QtChatListWindow.cpp" line="173"/> <source>Clear recents</source> <translation>Vymazať nedávne</translation> @@ -1546,57 +1860,77 @@ <name>Swift::QtChatView</name> <message> - <location filename="../QtUI/QtChatView.cpp" line="72"/> + <location filename="../QtUI/QtChatView.cpp" line="73"/> <source>Clear log</source> <translation>Vymazať záznam</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="73"/> + <location filename="../QtUI/QtChatView.cpp" line="74"/> <source>You are about to clear the contents of your chat log.</source> <translation>Práve idete vymazať záznam vášho rozhovoru.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="74"/> + <location filename="../QtUI/QtChatView.cpp" line="75"/> <source>Are you sure?</source> <translation>Ste si istý?</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="178"/> + <location filename="../QtUI/QtChatView.cpp" line="219"/> <source>%1 edited</source> <translation>%1 upravené</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="330"/> + <location filename="../QtUI/QtChatView.cpp" line="400"/> <source>Waiting for other side to accept the transfer.</source> <translation>Čakanie na prijatie prenosu druhou stranou.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="331"/> - <location filename="../QtUI/QtChatView.cpp" line="336"/> - <location filename="../QtUI/QtChatView.cpp" line="347"/> + <location filename="../QtUI/QtChatView.cpp" line="401"/> + <location filename="../QtUI/QtChatView.cpp" line="406"/> + <location filename="../QtUI/QtChatView.cpp" line="417"/> <source>Cancel</source> <translation>Zrušiť</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="335"/> + <location filename="../QtUI/QtChatView.cpp" line="405"/> <source>Negotiating...</source> <translation>Vyjednávanie…</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="350"/> + <location filename="../QtUI/QtChatView.cpp" line="420"/> <source>Transfer has been canceled!</source> <translation>Prenos bol zrušený!</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="354"/> + <location filename="../QtUI/QtChatView.cpp" line="424"/> <source>Transfer completed successfully.</source> <translation>Prenos bol úspešne dokončený.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="357"/> + <location filename="../QtUI/QtChatView.cpp" line="427"/> <source>Transfer failed.</source> <translation>Prenos zlyhal.</translation> </message> <message> - <location filename="../QtUI/QtChatView.cpp" line="367"/> + <location filename="../QtUI/QtChatView.cpp" line="437"/> + <source>Started whiteboard chat</source> + <translation>Spustený rozhovor s využitím tabule</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="438"/> + <source>Show whiteboard</source> + <translation>Zobraziť tabuľu</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="440"/> + <source>Whiteboard chat has been canceled</source> + <translation>Rozhovor s využitím tabule bol zrušený</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="442"/> + <source>Whiteboard chat request has been rejected</source> + <translation>Požiadavka na rozhovor s využitím tabule bola odmietnutá</translation> + </message> + <message> + <location filename="../QtUI/QtChatView.cpp" line="451"/> <source>Return to room</source> <translation>Vrátiť sa do miestnosti</translation> @@ -1606,10 +1940,10 @@ <name>Swift::QtChatWindow</name> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="481"/> + <location filename="../QtUI/QtChatWindow.cpp" line="546"/> <source>This message has not been received by your server yet.</source> <translation>Táto správa zatiaľ nebola prijatá vašim serverom.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="488"/> + <location filename="../QtUI/QtChatWindow.cpp" line="553"/> <source>This message may not have been transmitted.</source> <translation>Táto správa ešte nemusela byť prenesená.</translation> @@ -1624,20 +1958,20 @@ </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="144"/> + <location filename="../QtUI/QtChatWindow.cpp" line="157"/> <source>Correcting</source> <translation>Prebieha opravovanie</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="241"/> + <location filename="../QtUI/QtChatWindow.cpp" line="269"/> <source>This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message</source> <translation>Tento rozhovor nemusí podporovať opravu správ. Keď aj tak pošlete opravu, môže vyzerať ako duplicitná správa</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="243"/> + <location filename="../QtUI/QtChatWindow.cpp" line="271"/> <source>This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message</source> <translation>Tento rozhovor nepodporuje opravu správ. Keď aj tak pošlete opravu, bude vyzerať ako duplicitná správa</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="497"/> + <location filename="../QtUI/QtChatWindow.cpp" line="562"/> <source>The receipt for this message has been received.</source> <translation>Pre túto správu bola prijatá doručenka.</translation> @@ -1648,7 +1982,7 @@ </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="500"/> + <location filename="../QtUI/QtChatWindow.cpp" line="565"/> <source>The receipt for this message has not yet been received. The recipient(s) might not have received this message.</source> - <translation>Doručenka pre túto správu zatiaľ neprišla. Príjemca nemusel/-a obdržať túto správu.</translation> + <translation>Doručenka pre túto správu zatiaľ neprišla. Príjemca nemusel obdržať túto správu.</translation> </message> <message> @@ -1657,66 +1991,100 @@ </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="540"/> + <location filename="../QtUI/QtChatWindow.cpp" line="613"/> <source>Send file</source> <translation>Odoslať súbor</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="542"/> - <location filename="../QtUI/QtChatWindow.cpp" line="550"/> + <location filename="../QtUI/QtChatWindow.cpp" line="615"/> + <location filename="../QtUI/QtChatWindow.cpp" line="623"/> + <location filename="../QtUI/QtChatWindow.cpp" line="660"/> + <location filename="../QtUI/QtChatWindow.cpp" line="664"/> <source>Cancel</source> <translation>Zrušiť</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="543"/> + <location filename="../QtUI/QtChatWindow.cpp" line="616"/> <source>Set Description</source> <translation>Nastaviť popis</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="544"/> + <location filename="../QtUI/QtChatWindow.cpp" line="617"/> <source>Send</source> <translation>Odoslať</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="548"/> + <location filename="../QtUI/QtChatWindow.cpp" line="621"/> <source>Receiving file</source> <translation>Prijímanie súboru</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="551"/> + <location filename="../QtUI/QtChatWindow.cpp" line="624"/> + <location filename="../QtUI/QtChatWindow.cpp" line="665"/> <source>Accept</source> <translation>Prijať</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="590"/> + <location filename="../QtUI/QtChatWindow.cpp" line="659"/> + <source>Starting whiteboard chat</source> + <translation>Prebieha spúšťanie rozhovoru s využitím tabule</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="663"/> + <source>%1 would like to start a whiteboard chat</source> + <translation>%1 chce začať rozhovor s využitím tabule</translation> + </message> + <message> + <source> would like to start whiteboard chat</source> + <translation type="obsolete"> chce začať rozhovor s využitím tabule</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="700"/> <source>File transfer description</source> <translation>Popis prenosu súboru</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="591"/> + <location filename="../QtUI/QtChatWindow.cpp" line="701"/> <source>Description:</source> <translation>Popis:</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="605"/> + <location filename="../QtUI/QtChatWindow.cpp" line="715"/> <source>Save File</source> <translation>Uložiť súbor</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="782"/> + <location filename="../QtUI/QtChatWindow.cpp" line="906"/> + <source>Change subject…</source> + <translation>Zmeniť predmet…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="907"/> + <source>Configure room…</source> + <translation>Nastaviť miestnosť…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="908"/> + <source>Edit affiliations…</source> + <translation>Upraviť členstvo…</translation> + </message> + <message> + <location filename="../QtUI/QtChatWindow.cpp" line="910"/> + <source>Invite person to this room…</source> + <translation>Pozvať používateľa do tejto miestnosti…</translation> + </message> + <message> <source>Change subject</source> - <translation>Zmeniť predmet</translation> + <translation type="obsolete">Zmeniť predmet</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="783"/> <source>Configure room</source> - <translation>Nastaviť miestnosť</translation> + <translation type="obsolete">Nastaviť miestnosť</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="784"/> <source>Edit affiliations</source> - <translation>Upraviť členstvo</translation> + <translation type="obsolete">Upraviť členstvo</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="785"/> + <location filename="../QtUI/QtChatWindow.cpp" line="909"/> <source>Destroy room</source> <translatorcomment>Nájsť vhodnejšie slovo k odstrániť – zničiť?</translatorcomment> @@ -1724,45 +2092,42 @@ </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="786"/> <source>Invite person to this room</source> - <translation>Pozvať osobu do tejto mietnosti</translation> + <translation type="obsolete">Pozvať osobu do tejto mietnosti</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="793"/> + <location filename="../QtUI/QtChatWindow.cpp" line="920"/> <source>Change room subject</source> <translation>Zmeniť predmet miestnosti</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="793"/> + <location filename="../QtUI/QtChatWindow.cpp" line="920"/> <source>New subject:</source> <translation>Nový predmet:</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="811"/> + <location filename="../QtUI/QtChatWindow.cpp" line="938"/> <source>Confirm room destruction</source> <translation>Potvrdenie odstránenia miestnosti</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="812"/> + <location filename="../QtUI/QtChatWindow.cpp" line="939"/> <source>Are you sure you want to destroy the room?</source> - <translation>Chcete odstrániť miestnosť?</translation> + <translation>Naozaj chcete odstrániť miestnosť?</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="813"/> + <location filename="../QtUI/QtChatWindow.cpp" line="940"/> <source>This will destroy the room.</source> <translation>Týmto odstrániťe miestnosť.</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="822"/> <source>Enter person's address</source> - <translation>Zadajte adresu osoby</translation> + <translation type="obsolete">Zadajte adresu osoby</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="822"/> <source>Address:</source> - <translation>Adresa:</translation> + <translation type="obsolete">Adresa:</translation> </message> <message> - <location filename="../QtUI/QtChatWindow.cpp" line="868"/> + <location filename="../QtUI/QtChatWindow.cpp" line="991"/> <source>Accept Invite</source> <translation>Prijať pozvánku</translation> @@ -1772,5 +2137,5 @@ <name>Swift::QtContactEditWidget</name> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="121"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="122"/> <source>Name:</source> <translation>Meno:</translation> @@ -1782,5 +2147,5 @@ </message> <message> - <location filename="../QtUI/QtContactEditWidget.cpp" line="64"/> + <location filename="../QtUI/QtContactEditWidget.cpp" line="65"/> <source>New Group:</source> <translation>Nová skupina:</translation> @@ -1842,4 +2207,25 @@ </context> <context> + <name>Swift::QtHistoryWindow</name> + <message> + <location filename="../QtUI/QtHistoryWindow.cpp" line="57"/> + <source>History</source> + <translation>História</translation> + </message> +</context> +<context> + <name>Swift::QtInviteToChatWindow</name> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="25"/> + <source>Users to invite to this chat (one per line):</source> + <translation>Zoznam osôb, ktoré chcete pozvať do tohto rozhovoru (po jednej na riadku):</translation> + </message> + <message> + <location filename="../QtUI/QtInviteToChatWindow.cpp" line="31"/> + <source>If you want to provide a reason for the invitation, enter it here</source> + <translation>Ak chcete zadať dôvod pre pozvánku, napíšte ho sem</translation> + </message> +</context> +<context> <name>Swift::QtJoinMUCWindow</name> <message> @@ -1852,16 +2238,16 @@ <name>Swift::QtLoginWindow</name> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="91"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="92"/> <source>User address:</source> - <translation>Adresa používateľa:</translation> + <translation>Adresa osoby:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="96"/> <location filename="../QtUI/QtLoginWindow.cpp" line="97"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="98"/> <source>User address - looks like someuser@someserver.com</source> - <translation>Adresa používateľa - vyzerá ako nejakypouzivatel@nejakyserver.sk</translation> + <translation>Adresa osoby - vyzerá ako niekto@nejakyserver.sk</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="101"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="102"/> <source>Example: alice@wonderland.lit</source> <translatorcomment>lepsi priklad</translatorcomment> @@ -1869,57 +2255,57 @@ </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="107"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="108"/> <source>Password:</source> <translation>Heslo:</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="128"/> <location filename="../QtUI/QtLoginWindow.cpp" line="129"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="130"/> <source>Click if you have a personal certificate used for login to the service.</source> <translation>Kliknite ak máte osobný certifikát použiteľný pre prihlásenie k službe.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="135"/> - <location filename="../QtUI/QtLoginWindow.cpp" line="327"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="136"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="355"/> <source>Connect</source> <translation>Prihlásiť</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="146"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="154"/> <source>Remember Password?</source> <translation>Zapamätať si heslo</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="148"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="156"/> <source>Login Automatically?</source> <translation>Prihlásiť automaticky</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="160"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="168"/> <source>&Swift</source> <translation>&Swift</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="162"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> <source>&General</source> <translation>&Všeobecné</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="170"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="178"/> <source>&About %1</source> - <translation>O programe &%1</translation> + <translation>O &programe %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="175"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="183"/> <source>&Show Debug Console</source> <translation>&Zobraziť ladiacu konzolu</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="185"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="193"/> <source>&Play Sounds</source> <translation>&Prehrávať zvuky</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="191"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="199"/> <source>Display Pop-up &Notifications</source> <translatorcomment>notifikácia sa lepšie hodí</translatorcomment> @@ -1927,56 +2313,59 @@ </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="203"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="211"/> <source>&Quit</source> - <translation>&Ukončiť</translation> + <translation>U&končiť</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="237"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove profile</source> <translation>Odstrániť profil</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="237"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="253"/> <source>Remove the profile '%1'?</source> <translation>Odstrániť profil „%1“?</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="327"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="355"/> <source>Cancel</source> <translation>Zrušiť</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="341"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="369"/> <source>Confirm terms of use</source> <translation>Potvrdenie podmienok použitia</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="384"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="412"/> <source>Select an authentication certificate</source> - <translation>Vyberte overovací certifikát</translation> + <translation>Vyberte certifikát pre overenie</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="494"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="412"/> + <source>P12 files (*.cert *.p12 *.pfx);;All files (*.*)</source> + <translation>Súbory P12 (*.cert *.p12 *.pfx);;Všetky súbory (*.*)</translation> + </message> + <message> + <location filename="../QtUI/QtLoginWindow.cpp" line="525"/> <source>The certificate presented by the server is not valid.</source> <translatorcomment>správny význam?</translatorcomment> - <translation>Certifikát predstavený serverom nie je platný.</translation> + <translation>Certifikát, ktorý poskytol server nie je platný.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="495"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="526"/> <source>Would you like to permanently trust this certificate? This must only be done if you know it is correct.</source> - <translation>Chcete nastálo dôverovať tomuto certifikátu? Toto stačí spraviť iba raz, ak viete, že je správny.</translation> + <translation>Chcete natrvalo dôverovať tomuto certifikátu? Toto stačí spraviť iba raz, ak viete, že je správny.</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="497"/> <source>Subject: %1</source> - <translation>Predmet: %1</translation> + <translation type="obsolete">Predmet: %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="498"/> <source>SHA-1 Fingerprint: %1</source> - <translation>Odtlačok SHA-1: %1</translation> + <translation type="obsolete">Odtlačok SHA-1: %1</translation> </message> <message> - <location filename="../QtUI/QtLoginWindow.cpp" line="180"/> + <location filename="../QtUI/QtLoginWindow.cpp" line="188"/> <source>Show &File Transfer Overview</source> <translation>Zobraziť prehľad prenosu &súborov</translation> @@ -2008,26 +2397,26 @@ <name>Swift::QtMainWindow</name> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="68"/> + <location filename="../QtUI/QtMainWindow.cpp" line="79"/> <source>&Contacts</source> <translation>&Kontakty</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="77"/> - <location filename="../QtUI/QtMainWindow.cpp" line="174"/> + <location filename="../QtUI/QtMainWindow.cpp" line="88"/> + <location filename="../QtUI/QtMainWindow.cpp" line="205"/> <source>&Notices</source> <translation>&Udalosti</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="76"/> + <location filename="../QtUI/QtMainWindow.cpp" line="87"/> <source>C&hats</source> <translation>&Rozhovory</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="85"/> + <location filename="../QtUI/QtMainWindow.cpp" line="96"/> <source>&View</source> <translation>&Zobraziť</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="87"/> + <location filename="../QtUI/QtMainWindow.cpp" line="98"/> <source>&Show offline contacts</source> <translation>Zobraziť &offline kontakty</translation> @@ -2038,35 +2427,45 @@ </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="100"/> + <location filename="../QtUI/QtMainWindow.cpp" line="106"/> + <source>&Show Emoticons</source> + <translation>&Zobraziť emotikony</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="120"/> <source>&Actions</source> <translation>&Akcie</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="102"/> - <source>Edit &Profile</source> - <translation>Upraviť &profil</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="122"/> + <source>Edit &Profile…</source> + <translation>Upraviť &profil…</translation> + </message> + <message> + <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <source>Enter &Room…</source> + <translation>Vstúpiť do &miestnosti…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="105"/> - <source>Enter &Room</source> - <translation>Vstúpiť do &miestnosti</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="129"/> + <source>&View History…</source> + <translation>&Zobraziť históriu…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="108"/> - <source>&Add Contact</source> - <translation>Pridať &kontakt</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="133"/> + <source>&Add Contact…</source> + <translation>Pridať &kontakt…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="111"/> - <source>&Edit Selected Contact</source> - <translation>&Upraviť vybraný kontakt</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="136"/> + <source>&Edit Selected Contact…</source> + <translation>&Upraviť vybraný kontakt…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="115"/> - <source>Start &Chat</source> - <translation>Začať &rozhovor</translation> + <location filename="../QtUI/QtMainWindow.cpp" line="140"/> + <source>Start &Chat…</source> + <translation>Začať &rozhovor…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="121"/> + <location filename="../QtUI/QtMainWindow.cpp" line="146"/> <source>&Sign Out</source> <translation>&Odhlásiť sa</translation> @@ -2077,25 +2476,25 @@ </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="118"/> + <location filename="../QtUI/QtMainWindow.cpp" line="143"/> <source>Run Server Command</source> <translation>Spustiť príkaz na serveri</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="125"/> + <location filename="../QtUI/QtMainWindow.cpp" line="150"/> <source>&Request Delivery Receipts</source> <translation>Vyžadovať &doručenky</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="134"/> + <location filename="../QtUI/QtMainWindow.cpp" line="161"/> <source>Collecting commands...</source> - <translation>Získavanie príkazov…</translation> + <translation>Prebieha získavanie príkazov…</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="185"/> + <location filename="../QtUI/QtMainWindow.cpp" line="216"/> <source>&Chats</source> <translation>&Rozhovory</translation> </message> <message> - <location filename="../QtUI/QtMainWindow.cpp" line="278"/> + <location filename="../QtUI/QtMainWindow.cpp" line="339"/> <source>No Available Commands</source> <translation>Nie sú dostupné žiadne príkazy</translation> @@ -2130,15 +2529,15 @@ <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="46"/> <source>No actions for this user</source> - <translation>Pre používateľa nie sú dostupné žiadne akcie</translation> + <translation>Pre osobu nie sú dostupné žiadne akcie</translation> </message> <message> <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="55"/> <source>Kick user</source> - <translation>Vykopnúť používateľa</translation> + <translation>Vykopnúť osobu</translation> </message> <message> <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="56"/> <source>Kick and ban user</source> - <translation>Vykopnúť používateľa a zakázať prístup</translation> + <translation>Vykopnúť osobu a zakázať prístup</translation> </message> <message> @@ -2159,6 +2558,10 @@ <message> <location filename="../QtUI/Roster/QtOccupantListWidget.cpp" line="60"/> + <source>Add to contacts</source> + <translation>Pridať do kontaktov</translation> + </message> + <message> <source>Add contact</source> - <translation>Pridať kontakt</translation> + <translation type="obsolete">Pridať kontakt</translation> </message> </context> @@ -2182,38 +2585,55 @@ </context> <context> + <name>Swift::QtRosterHeader</name> + <message> + <location filename="../QtUI/QtRosterHeader.cpp" line="59"/> + <source>Connection is secured</source> + <translation>Spojenie je zabezpečené</translation> + </message> +</context> +<context> <name>Swift::QtRosterWidget</name> <message> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="57"/> <source>Edit</source> - <translation>Upraviť</translation> + <translation type="obsolete">Upraviť</translation> </message> <message> <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="58"/> + <source>Edit…</source> + <translation>Upraviť…</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="59"/> <source>Remove</source> <translation>Odstrániť</translation> </message> <message> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="62"/> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="76"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="63"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> <source>Send File</source> <translation>Odoslať súbor</translation> </message> <message> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="76"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="69"/> + <source>Start Whiteboard Chat</source> + <translation>Začať rozhovor s využitím tabule</translation> + </message> + <message> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="83"/> <source>All Files (*);;</source> <translation>Všetky súbory (*);;</translation> </message> <message> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="84"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="96"/> <source>Rename</source> <translation>Premenovať</translation> </message> <message> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="94"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> <source>Rename group</source> <translation>Premenovať skupinu</translation> </message> <message> - <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="94"/> + <location filename="../QtUI/Roster/QtRosterWidget.cpp" line="106"/> <source>Enter a new name for group '%1':</source> <translation>Zadajte nové meno pre skupinu „%1“:</translation> @@ -2225,5 +2645,5 @@ <location filename="../QtUI/QtStatusWidget.cpp" line="231"/> <source>Connecting</source> - <translation>Pripájam</translation> + <translation>Prebieha pripájanie</translation> </message> <message> @@ -2238,5 +2658,5 @@ <location filename="../QtUI/QtSubscriptionRequestWindow.cpp" line="24"/> <source>You have already replied to this request</source> - <translation>Už ste reagovali na túto žiadosť</translation> + <translation>Na túto žiadosť ste už reagovali</translation> </message> <message> @@ -2268,7 +2688,7 @@ If you choose to defer this choice, you will be asked again when you next login. <translatorcomment>formulácia</translatorcomment> <translation>%1 si vás chce pridať do zoznamu kontaktov. - Chcete si ju/ho pridať do vášho zoznamu kontaktov a zdielať stav, keď budete pripojení? + Chcete si ho pridať do vášho zoznamu kontaktov a keď budete pripojení, zdielať s ním stav? -Keď sa rozhodnete odložiť výber, môžete sa znovu rozhodnúť po nasledujúcom prihlásení.</translation> +Keď sa rozhodnete odložiť výber, po nasledujúcom prihlásení sa môžete znovu rozhodnúť.</translation> </message> </context> @@ -2316,5 +2736,5 @@ Keď sa rozhodnete odložiť výber, môžete sa znovu rozhodnúť po nasledujú <location filename="../QtUI/UserSearch/QtUserSearchDetailsPage.cpp" line="22"/> <source>Please choose a name for the contact, and select the groups you want to add the contact to.</source> - <translation>Zvoľte prosím meno pre kontakt a vyberte skupiny do ktorých kontakt chcete zaradiť.</translation> + <translation>Zvoľte meno pre kontakt a vyberte skupiny do ktorých chcete kontakt zaradiť.</translation> </message> </context> @@ -2324,15 +2744,15 @@ Keď sa rozhodnete odložiť výber, môžete sa znovu rozhodnúť po nasledujú <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> <source>%1. If you know their address you can enter it directly, or you can search for them.</source> - <translation>%1. Keď viete jej/jeho adresu, môžete ju zadať priamo, alebo ju/ho môžete vyhľadať.</translation> + <translation>%1. Keď viete jeho adresu, môžete ju zadať priamo, alebo ho môžete vyhľadať.</translation> </message> <message> <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> <source>Add another user to your contact list</source> - <translation>Nižšie môžete pridať iného používateľa/používateľku do zoznamu kontaktov</translation> + <translation>Pridať osobu do vášho zoznamu kontaktov</translation> </message> <message> <location filename="../QtUI/UserSearch/QtUserSearchFirstPage.cpp" line="16"/> <source>Chat to another user</source> - <translation>Rozhovor s inou osobou</translation> + <translation>Rozhovor s ďalšou osobou</translation> </message> </context> @@ -2355,21 +2775,21 @@ Keď sa rozhodnete odložiť výber, môžete sa znovu rozhodnúť po nasledujú </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="293"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> <source>How would you like to find the user to add?</source> <translatorcomment>kostrbaté</translatorcomment> - <translation>Ako chcete vyhľadať osobu, ktorú chcete pridať?</translation> + <translation>Ako vyhľadať osobu, ktorú pridať:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="296"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="299"/> <source>How would you like to find the user to chat to?</source> - <translation>Ako chcete nájsť osobu s ktorou chcete začať rozhovor?</translation> + <translation>Ako vyhľadať osobu s ktorou začať rozhovor:</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="323"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="326"/> <source>Error while searching</source> - <translation>Chyba pri hľadaní</translation> + <translation>Chyba pri vyhľadávaní</translation> </message> <message> - <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="329"/> + <location filename="../QtUI/UserSearch/QtUserSearchWindow.cpp" line="332"/> <source>This server doesn't support searching for users.</source> <translation>Tento server nepodporuje vyhľadávanie osôb.</translation> @@ -2395,4 +2815,12 @@ Keď sa rozhodnete odložiť výber, môžete sa znovu rozhodnúť po nasledujú </context> <context> + <name>Swift::QtWhiteboardWindow</name> + <message> + <location filename="../QtUI/Whiteboard/QtWhiteboardWindow.cpp" line="380"/> + <source>Closing window is equivalent closing the session. Are you sure you want to do this?</source> + <translation>Zatvorením okna ukončíte sedenie. Naozaj chcete toto spraviť?</translation> + </message> +</context> +<context> <name>Swift::QtXMLConsoleWidget</name> <message> @@ -2404,5 +2832,5 @@ Keď sa rozhodnete odložiť výber, môžete sa znovu rozhodnúť po nasledujú <location filename="../QtUI/QtXMLConsoleWidget.cpp" line="40"/> <source>Trace input/output</source> - <translation>Sledovať vstup/výstup</translation> + <translation>Sledovať vstup a výstup</translation> </message> <message> diff --git a/Swift/Translations/swift_sv.ts b/Swift/Translations/swift_sv.ts index fdd0c98..b979dab 100644 --- a/Swift/Translations/swift_sv.ts +++ b/Swift/Translations/swift_sv.ts @@ -1446,25 +1446,25 @@ <message> <location filename="../QtUI/QtMainWindow.cpp" line="86"/> - <source>Edit &Profile</source> - <translation>&Redigera profil</translation> + <source>Edit &Profile…</source> + <translation>&Redigera profil…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="89"/> - <source>Enter &Room</source> - <translation>&Anslut till rum</translation> + <source>Enter &Room…</source> + <translation>&Anslut till rum…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="92"/> - <source>&Add Contact</source> - <translation>&Lägg till kontakt</translation> + <source>&Add Contact…</source> + <translation>&Lägg till kontakt…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="95"/> - <source>&Edit Selected Contact</source> - <translation>&Redigera markerad kontakt</translation> + <source>&Edit Selected Contact…</source> + <translation>&Redigera markerad kontakt…</translation> </message> <message> <location filename="../QtUI/QtMainWindow.cpp" line="99"/> - <source>Start &Chat</source> + <source>Start &Chat…</source> <translation>Starta &chat</translation> </message> diff --git a/Swift/resources/Windows/Swift.rc b/Swift/resources/Windows/Swift.rc index 99201e6..887f389 100644 --- a/Swift/resources/Windows/Swift.rc +++ b/Swift/resources/Windows/Swift.rc @@ -2,5 +2,5 @@ 1 VERSIONINFO -// FILEVERSION 1.0 + FILEVERSION SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR, SWIFT_VERSION_PATCH // PRODUCTVERSION 1.0 FILEFLAGSMASK 0x3fL @@ -23,5 +23,5 @@ BEGIN VALUE "FileVersion", SWIFT_VERSION_STRING VALUE "InternalName", "Swift\0" - VALUE "LegalCopyright", "Copyright (C) 2010 Swift Team\0" + VALUE "LegalCopyright", "Copyright \251 " SWIFT_COPYRIGHT_YEAR " Swift Team\0" VALUE "OriginalFilename", "Swift.exe\0" VALUE "ProductName", "Swift\0" 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/certificate.png b/Swift/resources/icons/certificate.png Binary files differindex 6111b8e..b7cc23c 100644 --- a/Swift/resources/icons/certificate.png +++ b/Swift/resources/icons/certificate.png diff --git a/Swift/resources/icons/circle.png b/Swift/resources/icons/circle.png Binary files differnew file mode 100644 index 0000000..001f4d6 --- /dev/null +++ b/Swift/resources/icons/circle.png diff --git a/Swift/resources/icons/cursor.png b/Swift/resources/icons/cursor.png Binary files differnew file mode 100644 index 0000000..9e6a7d3 --- /dev/null +++ b/Swift/resources/icons/cursor.png diff --git a/Swift/resources/icons/eraser.png b/Swift/resources/icons/eraser.png Binary files differnew file mode 100644 index 0000000..b9b7522 --- /dev/null +++ b/Swift/resources/icons/eraser.png diff --git a/Swift/resources/icons/handline.png b/Swift/resources/icons/handline.png Binary files differnew file mode 100644 index 0000000..a085c14 --- /dev/null +++ b/Swift/resources/icons/handline.png diff --git a/Swift/resources/icons/line.png b/Swift/resources/icons/line.png Binary files differnew file mode 100644 index 0000000..3b6cf31 --- /dev/null +++ b/Swift/resources/icons/line.png diff --git a/Swift/resources/icons/lock.png b/Swift/resources/icons/lock.png Binary files differnew file mode 100644 index 0000000..e749505 --- /dev/null +++ b/Swift/resources/icons/lock.png diff --git a/Swift/resources/icons/lock.svg b/Swift/resources/icons/lock.svg new file mode 100644 index 0000000..755379d --- /dev/null +++ b/Swift/resources/icons/lock.svg @@ -0,0 +1,14739 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 12.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 51448) --> + +<svg + xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" + 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" + version="1.1" + id="Layer_1" + width="32" + height="32" + viewBox="0 0 31.999999 32.000001" + overflow="visible" + enable-background="new 0 0 972.512 969.251" + xml:space="preserve" + inkscape:version="0.47 r22583" + sodipodi:docname="lock.svg" + style="overflow:visible" + inkscape:export-filename="/Users/remko/src/swift/Swift/resources/icons/lock.png" + inkscape:export-xdpi="281.25" + inkscape:export-ydpi="281.25"><metadata + id="metadata7135"><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="defs7133"><inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 484.62549 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="972.51202 : 484.62549 : 1" + inkscape:persp3d-origin="486.25601 : 323.08366 : 1" + id="perspective7137" /> + <foreignObject + id="foreignObject3349" + height="1" + width="1" + y="0" + x="0" + requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/"> + <i:pgfRef + xlink:href="#adobe_illustrator_pgf"> + </i:pgfRef> + </foreignObject> + +<linearGradient + inkscape:collect="always" + xlink:href="#XMLID_228_" + id="linearGradient11972" + gradientUnits="userSpaceOnUse" + x1="145.5239" + y1="94.930199" + x2="145.5239" + y2="66.871597" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_229_" + id="linearGradient11974" + gradientUnits="userSpaceOnUse" + x1="115.77" + y1="256.41461" + x2="115.77" + y2="225.1748" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_230_" + id="linearGradient11976" + gradientUnits="userSpaceOnUse" + x1="145.5239" + y1="131.09669" + x2="145.5239" + y2="103.0381" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_231_" + id="linearGradient11978" + gradientUnits="userSpaceOnUse" + x1="145.5239" + y1="167.2637" + x2="145.5239" + y2="139.20509" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_232_" + id="linearGradient11980" + gradientUnits="userSpaceOnUse" + x1="155.4893" + y1="256.41461" + x2="155.4893" + y2="225.1748" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_233_" + id="linearGradient11982" + gradientUnits="userSpaceOnUse" + x1="198.8447" + y1="256.41501" + x2="198.8447" + y2="225.1758" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_234_" + id="linearGradient11984" + gradientUnits="userSpaceOnUse" + x1="72.9272" + y1="256.41461" + x2="72.9272" + y2="225.1748" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_235_" + id="linearGradient11986" + gradientUnits="userSpaceOnUse" + x1="319.18311" + y1="94.930199" + x2="319.18311" + y2="66.871597" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_236_" + id="linearGradient11988" + gradientUnits="userSpaceOnUse" + x1="316.18311" + y1="812.93073" + x2="316.18311" + y2="784.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_237_" + id="linearGradient11990" + gradientUnits="userSpaceOnUse" + x1="334.68311" + y1="849.93073" + x2="334.68311" + y2="821.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_238_" + id="linearGradient11992" + gradientUnits="userSpaceOnUse" + x1="455.0835" + y1="849.93073" + x2="455.0835" + y2="821.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_239_" + id="linearGradient11994" + gradientUnits="userSpaceOnUse" + x1="554.9834" + y1="849.93073" + x2="554.9834" + y2="821.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_240_" + id="linearGradient11996" + gradientUnits="userSpaceOnUse" + x1="399.18311" + y1="812.93073" + x2="399.18311" + y2="784.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_241_" + id="linearGradient11998" + gradientUnits="userSpaceOnUse" + x1="489.18259" + y1="812.93073" + x2="489.18259" + y2="784.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_242_" + id="linearGradient12000" + gradientUnits="userSpaceOnUse" + x1="724.7832" + y1="849.93073" + x2="724.7832" + y2="821.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_243_" + id="linearGradient12002" + gradientUnits="userSpaceOnUse" + x1="579.68262" + y1="812.93073" + x2="579.68262" + y2="784.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_244_" + id="linearGradient12004" + gradientUnits="userSpaceOnUse" + x1="635.38379" + y1="849.93073" + x2="635.38379" + y2="821.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_245_" + id="linearGradient12006" + gradientUnits="userSpaceOnUse" + x1="717.18262" + y1="812.93073" + x2="717.18262" + y2="784.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_246_" + id="linearGradient12008" + gradientUnits="userSpaceOnUse" + x1="449.18259" + y1="66.871597" + x2="449.18259" + y2="94.930199" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_247_" + id="linearGradient12010" + gradientUnits="userSpaceOnUse" + x1="445.293" + y1="464.8721" + x2="445.293" + y2="485.68069" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_248_" + id="linearGradient12012" + gradientUnits="userSpaceOnUse" + x1="502.293" + y1="485.68069" + x2="502.293" + y2="464.87259" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_249_" + id="linearGradient12014" + gradientUnits="userSpaceOnUse" + x1="319.18311" + y1="131.09669" + x2="319.18311" + y2="103.0381" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_250_" + id="linearGradient12016" + gradientUnits="userSpaceOnUse" + x1="319.18311" + y1="167.2637" + x2="319.18311" + y2="139.20509" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_251_" + id="linearGradient12018" + gradientUnits="userSpaceOnUse" + x1="449.18259" + y1="103.0381" + x2="449.18259" + y2="131.09669" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_252_" + id="linearGradient12020" + gradientUnits="userSpaceOnUse" + x1="449.18259" + y1="139.20509" + x2="449.18259" + y2="167.2637" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_253_" + id="linearGradient12022" + gradientUnits="userSpaceOnUse" + x1="583.18262" + y1="66.871597" + x2="583.18262" + y2="94.930199" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_254_" + id="linearGradient12024" + gradientUnits="userSpaceOnUse" + x1="583.18262" + y1="103.0381" + x2="583.18262" + y2="131.09669" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_255_" + id="linearGradient12026" + gradientUnits="userSpaceOnUse" + x1="583.18262" + y1="139.20509" + x2="583.18262" + y2="167.2637" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_256_" + id="linearGradient12028" + gradientUnits="userSpaceOnUse" + x1="239.8989" + y1="256.41501" + x2="239.8989" + y2="225.1758" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_257_" + id="linearGradient12030" + gradientUnits="userSpaceOnUse" + x1="116.2583" + y1="338.5596" + x2="116.2583" + y2="322.38129" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_258_" + id="linearGradient12032" + gradientUnits="userSpaceOnUse" + x1="155.4888" + y1="341.02689" + x2="155.4888" + y2="320.8916" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_259_" + id="linearGradient12034" + gradientUnits="userSpaceOnUse" + x1="198.8452" + y1="340.05029" + x2="198.8452" + y2="319.91599" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_260_" + id="linearGradient12036" + gradientUnits="userSpaceOnUse" + x1="72.439903" + y1="338.5596" + x2="72.439903" + y2="322.38129" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_261_" + id="linearGradient12038" + gradientUnits="userSpaceOnUse" + x1="239.8979" + y1="339.8013" + x2="239.8979" + y2="321.5845" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_262_" + id="linearGradient12040" + gradientUnits="userSpaceOnUse" + x1="298.32281" + y1="255.07179" + x2="298.32281" + y2="227.7796" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_263_" + id="linearGradient12042" + gradientUnits="userSpaceOnUse" + x1="305.63531" + y1="255.0723" + x2="305.63531" + y2="227.7812" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_264_" + id="linearGradient12044" + gradientUnits="userSpaceOnUse" + x1="290.80661" + y1="255.0728" + x2="290.80661" + y2="227.78059" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_265_" + id="linearGradient12046" + gradientUnits="userSpaceOnUse" + x1="298.1196" + y1="255.07179" + x2="298.1196" + y2="227.7807" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_266_" + id="linearGradient12048" + gradientUnits="userSpaceOnUse" + x1="342.12839" + y1="253.2529" + x2="342.12839" + y2="227.3506" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_267_" + id="linearGradient12050" + gradientUnits="userSpaceOnUse" + x1="339.34961" + y1="253.25391" + x2="339.34961" + y2="227.35429" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_268_" + id="linearGradient12052" + gradientUnits="userSpaceOnUse" + x1="387.65189" + y1="253.7007" + x2="387.65189" + y2="227.64101" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_269_" + id="linearGradient12054" + gradientUnits="userSpaceOnUse" + x1="387.65189" + y1="253.7012" + x2="387.65189" + y2="227.64169" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_270_" + id="linearGradient12056" + gradientUnits="userSpaceOnUse" + x1="394.97949" + y1="253.7012" + x2="394.97949" + y2="227.6405" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_271_" + id="linearGradient12058" + gradientUnits="userSpaceOnUse" + x1="398.08939" + y1="253.6987" + x2="398.08939" + y2="227.64301" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_272_" + id="linearGradient12060" + gradientUnits="userSpaceOnUse" + x1="395.07129" + y1="253.7002" + x2="395.07129" + y2="227.64211" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_273_" + id="linearGradient12062" + gradientUnits="userSpaceOnUse" + x1="387.6929" + y1="253.7007" + x2="387.6929" + y2="227.6412" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_274_" + id="linearGradient12064" + gradientUnits="userSpaceOnUse" + x1="380.1875" + y1="253.7007" + x2="380.1875" + y2="227.64259" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_275_" + id="linearGradient12066" + gradientUnits="userSpaceOnUse" + x1="376.98831" + y1="253.70309" + x2="376.98831" + y2="227.6385" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_276_" + id="linearGradient12068" + gradientUnits="userSpaceOnUse" + x1="379.9697" + y1="253.7021" + x2="379.9697" + y2="227.6414" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_277_" + id="linearGradient12070" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7071,0.7071,-0.7071,0.7071,297.6169,-164.1155)" + x1="378.96191" + y1="200.4263" + x2="378.96191" + y2="196.6382" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_278_" + id="linearGradient12072" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.5861,-0.5861,0.7071,0.7071,-49.6291,258.138)" + x1="430.58691" + y1="333.49951" + x2="430.58691" + y2="329.7114" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_279_" + id="linearGradient12074" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7071,0.7071,-0.7071,0.7071,297.6169,-164.1155)" + x1="384.64401" + y1="200.4258" + x2="384.64401" + y2="196.6382" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_280_" + id="linearGradient12076" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7071,-0.7071,0.7071,0.7071,-103.0637,311.5715)" + x1="421.15869" + y1="333.50049" + x2="421.15869" + y2="329.7114" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_281_" + id="linearGradient12078" + gradientUnits="userSpaceOnUse" + x1="475.08691" + y1="251.44971" + x2="475.08691" + y2="235.2085" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_282_" + id="linearGradient12080" + gradientUnits="userSpaceOnUse" + x1="467.021" + y1="251.44971" + x2="467.021" + y2="235.2085" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_283_" + id="linearGradient12082" + gradientUnits="userSpaceOnUse" + x1="475.0874" + y1="251.44971" + x2="475.0874" + y2="235.2083" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_284_" + id="linearGradient12084" + gradientUnits="userSpaceOnUse" + x1="467.021" + y1="251.4502" + x2="467.021" + y2="235.2086" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_285_" + id="linearGradient12086" + gradientUnits="userSpaceOnUse" + x1="471.0542" + y1="251.4502" + x2="471.0542" + y2="235.20821" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_285_" + id="linearGradient12088" + gradientUnits="userSpaceOnUse" + x1="471.0542" + y1="251.4502" + x2="471.0542" + y2="235.20821" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_287_" + id="linearGradient12090" + gradientUnits="userSpaceOnUse" + x1="561.4248" + y1="252.18159" + x2="561.4248" + y2="234.1817" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_288_" + id="linearGradient12092" + gradientUnits="userSpaceOnUse" + x1="550.01758" + y1="252.18159" + x2="550.01758" + y2="234.1817" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_289_" + id="linearGradient12094" + gradientUnits="userSpaceOnUse" + x1="555.72168" + y1="252.18159" + x2="555.72168" + y2="234.18159" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_289_" + id="linearGradient12096" + gradientUnits="userSpaceOnUse" + x1="555.72168" + y1="252.18159" + x2="555.72168" + y2="234.18159" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_291_" + id="linearGradient12098" + gradientUnits="userSpaceOnUse" + x1="555.72168" + y1="252.18159" + x2="555.72168" + y2="234.18159" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_291_" + id="linearGradient12100" + gradientUnits="userSpaceOnUse" + x1="555.72168" + y1="252.18159" + x2="555.72168" + y2="234.18159" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_293_" + id="linearGradient12102" + gradientUnits="userSpaceOnUse" + x1="512.33301" + y1="269.13431" + x2="512.33301" + y2="217.5298" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_294_" + id="linearGradient12104" + gradientUnits="userSpaceOnUse" + x1="512.33301" + y1="245.89259" + x2="512.33301" + y2="221.1452" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_295_" + id="linearGradient12106" + gradientUnits="userSpaceOnUse" + x1="598.62207" + y1="245.01511" + x2="598.62207" + y2="241.01511" + gradientTransform="translate(2.7332519,1.366626)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_296_" + id="linearGradient12108" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)" + x1="161.4917" + y1="35.549301" + x2="144.4146" + y2="35.549301" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_297_" + id="linearGradient12110" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)" + x1="161.4917" + y1="-234.356" + x2="144.4146" + y2="-234.356" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_298_" + id="linearGradient12112" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)" + x1="161.4917" + y1="-248.3564" + x2="144.4146" + y2="-248.3564" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_299_" + id="linearGradient12114" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,75.8691,645.0391)" + x1="285.26859" + y1="272.67969" + x2="300.64359" + y2="272.67969" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_300_" + id="linearGradient12116" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,75.8691,645.0391)" + x1="285.26859" + y1="282.01321" + x2="300.64359" + y2="282.01321" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_301_" + id="linearGradient12118" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)" + x1="161.4917" + y1="-1.8784" + x2="144.4146" + y2="-1.8784" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_302_" + id="linearGradient12120" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,75.8691,645.0391)" + x1="285.26859" + y1="308.65231" + x2="300.64359" + y2="308.65231" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_303_" + id="linearGradient12122" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,75.8691,645.0391)" + x1="285.26859" + y1="317.98581" + x2="300.64359" + y2="317.98581" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_304_" + id="linearGradient12124" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,75.8691,645.0391)" + x1="285.2695" + y1="350.91699" + x2="300.6445" + y2="350.91699" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_305_" + id="linearGradient12126" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)" + x1="161.5396" + y1="-285.78421" + x2="144.4624" + y2="-285.78421" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_306_" + id="linearGradient12128" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)" + x1="161.5396" + y1="-299.78421" + x2="144.4624" + y2="-299.78421" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_307_" + id="linearGradient12130" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,75.8691,645.0391)" + x1="284.9834" + y1="569.58398" + x2="300.3584" + y2="569.58398" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_308_" + id="linearGradient12132" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,635.8447,436.2764)" + x1="75.537102" + y1="-156.73289" + x2="92.613297" + y2="-156.73289" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_309_" + id="linearGradient12134" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,635.8447,436.2764)" + x1="75.537102" + y1="-170.7334" + x2="92.613297" + y2="-170.7334" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_310_" + id="linearGradient12136" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,725.8438,436.375)" + x1="75.587898" + y1="-191.9731" + x2="92.665001" + y2="-191.9731" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_311_" + id="linearGradient12138" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,725.8438,436.375)" + x1="75.587898" + y1="-205.9731" + x2="92.665001" + y2="-205.9731" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_312_" + id="linearGradient12140" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,986.918,-9.2988)" + x1="369.23581" + y1="475.77289" + x2="353.86081" + y2="475.77289" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_313_" + id="linearGradient12142" + gradientUnits="userSpaceOnUse" + x1="317.9194" + y1="427.99561" + x2="317.9194" + y2="400.4072" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_314_" + id="linearGradient12144" + gradientUnits="userSpaceOnUse" + x1="307.78809" + y1="493.12399" + x2="307.78809" + y2="455.12451" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_315_" + id="linearGradient12146" + gradientUnits="userSpaceOnUse" + x1="353.69681" + y1="488.66211" + x2="353.69681" + y2="460.24701" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_316_" + id="linearGradient12148" + gradientUnits="userSpaceOnUse" + x1="365.5" + y1="488.6611" + x2="365.5" + y2="460.24951" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_317_" + id="linearGradient12150" + gradientUnits="userSpaceOnUse" + x1="359.13089" + y1="488.6611" + x2="359.13089" + y2="460.24969" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_318_" + id="linearGradient12152" + gradientUnits="userSpaceOnUse" + x1="299.479" + y1="306.3486" + x2="299.479" + y2="274.82471" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_320_" + id="linearGradient12156" + gradientUnits="userSpaceOnUse" + x1="342.686" + y1="310.00781" + x2="342.686" + y2="274.3851" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_321_" + id="linearGradient12158" + gradientUnits="userSpaceOnUse" + x1="336.62061" + y1="310.0127" + x2="336.62061" + y2="274.38339" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_322_" + id="linearGradient12160" + gradientUnits="userSpaceOnUse" + x1="348.75241" + y1="310.0127" + x2="348.75241" + y2="274.38339" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_323_" + id="linearGradient12162" + gradientUnits="userSpaceOnUse" + x1="390.62109" + y1="305.54099" + x2="390.62109" + y2="272.64749" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_324_" + id="linearGradient12164" + gradientUnits="userSpaceOnUse" + x1="390.62109" + y1="305.5264" + x2="390.62109" + y2="272.6517" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_325_" + id="linearGradient12166" + gradientUnits="userSpaceOnUse" + x1="390.62061" + y1="305.543" + x2="390.62061" + y2="272.65201" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_326_" + id="linearGradient12168" + gradientUnits="userSpaceOnUse" + x1="385.3735" + y1="305.54349" + x2="385.3735" + y2="272.64749" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_327_" + id="linearGradient12170" + gradientUnits="userSpaceOnUse" + x1="390.62109" + y1="305.543" + x2="390.62109" + y2="272.64731" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_328_" + id="linearGradient12172" + gradientUnits="userSpaceOnUse" + x1="390.62109" + y1="305.54349" + x2="390.62109" + y2="272.6459" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_329_" + id="linearGradient12174" + gradientUnits="userSpaceOnUse" + x1="395.86871" + y1="305.543" + x2="395.86871" + y2="272.64731" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_330_" + id="linearGradient12176" + gradientUnits="userSpaceOnUse" + x1="433.57761" + y1="300.7515" + x2="433.57761" + y2="281.17371" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_331_" + id="linearGradient12178" + gradientUnits="userSpaceOnUse" + x1="433.57959" + y1="300.76901" + x2="433.57959" + y2="281.1731" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_332_" + id="linearGradient12180" + gradientUnits="userSpaceOnUse" + x1="446.51321" + y1="300.76459" + x2="446.51321" + y2="281.17319" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_333_" + id="linearGradient12182" + gradientUnits="userSpaceOnUse" + x1="434.9678" + y1="300.76459" + x2="434.9678" + y2="281.1734" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_334_" + id="linearGradient12184" + gradientUnits="userSpaceOnUse" + x1="437.1636" + y1="300.76459" + x2="437.1636" + y2="281.17331" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_335_" + id="linearGradient12186" + gradientUnits="userSpaceOnUse" + x1="433.57761" + y1="300.76459" + x2="433.57761" + y2="281.1738" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_336_" + id="linearGradient12188" + gradientUnits="userSpaceOnUse" + x1="429.99271" + y1="300.76459" + x2="429.99271" + y2="281.17331" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_337_" + id="linearGradient12190" + gradientUnits="userSpaceOnUse" + x1="425.78961" + y1="300.76459" + x2="425.78961" + y2="281.1734" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_333_" + id="linearGradient12192" + gradientUnits="userSpaceOnUse" + x1="434.9678" + y1="300.76459" + x2="434.9678" + y2="281.1734" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_337_" + id="linearGradient12194" + gradientUnits="userSpaceOnUse" + x1="425.78961" + y1="300.76459" + x2="425.78961" + y2="281.1734" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_340_" + id="linearGradient12196" + gradientUnits="userSpaceOnUse" + x1="433.57959" + y1="300.76459" + x2="433.57959" + y2="281.17111" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_341_" + id="linearGradient12198" + gradientUnits="userSpaceOnUse" + x1="433.57761" + y1="300.76459" + x2="433.57761" + y2="281.1709" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_342_" + id="linearGradient12200" + gradientUnits="userSpaceOnUse" + x1="420.64209" + y1="300.76459" + x2="420.64209" + y2="281.17319" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_343_" + id="linearGradient12202" + gradientUnits="userSpaceOnUse" + x1="479.01169" + y1="303.21729" + x2="479.01169" + y2="275.1619" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_344_" + id="linearGradient12204" + gradientUnits="userSpaceOnUse" + x1="479.01221" + y1="303.21729" + x2="479.01221" + y2="275.1619" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_344_" + id="linearGradient12206" + gradientUnits="userSpaceOnUse" + x1="479.01221" + y1="303.21729" + x2="479.01221" + y2="275.1619" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_346_" + id="linearGradient12208" + gradientUnits="userSpaceOnUse" + x1="479.01221" + y1="303.21439" + x2="479.01221" + y2="275.16449" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_347_" + id="linearGradient12210" + gradientUnits="userSpaceOnUse" + x1="479.0127" + y1="303.2085" + x2="479.0127" + y2="275.16531" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_348_" + id="linearGradient12212" + gradientUnits="userSpaceOnUse" + x1="479.0127" + y1="303.2153" + x2="479.0127" + y2="275.16681" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_349_" + id="linearGradient12214" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="191.75591" + x2="786.86517" + y2="212.5952" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_350_" + id="linearGradient12216" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="202.145" + x2="763.78809" + y2="202.145" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_351_" + id="linearGradient12218" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="222.45799" + x2="786.86517" + y2="243.29739" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_352_" + id="linearGradient12220" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="232.8472" + x2="774.78809" + y2="232.8472" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_353_" + id="linearGradient12222" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="253.1602" + x2="786.86517" + y2="273.99951" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_354_" + id="linearGradient12224" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="263.54929" + x2="782.78809" + y2="263.54929" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_355_" + id="linearGradient12226" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="283.8623" + x2="786.86517" + y2="304.70169" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_356_" + id="linearGradient12228" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="294.2515" + x2="792.78809" + y2="294.2515" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_357_" + id="linearGradient12230" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="314.56451" + x2="786.86517" + y2="335.40381" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_358_" + id="linearGradient12232" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="324.95361" + x2="802.78809" + y2="324.95361" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_359_" + id="linearGradient12234" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="345.2666" + x2="786.86517" + y2="366.1055" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_360_" + id="linearGradient12236" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="355.65579" + x2="813.78809" + y2="355.65579" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_361_" + id="linearGradient12238" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="375.96829" + x2="786.86517" + y2="396.80759" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_362_" + id="linearGradient12240" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="386.35739" + x2="827.78809" + y2="386.35739" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_363_" + id="linearGradient12242" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="406.67041" + x2="786.86517" + y2="427.5098" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_364_" + id="linearGradient12244" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="417.0596" + x2="839.78809" + y2="417.0596" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_365_" + id="linearGradient12246" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="437.37259" + x2="786.86517" + y2="458.21191" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_366_" + id="linearGradient12248" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="447.76169" + x2="849.78809" + y2="447.76169" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_367_" + id="linearGradient12250" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="68.948196" + x2="786.86517" + y2="89.787598" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_368_" + id="linearGradient12252" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="99.650398" + x2="786.86517" + y2="120.4893" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_369_" + id="linearGradient12254" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="110.0396" + x2="720.44238" + y2="110.0396" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_370_" + id="linearGradient12256" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="161.0537" + x2="786.86517" + y2="181.8931" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_371_" + id="linearGradient12258" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="171.4429" + x2="752.12109" + y2="171.4429" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_372_" + id="linearGradient12260" + gradientUnits="userSpaceOnUse" + x1="786.86517" + y1="130.35159" + x2="786.86517" + y2="151.1909" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_373_" + id="linearGradient12262" + gradientUnits="userSpaceOnUse" + x1="713.58398" + y1="140.74071" + x2="737.35938" + y2="140.74071" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_374_" + id="linearGradient12264" + gradientUnits="userSpaceOnUse" + x1="713.20312" + y1="478.49509" + x2="860.52728" + y2="478.49509" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_375_" + id="linearGradient12266" + gradientUnits="userSpaceOnUse" + x1="525.53607" + y1="306.67429" + x2="525.53607" + y2="279.9277" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_376_" + id="linearGradient12268" + gradientUnits="userSpaceOnUse" + x1="120.4946" + y1="293.02829" + x2="120.4946" + y2="276.85059" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_377_" + id="linearGradient12270" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,-1,138.1621,451.6377)" + x1="62.313999" + y1="158.60989" + x2="62.313999" + y2="174.78709" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_378_" + id="linearGradient12272" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,1,-1,0,346.8984,208.7393)" + x1="86.269997" + y1="190.9966" + x2="66.134804" + y2="190.9966" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_379_" + id="linearGradient12274" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0,-1,1,0,-47.459,242.9023)" + x1="-52.107899" + y1="247.44971" + x2="-31.9727" + y2="247.44971" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_380_" + id="linearGradient12276" + gradientUnits="userSpaceOnUse" + x1="240.87891" + y1="294.49609" + x2="240.87891" + y2="276.27881" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_381_" + id="linearGradient12278" + gradientUnits="userSpaceOnUse" + x1="302.44479" + y1="542.0625" + x2="302.44479" + y2="512.74933" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_382_" + id="linearGradient12280" + gradientUnits="userSpaceOnUse" + x1="418.94479" + y1="540.77832" + x2="418.94479" + y2="512.90918" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_383_" + id="linearGradient12282" + gradientUnits="userSpaceOnUse" + x1="302.44479" + y1="586.14648" + x2="302.44479" + y2="570.74921" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_384_" + id="linearGradient12284" + gradientUnits="userSpaceOnUse" + x1="418.94479" + y1="585.27338" + x2="418.94479" + y2="570.83252" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_385_" + id="linearGradient12286" + gradientUnits="userSpaceOnUse" + x1="569.32318" + y1="305.13129" + x2="569.32318" + y2="279.19141" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_386_" + id="linearGradient12288" + gradientUnits="userSpaceOnUse" + x1="569.2998" + y1="305.13129" + x2="569.2998" + y2="279.19119" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_387_" + id="linearGradient12290" + gradientUnits="userSpaceOnUse" + x1="560.87207" + y1="305.13129" + x2="560.87207" + y2="279.19101" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_388_" + id="linearGradient12292" + gradientUnits="userSpaceOnUse" + x1="577.61041" + y1="305.13129" + x2="577.61041" + y2="279.19101" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_389_" + id="linearGradient12294" + gradientUnits="userSpaceOnUse" + x1="620.3291" + y1="303.17871" + x2="620.3291" + y2="281.23141" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_390_" + id="linearGradient12296" + gradientUnits="userSpaceOnUse" + x1="488.5029" + y1="431.31049" + x2="488.5029" + y2="399.61911" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_391_" + id="linearGradient12298" + gradientUnits="userSpaceOnUse" + x1="489.94431" + y1="418.99951" + x2="489.94431" + y2="407.37079" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_392_" + id="linearGradient12300" + gradientUnits="userSpaceOnUse" + x1="482.57281" + y1="419.00241" + x2="482.57281" + y2="407.3692" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_393_" + id="linearGradient12302" + gradientUnits="userSpaceOnUse" + x1="486.25931" + y1="419.00101" + x2="486.25931" + y2="407.36929" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_394_" + id="linearGradient12304" + gradientUnits="userSpaceOnUse" + x1="486.25931" + y1="419.0015" + x2="486.25931" + y2="407.36981" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_395_" + id="linearGradient12306" + gradientUnits="userSpaceOnUse" + x1="486.25931" + y1="419.0015" + x2="486.25931" + y2="407.3696" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_395_" + id="linearGradient12308" + gradientUnits="userSpaceOnUse" + x1="486.25931" + y1="419.0015" + x2="486.25931" + y2="407.3696" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_397_" + id="linearGradient12310" + gradientUnits="userSpaceOnUse" + x1="588.84082" + y1="431.31049" + x2="588.84082" + y2="399.61911" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_398_" + id="linearGradient12312" + gradientUnits="userSpaceOnUse" + x1="536.01068" + y1="431.31049" + x2="536.01068" + y2="399.61911" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_399_" + id="linearGradient12314" + gradientUnits="userSpaceOnUse" + x1="533.64941" + y1="414.68649" + x2="533.64941" + y2="411.41019" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_400_" + id="linearGradient12316" + gradientUnits="userSpaceOnUse" + x1="639.00879" + y1="431.31049" + x2="639.00879" + y2="399.61911" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_401_" + id="linearGradient12318" + gradientUnits="userSpaceOnUse" + x1="328.26169" + y1="655.12598" + x2="328.26169" + y2="609.95172" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_402_" + id="linearGradient12320" + gradientUnits="userSpaceOnUse" + x1="303.26169" + y1="655.12598" + x2="303.26169" + y2="609.95172" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_403_" + id="linearGradient12322" + gradientUnits="userSpaceOnUse" + x1="313.3179" + y1="655.125" + x2="313.3179" + y2="609.95068" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_404_" + id="linearGradient12324" + gradientUnits="userSpaceOnUse" + x1="474.26169" + y1="655.12598" + x2="474.26169" + y2="609.95172" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_405_" + id="linearGradient12326" + gradientUnits="userSpaceOnUse" + x1="449.26169" + y1="655.12598" + x2="449.26169" + y2="609.95172" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_406_" + id="linearGradient12328" + gradientUnits="userSpaceOnUse" + x1="459.78809" + y1="655.125" + x2="459.78809" + y2="609.95068" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_407_" + id="linearGradient12330" + gradientUnits="userSpaceOnUse" + x1="398.26169" + y1="655.12598" + x2="398.26169" + y2="609.95172" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_408_" + id="linearGradient12332" + gradientUnits="userSpaceOnUse" + x1="373.26169" + y1="655.12598" + x2="373.26169" + y2="609.95172" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_409_" + id="linearGradient12334" + gradientUnits="userSpaceOnUse" + x1="782.18262" + y1="849.93073" + x2="782.18262" + y2="821.87207" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_410_" + id="linearGradient12336" + gradientUnits="userSpaceOnUse" + x1="566.6416" + y1="550.96191" + x2="654.10938" + y2="550.96191" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_411_" + id="linearGradient12338" + gradientUnits="userSpaceOnUse" + x1="713.77832" + y1="517.71973" + x2="735.79791" + y2="517.71973" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_412_" + id="linearGradient12340" + gradientUnits="userSpaceOnUse" + x1="737.02832" + y1="517.71973" + x2="759.04791" + y2="517.71973" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_413_" + id="linearGradient12342" + gradientUnits="userSpaceOnUse" + x1="760.27832" + y1="517.71973" + x2="782.29791" + y2="517.71973" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_414_" + id="linearGradient12344" + gradientUnits="userSpaceOnUse" + x1="783.52832" + y1="517.71973" + x2="805.54791" + y2="517.71973" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_415_" + id="linearGradient12346" + gradientUnits="userSpaceOnUse" + x1="806.77832" + y1="517.71973" + x2="828.79791" + y2="517.71973" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_416_" + id="linearGradient12348" + gradientUnits="userSpaceOnUse" + x1="713.77832" + y1="545.0459" + x2="735.79791" + y2="545.0459" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_417_" + id="linearGradient12350" + gradientUnits="userSpaceOnUse" + x1="737.02832" + y1="545.0459" + x2="759.04791" + y2="545.0459" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_418_" + id="linearGradient12352" + gradientUnits="userSpaceOnUse" + x1="760.27832" + y1="545.0459" + x2="782.29791" + y2="545.0459" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_419_" + id="linearGradient12354" + gradientUnits="userSpaceOnUse" + x1="783.52832" + y1="545.0459" + x2="805.54791" + y2="545.0459" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_420_" + id="linearGradient12356" + gradientUnits="userSpaceOnUse" + x1="713.77832" + y1="572.37402" + x2="735.79791" + y2="572.37402" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_421_" + id="linearGradient12358" + gradientUnits="userSpaceOnUse" + x1="737.02832" + y1="572.37402" + x2="759.04791" + y2="572.37402" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_422_" + id="linearGradient12360" + gradientUnits="userSpaceOnUse" + x1="760.27832" + y1="572.37402" + x2="782.29791" + y2="572.37402" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_423_" + id="linearGradient12362" + gradientUnits="userSpaceOnUse" + x1="713.77832" + y1="599.7002" + x2="735.79791" + y2="599.7002" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_424_" + id="linearGradient12364" + gradientUnits="userSpaceOnUse" + x1="737.02832" + y1="599.7002" + x2="759.04791" + y2="599.7002" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_425_" + id="linearGradient12366" + gradientUnits="userSpaceOnUse" + x1="713.77832" + y1="627.02643" + x2="735.79791" + y2="627.02643" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_426_" + id="linearGradient12368" + gradientUnits="userSpaceOnUse" + x1="526.3291" + y1="648.7998" + x2="526.3291" + y2="620.74121" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_427_" + id="linearGradient12370" + gradientUnits="userSpaceOnUse" + x1="572.3291" + y1="648.7998" + x2="572.3291" + y2="620.74121" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_428_" + id="linearGradient12372" + gradientUnits="userSpaceOnUse" + x1="618.3291" + y1="648.7998" + x2="618.3291" + y2="620.74121" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_429_" + id="linearGradient12374" + gradientUnits="userSpaceOnUse" + x1="664.3291" + y1="648.7998" + x2="664.3291" + y2="620.74121" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_430_" + id="linearGradient12376" + gradientUnits="userSpaceOnUse" + x1="77.7603" + y1="798.29791" + x2="77.7603" + y2="767.74219" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_431_" + id="linearGradient12378" + gradientUnits="userSpaceOnUse" + x1="122.668" + y1="798.29791" + x2="122.668" + y2="767.74219" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_432_" + id="linearGradient12380" + gradientUnits="userSpaceOnUse" + x1="167.57471" + y1="798.29791" + x2="167.57471" + y2="767.74219" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_433_" + id="linearGradient12382" + gradientUnits="userSpaceOnUse" + x1="212.48241" + y1="798.29791" + x2="212.48241" + y2="767.74219" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_434_" + id="linearGradient12384" + gradientUnits="userSpaceOnUse" + x1="94.600601" + y1="9" + x2="94.600601" + y2="35" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_435_" + id="linearGradient12386" + gradientUnits="userSpaceOnUse" + x1="555.24799" + y1="756.67291" + x2="555.24799" + y2="674.1123" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_436_" + id="linearGradient12388" + gradientUnits="userSpaceOnUse" + x1="735.18262" + y1="763.14752" + x2="735.18262" + y2="735.08893" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_437_" + id="linearGradient12390" + gradientUnits="userSpaceOnUse" + x1="776.96289" + y1="735.08893" + x2="776.96289" + y2="763.14752" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_438_" + id="linearGradient12392" + gradientUnits="userSpaceOnUse" + x1="735.18262" + y1="727.78027" + x2="735.18262" + y2="699.72168" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_439_" + id="linearGradient12394" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,-1,1675.9258,1484.2363)" + x1="982.52252" + y1="784.51459" + x2="982.52252" + y2="756.45612" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_442_" + id="linearGradient12400" + gradientUnits="userSpaceOnUse" + x1="241.65089" + y1="390.2627" + x2="241.65089" + y2="361.52829" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_443_" + id="linearGradient12402" + gradientUnits="userSpaceOnUse" + x1="241.65089" + y1="378.89551" + x2="241.65089" + y2="372.89551" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_444_" + id="linearGradient12404" + gradientUnits="userSpaceOnUse" + x1="235.7881" + y1="479.59329" + x2="235.7881" + y2="449.59329" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_445_" + id="linearGradient12406" + gradientUnits="userSpaceOnUse" + x1="235.7881" + y1="527.09277" + x2="235.7881" + y2="497.0928" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_446_" + id="linearGradient12408" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,471.5762,0)" + x1="235.7881" + y1="574.63672" + x2="235.7881" + y2="544.63672" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_447_" + id="linearGradient12410" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(-1,0,0,1,471.5762,0)" + x1="235.7881" + y1="619.13672" + x2="235.7881" + y2="589.13672" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_448_" + id="linearGradient12412" + gradientUnits="userSpaceOnUse" + x1="638.75098" + y1="257.35501" + x2="638.75098" + y2="231.4028" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_449_" + id="linearGradient12414" + gradientUnits="userSpaceOnUse" + x1="164.2041" + y1="432.55811" + x2="164.2041" + y2="410.55811" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_450_" + id="linearGradient12416" + gradientUnits="userSpaceOnUse" + x1="209.30811" + y1="432.0845" + x2="209.30811" + y2="411.0845" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <linearGradient + id="XMLID_257_" + gradientUnits="userSpaceOnUse" + x1="116.2583" + y1="338.5596" + x2="116.2583" + y2="322.38129"> + <stop + offset="0" + style="stop-color:#636466" + id="stop3919" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop3921" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop3923" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop3925" /> + </linearGradient> + + + <linearGradient + id="XMLID_258_" + gradientUnits="userSpaceOnUse" + x1="155.4888" + y1="341.02689" + x2="155.4888" + y2="320.8916"> + <stop + offset="0" + style="stop-color:#636466" + id="stop3934" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop3936" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop3938" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop3940" /> + </linearGradient> + + + <linearGradient + id="XMLID_259_" + gradientUnits="userSpaceOnUse" + x1="198.8452" + y1="340.05029" + x2="198.8452" + y2="319.91599"> + <stop + offset="0" + style="stop-color:#636466" + id="stop3949" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop3951" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop3953" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop3955" /> + </linearGradient> + + + <linearGradient + id="XMLID_260_" + gradientUnits="userSpaceOnUse" + x1="72.439903" + y1="338.5596" + x2="72.439903" + y2="322.38129"> + <stop + offset="0" + style="stop-color:#636466" + id="stop3964" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop3966" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop3968" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop3970" /> + </linearGradient> + + + + + + + + + + + <linearGradient + id="XMLID_295_" + gradientUnits="userSpaceOnUse" + x1="598.62207" + y1="245.01511" + x2="598.62207" + y2="241.01511"> + <stop + offset="0" + style="stop-color:#636466" + id="stop4346" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop4348" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop4350" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop4352" /> + </linearGradient> + + + <linearGradient + id="XMLID_296_" + gradientUnits="userSpaceOnUse" + x1="161.4917" + y1="35.549301" + x2="144.4146" + y2="35.549301" + gradientTransform="matrix(0,1,-1,0,336.9434,199.3672)"> + <stop + offset="0" + style="stop-color:#636466" + id="stop4357" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop4359" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop4361" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop4363" /> + </linearGradient> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <linearGradient + id="XMLID_376_" + gradientUnits="userSpaceOnUse" + x1="120.4946" + y1="293.02829" + x2="120.4946" + y2="276.85059"> + <stop + offset="0" + style="stop-color:#636466" + id="stop5326" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop5328" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop5330" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop5332" /> + </linearGradient> + + + <linearGradient + id="XMLID_377_" + gradientUnits="userSpaceOnUse" + x1="62.313999" + y1="158.60989" + x2="62.313999" + y2="174.78709" + gradientTransform="matrix(-1,0,0,-1,138.1621,451.6377)"> + <stop + offset="0" + style="stop-color:#636466" + id="stop5337" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop5339" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop5341" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop5343" /> + </linearGradient> + + + <linearGradient + id="XMLID_378_" + gradientUnits="userSpaceOnUse" + x1="86.269997" + y1="190.9966" + x2="66.134804" + y2="190.9966" + gradientTransform="matrix(0,1,-1,0,346.8984,208.7393)"> + <stop + offset="0" + style="stop-color:#636466" + id="stop5348" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop5350" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop5352" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop5354" /> + </linearGradient> + + + <linearGradient + id="XMLID_379_" + gradientUnits="userSpaceOnUse" + x1="-52.107899" + y1="247.44971" + x2="-31.9727" + y2="247.44971" + gradientTransform="matrix(0,-1,1,0,-47.459,242.9023)"> + <stop + offset="0" + style="stop-color:#636466" + id="stop5359" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop5361" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop5363" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop5365" /> + </linearGradient> + + + <linearGradient + id="XMLID_381_" + gradientUnits="userSpaceOnUse" + x1="302.44479" + y1="542.0625" + x2="302.44479" + y2="512.74933"> + <stop + offset="0" + style="stop-color:#636466" + id="stop5383" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop5385" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop5387" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop5389" /> + </linearGradient> + + + + + <linearGradient + id="XMLID_383_" + gradientUnits="userSpaceOnUse" + x1="302.44479" + y1="586.14648" + x2="302.44479" + y2="570.74921"> + <stop + offset="0" + style="stop-color:#636466" + id="stop5413" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop5415" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop5417" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop5419" /> + </linearGradient> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <linearGradient + id="XMLID_440_" + gradientUnits="userSpaceOnUse" + x1="73.122597" + y1="388.56149" + x2="73.122597" + y2="363.22849"> + <stop + offset="0" + style="stop-color:#636466" + id="stop6923" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop6925" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop6927" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop6929" /> + </linearGradient> + + + + <linearGradient + id="XMLID_441_" + gradientUnits="userSpaceOnUse" + x1="203.70509" + y1="390.2627" + x2="203.70509" + y2="361.52829"> + <stop + offset="0" + style="stop-color:#636466" + id="stop6942" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop6944" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop6946" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop6948" /> + </linearGradient> + + + + + + + + + + + + + + + + + + + + + + + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_441_" + id="linearGradient12583" + gradientUnits="userSpaceOnUse" + x1="203.70509" + y1="390.2627" + x2="203.70509" + y2="361.52829" + gradientTransform="translate(2.7332519,1.366626)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_440_" + id="linearGradient12590" + gradientUnits="userSpaceOnUse" + x1="73.122597" + y1="388.56149" + x2="73.122597" + y2="363.22849" + gradientTransform="translate(2.7332519,1.366626)" /> + <linearGradient + y2="274.38519" + x2="342.68649" + y1="310.00931" + x1="342.68649" + gradientUnits="userSpaceOnUse" + id="XMLID_319_"> + <stop + id="stop4667" + style="stop-color:#636466" + offset="0" /> + <stop + id="stop4669" + style="stop-color:#464648" + offset="0.5" /> + <stop + id="stop4671" + style="stop-color:#5D5E60" + offset="0.5" /> + <stop + id="stop4673" + style="stop-color:#535355" + offset="1" /> + </linearGradient> + + + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_319_" + id="linearGradient14446" + gradientUnits="userSpaceOnUse" + x1="342.68649" + y1="310.00931" + x2="342.68649" + y2="274.38519" + gradientTransform="matrix(1.0465963,0,0,0.68881983,-342.93131,-182.12352)" /> + <linearGradient + id="XMLID_320_" + gradientUnits="userSpaceOnUse" + x1="342.686" + y1="310.00781" + x2="342.686" + y2="274.3851"> + <stop + offset="0" + style="stop-color:#636466" + id="stop4680" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop4682" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop4684" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop4686" /> + </linearGradient> + + <linearGradient + id="XMLID_321_" + gradientUnits="userSpaceOnUse" + x1="336.62061" + y1="310.0127" + x2="336.62061" + y2="274.38339"> + <stop + offset="0" + style="stop-color:#636466" + id="stop4691" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop4693" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop4695" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop4697" /> + </linearGradient> + + <linearGradient + id="XMLID_322_" + gradientUnits="userSpaceOnUse" + x1="348.75241" + y1="310.0127" + x2="348.75241" + y2="274.38339"> + <stop + offset="0" + style="stop-color:#636466" + id="stop4702" /> + <stop + offset="0.5" + style="stop-color:#464648" + id="stop4704" /> + <stop + offset="0.5" + style="stop-color:#5D5E60" + id="stop4706" /> + <stop + offset="1" + style="stop-color:#535355" + id="stop4708" /> + </linearGradient> + + <linearGradient + inkscape:collect="always" + xlink:href="#XMLID_322_" + id="linearGradient14582" + gradientUnits="userSpaceOnUse" + x1="348.75241" + y1="310.0127" + x2="348.75241" + y2="274.38339" + gradientTransform="matrix(1.1440605,0,0,1.1440605,-376.50603,-313.0783)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_321_" + id="linearGradient14585" + gradientUnits="userSpaceOnUse" + x1="336.62061" + y1="310.0127" + x2="336.62061" + y2="274.38339" + gradientTransform="matrix(1.1440605,0,0,1.1440605,-376.50603,-313.0783)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_320_" + id="linearGradient14588" + gradientUnits="userSpaceOnUse" + x1="342.686" + y1="310.00781" + x2="342.686" + y2="274.3851" + gradientTransform="matrix(1.1440605,0,0,1.1440605,-376.50603,-313.0783)" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_320_" + id="linearGradient14596" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0465963,0,0,1.0465963,-342.93162,-283.98711)" + x1="342.686" + y1="310.00781" + x2="342.686" + y2="274.3851" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_322_" + id="linearGradient14600" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0465963,0,0,1.0465963,-342.93131,-284.02899)" + x1="348.75241" + y1="310.0127" + x2="348.75241" + y2="274.38339" /><linearGradient + inkscape:collect="always" + xlink:href="#XMLID_321_" + id="linearGradient14602" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0465963,0,0,1.0465963,-342.93131,-284.02899)" + x1="336.62061" + y1="310.0127" + x2="336.62061" + y2="274.38339" /></defs><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="905" + inkscape:window-height="806" + id="namedview7131" + showgrid="false" + inkscape:zoom="18.03125" + inkscape:cx="16" + inkscape:cy="16" + inkscape:window-x="300" + inkscape:window-y="43" + inkscape:window-maximized="0" + inkscape:current-layer="Layer_1" + showguides="true" + inkscape:guide-bbox="true" /> +<font + horiz-adv-x="1000" + id="font3" + horiz-origin-x="0" + horiz-origin-y="0" + vert-origin-x="0" + vert-origin-y="0" + vert-adv-y="0"> +<!-- Copyright 2000, 2004 Adobe Systems Incorporated. All Rights Reserved. U.S. Patent D454,582.Myriad is a registered trademark of Adobe Systems Incorporated. --> +<font-face + font-family="MyriadPro-Semibold" + units-per-em="1000" + underline-position="-100" + underline-thickness="50" + id="font-face5" /> +<missing-glyph + horiz-adv-x="500" + d="M0,0l500,0l0,700l-500,0M250,395l-170,255l340,0M280,350l170,255l0,-510M80,50l170,255l170,-255M50,605l170,-255l-170,-255z" + id="missing-glyph7" /> +<glyph + unicode="A" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166z" + id="glyph9" /> +<glyph + unicode="Æ" + horiz-adv-x="832" + d="M128,0l89,208l201,0l27,-208l357,0l0,101l-252,0l-25,196l238,0l0,100l-253,0l-21,176l280,0l1,101l-473,0l-296,-674M255,307l74,178C342,517 356,554 368,587l3,0C374,554 379,515 383,484l21,-177z" + id="glyph11" /> +<glyph + unicode="Ǽ" + horiz-adv-x="832" + d="M128,0l89,208l201,0l27,-208l357,0l0,101l-252,0l-25,196l238,0l0,100l-253,0l-21,176l280,0l1,101l-473,0l-296,-674M255,307l74,178C342,517 356,554 368,587l3,0C374,554 379,515 383,484l21,-177M484,830l-88,-120l99,0l124,120z" + id="glyph13" /> +<glyph + unicode="Á" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M343,830l-88,-120l99,0l124,120z" + id="glyph15" /> +<glyph + unicode="Ă" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M184,827C186,760 224,709 316,709C408,709 448,760 450,827l-66,0C379,802 362,779 317,779C272,779 256,804 252,827z" + id="glyph17" /> +<glyph + unicode="Â" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M273,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph19" /> +<glyph + unicode="Ä" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M223,715C257,715 281,743 281,776C281,810 256,836 223,836C189,836 163,810 163,776C163,743 188,715 222,715M420,715C455,715 479,743 479,776C479,810 454,836 421,836C386,836 361,810 361,776C361,743 385,715 419,715z" + id="glyph21" /> +<glyph + unicode="À" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M157,830l124,-119l99,0l-88,119z" + id="glyph23" /> +<glyph + unicode="Α" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166z" + id="glyph25" /> +<glyph + unicode="Ά" + horiz-adv-x="642" + d="M425,191l61,-191l133,0l-219,674l-157,0l-217,-674l128,0l57,191M231,284l52,166C295,490 306,537 315,576l3,0C328,537 339,491 352,450l53,-166M43,674l-4,-165l71,0l35,165z" + id="glyph27" /> +<glyph + unicode="Ā" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M190,803l0,-69l251,0l0,69z" + id="glyph29" /> +<glyph + unicode="Ą" + horiz-adv-x="636" + d="M394,674l-157,0l-216,-674l127,0l58,191l214,0l60,-189C463,-20 433,-68 433,-115C433,-180 474,-213 537,-213C566,-213 605,-204 630,-185l-18,59C601,-131 585,-135 567,-135C537,-135 514,-113 514,-80C514,-50 540,-17 552,-1l61,1M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166z" + id="glyph31" /> +<glyph + unicode="Å" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M317,889C248,889 209,846 209,793C209,742 250,701 317,701C383,701 425,742 425,794C425,847 387,889 318,889M316,845C345,845 362,821 362,795C362,767 345,745 316,745C288,745 270,769 270,793C270,820 286,845 315,845z" + id="glyph33" /> +<glyph + unicode="Ã" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M248,715C250,743 259,758 273,758C287,758 299,750 317,740C337,729 356,721 381,721C430,721 456,756 454,833l-58,0C393,801 385,792 369,792C355,792 339,801 323,811C303,822 286,831 261,831C215,831 186,789 188,715z" + id="glyph35" /> +<glyph + unicode="B" + horiz-adv-x="576" + d="M71,2C102,-2 154,-7 222,-7C345,-7 424,14 472,56C511,88 537,135 537,195C537,288 472,344 401,362l0,2C474,391 513,448 513,510C513,567 485,611 443,636C397,668 341,679 250,679C181,679 110,673 71,665M193,583C206,586 227,588 263,588C341,588 389,559 389,496C389,439 341,399 256,399l-63,0M193,309l61,0C339,309 407,277 407,199C407,116 337,86 258,86C230,86 209,86 193,89z" + id="glyph37" /> +<glyph + unicode="Β" + horiz-adv-x="576" + d="M71,2C102,-2 154,-7 222,-7C345,-7 424,14 472,56C511,88 537,135 537,195C537,288 472,344 401,362l0,2C474,391 513,448 513,510C513,567 485,611 443,636C397,668 341,679 250,679C181,679 110,673 71,665M193,583C206,586 227,588 263,588C341,588 389,559 389,496C389,439 341,399 256,399l-63,0M193,309l61,0C339,309 407,277 407,199C407,116 337,86 258,86C230,86 209,86 193,89z" + id="glyph39" /> +<glyph + unicode="C" + horiz-adv-x="588" + d="M534,117C502,102 450,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,124 166,-11 380,-11C461,-11 525,5 554,20z" + id="glyph41" /> +<glyph + unicode="Ć" + horiz-adv-x="588" + d="M534,117C502,102 450,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,124 166,-11 380,-11C461,-11 525,5 554,20M388,831l-88,-120l99,0l124,120z" + id="glyph43" /> +<glyph + unicode="Č" + horiz-adv-x="588" + d="M534,117C502,102 450,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,124 166,-11 380,-11C461,-11 525,5 554,20M404,712l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph45" /> +<glyph + unicode="Ç" + horiz-adv-x="592" + d="M534,117C502,102 449,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,142 142,16 321,-6l-40,-80C332,-92 359,-104 359,-126C359,-146 342,-154 321,-154C301,-154 280,-148 265,-140l-18,-55C268,-206 298,-213 327,-213C381,-213 438,-190 438,-125C438,-81 405,-55 367,-47l21,37C467,-9 526,6 554,20z" + id="glyph47" /> +<glyph + unicode="Ĉ" + horiz-adv-x="588" + d="M534,117C502,102 450,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,124 166,-11 380,-11C461,-11 525,5 554,20M307,833l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph49" /> +<glyph + unicode="Ċ" + horiz-adv-x="588" + d="M534,117C502,102 450,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,124 166,-11 380,-11C461,-11 525,5 554,20M354,716C388,716 413,744 413,776C413,809 388,836 355,836C320,836 293,809 293,776C293,744 320,716 353,716z" + id="glyph51" /> +<glyph + unicode="Χ" + horiz-adv-x="594" + d="M574,0l-205,345l197,329l-141,0l-72,-139C332,494 318,464 300,422l-2,0C280,460 264,493 242,535l-73,139l-141,0l192,-333l-200,-341l141,0l67,134C255,186 273,222 289,261l3,0C311,222 330,186 359,134l73,-134z" + id="glyph53" /> +<glyph + unicode="D" + horiz-adv-x="683" + d="M71,2C111,-3 165,-7 237,-7C368,-7 475,24 542,85C606,143 647,233 647,353C647,468 607,548 542,601C480,653 393,679 267,679C194,679 125,674 71,665M194,576C211,580 239,583 278,583C432,583 518,498 517,349C517,178 422,90 262,91C237,91 211,91 194,94z" + id="glyph55" /> +<glyph + unicode="Ď" + horiz-adv-x="683" + d="M71,2C111,-3 165,-7 237,-7C368,-7 475,24 542,85C606,143 647,233 647,353C647,468 607,548 542,601C480,653 393,679 267,679C194,679 125,674 71,665M194,576C211,580 239,583 278,583C432,583 518,498 517,349C517,178 422,90 262,91C237,91 211,91 194,94M363,711l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph57" /> +<glyph + unicode="Đ" + horiz-adv-x="689" + d="M1,294l76,0l0,-294C117,-5 172,-9 243,-9C374,-9 481,22 549,84C612,142 654,232 654,353C654,469 614,552 548,605C486,657 400,682 273,682C200,682 132,676 77,667l0,-276l-76,0M355,391l-155,0l0,188C218,582 245,586 284,586C439,586 524,503 524,351C524,176 430,89 269,89C243,89 217,89 200,92l0,202l155,0z" + id="glyph59" /> +<glyph + unicode="∆" + horiz-adv-x="603" + d="M569,0l0,65l-193,588l-145,0l-197,-589l0,-64M438,100l-280,0l98,302C270,449 287,517 295,551l3,0C309,508 329,438 343,392z" + id="glyph61" /> +<glyph + unicode="E" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0z" + id="glyph63" /> +<glyph + unicode="É" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M295,830l-88,-120l99,0l124,120z" + id="glyph65" /> +<glyph + unicode="Ĕ" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M133,832C135,765 173,714 265,714C357,714 397,765 399,832l-66,0C328,807 311,784 266,784C221,784 205,809 201,832z" + id="glyph67" /> +<glyph + unicode="Ě" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M312,710l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph69" /> +<glyph + unicode="Ê" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M222,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph71" /> +<glyph + unicode="Ë" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M166,716C200,716 224,744 224,777C224,811 199,837 166,837C132,837 106,811 106,777C106,744 131,716 165,716M363,716C398,716 422,744 422,777C422,811 397,837 364,837C329,837 304,811 304,777C304,744 328,716 362,716z" + id="glyph73" /> +<glyph + unicode="Ė" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M261,717C295,717 320,745 320,777C320,810 295,837 262,837C227,837 200,810 200,777C200,745 227,717 260,717z" + id="glyph75" /> +<glyph + unicode="È" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M115,830l124,-119l99,0l-88,119z" + id="glyph77" /> +<glyph + unicode="Ē" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M140,803l0,-69l251,0l0,69z" + id="glyph79" /> +<glyph + unicode="Ŋ" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l183,-308C477,-67 439,-98 366,-114l27,-94C525,-191 605,-115 605,44l0,630l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674z" + id="glyph81" /> +<glyph + unicode="Ę" + horiz-adv-x="515" + d="M466,-126C452,-132 438,-134 423,-134C399,-134 382,-121 382,-98C382,-64 420,-24 457,0C464,0 471,0 478,0l0,101l-284,0l0,198l254,0l0,100l-254,0l0,174l269,0l0,101l-392,0l0,-674l294,0C331,-24 290,-70 290,-126C290,-181 327,-213 388,-213C425,-213 458,-204 484,-186z" + id="glyph83" /> +<glyph + unicode="Ε" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0z" + id="glyph85" /> +<glyph + unicode="Έ" + horiz-adv-x="606" + d="M539,399l-254,0l0,174l269,0l0,101l-393,0l0,-674l408,0l0,101l-284,0l0,198l254,0M5,674l-5,-165l71,0l36,165z" + id="glyph87" /> +<glyph + unicode="Η" + horiz-adv-x="672" + d="M71,674l0,-674l123,0l0,297l285,0l0,-297l123,0l0,674l-123,0l0,-270l-285,0l0,270z" + id="glyph89" /> +<glyph + unicode="Ή" + horiz-adv-x="763" + d="M161,674l0,-674l123,0l0,297l285,0l0,-297l123,0l0,674l-123,0l0,-270l-285,0l0,270M5,674l-5,-165l71,0l36,165z" + id="glyph91" /> +<glyph + unicode="Ð" + horiz-adv-x="689" + d="M1,294l76,0l0,-294C117,-5 172,-9 243,-9C374,-9 481,22 549,84C612,142 654,232 654,353C654,469 614,552 548,605C486,657 400,682 273,682C200,682 132,676 77,667l0,-276l-76,0M355,391l-155,0l0,188C218,582 245,586 284,586C439,586 524,503 524,351C524,176 430,89 269,89C243,89 217,89 200,92l0,202l155,0z" + id="glyph93" /> +<glyph + unicode="€" + horiz-adv-x="536" + d="M492,118C469,106 423,89 374,89C322,89 274,106 240,143C219,166 204,198 197,241l265,0l0,61l-276,0C186,308 186,313 186,319C186,331 186,343 188,354l274,0l0,62l-262,0C208,454 223,487 244,509C277,546 321,564 371,564C416,564 457,551 483,537l25,93C475,646 425,661 365,661C276,661 203,627 149,567C113,529 86,477 74,416l-67,0l0,-62l58,0C64,343 63,330 63,318C63,313 63,307 63,302l-56,0l0,-61l64,0C80,179 102,129 134,92C187,25 269,-11 360,-11C424,-11 480,9 513,29z" + id="glyph95" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M483,105C460,93 418,81 374,81C328,81 290,94 258,124C244,138 234,156 228,178l226,0l0,59l-240,0C213,248 213,256 213,264C213,270 213,279 214,289l240,0l0,59l-226,0C233,371 245,391 261,406C291,436 331,449 373,449C417,449 450,438 470,428l25,84C470,524 427,539 367,539C293,539 221,513 170,462C140,432 117,393 106,348l-65,0l0,-59l56,0C95,278 95,267 95,260C95,255 96,247 96,237l-56,0l0,-59l65,0C116,132 138,91 168,61C215,13 283,-11 354,-11C416,-11 470,6 497,22z" + id="glyph97" /> +<glyph + unicode="F" + horiz-adv-x="509" + d="M71,0l123,0l0,286l248,0l0,101l-248,0l0,186l266,0l0,101l-389,0z" + id="glyph99" /> +<glyph + unicode="G" + horiz-adv-x="666" + d="M611,372l-238,0l0,-97l119,0l0,-168C476,99 443,93 399,93C259,93 165,184 165,337C165,494 266,579 410,579C481,579 526,566 563,550l27,99C559,664 494,681 411,681C188,681 36,544 36,331C35,229 70,140 130,83C194,22 280,-7 394,-7C484,-7 568,15 611,31z" + id="glyph101" /> +<glyph + unicode="" + horiz-adv-x="666" + d="M611,372l-238,0l0,-97l119,0l0,-168C476,99 443,93 399,93C259,93 165,184 165,337C165,494 266,579 410,579C481,579 526,566 563,550l27,99C559,664 494,681 411,681C188,681 36,544 36,331C35,229 70,140 130,83C194,22 280,-7 394,-7C484,-7 568,15 611,31M305,715C307,743 316,758 330,758C344,758 356,750 374,740C394,729 413,721 438,721C487,721 513,756 511,833l-58,0C450,801 442,792 426,792C412,792 396,801 380,811C360,822 343,831 318,831C272,831 243,789 245,715z" + id="glyph103" /> +<glyph + unicode="Γ" + horiz-adv-x="468" + d="M71,674l0,-674l123,0l0,571l264,0l0,103z" + id="glyph105" /> +<glyph + unicode="Ğ" + horiz-adv-x="666" + d="M611,372l-238,0l0,-97l119,0l0,-168C476,99 443,93 399,93C259,93 165,184 165,337C165,494 266,579 410,579C481,579 526,566 563,550l27,99C559,664 494,681 411,681C188,681 36,544 36,331C35,229 70,140 130,83C194,22 280,-7 394,-7C484,-7 568,15 611,31M234,835C236,768 274,717 366,717C458,717 498,768 500,835l-66,0C429,810 412,787 367,787C322,787 306,812 302,835z" + id="glyph107" /> +<glyph + unicode="Ĝ" + horiz-adv-x="666" + d="M611,372l-238,0l0,-97l119,0l0,-168C476,99 443,93 399,93C259,93 165,184 165,337C165,494 266,579 410,579C481,579 526,566 563,550l27,99C559,664 494,681 411,681C188,681 36,544 36,331C35,229 70,140 130,83C194,22 280,-7 394,-7C484,-7 568,15 611,31M320,832l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph109" /> +<glyph + unicode="Ģ" + horiz-adv-x="666" + d="M611,372l-238,0l0,-97l119,0l0,-168C476,99 443,93 399,93C259,93 165,184 165,337C165,494 266,579 410,579C481,579 526,566 563,550l27,99C559,664 494,681 411,681C188,681 36,544 36,331C35,229 70,140 130,83C194,22 280,-7 394,-7C484,-7 568,15 611,31M291,-232C367,-229 437,-195 437,-116C437,-82 417,-54 397,-40l-87,-16C325,-70 341,-92 341,-116C341,-152 307,-172 272,-179z" + id="glyph111" /> +<glyph + unicode="Ġ" + horiz-adv-x="666" + d="M611,372l-238,0l0,-97l119,0l0,-168C476,99 443,93 399,93C259,93 165,184 165,337C165,494 266,579 410,579C481,579 526,566 563,550l27,99C559,664 494,681 411,681C188,681 36,544 36,331C35,229 70,140 130,83C194,22 280,-7 394,-7C484,-7 568,15 611,31M369,716C403,716 428,744 428,776C428,809 403,836 370,836C335,836 308,809 308,776C308,744 335,716 368,716z" + id="glyph113" /> +<glyph + unicode="H" + horiz-adv-x="672" + d="M71,674l0,-674l123,0l0,297l285,0l0,-297l123,0l0,674l-123,0l0,-270l-285,0l0,270z" + id="glyph115" /> +<glyph + unicode="Ħ" + horiz-adv-x="681" + d="M667,475l0,75l-61,0l0,124l-123,0l0,-124l-285,0l0,124l-123,0l0,-124l-61,0l0,-75l61,0l0,-475l123,0l0,277l285,0l0,-277l123,0l0,475M483,383l-285,0l0,92l285,0z" + id="glyph117" /> +<glyph + unicode="Ĥ" + horiz-adv-x="672" + d="M71,674l0,-674l123,0l0,297l285,0l0,-297l123,0l0,674l-123,0l0,-270l-285,0l0,270M290,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph119" /> +<glyph + unicode="I" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674z" + id="glyph121" /> +<glyph + unicode="IJ" + horiz-adv-x="657" + d="M71,674l0,-674l123,0l0,674M469,241C469,124 426,92 356,92C327,92 300,98 280,104l-15,-99C291,-5 334,-11 368,-11C502,-11 592,51 592,237l0,437l-123,0z" + id="glyph123" /> +<glyph + unicode="Í" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M156,830l-88,-120l99,0l124,120z" + id="glyph125" /> +<glyph + unicode="Ĭ" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M-1,832C1,765 39,714 131,714C223,714 263,765 265,832l-66,0C194,807 177,784 132,784C87,784 71,809 67,832z" + id="glyph127" /> +<glyph + unicode="Î" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M85,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph129" /> +<glyph + unicode="Ï" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M34,716C68,716 92,744 92,777C92,811 67,837 34,837C0,837 -26,811 -26,777C-26,744 -1,716 33,716M231,716C266,716 290,744 290,777C290,811 265,837 232,837C197,837 172,811 172,777C172,744 196,716 230,716z" + id="glyph131" /> +<glyph + unicode="İ" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M133,717C167,717 192,745 192,777C192,810 167,837 134,837C99,837 72,810 72,777C72,745 99,717 132,717z" + id="glyph133" /> +<glyph + unicode="Ì" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M-26,830l124,-119l99,0l-88,119z" + id="glyph135" /> +<glyph + unicode="Ī" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M7,803l0,-69l251,0l0,69z" + id="glyph137" /> +<glyph + unicode="Į" + horiz-adv-x="264" + d="M194,674l-123,0l0,-674C58,-23 31,-70 31,-118C31,-182 72,-215 134,-215C164,-215 203,-206 227,-187l-18,59C199,-133 184,-136 165,-136C134,-136 112,-116 112,-82C112,-52 131,-19 143,0l51,0z" + id="glyph139" /> +<glyph + unicode="Ι" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674z" + id="glyph141" /> +<glyph + unicode="Ϊ" + horiz-adv-x="264" + d="M35,713C69,713 93,740 93,773C93,807 68,833 35,833C1,833 -25,806 -25,773C-25,740 0,713 34,713M230,713C265,713 289,740 289,773C289,807 264,833 230,833C196,833 171,806 171,773C171,740 195,713 229,713M71,674l0,-674l123,0l0,674z" + id="glyph143" /> +<glyph + unicode="Ί" + horiz-adv-x="355" + d="M161,674l0,-674l124,0l0,674M5,674l-5,-165l71,0l36,165z" + id="glyph145" /> +<glyph + unicode="Ĩ" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M59,715C61,743 70,758 84,758C98,758 110,750 128,740C148,729 167,721 192,721C241,721 267,756 265,833l-58,0C204,801 196,792 180,792C166,792 150,801 134,811C114,822 97,831 72,831C26,831 -3,789 -1,715z" + id="glyph147" /> +<glyph + unicode="J" + horiz-adv-x="392" + d="M205,241C205,124 162,92 92,92C63,92 36,98 16,104l-15,-99C27,-5 70,-11 104,-11C238,-11 328,51 328,237l0,437l-123,0z" + id="glyph149" /> +<glyph + unicode="Ĵ" + horiz-adv-x="392" + d="M205,241C205,124 162,92 92,92C63,92 36,98 16,104l-15,-99C27,-5 70,-11 104,-11C238,-11 328,51 328,237l0,437l-123,0M217,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph151" /> +<glyph + unicode="K" + horiz-adv-x="582" + d="M71,0l122,0l0,237l60,72l191,-309l144,0l-247,387l232,287l-152,0l-176,-237C229,414 212,390 196,364l-3,0l0,310l-122,0z" + id="glyph153" /> +<glyph + unicode="Κ" + horiz-adv-x="582" + d="M71,0l122,0l0,237l60,72l191,-309l144,0l-247,387l232,287l-152,0l-176,-237C229,414 212,390 196,364l-3,0l0,310l-122,0z" + id="glyph155" /> +<glyph + unicode="Ķ" + horiz-adv-x="582" + d="M71,0l122,0l0,237l60,72l191,-309l144,0l-247,387l232,287l-152,0l-176,-237C229,414 212,390 196,364l-3,0l0,310l-122,0M229,-231C305,-228 375,-194 375,-115C375,-81 355,-53 335,-39l-87,-16C263,-69 279,-91 279,-115C279,-151 245,-171 210,-178z" + id="glyph157" /> +<glyph + unicode="L" + horiz-adv-x="493" + d="M71,0l400,0l0,103l-277,0l0,571l-123,0z" + id="glyph159" /> +<glyph + unicode="Ĺ" + horiz-adv-x="493" + d="M71,0l400,0l0,103l-277,0l0,571l-123,0M172,830l-88,-120l99,0l124,120z" + id="glyph161" /> +<glyph + unicode="Λ" + horiz-adv-x="630" + d="M614,0l-222,674l-155,0l-216,-674l128,0l128,431C289,477 299,523 309,568l2,0C322,524 333,478 346,431l134,-431z" + id="glyph163" /> +<glyph + unicode="Ľ" + horiz-adv-x="493" + d="M71,0l400,0l0,103l-277,0l0,571l-123,0M278,494C354,497 424,531 424,610C424,644 404,672 384,686l-87,-16C312,656 328,634 328,610C328,574 294,554 259,547z" + id="glyph165" /> +<glyph + unicode="Ļ" + horiz-adv-x="493" + d="M71,0l400,0l0,103l-277,0l0,571l-123,0M188,-232C264,-229 334,-195 334,-116C334,-82 314,-54 294,-40l-87,-16C222,-70 238,-92 238,-116C238,-152 204,-172 169,-179z" + id="glyph167" /> +<glyph + unicode="Ŀ" + horiz-adv-x="493" + d="M71,0l400,0l0,103l-277,0l0,571l-123,0M364,315C398,315 423,343 423,375C423,408 398,435 365,435C330,435 303,408 303,375C303,343 330,315 363,315z" + id="glyph169" /> +<glyph + unicode="Ł" + horiz-adv-x="499" + d="M476,0l0,103l-277,0l0,230l130,90l0,89l-130,-90l0,252l-123,0l0,-335l-79,-56l0,-88l79,56l0,-251z" + id="glyph171" /> +<glyph + unicode="M" + horiz-adv-x="827" + d="M653,0l120,0l-39,674l-160,0l-93,-270C455,325 432,242 414,169l-3,0C394,244 373,324 349,403l-88,271l-162,0l-45,-674l115,0l15,271C189,363 193,470 196,559l2,0C216,474 240,382 266,296l90,-288l95,0l98,292C579,385 607,476 629,559l3,0C631,467 636,362 640,275z" + id="glyph173" /> +<glyph + unicode="" + horiz-adv-x="827" + d="M653,0l120,0l-39,674l-160,0l-93,-270C455,325 432,242 414,169l-3,0C394,244 373,324 349,403l-88,271l-162,0l-45,-674l115,0l15,271C189,363 193,470 196,559l2,0C216,474 240,382 266,296l90,-288l95,0l98,292C579,385 607,476 629,559l3,0C631,467 636,362 640,275M366,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph175" /> +<glyph + unicode="Μ" + horiz-adv-x="827" + d="M653,0l120,0l-39,674l-160,0l-93,-270C455,325 432,242 414,169l-3,0C394,244 373,324 349,403l-88,271l-162,0l-45,-674l115,0l15,271C189,363 193,470 196,559l2,0C216,474 240,382 266,296l90,-288l95,0l98,292C579,385 607,476 629,559l3,0C631,467 636,362 640,275z" + id="glyph177" /> +<glyph + unicode="N" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674z" + id="glyph179" /> +<glyph + unicode="" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674M291,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph181" /> +<glyph + unicode="Ń" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674M361,830l-88,-120l99,0l124,120z" + id="glyph183" /> +<glyph + unicode="Ň" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674M387,707l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph185" /> +<glyph + unicode="Ņ" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674M273,-231C349,-228 419,-194 419,-115C419,-81 399,-53 379,-39l-87,-16C307,-69 323,-91 323,-115C323,-151 289,-171 254,-178z" + id="glyph187" /> +<glyph + unicode="Ñ" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674M268,715C270,743 279,758 293,758C307,758 319,750 337,740C357,729 376,721 401,721C450,721 476,756 474,833l-58,0C413,801 405,792 389,792C375,792 359,801 343,811C323,822 306,831 281,831C235,831 206,789 208,715z" + id="glyph189" /> +<glyph + unicode="Ν" + horiz-adv-x="676" + d="M184,0l0,241C184,352 183,441 178,528l3,1C214,453 259,370 302,297l176,-297l127,0l0,674l-114,0l0,-235C491,336 494,249 502,159l-2,0C469,232 430,310 385,385l-174,289l-140,0l0,-674z" + id="glyph191" /> +<glyph + unicode="O" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587z" + id="glyph193" /> +<glyph + unicode="Œ" + horiz-adv-x="917" + d="M879,101l-284,0l0,198l254,0l0,100l-254,0l0,174l269,0l0,101l-339,0C507,674 486,677 461,680C437,682 408,685 372,685C173,685 36,550 36,332C36,145 148,-11 368,-11C401,-11 427,-8 451,-6C475,-3 496,0 515,0l364,0M472,100C452,92 420,87 390,87C247,87 165,189 166,336C165,492 255,587 381,587C420,587 450,581 472,572z" + id="glyph195" /> +<glyph + unicode="Ó" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M378,832l-88,-120l99,0l124,120z" + id="glyph197" /> +<glyph + unicode="Ŏ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M219,838C221,771 259,720 351,720C443,720 483,771 485,838l-66,0C414,813 397,790 352,790C307,790 291,815 287,838z" + id="glyph199" /> +<glyph + unicode="Ô" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M305,831l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph201" /> +<glyph + unicode="Ö" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M254,716C288,716 312,744 312,777C312,811 287,837 254,837C220,837 194,811 194,777C194,744 219,716 253,716M451,716C486,716 510,744 510,777C510,811 485,837 452,837C417,837 392,811 392,777C392,744 416,716 450,716z" + id="glyph203" /> +<glyph + unicode="Ò" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M194,832l124,-119l99,0l-88,119z" + id="glyph205" /> +<glyph + unicode="Ơ" + horiz-adv-x="704" + d="M353,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87M597,742C607,722 616,698 616,673C616,645 604,631 575,631C546,631 503,655 471,666C443,676 403,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,439 641,513 603,564C675,563 708,607 708,665C708,703 698,734 687,758z" + id="glyph207" /> +<glyph + unicode="Ő" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M298,833l-72,-114l87,0l106,114M464,833l-72,-114l87,0l106,114z" + id="glyph209" /> +<glyph + unicode="Ō" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M227,806l0,-69l251,0l0,69z" + id="glyph211" /> +<glyph + unicode="Ω" + horiz-adv-x="727" + d="M555,98C614,149 682,240 682,374C682,540 566,685 369,685C179,685 44,547 44,364C44,240 108,148 169,98l0,-3l-138,0l0,-95l279,0l0,74C235,120 172,233 172,350C172,475 245,587 366,587C488,587 555,469 555,351C555,223 492,127 418,74l0,-74l277,0l0,95l-140,0z" + id="glyph213" /> +<glyph + unicode="Ώ" + horiz-adv-x="782" + d="M610,98C670,148 737,239 737,373C737,539 621,685 424,685C234,685 99,546 99,363C99,240 163,148 225,98l0,-3l-139,0l0,-95l279,0l0,74C290,120 227,225 227,350C227,474 300,587 421,587C543,587 610,468 610,350C611,216 547,127 473,74l0,-74l277,0l0,95l-140,0M5,674l-5,-165l71,0l36,165z" + id="glyph215" /> +<glyph + unicode="Ο" + horiz-adv-x="704" + d="M347,-11C532,-11 669,118 669,344C669,536 553,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11M352,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87z" + id="glyph217" /> +<glyph + unicode="Ό" + horiz-adv-x="763" + d="M406,-11C591,-11 728,118 728,344C728,536 612,685 415,685C225,685 94,539 94,332C94,134 215,-11 405,-11M410,87C293,87 224,201 224,335C224,469 289,587 412,587C535,587 598,466 598,339C598,198 530,87 411,87M5,674l-5,-165l71,0l36,165z" + id="glyph219" /> +<glyph + unicode="Ø" + horiz-adv-x="704" + d="M135,-56l62,88C242,4 295,-11 353,-11C528,-11 669,115 669,342C669,439 639,528 581,590l59,85l-71,48l-57,-81C466,671 412,685 354,685C167,685 36,539 36,335C36,235 68,145 127,84l-59,-86M198,189l-2,-1C173,233 160,279 160,337C160,474 228,588 353,588C393,588 426,575 454,553M509,481l2,0C536,434 545,386 545,339C545,196 474,86 352,86C312,86 281,98 255,120z" + id="glyph221" /> +<glyph + unicode="Ǿ" + horiz-adv-x="704" + d="M135,-56l62,88C242,4 295,-11 353,-11C528,-11 669,115 669,342C669,439 639,528 581,590l59,85l-71,48l-57,-81C466,671 412,685 354,685C167,685 36,539 36,335C36,235 68,145 127,84l-59,-86M198,189l-2,-1C173,233 160,279 160,337C160,474 228,588 353,588C393,588 426,575 454,553M509,481l2,0C536,434 545,386 545,339C545,196 474,86 352,86C312,86 281,98 255,120M379,836l-88,-120l99,0l124,120z" + id="glyph223" /> +<glyph + unicode="Õ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M279,716C281,744 290,759 304,759C318,759 330,751 348,741C368,730 387,722 412,722C461,722 487,757 485,834l-58,0C424,802 416,793 400,793C386,793 370,802 354,812C334,823 317,832 292,832C246,832 217,790 219,716z" + id="glyph225" /> +<glyph + unicode="P" + horiz-adv-x="559" + d="M71,0l122,0l0,254C209,251 230,250 253,250C340,250 417,273 466,322C504,358 524,411 524,475C524,538 498,591 458,624C415,660 349,679 259,679C176,679 115,673 71,665M193,579C206,582 230,585 264,585C349,585 401,545 401,470C401,391 345,346 253,346C228,346 208,347 193,351z" + id="glyph227" /> +<glyph + unicode="Φ" + horiz-adv-x="755" + d="M435,27C580,43 720,144 720,339C720,535 578,636 436,650l0,51l-116,0l0,-51C191,637 35,537 35,334C35,137 177,42 318,27l0,-54l117,0M319,114C241,126 159,202 159,335C159,480 246,554 319,564M435,564C513,554 595,482 595,336C595,194 513,124 435,114z" + id="glyph229" /> +<glyph + unicode="Π" + horiz-adv-x="652" + d="M581,674l-510,0l0,-674l123,0l0,570l264,0l0,-570l123,0z" + id="glyph231" /> +<glyph + unicode="Ψ" + horiz-adv-x="717" + d="M673,674l-119,0l0,-179C554,341 490,293 416,288l0,386l-114,0l0,-385C221,294 163,337 163,469l0,205l-119,0l0,-225C44,271 167,201 302,194l0,-194l114,0l0,194C561,202 673,289 673,479z" + id="glyph233" /> +<glyph + unicode="Q" + horiz-adv-x="704" + d="M673,-11C618,0 553,14 495,30l0,4C594,73 669,182 669,345C669,535 553,685 358,685C165,685 36,539 36,331C36,112 175,-3 331,-11C346,-12 360,-16 375,-21C460,-54 544,-82 638,-108M352,87C234,87 165,199 166,334C165,469 231,587 354,587C476,587 539,468 539,339C539,198 471,87 353,87z" + id="glyph235" /> +<glyph + unicode="R" + horiz-adv-x="569" + d="M71,0l122,0l0,277l62,0C325,275 357,247 376,159C395,75 411,20 421,0l127,0C534,26 516,104 495,188C478,253 449,300 401,318l0,3C464,344 523,403 523,490C523,547 502,594 465,625C420,663 354,679 257,679C186,679 117,673 71,665M193,580C205,583 230,586 269,586C349,586 401,552 401,478C401,411 349,367 266,367l-73,0z" + id="glyph237" /> +<glyph + unicode="Ŕ" + horiz-adv-x="569" + d="M71,0l122,0l0,277l62,0C325,275 357,247 376,159C395,75 411,20 421,0l127,0C534,26 516,104 495,188C478,253 449,300 401,318l0,3C464,344 523,403 523,490C523,547 502,594 465,625C420,663 354,679 257,679C186,679 117,673 71,665M193,580C205,583 230,586 269,586C349,586 401,552 401,478C401,411 349,367 266,367l-73,0M299,832l-88,-120l99,0l124,120z" + id="glyph239" /> +<glyph + unicode="Ř" + horiz-adv-x="569" + d="M71,0l122,0l0,277l62,0C325,275 357,247 376,159C395,75 411,20 421,0l127,0C534,26 516,104 495,188C478,253 449,300 401,318l0,3C464,344 523,403 523,490C523,547 502,594 465,625C420,663 354,679 257,679C186,679 117,673 71,665M193,580C205,583 230,586 269,586C349,586 401,552 401,478C401,411 349,367 266,367l-73,0M325,712l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph241" /> +<glyph + unicode="Ŗ" + horiz-adv-x="569" + d="M71,0l122,0l0,277l62,0C325,275 357,247 376,159C395,75 411,20 421,0l127,0C534,26 516,104 495,188C478,253 449,300 401,318l0,3C464,344 523,403 523,490C523,547 502,594 465,625C420,663 354,679 257,679C186,679 117,673 71,665M193,580C205,583 230,586 269,586C349,586 401,552 401,478C401,411 349,367 266,367l-73,0M220,-228C296,-225 366,-191 366,-112C366,-78 346,-50 326,-36l-87,-16C254,-66 270,-88 270,-112C270,-148 236,-168 201,-175z" + id="glyph243" /> +<glyph + unicode="Ρ" + horiz-adv-x="559" + d="M71,0l122,0l0,254C209,251 230,250 253,250C340,250 417,273 466,322C504,358 524,411 524,475C524,538 498,591 458,624C415,660 349,679 259,679C176,679 115,673 71,665M193,579C206,582 230,585 264,585C349,585 401,545 401,470C401,391 345,346 253,346C228,346 208,347 193,351z" + id="glyph245" /> +<glyph + unicode="S" + horiz-adv-x="519" + d="M41,32C78,10 152,-11 223,-11C397,-11 479,84 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134z" + id="glyph247" /> +<glyph + unicode="Ś" + horiz-adv-x="519" + d="M41,32C78,10 152,-11 223,-11C397,-11 479,84 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134M296,832l-88,-120l99,0l124,120z" + id="glyph249" /> +<glyph + unicode="Š" + horiz-adv-x="519" + d="M41,32C78,10 152,-11 223,-11C397,-11 479,84 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134M317,712l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph251" /> +<glyph + unicode="Ş" + horiz-adv-x="519" + d="M41,32C83,8 160,-11 218,-11C220,-11 222,-11 223,-11l-38,-70C236,-86 263,-99 263,-121C263,-140 244,-149 224,-149C206,-149 186,-142 170,-133l-18,-50C170,-194 198,-202 228,-202C286,-202 341,-179 341,-117C341,-79 308,-52 270,-47l24,43C420,18 479,100 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134z" + id="glyph253" /> +<glyph + unicode="" + horiz-adv-x="340" + d="M41,32C83,8 160,-11 218,-11C220,-11 222,-11 223,-11l-38,-70C236,-86 263,-99 263,-121C263,-140 244,-149 224,-149C206,-149 186,-142 170,-133l-18,-50C170,-194 198,-202 228,-202C286,-202 341,-179 341,-117C341,-79 308,-52 270,-47l24,43C420,18 479,100 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134z" + id="glyph255" /> +<glyph + unicode="Ŝ" + horiz-adv-x="519" + d="M41,32C78,10 152,-11 223,-11C397,-11 479,84 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134M214,831l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph257" /> +<glyph + unicode="Ș" + horiz-adv-x="519" + d="M41,32C78,10 152,-11 223,-11C397,-11 479,84 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134M192,-232C268,-229 338,-195 338,-116C338,-82 318,-54 298,-40l-87,-16C226,-70 242,-92 242,-116C242,-152 208,-172 173,-179z" + id="glyph259" /> +<glyph + unicode="Σ" + horiz-adv-x="566" + d="M186,105l191,234l-180,230l0,4l310,0l0,101l-467,0l0,-81l204,-259l-216,-263l0,-71l508,0l0,101l-350,0z" + id="glyph261" /> +<glyph + unicode="T" + horiz-adv-x="525" + d="M200,0l123,0l0,571l194,0l0,103l-509,0l0,-103l192,0z" + id="glyph263" /> +<glyph + unicode="Τ" + horiz-adv-x="525" + d="M200,0l123,0l0,571l194,0l0,103l-509,0l0,-103l192,0z" + id="glyph265" /> +<glyph + unicode="Ŧ" + horiz-adv-x="525" + d="M517,571l0,103l-509,0l0,-103l192,0l0,-192l-120,0l0,-76l120,0l0,-303l123,0l0,303l119,0l0,76l-119,0l0,192z" + id="glyph267" /> +<glyph + unicode="Ť" + horiz-adv-x="525" + d="M200,0l123,0l0,571l194,0l0,103l-509,0l0,-103l192,0M309,710l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph269" /> +<glyph + unicode="Ţ" + horiz-adv-x="525" + d="M200,0l123,0l0,571l194,0l0,103l-509,0l0,-103l192,0M186,-232C262,-229 332,-195 332,-116C332,-82 312,-54 292,-40l-87,-16C220,-70 236,-92 236,-116C236,-152 202,-172 167,-179z" + id="glyph271" /> +<glyph + unicode="Θ" + horiz-adv-x="706" + d="M348,-11C533,-11 670,118 670,344C670,536 554,685 357,685C167,685 36,539 36,332C36,134 157,-11 347,-11M352,87C234,87 165,200 165,335C165,470 230,587 354,587C477,587 541,467 541,339C541,198 473,87 353,87M234,395l0,-100l237,0l0,100z" + id="glyph273" /> +<glyph + unicode="Þ" + horiz-adv-x="558" + d="M71,0l121,0l0,143C209,141 234,138 264,138C409,138 523,205 523,360C523,416 501,465 462,499C419,536 360,553 281,553C241,553 215,551 192,548l0,126l-121,0M192,452C206,456 232,459 268,459C350,459 401,423 401,350C401,281 354,234 260,234C235,234 210,236 192,240z" + id="glyph275" /> +<glyph + unicode="U" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393z" + id="glyph277" /> +<glyph + unicode="Ú" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M357,830l-88,-120l99,0l124,120z" + id="glyph279" /> +<glyph + unicode="Ŭ" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M200,832C202,765 240,714 332,714C424,714 464,765 466,832l-66,0C395,807 378,784 333,784C288,784 272,809 268,832z" + id="glyph281" /> +<glyph + unicode="Û" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M295,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph283" /> +<glyph + unicode="Ü" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M239,716C273,716 297,744 297,777C297,811 272,837 239,837C205,837 179,811 179,777C179,744 204,716 238,716M436,716C471,716 495,744 495,777C495,811 470,837 437,837C402,837 377,811 377,777C377,744 401,716 435,716z" + id="glyph285" /> +<glyph + unicode="Ù" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M178,830l124,-119l99,0l-88,119z" + id="glyph287" /> +<glyph + unicode="Ư" + horiz-adv-x="705" + d="M603,775C615,755 622,733 622,713C622,687 610,674 572,674l-99,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,317l16,1C680,612 712,650 712,705C712,737 704,766 692,790z" + id="glyph289" /> +<glyph + unicode="Ű" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M269,831l-72,-114l87,0l106,114M435,831l-72,-114l87,0l106,114z" + id="glyph291" /> +<glyph + unicode="Ū" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M208,803l0,-69l251,0l0,69z" + id="glyph293" /> +<glyph + unicode="Ų" + horiz-adv-x="666" + d="M436,-124C422,-130 407,-133 392,-133C368,-133 349,-120 349,-94C349,-61 381,-24 407,0C523,25 596,118 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,82 166,-5 311,-10C313,-10 316,-10 318,-10C292,-37 260,-79 260,-127C260,-179 295,-212 357,-212C391,-212 428,-201 454,-183z" + id="glyph295" /> +<glyph + unicode="Υ" + horiz-adv-x="609" + d="M9,579C115,558 194,452 227,293C235,249 239,209 239,159l0,-159l123,0l0,158C362,207 369,253 380,296C418,446 517,550 603,577l-20,103C535,676 466,640 416,585C369,533 325,456 311,359l-5,0C295,456 260,535 211,590C159,648 82,678 29,680z" + id="glyph297" /> +<glyph + unicode="Ϋ" + horiz-adv-x="609" + d="M211,697C245,697 269,725 269,757C269,792 244,817 211,817C177,817 151,791 151,757C151,725 176,697 210,697M406,697C441,697 465,725 465,757C465,792 440,817 407,817C372,817 347,791 347,757C347,725 371,697 405,697M9,579C115,558 194,452 227,293C235,249 239,209 239,159l0,-159l123,0l0,158C362,207 369,253 380,296C418,446 517,550 603,577l-20,103C535,676 466,640 416,585C369,533 325,456 311,359l-5,0C295,456 260,535 211,590C159,648 82,678 29,680z" + id="glyph299" /> +<glyph + unicode="Ύ" + horiz-adv-x="746" + d="M145,579C251,558 330,452 363,293C371,249 375,209 375,159l0,-159l123,0l0,158C498,207 505,253 516,296C554,446 653,550 739,577l-20,103C671,676 602,640 552,585C505,533 461,456 447,359l-6,0C431,456 396,535 347,590C295,648 218,678 165,680M5,674l-5,-165l71,0l36,165z" + id="glyph301" /> +<glyph + unicode="Ů" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M333,880C264,880 225,837 225,784C225,733 266,692 333,692C399,692 441,733 441,785C441,838 403,880 334,880M332,836C361,836 378,812 378,786C378,758 361,736 332,736C304,736 286,760 286,784C286,811 302,836 331,836z" + id="glyph303" /> +<glyph + unicode="Ũ" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M260,715C262,743 271,758 285,758C299,758 311,750 329,740C349,729 368,721 393,721C442,721 468,756 466,833l-58,0C405,801 397,792 381,792C367,792 351,801 335,811C315,822 298,831 273,831C227,831 198,789 200,715z" + id="glyph305" /> +<glyph + unicode="V" + horiz-adv-x="601" + d="M366,0l233,674l-131,0l-98,-308C343,284 319,202 301,122l-2,0C281,204 259,283 233,368l-92,306l-134,0l218,-674z" + id="glyph307" /> +<glyph + unicode="W" + horiz-adv-x="869" + d="M313,0l78,317C411,394 424,459 436,531l2,0C446,458 458,394 475,317l70,-317l132,0l181,674l-125,0l-70,-305C645,287 629,210 617,136l-2,0C605,209 591,284 574,364l-67,310l-129,0l-73,-305C286,284 267,204 256,133l-2,0C243,200 227,286 210,368l-64,306l-131,0l165,-674z" + id="glyph309" /> +<glyph + unicode="Ẃ" + horiz-adv-x="869" + d="M313,0l78,317C411,394 424,459 436,531l2,0C446,458 458,394 475,317l70,-317l132,0l181,674l-125,0l-70,-305C645,287 629,210 617,136l-2,0C605,209 591,284 574,364l-67,310l-129,0l-73,-305C286,284 267,204 256,133l-2,0C243,200 227,286 210,368l-64,306l-131,0l165,-674M472,830l-88,-120l99,0l124,120z" + id="glyph311" /> +<glyph + unicode="Ŵ" + horiz-adv-x="869" + d="M313,0l78,317C411,394 424,459 436,531l2,0C446,458 458,394 475,317l70,-317l132,0l181,674l-125,0l-70,-305C645,287 629,210 617,136l-2,0C605,209 591,284 574,364l-67,310l-129,0l-73,-305C286,284 267,204 256,133l-2,0C243,200 227,286 210,368l-64,306l-131,0l165,-674M391,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph313" /> +<glyph + unicode="Ẅ" + horiz-adv-x="869" + d="M313,0l78,317C411,394 424,459 436,531l2,0C446,458 458,394 475,317l70,-317l132,0l181,674l-125,0l-70,-305C645,287 629,210 617,136l-2,0C605,209 591,284 574,364l-67,310l-129,0l-73,-305C286,284 267,204 256,133l-2,0C243,200 227,286 210,368l-64,306l-131,0l165,-674M343,716C377,716 401,744 401,777C401,811 376,837 343,837C309,837 283,811 283,777C283,744 308,716 342,716M540,716C575,716 599,744 599,777C599,811 574,837 541,837C506,837 481,811 481,777C481,744 505,716 539,716z" + id="glyph315" /> +<glyph + unicode="Ẁ" + horiz-adv-x="869" + d="M313,0l78,317C411,394 424,459 436,531l2,0C446,458 458,394 475,317l70,-317l132,0l181,674l-125,0l-70,-305C645,287 629,210 617,136l-2,0C605,209 591,284 574,364l-67,310l-129,0l-73,-305C286,284 267,204 256,133l-2,0C243,200 227,286 210,368l-64,306l-131,0l165,-674M274,830l124,-119l99,0l-88,119z" + id="glyph317" /> +<glyph + unicode="X" + horiz-adv-x="594" + d="M574,0l-205,345l197,329l-141,0l-72,-139C332,494 318,464 300,422l-2,0C280,460 264,493 242,535l-73,139l-141,0l192,-333l-200,-341l141,0l67,134C255,186 273,222 289,261l3,0C311,222 330,186 359,134l73,-134z" + id="glyph319" /> +<glyph + unicode="Ξ" + horiz-adv-x="559" + d="M55,674l0,-100l456,0l0,100M87,397l0,-100l391,0l0,100M43,101l0,-101l480,0l0,101z" + id="glyph321" /> +<glyph + unicode="Y" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281z" + id="glyph323" /> +<glyph + unicode="Ý" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M320,827l-88,-120l99,0l124,120z" + id="glyph325" /> +<glyph + unicode="Ŷ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M241,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph327" /> +<glyph + unicode="Ÿ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M198,712C232,712 256,740 256,773C256,807 231,833 198,833C164,833 138,807 138,773C138,740 163,712 197,712M395,712C430,712 454,740 454,773C454,807 429,833 396,833C361,833 336,807 336,773C336,740 360,712 394,712z" + id="glyph329" /> +<glyph + unicode="Ỳ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M136,826l124,-119l99,0l-88,119z" + id="glyph331" /> +<glyph + unicode="Z" + horiz-adv-x="566" + d="M26,0l510,0l0,102l-345,0l0,4l340,496l0,72l-474,0l0,-102l314,0l0,-3l-345,-501z" + id="glyph333" /> +<glyph + unicode="Ź" + horiz-adv-x="566" + d="M26,0l510,0l0,102l-345,0l0,4l340,496l0,72l-474,0l0,-102l314,0l0,-3l-345,-501M311,830l-88,-120l99,0l124,120z" + id="glyph335" /> +<glyph + unicode="Ž" + horiz-adv-x="566" + d="M26,0l510,0l0,102l-345,0l0,4l340,496l0,72l-474,0l0,-102l314,0l0,-3l-345,-501M338,710l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph337" /> +<glyph + unicode="Ż" + horiz-adv-x="566" + d="M26,0l510,0l0,102l-345,0l0,4l340,496l0,72l-474,0l0,-102l314,0l0,-3l-345,-501M287,716C321,716 346,744 346,776C346,809 321,836 288,836C253,836 226,809 226,776C226,744 253,716 286,716z" + id="glyph339" /> +<glyph + unicode="Ζ" + horiz-adv-x="566" + d="M26,0l510,0l0,102l-345,0l0,4l340,496l0,72l-474,0l0,-102l314,0l0,-3l-345,-501z" + id="glyph341" /> +<glyph + unicode="a" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph343" /> +<glyph + unicode="á" + horiz-adv-x="508" + d="M279,698l-87,-148l85,0l123,148M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph345" /> +<glyph + unicode="ă" + horiz-adv-x="508" + d="M122,688C122,616 163,553 254,553C337,553 386,608 386,688l-67,0C317,655 297,623 253,623C216,623 194,651 189,688M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph347" /> +<glyph + unicode="â" + horiz-adv-x="508" + d="M208,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph349" /> +<glyph + unicode="´" + horiz-adv-x="300" + d="M174,698l-87,-148l85,0l123,148z" + id="glyph351" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M177,830l-88,-120l99,0l124,120z" + id="glyph353" /> +<glyph + unicode="ä" + horiz-adv-x="508" + d="M155,563C190,563 215,591 215,624C215,660 189,686 156,686C120,686 93,659 93,624C93,591 119,563 154,563M354,563C389,563 414,591 414,624C414,660 389,686 354,686C319,686 293,659 293,624C293,591 318,563 353,563M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph355" /> +<glyph + unicode="æ" + horiz-adv-x="790" + d="M754,216C755,227 757,246 757,264C757,365 705,498 559,498C485,498 427,464 391,408l-2,0C364,464 312,498 230,498C168,498 106,476 67,453l26,-79C122,393 172,411 220,411C307,411 322,348 322,330l0,-17C143,314 32,251 32,135C32,58 89,-11 193,-11C277,-11 340,26 374,85l3,0C408,23 481,-11 563,-11C624,-11 686,0 733,24l-19,84C681,94 643,82 580,82C496,82 437,131 437,216M325,175C325,163 324,152 321,143C308,108 275,77 225,77C186,77 153,101 153,145C153,218 239,237 325,235M437,301C438,346 471,415 545,415C623,415 643,340 642,301z" + id="glyph357" /> +<glyph + unicode="ǽ" + horiz-adv-x="790" + d="M421,698l-87,-148l85,0l123,148M754,216C755,227 757,246 757,264C757,365 705,498 559,498C485,498 427,464 391,408l-2,0C364,464 312,498 230,498C168,498 106,476 67,453l26,-79C122,393 172,411 220,411C307,411 322,348 322,330l0,-17C143,314 32,251 32,135C32,58 89,-11 193,-11C277,-11 340,26 374,85l3,0C408,23 481,-11 563,-11C624,-11 686,0 733,24l-19,84C681,94 643,82 580,82C496,82 437,131 437,216M325,175C325,163 324,152 321,143C308,108 275,77 225,77C186,77 153,101 153,145C153,218 239,237 325,235M437,301C438,346 471,415 545,415C623,415 643,340 642,301z" + id="glyph359" /> +<glyph + unicode="А" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166z" + id="glyph361" /> +<glyph + unicode="Б" + horiz-adv-x="578" + d="M71,674l0,-672C102,-2 153,-7 222,-7C320,-7 419,14 480,70C517,105 540,155 540,219C540,295 507,349 458,382C409,417 342,433 276,433C251,433 216,431 193,428l0,145l294,0l0,101M193,336C213,338 235,340 254,340C295,340 336,331 366,308C394,289 411,259 411,217C411,180 398,150 376,129C348,101 303,87 258,87C233,87 211,88 193,91z" + id="glyph363" /> +<glyph + unicode="В" + horiz-adv-x="576" + d="M71,2C102,-2 154,-7 222,-7C345,-7 424,14 472,56C511,88 537,135 537,195C537,288 472,344 401,362l0,2C474,391 513,448 513,510C513,567 485,611 443,636C397,668 341,679 250,679C181,679 110,673 71,665M193,583C206,586 227,588 263,588C341,588 389,559 389,496C389,439 341,399 256,399l-63,0M193,309l61,0C339,309 407,277 407,199C407,116 337,86 258,86C230,86 209,86 193,89z" + id="glyph365" /> +<glyph + unicode="Г" + horiz-adv-x="464" + d="M71,674l0,-674l123,0l0,573l262,0l0,101z" + id="glyph367" /> +<glyph + unicode="Д" + horiz-adv-x="662" + d="M160,674l0,-176C160,401 152,319 131,247C116,194 92,145 66,98l-54,-3l6,-262l94,0l7,167l409,0l7,-167l94,0l7,262l-64,3l0,576M274,573l175,0l0,-472l-257,0C210,134 229,177 242,221C264,292 274,375 274,467z" + id="glyph369" /> +<glyph + unicode="Е" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0z" + id="glyph371" /> +<glyph + unicode="Ё" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M166,716C200,716 224,744 224,777C224,811 199,837 166,837C132,837 106,811 106,777C106,744 131,716 165,716M363,716C398,716 422,744 422,777C422,811 397,837 364,837C329,837 304,811 304,777C304,744 328,716 362,716z" + id="glyph373" /> +<glyph + unicode="Ж" + horiz-adv-x="842" + d="M12,674l234,-305C149,354 106,289 75,198C53,134 35,64 6,0l125,0C152,45 166,105 181,153C208,234 240,294 334,294l27,0l0,-294l119,0l0,294l26,0C600,294 633,234 659,153C674,105 688,45 710,0l125,0C806,64 790,134 767,201C736,292 693,359 595,369l234,305l-143,0l-186,-289l-20,0l0,289l-119,0l0,-289l-19,0l-186,289z" + id="glyph375" /> +<glyph + unicode="З" + horiz-adv-x="520" + d="M75,544C113,564 168,585 220,585C288,585 331,552 331,497C331,436 271,395 193,395l-53,0l0,-92l53,0C267,303 353,281 353,195C353,131 303,89 215,89C156,89 91,112 58,130l-29,-95C84,3 154,-11 220,-11C345,-11 483,43 483,189C483,282 411,342 322,352l0,2C401,373 459,432 459,516C459,617 375,685 241,685C156,685 92,661 45,632z" + id="glyph377" /> +<glyph + unicode="И" + horiz-adv-x="675" + d="M71,674l0,-674l137,0l170,285C421,357 466,441 498,518l2,0C492,431 491,345 491,239l0,-239l114,0l0,674l-125,0l-178,-302C258,296 214,215 179,139l-2,1C182,230 185,329 185,436l0,238z" + id="glyph379" /> +<glyph + unicode="Й" + horiz-adv-x="675" + d="M71,674l0,-674l137,0l170,285C421,357 466,441 498,518l2,0C492,431 491,345 491,239l0,-239l114,0l0,674l-125,0l-178,-302C258,296 214,215 179,139l-2,1C182,230 185,329 185,436l0,238M199,822C205,746 250,706 335,706C422,706 472,745 477,822l-86,0C387,786 375,761 338,761C299,761 288,787 286,822z" + id="glyph381" /> +<glyph + unicode="К" + horiz-adv-x="577" + d="M71,674l0,-674l121,0l0,294l29,0C317,294 353,235 380,153C397,102 413,46 436,0l132,0C538,64 517,132 492,203C462,288 421,352 323,370l244,304l-149,0l-206,-289l-20,0l0,289z" + id="glyph383" /> +<glyph + unicode="Л" + horiz-adv-x="622" + d="M123,674l0,-290C123,279 116,173 69,125C52,109 25,94 -5,89l15,-98C57,-9 101,5 130,23C229,85 244,231 244,393l0,180l184,0l0,-573l123,0l0,674z" + id="glyph385" /> +<glyph + unicode="М" + horiz-adv-x="827" + d="M653,0l120,0l-39,674l-160,0l-93,-270C455,325 432,242 414,169l-3,0C394,244 373,324 349,403l-88,271l-162,0l-45,-674l115,0l15,271C189,363 193,470 196,559l2,0C216,474 240,382 266,296l90,-288l95,0l98,292C579,385 607,476 629,559l3,0C631,467 636,362 640,275z" + id="glyph387" /> +<glyph + unicode="Н" + horiz-adv-x="672" + d="M71,674l0,-674l123,0l0,297l285,0l0,-297l123,0l0,674l-123,0l0,-270l-285,0l0,270z" + id="glyph389" /> +<glyph + unicode="О" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587z" + id="glyph391" /> +<glyph + unicode="П" + horiz-adv-x="660" + d="M71,674l0,-674l123,0l0,573l272,0l0,-573l123,0l0,674z" + id="glyph393" /> +<glyph + unicode="Р" + horiz-adv-x="559" + d="M71,0l122,0l0,254C209,251 230,250 253,250C340,250 417,273 466,322C504,358 524,411 524,475C524,538 498,591 458,624C415,660 349,679 259,679C176,679 115,673 71,665M193,579C206,582 230,585 264,585C349,585 401,545 401,470C401,391 345,346 253,346C228,346 208,347 193,351z" + id="glyph395" /> +<glyph + unicode="С" + horiz-adv-x="588" + d="M534,117C502,102 450,92 400,92C252,92 165,187 165,335C165,497 264,583 401,583C457,583 501,571 533,557l27,98C535,668 477,685 396,685C190,685 36,551 36,329C36,124 166,-11 380,-11C461,-11 525,5 554,20z" + id="glyph397" /> +<glyph + unicode="Т" + horiz-adv-x="525" + d="M200,0l123,0l0,571l194,0l0,103l-509,0l0,-103l192,0z" + id="glyph399" /> +<glyph + unicode="У" + horiz-adv-x="558" + d="M3,675l225,-463C235,199 237,190 231,176C214,138 178,90 121,90C102,90 89,92 79,94l-10,-99C85,-10 103,-14 132,-14C203,-14 256,17 300,72C348,135 391,237 430,335l133,340l-130,0l-88,-261C329,361 317,318 307,287l-3,0C288,327 274,367 256,411l-114,264z" + id="glyph401" /> +<glyph + unicode="Ф" + horiz-adv-x="760" + d="M323,702l0,-60C193,633 36,550 36,334C36,124 177,44 321,34l0,-62l116,0l0,62C584,46 725,128 725,338C725,549 582,633 439,642l0,60M322,121C244,129 160,192 160,337C160,494 250,550 322,557M438,557C517,550 600,494 600,338C600,185 514,128 438,121z" + id="glyph403" /> +<glyph + unicode="Х" + horiz-adv-x="594" + d="M574,0l-205,345l197,329l-141,0l-72,-139C332,494 318,464 300,422l-2,0C280,460 264,493 242,535l-73,139l-141,0l192,-333l-200,-341l141,0l67,134C255,186 273,222 289,261l3,0C311,222 330,186 359,134l73,-134z" + id="glyph405" /> +<glyph + unicode="Ц" + horiz-adv-x="675" + d="M71,674l0,-674l470,0l7,-167l95,0l7,262l-64,3l0,576l-123,0l0,-573l-269,0l0,573z" + id="glyph407" /> +<glyph + unicode="Ч" + horiz-adv-x="622" + d="M65,674l0,-227C65,293 163,240 270,240C328,240 378,253 426,278l3,0l0,-285l122,0l0,681l-123,0l0,-303C397,352 354,339 314,339C232,339 188,383 188,467l0,207z" + id="glyph409" /> +<glyph + unicode="Ш" + horiz-adv-x="884" + d="M71,674l0,-674l742,0l0,674l-121,0l0,-573l-190,0l0,573l-121,0l0,-573l-190,0l0,573z" + id="glyph411" /> +<glyph + unicode="Щ" + horiz-adv-x="903" + d="M71,674l0,-674l698,0l7,-167l94,0l7,262l-64,3l0,576l-121,0l0,-573l-190,0l0,573l-121,0l0,-573l-190,0l0,573z" + id="glyph413" /> +<glyph + unicode="Ъ" + horiz-adv-x="672" + d="M5,674l0,-101l172,0l0,-571C209,-2 261,-7 328,-7C426,-7 521,14 582,76C615,111 635,158 635,222C635,368 512,437 381,437C352,437 319,435 300,432l0,242M299,337C318,340 340,341 361,341C436,341 506,305 506,217C506,131 443,87 361,87C335,87 316,88 299,91z" + id="glyph415" /> +<glyph + unicode="Ы" + horiz-adv-x="805" + d="M71,674l0,-672C102,-2 154,-7 221,-7C316,-7 421,14 481,76C514,111 534,158 534,220C534,371 409,437 271,437C247,437 213,435 193,431l0,243M193,337C206,339 228,341 253,341C326,341 405,306 405,217C405,130 333,87 253,87C228,87 208,88 193,91M602,674l0,-674l123,0l0,674z" + id="glyph417" /> +<glyph + unicode="Ь" + horiz-adv-x="577" + d="M71,674l0,-672C102,-2 154,-7 223,-7C322,-7 427,14 487,76C520,111 540,158 540,222C540,372 417,437 276,437C250,437 214,435 193,432l0,242M193,337C213,340 235,341 257,341C340,341 411,306 411,216C411,128 340,87 256,87C231,87 211,88 193,91z" + id="glyph419" /> +<glyph + unicode="Э" + horiz-adv-x="578" + d="M110,295l300,0C401,177 321,90 191,90C142,90 88,102 51,118l-23,-96C69,4 134,-11 203,-11C392,-11 543,112 543,336C543,527 439,685 213,685C144,685 82,666 37,646l26,-91C102,571 150,585 204,585C329,585 400,500 410,391l-300,0z" + id="glyph421" /> +<glyph + unicode="Ю" + horiz-adv-x="896" + d="M71,674l0,-674l122,0l0,295l87,0C292,113 400,-11 565,-11C736,-11 862,116 862,344C862,536 757,685 574,685C416,685 306,572 283,398l-90,0l0,276M569,87C461,87 400,201 400,335C400,469 458,587 570,587C682,587 737,466 737,339C737,198 678,87 570,87z" + id="glyph423" /> +<glyph + unicode="Я" + horiz-adv-x="574" + d="M142,0C153,20 159,35 165,53C187,113 205,204 249,248C270,268 297,277 332,277l50,0l0,-277l122,0l0,665C457,673 391,679 325,679C239,679 164,664 117,627C77,595 51,547 51,486C51,391 117,335 196,321l0,-3C175,311 155,299 140,284C87,232 70,146 42,69C33,44 23,20 11,0M382,369C366,367 342,367 318,367C237,367 174,406 174,477C174,557 236,586 310,586C345,586 369,582 382,579z" + id="glyph425" /> +<glyph + unicode="Ґ" + horiz-adv-x="471" + d="M71,674l0,-674l123,0l0,573l263,0l11,227l-89,0l-16,-126z" + id="glyph427" /> +<glyph + unicode="Ђ" + horiz-adv-x="696" + d="M8,674l0,-102l180,0l0,-572l123,0l0,319C334,336 366,350 403,350C445,350 475,333 498,300C519,270 530,224 530,165C530,95 512,42 486,11C460,-21 423,-38 388,-47l20,-99C448,-140 497,-122 539,-83C616,-34 659,60 659,175C659,249 642,318 604,369C567,418 515,450 440,450C391,450 343,434 314,416l-3,0l0,156l238,0l0,102z" + id="glyph429" /> +<glyph + unicode="Ѓ" + horiz-adv-x="464" + d="M71,674l0,-674l123,0l0,573l262,0l0,101M278,830l-88,-120l99,0l124,120z" + id="glyph431" /> +<glyph + unicode="Є" + horiz-adv-x="587" + d="M556,652C530,665 477,685 392,685C160,685 36,520 36,335C36,121 172,-11 380,-11C460,-11 522,6 553,22l-20,94C496,100 446,91 400,91C264,91 176,178 169,294l323,0l0,96l-324,0C183,498 262,585 397,585C451,585 499,572 530,557z" + id="glyph433" /> +<glyph + unicode="Ѕ" + horiz-adv-x="519" + d="M41,32C78,10 152,-11 223,-11C397,-11 479,84 479,192C479,289 422,348 305,392C215,427 176,451 176,503C176,542 210,584 288,584C351,584 398,565 422,552l30,99C417,669 363,685 290,685C144,685 52,601 52,491C52,394 123,335 234,295C320,264 354,234 354,183C354,128 310,91 231,91C168,91 108,111 68,134z" + id="glyph435" /> +<glyph + unicode="І" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674z" + id="glyph437" /> +<glyph + unicode="Ї" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M34,716C68,716 92,744 92,777C92,811 67,837 34,837C0,837 -26,811 -26,777C-26,744 -1,716 33,716M231,716C266,716 290,744 290,777C290,811 265,837 232,837C197,837 172,811 172,777C172,744 196,716 230,716z" + id="glyph439" /> +<glyph + unicode="Ј" + horiz-adv-x="392" + d="M205,241C205,124 162,92 92,92C63,92 36,98 16,104l-15,-99C27,-5 70,-11 104,-11C238,-11 328,51 328,237l0,437l-123,0z" + id="glyph441" /> +<glyph + unicode="Љ" + horiz-adv-x="916" + d="M124,674l0,-290C124,279 117,173 70,127C54,110 26,94 -5,89l17,-98C57,-9 101,5 131,23C229,85 244,231 244,393l0,180l170,0l0,-571C446,-2 496,-7 565,-7C663,-7 758,13 818,69C856,104 879,155 879,219C879,368 754,433 618,433C593,433 558,431 537,428l0,246M537,336C556,338 578,340 598,340C679,340 752,305 752,217C752,130 680,87 597,87C574,87 556,87 537,90z" + id="glyph443" /> +<glyph + unicode="Њ" + horiz-adv-x="928" + d="M71,674l0,-674l123,0l0,312l232,0l0,-310C458,-2 508,-7 578,-7C677,-7 770,12 830,69C867,103 891,153 891,219C891,363 772,427 636,427C603,427 571,425 549,423l0,251l-123,0l0,-259l-232,0l0,259M548,331C566,333 586,335 613,335C693,335 763,301 763,215C763,128 693,87 610,87C587,87 568,88 548,91z" + id="glyph445" /> +<glyph + unicode="Ћ" + horiz-adv-x="705" + d="M8,674l0,-102l178,0l0,-572l123,0l0,318C337,338 378,354 416,354C449,354 472,343 489,325C509,303 517,272 517,229l0,-229l123,0l0,245C640,313 623,367 588,404C559,435 517,454 459,454C404,454 348,433 312,411l-3,0l0,161l230,0l0,102z" + id="glyph447" /> +<glyph + unicode="Ќ" + horiz-adv-x="577" + d="M71,674l0,-674l121,0l0,294l29,0C317,294 353,235 380,153C397,102 413,46 436,0l132,0C538,64 517,132 492,203C462,288 421,352 323,370l244,304l-149,0l-206,-289l-20,0l0,289M315,825l-88,-120l99,0l124,120z" + id="glyph449" /> +<glyph + unicode="Ў" + horiz-adv-x="558" + d="M3,675l225,-463C235,199 237,190 231,176C214,138 178,90 121,90C102,90 89,92 79,94l-10,-99C85,-10 103,-14 132,-14C203,-14 256,17 300,72C348,135 391,237 430,335l133,340l-130,0l-88,-261C329,361 317,318 307,287l-3,0C288,327 274,367 256,411l-114,264M141,824C147,748 192,708 277,708C364,708 414,747 419,824l-86,0C329,788 317,763 280,763C241,763 230,789 228,824z" + id="glyph451" /> +<glyph + unicode="а" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph453" /> +<glyph + unicode="б" + horiz-adv-x="555" + d="M470,720C436,710 379,703 336,697C244,684 177,655 130,596C67,528 38,431 38,312C38,137 110,-12 281,-12C424,-12 520,87 520,245C520,395 434,487 307,487C237,487 174,448 143,387l-4,0C143,432 162,489 192,526C227,570 280,592 355,602C389,606 437,613 463,622M394,239C394,161 364,78 281,78C198,78 165,177 165,249C165,289 174,321 191,348C208,378 238,400 279,400C367,400 394,311 394,240z" + id="glyph455" /> +<glyph + unicode="в" + horiz-adv-x="528" + d="M65,1C94,-2 159,-6 220,-6C318,-6 494,7 494,141C494,209 444,249 372,260l0,2C424,273 472,308 472,370C472,482 324,493 242,493C175,493 101,487 65,481M185,410C204,412 220,414 244,414C317,414 351,393 351,354C351,308 299,290 236,290l-51,0M184,216l53,0C314,216 368,198 368,143C368,86 300,71 244,71C219,71 204,72 184,74z" + id="glyph457" /> +<glyph + unicode="г" + horiz-adv-x="402" + d="M65,487l0,-487l123,0l0,390l199,0l0,97z" + id="glyph459" /> +<glyph + unicode="д" + horiz-adv-x="563" + d="M126,487l0,-123C126,304 118,245 101,189C90,154 73,120 53,89l-44,-3l4,-242l94,0l6,156l320,0l5,-156l94,0l5,243l-52,2l0,398M233,395l133,0l0,-304l-190,0C191,117 204,147 212,178C226,225 233,277 233,329z" + id="glyph461" /> +<glyph + unicode="е" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph463" /> +<glyph + unicode="ё" + horiz-adv-x="516" + d="M176,563C211,563 236,591 236,624C236,660 210,686 177,686C141,686 114,659 114,624C114,591 140,563 175,563M375,563C410,563 435,591 435,624C435,660 410,686 375,686C340,686 314,659 314,624C314,591 339,563 374,563M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph465" /> +<glyph + unicode="ж" + horiz-adv-x="706" + d="M6,487l180,-215C123,262 84,214 58,145C40,95 23,43 2,0l120,0C138,27 150,64 166,111C191,177 225,203 280,203l14,0l0,-203l118,0l0,203l13,0C476,203 513,177 537,111C552,66 565,26 580,0l124,0C682,42 666,95 648,146C623,214 583,263 519,272l180,215l-138,0l-133,-203l-16,0l0,203l-118,0l0,-203l-17,0l-133,203z" + id="glyph467" /> +<glyph + unicode="з" + horiz-adv-x="462" + d="M111,212l60,0C240,212 293,196 293,145C293,107 256,78 188,78C139,79 87,94 53,112l-28,-78C73,6 137,-10 201,-10C301,-10 428,25 428,138C428,210 364,249 299,256l0,2C350,270 406,305 406,368C406,465 306,497 224,497C161,497 95,482 42,449l26,-73C102,397 147,411 188,411C243,411 276,387 276,352C276,317 237,290 170,290l-59,0z" + id="glyph469" /> +<glyph + unicode="и" + horiz-adv-x="578" + d="M65,487l0,-487l147,0C254,80 298,168 336,240C360,287 376,322 400,380l4,0C399,288 396,252 396,174l0,-174l117,0l0,487l-149,0l-120,-228C219,208 204,172 178,115l-3,0C179,183 182,235 182,312l0,175z" + id="glyph471" /> +<glyph + unicode="й" + horiz-adv-x="578" + d="M65,487l0,-487l147,0C254,80 298,168 336,240C360,287 376,322 400,380l4,0C399,288 396,252 396,174l0,-174l117,0l0,487l-149,0l-120,-228C219,208 204,172 178,115l-3,0C179,183 182,235 182,312l0,175M151,686C155,608 194,553 285,553C367,553 420,599 426,686l-82,0C340,643 323,613 287,613C253,613 234,643 233,686z" + id="glyph473" /> +<glyph + unicode="к" + horiz-adv-x="507" + d="M66,487l0,-487l123,0l0,202l16,0C269,202 306,170 332,104C350,57 364,21 377,0l128,0C483,42 465,96 450,132C417,210 376,263 309,273l194,214l-146,0l-150,-202l-18,0l0,202z" + id="glyph475" /> +<glyph + unicode="л" + horiz-adv-x="528" + d="M93,487l0,-196C93,197 89,141 53,111C41,99 22,91 1,87l13,-96C72,-9 108,4 136,31C194,74 208,160 208,287l0,105l132,0l0,-392l124,0l0,487z" + id="glyph477" /> +<glyph + unicode="м" + horiz-adv-x="683" + d="M43,0l111,0l10,209C167,256 169,322 170,382l4,0C186,336 204,266 218,220l71,-214l89,0l78,219C482,302 490,328 506,384l3,0C510,325 514,259 516,212l9,-212l115,0l-31,487l-155,0l-65,-191C373,246 352,183 342,148l-2,0C329,189 312,243 302,275l-69,212l-155,0z" + id="glyph479" /> +<glyph + unicode="н" + horiz-adv-x="564" + d="M65,487l0,-487l123,0l0,205l188,0l0,-205l123,0l0,487l-123,0l0,-186l-188,0l0,186z" + id="glyph481" /> +<glyph + unicode="о" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph483" /> +<glyph + unicode="п" + horiz-adv-x="560" + d="M65,487l0,-487l123,0l0,392l183,0l0,-392l124,0l0,487z" + id="glyph485" /> +<glyph + unicode="р" + horiz-adv-x="585" + d="M66,-198l123,0l0,252l2,0C216,15 267,-11 328,-11C439,-11 550,74 550,250C550,402 457,498 345,498C271,498 214,467 178,412l-2,0l-6,75l-108,0C64,441 66,389 66,325M189,279C189,289 191,300 194,310C206,364 253,401 304,401C383,401 425,331 425,245C425,149 379,84 301,84C249,84 205,120 193,170C190,181 189,192 189,204z" + id="glyph487" /> +<glyph + unicode="с" + horiz-adv-x="449" + d="M407,106C383,96 353,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,87 134,-11 286,-11C347,-11 398,3 424,15z" + id="glyph489" /> +<glyph + unicode="т" + horiz-adv-x="438" + d="M12,487l0,-95l146,0l0,-392l123,0l0,392l146,0l0,95z" + id="glyph491" /> +<glyph + unicode="у" + horiz-adv-x="500" + d="M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph493" /> +<glyph + unicode="ф" + horiz-adv-x="684" + d="M285,706l1,-211C149,483 35,400 35,240C35,86 148,0 286,-10l-1,-188l115,0l-1,189C534,1 649,84 649,243C649,397 539,483 399,496l1,210M286,72C203,87 157,155 157,243C157,329 202,399 286,414M398,414C482,398 528,331 528,244C528,156 482,88 398,72z" + id="glyph495" /> +<glyph + unicode="х" + horiz-adv-x="494" + d="M11,487l163,-239l-169,-248l136,0l56,97C213,124 227,148 241,176l2,0C257,149 271,123 287,97l60,-97l139,0l-166,253l163,234l-134,0l-54,-90C281,372 267,347 253,320l-3,0C236,345 222,369 206,395l-58,92z" + id="glyph497" /> +<glyph + unicode="ц" + horiz-adv-x="572" + d="M65,487l0,-487l377,0l5,-156l94,0l6,243l-53,2l0,398l-123,0l0,-392l-183,0l0,392z" + id="glyph499" /> +<glyph + unicode="ч" + horiz-adv-x="539" + d="M60,487l0,-170C60,204 123,149 222,149C268,149 318,162 349,182l2,0l0,-189l123,0l0,494l-123,0l0,-223C331,249 299,238 268,238C204,238 183,272 183,329l0,158z" + id="glyph501" /> +<glyph + unicode="ш" + horiz-adv-x="779" + d="M65,487l0,-487l649,0l0,487l-121,0l0,-392l-142,0l0,392l-122,0l0,-392l-142,0l0,392z" + id="glyph503" /> +<glyph + unicode="щ" + horiz-adv-x="792" + d="M65,487l0,-487l597,0l5,-156l94,0l6,243l-53,2l0,398l-121,0l0,-392l-142,0l0,392l-122,0l0,-392l-142,0l0,392z" + id="glyph505" /> +<glyph + unicode="ъ" + horiz-adv-x="618" + d="M17,487l0,-95l140,0l0,-390C202,-2 253,-5 306,-5C378,-5 467,5 525,49C561,76 585,114 585,169C585,298 468,339 362,339C331,339 303,337 280,334l0,153M280,249C297,251 313,254 333,254C392,254 462,237 462,165C462,100 399,79 338,79C311,79 295,81 280,83z" + id="glyph507" /> +<glyph + unicode="ы" + horiz-adv-x="727" + d="M65,487l0,-485C109,-2 161,-5 213,-5C282,-5 364,6 422,50C458,76 482,115 482,172C482,294 373,339 266,339C234,339 207,336 188,333l0,154M187,249C203,251 221,254 245,254C300,254 359,233 359,168C359,102 298,79 240,79C219,79 202,80 187,82M539,487l0,-487l123,0l0,487z" + id="glyph509" /> +<glyph + unicode="ь" + horiz-adv-x="529" + d="M65,487l0,-485C109,-2 161,-5 213,-5C286,-5 378,6 436,50C472,76 496,115 496,172C496,301 378,339 266,339C240,339 209,336 188,333l0,154M187,249C204,252 221,254 242,254C297,254 373,238 373,167C373,98 301,79 244,79C219,79 203,81 187,83z" + id="glyph511" /> +<glyph + unicode="э" + horiz-adv-x="475" + d="M88,206l225,0C302,130 244,84 161,84C109,84 67,98 45,107l-20,-85C65,1 117,-11 171,-11C339,-11 440,95 440,243C440,327 411,391 366,434C321,477 257,498 189,498C136,498 76,485 32,461l24,-80C82,393 119,406 168,406C260,406 306,354 315,286l-227,0z" + id="glyph513" /> +<glyph + unicode="ю" + horiz-adv-x="741" + d="M65,487l0,-487l122,0l0,204l70,0C270,70 359,-11 477,-11C600,-11 706,82 706,248C706,397 615,498 483,498C363,498 278,418 259,295l-72,0l0,192M478,78C408,78 374,156 374,242C374,327 408,408 480,408C551,408 583,327 583,245C583,158 550,78 479,78z" + id="glyph515" /> +<glyph + unicode="я" + horiz-adv-x="528" + d="M463,483C420,489 358,494 290,494C225,494 160,485 118,463C75,441 43,406 43,350C43,274 112,236 170,230l0,-3C146,222 127,209 112,194C75,160 58,97 39,54C30,34 21,15 9,0l132,0C149,14 154,26 160,40C177,82 189,134 220,161C237,180 261,193 295,193l46,0l0,-193l122,0M341,271C325,270 309,270 280,270C232,270 171,292 171,343C171,397 226,416 280,416C307,416 324,414 341,411z" + id="glyph517" /> +<glyph + unicode="ґ" + horiz-adv-x="414" + d="M65,487l0,-487l123,0l0,390l201,0l7,211l-86,0l-13,-114z" + id="glyph519" /> +<glyph + unicode="ђ" + horiz-adv-x="573" + d="M4,634l0,-83l77,0l0,-551l123,0l0,297C204,318 208,332 218,344C238,369 269,389 303,389C337,389 364,368 381,340C404,298 412,243 412,191C412,99 390,28 359,-6C334,-33 309,-43 278,-49l23,-98C340,-143 383,-126 421,-98C494,-47 535,57 535,200C535,280 521,350 486,404C454,456 406,488 343,488C287,488 238,461 206,428l-2,0l0,123l218,0l0,83l-218,0l0,76l-123,0l0,-76z" + id="glyph521" /> +<glyph + unicode="ѓ" + horiz-adv-x="402" + d="M65,487l0,-487l123,0l0,390l199,0l0,97M244,698l-87,-148l85,0l123,148z" + id="glyph523" /> +<glyph + unicode="є" + horiz-adv-x="467" + d="M442,469C417,483 366,498 313,498C156,498 35,401 35,239C35,92 133,-11 296,-11C362,-11 416,5 442,19l-20,85C399,95 360,83 316,83C233,83 174,129 164,206l221,0l0,80l-222,0C173,355 225,407 314,407C360,407 396,394 418,383z" + id="glyph525" /> +<glyph + unicode="ѕ" + horiz-adv-x="417" + d="M36,24C72,4 125,-11 184,-11C313,-11 382,54 382,142C381,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C48,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph527" /> +<glyph + unicode="і" + horiz-adv-x="256" + d="M190,0l0,487l-124,0l0,-487M128,690C87,690 59,661 59,623C59,586 86,557 127,557C170,557 197,586 197,623C196,661 170,690 128,690z" + id="glyph529" /> +<glyph + unicode="ї" + horiz-adv-x="256" + d="M29,563C64,563 89,591 89,624C89,660 63,686 30,686C-6,686 -33,659 -33,624C-33,591 -7,563 28,563M228,563C263,563 288,591 288,624C288,660 263,686 228,686C193,686 167,659 167,624C167,591 192,563 227,563M190,0l0,487l-124,0l0,-487z" + id="glyph531" /> +<glyph + unicode="ј" + horiz-adv-x="270" + d="M-30,-213C31,-213 99,-196 142,-156C186,-112 205,-49 205,52l0,435l-123,0l0,-400C82,-19 73,-58 50,-82C30,-104 -2,-114 -42,-117M144,690C102,690 75,661 75,623C75,587 101,557 142,557C186,557 212,587 212,623C212,661 186,690 144,690z" + id="glyph533" /> +<glyph + unicode="љ" + horiz-adv-x="802" + d="M96,487l0,-196C96,201 90,139 53,110C41,99 22,91 1,87l13,-96C72,-9 108,5 135,31C193,74 210,160 210,287l0,105l131,0l0,-390C385,-2 440,-5 491,-5C562,-5 655,4 713,51C746,76 769,113 769,168C769,295 652,337 541,337C516,337 485,335 464,332l0,155M463,248C481,250 501,253 525,253C577,253 646,235 646,166C646,98 575,79 520,79C495,79 479,81 463,82z" + id="glyph535" /> +<glyph + unicode="њ" + horiz-adv-x="814" + d="M65,487l0,-487l123,0l0,222l168,0l0,-220C400,-2 455,-5 506,-5C577,-5 666,3 725,49C757,75 780,112 780,167C780,290 664,334 558,334C531,334 500,332 479,329l0,158l-123,0l0,-172l-168,0l0,172M478,246C496,248 514,250 536,250C592,250 657,233 657,165C657,98 590,79 535,79C510,79 493,81 478,82z" + id="glyph537" /> +<glyph + unicode="ћ" + horiz-adv-x="586" + d="M4,634l0,-83l77,0l0,-551l123,0l0,279C204,294 206,308 210,319C223,354 255,388 305,388C373,388 398,334 398,263l0,-263l123,0l0,278C521,414 456,488 351,488C320,488 290,480 265,466C242,453 222,435 206,412l-2,0l0,139l201,0l0,83l-201,0l0,76l-123,0l0,-76z" + id="glyph539" /> +<glyph + unicode="ќ" + horiz-adv-x="507" + d="M66,487l0,-487l123,0l0,202l16,0C269,202 306,170 332,104C350,57 364,21 377,0l128,0C483,42 465,96 450,132C417,210 376,263 309,273l194,214l-146,0l-150,-202l-18,0l0,202M281,698l-87,-148l85,0l123,148z" + id="glyph541" /> +<glyph + unicode="ў" + horiz-adv-x="500" + d="M112,686C116,608 155,553 246,553C328,553 381,599 387,686l-82,0C301,643 284,613 248,613C214,613 195,643 194,686M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph543" /> +<glyph + unicode="Џ" + horiz-adv-x="656" + d="M71,674l0,-674l200,0l7,-180l102,0l7,180l200,0l0,674l-123,0l0,-573l-270,0l0,573z" + id="glyph545" /> +<glyph + unicode="џ" + horiz-adv-x="559" + d="M65,487l0,-487l158,0l7,-161l99,0l7,161l158,0l0,487l-123,0l0,-392l-183,0l0,392z" + id="glyph547" /> +<glyph + unicode="ә" + horiz-adv-x="328" + d="M77,381C119,395 159,405 214,405C298,405 359,366 362,277l-326,0C34,267 32,248 32,226C32,119 84,-11 243,-11C399,-11 480,117 480,250C480,400 387,498 231,498C160,498 102,483 59,464M362,191C357,142 328,74 251,74C169,74 147,147 148,191z" + id="glyph549" /> +<glyph + unicode="ℓ" + horiz-adv-x="532" + d="M227,499C227,595 254,630 284,630C313,630 334,605 334,549C334,476 295,399 227,328M445,173C419,138 376,91 317,91C264,91 228,122 227,202l0,13C359,330 429,431 429,550C429,648 373,712 285,712C185,712 110,643 110,480l0,-240C84,219 55,199 28,180l33,-66C79,125 95,136 112,148C113,146 113,144 113,141C129,47 185,-11 287,-11C373,-11 450,27 503,118z" + id="glyph551" /> +<glyph + unicode="№" + horiz-adv-x="945" + d="M751,638C654,638 582,571 582,460C582,350 654,287 749,287C837,287 917,340 917,465C917,568 854,638 752,638M749,574C799,574 820,516 820,463C820,398 795,351 750,351C710,351 679,398 679,461C679,513 701,574 748,574M890,182l0,64l-280,0l0,-64M176,0l0,224C176,336 174,424 170,507l3,0C201,434 233,354 267,285l139,-285l124,0l0,653l-107,0l0,-220C423,330 426,243 433,157l-1,-1C405,225 372,302 339,374l-136,279l-134,0l0,-653z" + id="glyph553" /> +<glyph + unicode="à" + horiz-adv-x="508" + d="M98,698l122,-148l86,0l-87,148M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph555" /> +<glyph + unicode="α" + horiz-adv-x="568" + d="M410,487C406,466 402,443 399,413l-4,0C368,468 317,498 255,498C144,498 35,395 35,234C35,85 122,-11 236,-11C307,-11 364,25 397,84l3,0C409,18 445,-11 495,-11C511,-11 531,-8 538,-5l8,87C516,81 499,100 499,155C499,271 507,421 513,487M160,239C160,330 206,403 275,403C328,403 368,355 378,295C381,281 381,267 381,249C381,227 380,211 377,192C364,137 321,87 269,87C202,87 160,156 160,238z" + id="glyph557" /> +<glyph + unicode="ά" + horiz-adv-x="569" + d="M286,694l-49,-148l81,0l82,148M410,487C406,466 402,443 399,413l-4,0C368,468 317,498 255,498C144,498 35,395 35,234C35,85 122,-11 236,-11C307,-11 364,25 397,84l3,0C409,18 445,-11 495,-11C511,-11 531,-8 538,-5l8,87C516,81 499,100 499,155C499,271 507,421 513,487M160,239C160,330 206,403 275,403C328,403 368,355 378,295C381,281 381,267 381,249C381,227 380,211 377,192C364,137 321,87 269,87C202,87 160,156 160,238z" + id="glyph559" /> +<glyph + unicode="ā" + horiz-adv-x="508" + d="M135,655l0,-71l239,0l0,71M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph561" /> +<glyph + unicode="&" + horiz-adv-x="645" + d="M641,0C599,44 562,85 525,126C581,190 612,275 630,379l-110,0C508,308 488,245 459,200C422,240 370,300 323,354l1,4C423,407 463,464 463,534C463,626 391,685 296,685C171,685 107,596 107,512C107,464 129,415 161,377l0,-3C84,333 31,269 31,179C31,82 105,-11 254,-11C331,-11 393,13 444,56C464,34 480,17 497,0M272,79C197,79 148,132 148,199C148,255 185,290 217,312C280,239 344,168 385,125C360,99 320,79 273,79M290,606C337,606 357,566 357,530C357,483 324,452 267,416C237,450 218,483 218,524C218,566 243,606 289,606z" + id="glyph563" /> +<glyph + unicode="·" + horiz-adv-x="250" + d="M125,231C175,231 208,268 208,317C207,368 174,403 125,403C77,403 42,367 42,317C42,268 76,231 124,231z" + id="glyph565" /> +<glyph + unicode="ą" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-52C326,-23 300,-68 300,-119C300,-180 341,-213 397,-213C429,-213 463,-205 492,-183l-18,60C461,-129 447,-133 429,-133C401,-133 381,-116 381,-82C381,-53 399,-19 411,0l41,0C446,29 444,71 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph567" /> +<glyph + unicode="≈" + horiz-adv-x="596" + d="M525,427C504,402 463,365 415,365C367,365 334,387 304,399C272,413 236,435 180,435C112,435 54,393 21,349l43,-53C90,325 131,357 179,357C227,357 260,336 290,323C323,309 360,287 416,287C481,287 540,327 573,376M530,221C507,192 466,159 417,159C370,159 337,181 307,193C274,207 238,229 181,229C117,229 57,189 24,144l43,-54C93,119 135,151 180,151C230,151 263,130 293,117C325,103 363,81 417,81C483,81 543,121 576,172z" + id="glyph569" /> +<glyph + unicode="å" + horiz-adv-x="508" + d="M254,538C318,538 361,579 361,633C361,688 322,730 255,730C185,730 145,687 145,632C145,579 188,538 254,538M253,582C224,582 206,608 206,632C206,660 223,685 252,685C284,685 302,661 302,634C302,606 284,582 253,582M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph571" /> +<glyph + unicode="^" + horiz-adv-x="596" + d="M546,182l-209,468l-78,0l-209,-468l91,0l155,371l3,0l155,-371z" + id="glyph573" /> +<glyph + unicode="~" + horiz-adv-x="596" + d="M125,200C128,263 148,284 177,284C207,284 238,265 284,241C345,210 382,194 426,194C496,194 556,238 551,366l-80,0C470,309 454,284 425,284C395,284 363,300 316,324C255,356 221,371 176,371C100,371 43,319 45,200z" + id="glyph575" /> +<glyph + unicode="*" + horiz-adv-x="437" + d="M277,685l-58,-147l-3,0l-58,147l-76,-44l100,-124l-1,-3l-151,26l0,-86l153,25l0,-3l-101,-123l72,-45l61,148l2,1l59,-148l77,44l-103,122l0,3l157,-24l0,86l-156,-26l0,2l102,126z" + id="glyph577" /> +<glyph + unicode="@" + horiz-adv-x="755" + d="M445,252C435,193 390,133 343,133C306,133 287,161 287,202C287,287 347,361 423,361C440,361 453,358 463,355M519,-16C479,-37 425,-49 371,-49C223,-49 115,50 115,217C115,414 250,538 408,538C563,538 648,436 648,298C648,183 593,122 546,124C516,124 507,155 519,222l32,183C525,418 481,428 437,428C295,428 201,316 201,193C201,113 250,65 312,65C368,65 413,92 446,147l3,0C451,90 486,65 530,65C631,65 714,155 714,303C714,470 593,592 417,592C193,592 47,415 47,210C47,15 190,-105 355,-105C424,-105 478,-95 536,-67z" + id="glyph579" /> +<glyph + unicode="" + horiz-adv-x="755" + d="M445,345C435,286 390,226 343,226C306,226 287,254 287,295C287,380 347,454 423,454C440,454 453,451 463,448M519,77C479,56 425,44 371,44C223,44 115,143 115,310C115,507 250,631 408,631C563,631 648,529 648,391C648,276 593,215 546,217C516,217 507,248 519,315l32,183C525,511 481,521 437,521C295,521 201,409 201,286C201,206 250,158 312,158C368,158 413,185 446,240l3,0C451,183 486,158 530,158C631,158 714,248 714,396C714,563 593,685 417,685C193,685 47,508 47,303C47,108 190,-12 355,-12C424,-12 478,-2 536,26z" + id="glyph581" /> +<glyph + unicode="ã" + horiz-adv-x="508" + d="M175,562C176,591 185,607 200,607C212,607 221,601 240,590C261,579 281,569 306,569C356,569 384,605 381,685l-59,0C319,651 311,642 295,642C283,642 269,651 253,660C232,671 213,681 189,681C142,681 111,638 113,562M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238z" + id="glyph583" /> +<glyph + unicode="b" + horiz-adv-x="585" + d="M66,137C66,87 64,33 62,0l105,0l6,73l2,0C210,13 266,-11 329,-11C439,-11 550,76 550,251C551,399 466,498 347,498C274,498 221,467 191,420l-2,0l0,290l-123,0M189,280C189,292 191,303 193,312C207,366 252,402 302,402C384,402 425,332 425,246C425,148 378,86 301,86C248,86 205,123 192,172C190,181 189,191 189,201z" + id="glyph585" /> +<glyph + unicode="\" + horiz-adv-x="335" + d="M327,-40l-234,725l-86,0l234,-725z" + id="glyph587" /> +<glyph + unicode="|" + horiz-adv-x="263" + d="M87,750l0,-1000l89,0l0,1000z" + id="glyph589" /> +<glyph + unicode="β" + horiz-adv-x="575" + d="M185,41C222,6 269,-11 329,-11C442,-11 539,68 539,198C539,301 472,369 399,385l0,5C448,415 483,477 483,542C483,644 410,721 295,721C225,721 177,698 141,664C100,626 64,564 64,422l0,-449C64,-96 68,-168 81,-198l115,0C185,-157 183,-80 183,-21l0,62M183,442C183,570 221,625 286,625C337,625 372,584 372,523C372,467 339,417 278,410C272,408 267,408 261,408l0,-79C269,329 277,330 285,329C358,325 415,283 415,201C415,136 372,82 300,82C247,82 206,113 183,146z" + id="glyph591" /> +<glyph + unicode="{" + horiz-adv-x="301" + d="M29,255C99,254 107,213 107,184C107,158 104,132 101,105C97,78 94,50 94,25C94,-76 156,-112 243,-112l29,0l0,71l-18,0C204,-40 184,-12 184,38C184,58 187,79 191,101C193,123 197,147 197,174C198,244 168,275 117,287l0,2C168,301 198,331 197,401C197,428 193,451 191,473C187,495 184,517 184,537C184,585 202,614 254,615l18,0l0,71l-29,0C154,686 94,646 94,553C94,527 97,499 101,472C104,444 107,417 107,391C107,359 99,321 29,320z" + id="glyph593" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M29,294C99,293 107,252 107,223C107,197 104,171 101,144C97,117 94,89 94,64C94,-37 156,-73 243,-73l29,0l0,71l-18,0C204,-1 184,27 184,77C184,97 187,118 191,140C193,162 197,186 197,213C198,283 168,314 117,326l0,2C168,340 198,370 197,440C197,467 193,490 191,512C187,534 184,556 184,576C184,624 202,653 254,654l18,0l0,71l-29,0C154,725 94,685 94,592C94,566 97,538 101,511C104,483 107,456 107,430C107,398 99,360 29,359z" + id="glyph595" /> +<glyph + unicode="}" + horiz-adv-x="301" + d="M271,320C202,321 193,359 193,391C193,417 197,444 200,472C203,499 207,527 207,553C207,646 147,686 58,686l-29,0l0,-71l18,0C98,614 116,585 116,537C116,517 113,495 110,473C107,451 104,428 104,401C103,331 133,301 183,289l0,-2C133,275 103,244 104,174C104,147 107,123 110,101C113,79 116,58 116,38C116,-12 97,-40 47,-41l-18,0l0,-71l29,0C145,-112 207,-76 207,25C207,50 203,78 200,105C197,132 193,158 193,184C193,213 202,254 271,255z" + id="glyph597" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M271,359C202,360 193,398 193,430C193,456 197,483 200,511C203,538 207,566 207,592C207,685 147,725 58,725l-29,0l0,-71l18,0C98,653 116,624 116,576C116,556 113,534 110,512C107,490 104,467 104,440C103,370 133,340 183,328l0,-2C133,314 103,283 104,213C104,186 107,162 110,140C113,118 116,97 116,77C116,27 97,-1 47,-2l-18,0l0,-71l29,0C145,-73 207,-37 207,64C207,89 203,117 200,144C197,171 193,197 193,223C193,252 202,293 271,294z" + id="glyph599" /> +<glyph + unicode="[" + horiz-adv-x="301" + d="M269,-112l0,71l-104,0l0,656l104,0l0,71l-195,0l0,-798z" + id="glyph601" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M269,-73l0,71l-104,0l0,656l104,0l0,71l-195,0l0,-798z" + id="glyph603" /> +<glyph + unicode="]" + horiz-adv-x="301" + d="M32,686l0,-71l103,0l0,-656l-103,0l0,-71l194,0l0,798z" + id="glyph605" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M32,725l0,-71l103,0l0,-656l-103,0l0,-71l194,0l0,798z" + id="glyph607" /> +<glyph + unicode="˘" + horiz-adv-x="300" + d="M18,688C18,616 59,553 150,553C233,553 282,608 282,688l-67,0C213,655 193,623 149,623C112,623 90,651 85,688z" + id="glyph609" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M17,841C19,774 57,723 149,723C241,723 281,774 283,841l-66,0C212,816 195,793 150,793C105,793 89,818 85,841z" + id="glyph611" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M13,686C17,608 56,553 147,553C229,553 282,599 288,686l-82,0C202,643 185,613 149,613C115,613 96,643 95,686z" + id="glyph613" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M11,840C17,764 62,724 147,724C234,724 284,763 289,840l-86,0C199,804 187,779 150,779C111,779 100,805 98,840z" + id="glyph615" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M189,793l-83,-140l68,0l118,140M8,665C10,592 59,532 150,532C233,532 289,585 292,665l-62,0C227,632 200,601 150,601C106,601 76,628 71,665z" + id="glyph617" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M18,812C25,750 67,699 151,699C236,699 280,753 287,812l-57,0C222,784 201,765 153,765C106,765 84,788 78,812M180,912l-71,-110l69,0l118,110z" + id="glyph619" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M8,793l118,-140l67,0l-82,140M6,665C8,592 56,532 147,532C231,532 287,585 289,665l-62,0C224,632 198,601 147,601C103,601 74,628 68,665z" + id="glyph621" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M14,912l118,-110l69,0l-71,110M19,812C26,750 67,699 152,699C236,699 281,753 288,812l-58,0C222,784 201,765 154,765C107,765 85,788 78,812z" + id="glyph623" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M169,643C194,689 239,698 239,751C239,792 201,826 153,826C100,826 68,796 48,757l39,-24C97,748 112,767 131,767C149,767 162,754 162,737C162,706 128,697 114,656M8,665C10,592 59,532 150,532C233,532 289,585 292,665l-62,0C227,632 200,601 150,601C106,601 76,628 71,665z" + id="glyph625" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M172,789C198,835 243,844 243,898C243,938 206,972 158,972C105,972 73,942 53,903l39,-24C102,895 117,914 137,914C154,914 167,901 167,883C167,853 133,844 119,802M19,812C27,750 68,699 153,699C237,699 282,753 289,812l-58,0C223,784 202,765 154,765C107,765 86,788 79,812z" + id="glyph627" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M80,705C82,721 89,736 104,736C116,736 126,730 145,720C166,710 184,699 209,699C260,699 287,733 285,806l-59,0C223,774 215,765 199,765C187,765 172,773 156,782C135,793 117,802 93,802C46,802 18,762 18,705M12,665C14,593 59,532 150,532C233,532 286,585 288,665l-62,0C223,632 198,598 149,598C107,598 80,628 74,665z" + id="glyph629" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M82,844C84,860 91,874 106,874C118,874 128,869 147,859C168,848 186,838 211,838C262,838 289,872 287,944l-59,0C225,912 217,904 201,904C189,904 174,912 158,921C137,932 119,941 95,941C48,941 20,901 19,844M16,812C23,750 65,699 149,699C234,699 278,753 285,812l-57,0C220,784 199,765 151,765C104,765 82,788 76,812z" + id="glyph631" /> +<glyph + unicode="¦" + horiz-adv-x="263" + d="M87,174l0,-350l89,0l0,350M87,674l0,-350l89,0l0,350z" + id="glyph633" /> +<glyph + unicode="•" + horiz-adv-x="313" + d="M156,142C223,142 275,195 275,261C275,328 222,380 156,380C91,380 36,328 38,261C38,193 91,142 155,142z" + id="glyph635" /> +<glyph + unicode="c" + horiz-adv-x="449" + d="M407,106C383,96 353,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,87 134,-11 286,-11C347,-11 398,3 424,15z" + id="glyph637" /> +<glyph + unicode="ć" + horiz-adv-x="449" + d="M296,698l-87,-148l85,0l123,148M407,106C383,96 353,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,87 134,-11 286,-11C347,-11 398,3 424,15z" + id="glyph639" /> +<glyph + unicode="ˇ" + horiz-adv-x="300" + d="M193,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148z" + id="glyph641" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M197,710l101,119l-91,0l-55,-66l-2,0l-57,66l-92,0l103,-119z" + id="glyph643" /> +<glyph + unicode="č" + horiz-adv-x="449" + d="M308,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148M407,106C383,96 353,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,87 134,-11 286,-11C347,-11 398,3 424,15z" + id="glyph645" /> +<glyph + unicode="ç" + horiz-adv-x="449" + d="M407,106C383,96 352,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,104 114,11 239,-6l-41,-81C250,-92 276,-103 277,-126C277,-147 258,-154 237,-154C218,-154 197,-149 182,-140l-17,-54C185,-207 216,-213 243,-213C298,-213 355,-190 355,-124C355,-80 321,-55 284,-47l21,37C359,-9 402,4 424,15z" + id="glyph647" /> +<glyph + unicode="ĉ" + horiz-adv-x="449" + d="M221,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M407,106C383,96 353,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,87 134,-11 286,-11C347,-11 398,3 424,15z" + id="glyph649" /> +<glyph + unicode="ċ" + horiz-adv-x="449" + d="M272,563C307,563 332,591 332,625C332,660 306,687 272,687C237,687 210,660 210,625C210,591 236,563 271,563M407,106C383,96 353,87 310,87C226,87 161,144 161,244C160,333 216,402 310,402C354,402 383,392 403,383l22,92C397,488 351,498 306,498C135,498 35,384 35,238C35,87 134,-11 286,-11C347,-11 398,3 424,15z" + id="glyph651" /> +<glyph + unicode="¸" + horiz-adv-x="300" + d="M128,3l-45,-84C135,-85 161,-98 161,-120C161,-141 142,-149 122,-149C106,-149 85,-143 68,-133l-18,-50C68,-195 96,-202 125,-202C185,-202 239,-180 239,-118C239,-78 205,-51 167,-47l30,50z" + id="glyph653" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M129,3l-44,-90C137,-92 164,-104 164,-127C164,-146 146,-154 125,-154C105,-154 84,-148 69,-140l-17,-54C72,-206 103,-213 131,-213C186,-213 243,-190 243,-125C243,-81 209,-55 171,-47l27,50z" + id="glyph655" /> +<glyph + unicode="¢" + horiz-adv-x="536" + d="M356,-13l0,98C400,88 439,99 461,111l-17,89C419,189 386,179 338,179C260,179 196,232 196,332C196,420 251,486 340,486C384,486 414,476 436,465l22,92C432,568 394,578 356,579l0,94l-82,0l0,-98C147,552 71,451 71,327C71,258 93,200 127,160C163,120 210,95 274,87l0,-100z" + id="glyph657" /> +<glyph + unicode="" + horiz-adv-x="323" + d="M218,-6l0,57C244,52 267,59 282,66l-12,62C255,122 235,117 208,117C163,117 125,144 125,201C125,250 156,284 208,284C233,284 251,278 264,272l14,65C261,344 239,349 218,349l0,54l-57,0l0,-56C80,332 36,269 36,196C36,157 48,122 70,98C92,74 121,58 161,52l0,-58z" + id="glyph659" /> +<glyph + unicode="" + horiz-adv-x="323" + d="M218,-153l0,57C244,-95 267,-88 282,-81l-12,62C255,-25 235,-30 208,-30C163,-30 125,-3 125,54C125,103 156,137 208,137C233,137 251,131 264,125l14,65C261,197 239,202 218,202l0,54l-57,0l0,-56C80,185 36,122 36,49C36,10 48,-25 70,-49C92,-73 121,-89 161,-95l0,-58z" + id="glyph661" /> +<glyph + unicode="" + horiz-adv-x="323" + d="M218,260l0,57C244,318 267,325 282,332l-12,62C255,388 235,383 208,383C163,383 125,410 125,467C125,516 156,550 208,550C233,550 251,544 264,538l14,65C261,610 239,615 218,615l0,54l-57,0l0,-56C80,598 36,535 36,462C36,423 48,388 70,364C92,340 121,324 161,318l0,-58z" + id="glyph663" /> +<glyph + unicode="" + horiz-adv-x="323" + d="M218,437l0,57C244,495 267,502 282,509l-12,62C255,565 235,560 208,560C163,560 125,587 125,644C125,693 156,727 208,727C233,727 251,721 264,715l14,65C261,787 239,792 218,792l0,54l-57,0l0,-56C80,775 36,712 36,639C36,600 48,565 70,541C92,517 121,501 161,495l0,-58z" + id="glyph665" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M417,143C395,133 363,125 321,125C252,125 195,166 195,250C195,326 246,375 322,375C360,375 391,368 410,359l19,80C407,448 370,457 335,457l0,68l-72,0l0,-71C149,435 80,350 80,245C80,131 154,57 263,43l0,-74l72,0l0,73C375,43 413,54 432,63z" + id="glyph667" /> +<glyph + unicode="χ" + horiz-adv-x="483" + d="M10,487l168,-331l-173,-347l105,-29l80,176C208,-5 221,28 232,53l4,1l129,-274l116,28l-173,349l170,330l-118,0l-64,-144C279,307 266,277 254,248l-3,0l-108,239z" + id="glyph669" /> +<glyph + unicode="ˆ" + horiz-adv-x="300" + d="M108,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148z" + id="glyph671" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M103,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph673" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M91,561C95,605 113,635 149,635C182,635 201,605 203,561l82,0C281,640 241,695 151,695C69,695 15,650 10,561z" + id="glyph675" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M289,723C284,796 238,838 153,838C66,838 17,798 11,723l87,0C102,757 113,783 151,783C189,783 200,756 203,723z" + id="glyph677" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M106,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M321,787l-73,-141l66,0l108,141z" + id="glyph679" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M110,829l-91,-119l84,0l50,66l2,0l49,-66l85,0l-92,119M320,908l-80,-110l63,0l128,110z" + id="glyph681" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M190,788l100,-142l65,0l-66,142M106,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148z" + id="glyph683" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M181,892l114,-109l68,0l-69,109M106,829l-91,-119l84,0l49,66l2,0l50,-66l85,0l-92,119z" + id="glyph685" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M106,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M317,634C343,680 387,689 387,742C387,782 350,817 301,817C249,817 216,787 196,748l39,-24C245,739 261,758 280,758C297,758 310,745 310,728C310,697 277,688 262,647z" + id="glyph687" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M111,829l-91,-119l84,0l49,66l2,0l49,-66l86,0l-93,119M324,756C350,805 394,814 394,865C394,907 358,940 309,940C257,940 225,910 205,871l39,-24C254,864 270,883 291,883C309,883 323,870 323,852C323,821 287,811 272,769z" + id="glyph689" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M106,675l-92,-125l85,0l50,72l2,0l50,-72l86,0l-93,125M81,689C83,713 91,727 106,727C118,727 129,722 147,712C168,701 187,691 212,691C262,691 290,725 288,797l-60,0C226,765 217,757 201,757C189,757 175,765 159,774C138,785 119,794 95,794C48,794 18,753 20,689z" + id="glyph691" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M105,829l-101,-119l90,0l57,65l2,0l56,-65l92,0l-103,119M83,844C85,867 93,882 108,882C120,882 131,876 149,866C170,856 189,845 214,845C264,845 292,879 289,952l-59,0C228,920 219,911 203,911C191,911 177,919 161,928C140,939 121,948 97,948C50,948 20,908 22,844z" + id="glyph693" /> +<glyph + unicode=":" + horiz-adv-x="236" + d="M127,326C173,326 203,359 203,404C202,450 172,483 127,483C83,483 51,450 51,404C51,359 82,326 126,326M127,-11C173,-11 203,23 203,67C202,114 172,146 127,146C83,146 51,113 51,67C51,23 82,-11 126,-11z" + id="glyph695" /> +<glyph + unicode="," + horiz-adv-x="236" + d="M92,-113C131,-49 176,59 204,153l-129,-10C62,60 35,-45 8,-121z" + id="glyph697" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M59,-64C88,-17 115,46 134,108l-94,-6C31,47 15,-15 -4,-69z" + id="glyph699" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M59,-211C88,-164 115,-101 134,-39l-94,-6C31,-100 15,-162 -4,-216z" + id="glyph701" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M59,202C88,249 115,312 134,374l-94,-6C31,313 15,251 -4,197z" + id="glyph703" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M59,379C88,426 115,489 134,551l-94,-6C31,490 15,428 -4,374z" + id="glyph705" /> +<glyph + unicode="©" + horiz-adv-x="677" + d="M340,648C169,648 34,510 34,340C34,168 169,30 340,30C510,30 643,168 643,340C643,510 510,648 341,648M339,598C476,598 585,483 585,339C585,194 476,79 339,80C201,80 92,194 92,338C92,483 201,598 338,598M474,493C461,505 414,520 364,520C262,520 171,448 171,334C171,232 238,153 355,153C398,153 444,161 479,185l-14,45C442,215 405,205 364,205C282,205 233,262 233,337C233,416 280,468 364,468C410,468 448,453 461,445z" + id="glyph707" /> +<glyph + unicode="¤" + horiz-adv-x="536" + d="M268,543C223,543 182,529 153,504l-73,81l-57,-59l81,-74C86,425 68,386 68,331C68,279 85,237 104,211l-78,-75l56,-56l69,80C179,135 225,121 266,121C306,121 353,135 383,162l71,-82l56,56l-80,78C448,239 465,280 465,335C465,387 449,426 432,452l83,75l-57,58l-74,-80C355,528 315,543 269,543M267,459C331,459 370,401 370,334C370,243 316,205 266,205C220,205 162,245 162,332C162,405 205,459 266,459z" + id="glyph709" /> +<glyph + unicode="d" + horiz-adv-x="581" + d="M392,710l0,-276l-2,0C368,470 320,498 253,498C136,498 34,401 35,238C35,88 127,-11 243,-11C314,-11 373,23 402,77l2,0l5,-77l110,0C517,33 515,87 515,137l0,573M392,211C392,198 391,186 388,175C376,122 332,88 282,88C204,88 160,153 160,242C160,333 204,403 283,403C339,403 378,364 389,316C391,306 392,294 392,284z" + id="glyph711" /> +<glyph + unicode="†" + horiz-adv-x="513" + d="M201,674l11,-205l-173,9l0,-97l173,10l-11,-441l112,0l-11,441l173,-10l0,97l-173,-9l11,205z" + id="glyph713" /> +<glyph + unicode="‡" + horiz-adv-x="513" + d="M201,674l9,-192l-172,10l0,-98l172,11l0,-179l-172,10l0,-97l171,9l-8,-198l111,0l-8,198l171,-9l0,97l-171,-10l0,179l171,-11l0,98l-171,-10l8,192z" + id="glyph715" /> +<glyph + unicode="ď" + horiz-adv-x="591" + d="M392,710l0,-276l-2,0C368,470 320,498 253,498C136,498 34,401 35,238C35,88 127,-11 243,-11C314,-11 373,23 402,77l2,0l5,-77l110,0C517,33 515,87 515,137l0,573M392,211C392,198 391,186 388,175C376,122 332,88 282,88C204,88 160,153 160,242C160,333 204,403 283,403C339,403 378,364 389,316C391,306 392,294 392,284M609,507C625,520 678,564 678,646C678,679 662,709 645,720l-81,-15C577,689 588,664 588,639C588,599 572,565 557,543z" + id="glyph717" /> +<glyph + unicode="đ" + horiz-adv-x="581" + d="M594,545l0,71l-79,0l0,94l-123,0l0,-94l-189,0l0,-71l189,0l0,-111l-2,0C368,470 320,498 253,498C136,498 34,401 35,238C35,88 127,-11 243,-11C314,-11 373,23 402,77l2,0l5,-77l110,0C517,33 515,87 515,137l0,408M392,211C392,198 391,186 388,175C376,122 332,88 282,88C204,88 160,153 160,242C160,333 204,403 283,403C339,403 378,364 389,316C391,306 392,294 392,284z" + id="glyph719" /> +<glyph + unicode="°" + horiz-adv-x="339" + d="M171,685C91,685 27,623 28,543C28,468 86,405 170,405C246,405 314,463 314,547C314,621 258,685 172,685M171,626C222,626 246,584 246,545C246,499 214,465 171,465C128,465 96,499 96,543C96,585 124,626 170,626z" + id="glyph721" /> +<glyph + unicode="δ" + horiz-adv-x="562" + d="M486,672C456,695 393,721 316,721C193,721 130,657 130,597C130,550 168,515 188,496l21,-17l0,-4C120,432 35,355 35,232C35,81 146,-11 279,-11C417,-11 527,87 527,239C527,331 480,402 402,463l-81,65C280,560 257,577 257,597C257,617 279,632 324,632C377,632 433,608 452,594M326,377C376,336 403,285 403,229C403,136 351,78 286,78C220,78 161,141 161,233C161,325 215,383 277,417z" + id="glyph723" /> +<glyph + unicode="¨" + horiz-adv-x="300" + d="M51,563C86,563 111,591 111,624C111,660 85,686 52,686C16,686 -11,659 -11,624C-11,591 15,563 50,563M250,563C285,563 310,591 310,624C310,660 285,686 250,686C215,686 189,659 189,624C189,591 214,563 249,563z" + id="glyph725" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M52,716C86,716 110,744 110,777C110,811 85,837 52,837C18,837 -8,811 -8,777C-8,744 17,716 51,716M249,716C284,716 308,744 308,777C308,811 283,837 250,837C215,837 190,811 190,777C190,744 214,716 248,716z" + id="glyph727" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M8,566C41,566 65,592 65,625C65,658 41,683 9,683C-24,683 -49,658 -49,625C-49,593 -25,566 7,566M289,566C323,566 347,591 347,625C347,658 323,683 290,683C257,683 232,658 232,625C232,593 256,566 288,566M136,706l-67,-148l77,0l102,148z" + id="glyph729" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M-8,717C24,717 48,742 48,774C48,807 24,832 -7,832C-40,832 -65,807 -65,774C-65,743 -41,717 -9,717M310,717C343,717 367,741 367,774C367,807 343,832 311,832C279,832 254,807 254,774C254,743 278,717 309,717M126,832l-78,-121l90,0l116,121z" + id="glyph731" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M11,566C44,566 68,592 68,625C68,658 44,683 12,683C-21,683 -47,658 -47,625C-47,593 -23,566 10,566M288,566C322,566 346,591 346,625C346,658 322,683 289,683C256,683 231,658 231,625C231,593 255,566 287,566M52,706l103,-148l76,0l-66,148z" + id="glyph733" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M-9,717C24,717 48,742 48,774C48,807 24,832 -7,832C-40,832 -65,807 -65,774C-65,743 -41,717 -10,717M310,717C343,717 367,741 367,774C367,807 343,832 311,832C279,832 254,807 254,774C254,743 278,717 309,717M46,832l116,-121l91,0l-79,121z" + id="glyph735" /> +<glyph + unicode="΅" + horiz-adv-x="395" + d="M168,720l-21,-166l64,0l52,166M61,568C92,568 115,594 115,624C115,656 92,680 62,680C30,680 5,656 5,624C5,594 29,568 60,568M332,568C365,568 387,594 387,624C387,656 364,680 332,680C301,680 277,656 277,624C277,594 300,568 331,568z" + id="glyph737" /> +<glyph + unicode="÷" + horiz-adv-x="596" + d="M298,377C337,377 362,404 362,442C362,482 337,508 298,508C260,508 234,482 234,442C234,404 260,377 297,377M556,228l0,78l-516,0l0,-78M298,26C337,26 362,53 362,91C362,131 337,157 298,157C260,157 234,131 234,91C234,53 260,26 297,26z" + id="glyph739" /> +<glyph + unicode="$" + horiz-adv-x="536" + d="M304,-86l0,103C414,35 473,110 473,195C473,284 423,338 311,380C226,414 190,437 190,478C190,512 215,548 285,548C353,548 397,525 421,514l27,92C416,622 373,637 310,639l0,93l-82,0l0,-98C127,616 67,550 67,464C67,371 135,323 240,285C314,258 349,229 349,184C349,136 306,105 243,105C182,105 126,126 87,149l-27,-94C96,31 159,13 222,11l0,-97z" + id="glyph741" /> +<glyph + unicode="" + horiz-adv-x="336" + d="M194,-51l0,61C265,21 303,67 303,119C303,174 273,207 200,231C141,251 124,262 124,284C124,302 137,321 179,321C223,321 253,306 268,299l18,64C267,372 234,382 197,383l0,53l-56,0l0,-56C75,368 37,327 37,274C37,215 80,189 149,165C198,148 216,136 216,112C216,87 193,71 154,71C114,71 78,83 51,98l-18,-65C57,18 97,7 139,6l0,-57z" + id="glyph743" /> +<glyph + unicode="" + horiz-adv-x="336" + d="M194,-198l0,61C265,-126 303,-80 303,-28C303,27 273,60 200,84C141,104 124,115 124,137C124,155 137,174 179,174C223,174 253,159 268,152l18,64C267,225 234,235 197,236l0,53l-56,0l0,-56C75,221 37,180 37,127C37,68 80,42 149,18C198,1 216,-11 216,-35C216,-60 193,-76 154,-76C114,-76 78,-64 51,-49l-18,-65C57,-129 97,-140 139,-141l0,-57z" + id="glyph745" /> +<glyph + unicode="" + horiz-adv-x="336" + d="M194,215l0,61C265,287 303,333 303,385C303,440 273,473 200,497C141,517 124,528 124,550C124,568 137,587 179,587C223,587 253,572 268,565l18,64C267,638 234,648 197,649l0,53l-56,0l0,-56C75,634 37,593 37,540C37,481 80,455 149,431C198,414 216,402 216,378C216,353 193,337 154,337C114,337 78,349 51,364l-18,-65C57,284 97,273 139,272l0,-57z" + id="glyph747" /> +<glyph + unicode="" + horiz-adv-x="336" + d="M194,392l0,61C265,464 303,510 303,562C303,617 273,650 200,674C141,694 124,705 124,727C124,745 137,764 179,764C223,764 253,749 268,742l18,64C267,815 234,825 197,826l0,53l-56,0l0,-56C75,811 37,770 37,717C37,658 80,632 149,608C198,591 216,579 216,555C216,530 193,514 154,514C114,514 78,526 51,541l-18,-65C57,461 97,450 139,449l0,-57z" + id="glyph749" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M311,-85l0,86C414,18 468,79 468,145C468,211 425,256 315,290C234,316 216,330 216,359C216,388 252,407 300,407C348,407 395,390 420,376l27,80C419,471 372,486 317,489l0,85l-75,0l0,-89C151,471 93,421 93,352C93,283 139,244 248,209C318,186 340,169 340,139C340,104 301,85 250,85C201,85 140,103 109,123l-28,-83C117,17 179,0 237,-2l0,-83z" + id="glyph751" /> +<glyph + unicode="₫" + horiz-adv-x="536" + d="M88,101l0,-75l363,0l0,75M342,670l0,-62l-122,0l0,-61l122,0l0,-76l-2,0C322,497 285,516 237,516C143,516 65,446 65,328C65,220 136,149 227,149C282,149 327,172 350,211l2,0l4,-54l94,0C448,181 447,220 447,256l0,291l63,0l0,61l-63,0l0,62M342,314C342,305 341,296 339,288C331,252 296,229 258,229C204,229 172,272 172,332C172,393 203,439 260,439C300,439 333,412 340,380C342,374 342,365 342,357z" + id="glyph753" /> +<glyph + unicode="" + horiz-adv-x="536" + d="M342,670l0,-62l-122,0l0,-61l122,0l0,-76l-2,0C322,497 285,516 237,516C143,516 65,446 65,328C65,220 136,149 227,149C282,149 327,172 350,211l2,0l4,-54l94,0C448,181 447,220 447,256l0,291l63,0l0,61l-63,0l0,62M342,314C342,305 341,296 339,288C331,252 296,229 258,229C204,229 172,272 172,332C172,393 203,439 260,439C300,439 333,412 340,380C342,374 342,365 342,357z" + id="glyph755" /> +<glyph + unicode="˙" + horiz-adv-x="300" + d="M130,563C165,563 190,591 190,625C190,660 164,687 130,687C95,687 68,660 68,625C68,591 94,563 129,563z" + id="glyph757" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M151,717C185,717 210,745 210,777C210,810 185,837 152,837C117,837 90,810 90,777C90,745 117,717 150,717z" + id="glyph759" /> +<glyph + unicode="ı" + horiz-adv-x="256" + d="M190,0l0,487l-124,0l0,-487z" + id="glyph761" /> +<glyph + unicode="e" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph763" /> +<glyph + unicode="é" + horiz-adv-x="516" + d="M302,698l-87,-148l85,0l123,148M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph765" /> +<glyph + unicode="ĕ" + horiz-adv-x="516" + d="M129,688C129,616 170,553 261,553C344,553 393,608 393,688l-67,0C324,655 304,623 260,623C223,623 201,651 196,688M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph767" /> +<glyph + unicode="ě" + horiz-adv-x="516" + d="M304,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph769" /> +<glyph + unicode="ê" + horiz-adv-x="516" + d="M227,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph771" /> +<glyph + unicode="ë" + horiz-adv-x="516" + d="M176,563C211,563 236,591 236,624C236,660 210,686 177,686C141,686 114,659 114,624C114,591 140,563 175,563M375,563C410,563 435,591 435,624C435,660 410,686 375,686C340,686 314,659 314,624C314,591 339,563 374,563M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph773" /> +<glyph + unicode="ė" + horiz-adv-x="516" + d="M263,563C298,563 323,591 323,625C323,660 297,687 263,687C228,687 201,660 201,625C201,591 227,563 262,563M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph775" /> +<glyph + unicode="è" + horiz-adv-x="516" + d="M126,698l122,-148l86,0l-87,148M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph777" /> +<glyph + unicode="8" + horiz-adv-x="536" + d="M156,336C81,303 34,248 34,167C34,76 113,-11 265,-11C404,-11 502,67 502,183C502,264 449,320 378,347l0,3C449,385 478,442 478,497C478,577 414,661 274,661C151,661 57,589 57,483C57,426 88,372 156,339M269,76C200,76 158,126 160,181C160,235 195,277 256,295C328,275 377,239 377,173C377,117 334,76 269,76M268,576C334,576 361,529 361,484C361,435 324,399 280,386C218,403 174,435 174,488C174,535 206,576 268,576z" + id="glyph779" /> +<glyph + unicode="" + horiz-adv-x="346" + d="M175,395C97,395 37,352 37,286C37,252 58,219 98,201l0,-2C53,180 23,149 23,102C23,52 68,-5 170,-5C263,-5 323,44 323,112C323,162 285,194 246,208l0,2C286,228 306,261 306,296C306,345 265,395 176,395M173,56C135,56 112,82 112,111C112,140 131,161 166,172C202,162 233,145 233,107C233,77 209,56 174,56M173,336C208,336 225,311 225,286C225,260 205,241 180,233C143,244 119,261 119,289C119,315 138,336 172,336z" + id="glyph781" /> +<glyph + unicode="" + horiz-adv-x="536" + d="M156,336C81,303 34,248 34,167C34,76 113,-11 265,-11C404,-11 502,67 502,183C502,264 449,320 378,347l0,3C449,385 478,442 478,497C478,577 414,661 274,661C151,661 57,589 57,483C57,426 88,372 156,339M269,76C200,76 158,126 160,181C160,235 195,277 256,295C328,275 377,239 377,173C377,117 334,76 269,76M268,576C334,576 361,529 361,484C361,435 324,399 280,386C218,403 174,435 174,488C174,535 206,576 268,576z" + id="glyph783" /> +<glyph + unicode="₈" + horiz-adv-x="346" + d="M175,248C97,248 37,205 37,139C37,105 58,72 98,54l0,-2C53,33 23,2 23,-45C23,-95 68,-152 170,-152C263,-152 323,-103 323,-35C323,15 285,47 246,61l0,2C286,81 306,114 306,149C306,198 265,248 176,248M173,-91C135,-91 112,-65 112,-36C112,-7 131,14 166,25C202,15 233,-2 233,-40C233,-70 209,-91 174,-91M173,189C208,189 225,164 225,139C225,113 205,94 180,86C143,97 119,114 119,142C119,168 138,189 172,189z" + id="glyph785" /> +<glyph + unicode="" + horiz-adv-x="346" + d="M175,661C97,661 37,618 37,552C37,518 58,485 98,467l0,-2C53,446 23,415 23,368C23,318 68,261 170,261C263,261 323,310 323,378C323,428 285,460 246,474l0,2C286,494 306,527 306,562C306,611 265,661 176,661M173,322C135,322 112,348 112,377C112,406 131,427 166,438C202,428 233,411 233,373C233,343 209,322 174,322M173,602C208,602 225,577 225,552C225,526 205,507 180,499C143,510 119,527 119,555C119,581 138,602 172,602z" + id="glyph787" /> +<glyph + unicode="" + horiz-adv-x="535" + d="M376,325C436,354 474,397 474,458C474,540 402,613 273,613C147,613 59,543 59,444C59,384 98,342 154,317l0,-4C84,282 36,230 36,159C36,60 127,-11 263,-11C397,-11 498,62 498,172C498,246 443,295 376,321M272,534C318,534 362,505 362,450C362,403 326,371 284,357C218,375 179,407 179,452C179,500 217,534 271,534M265,74C201,74 159,115 159,170C159,219 195,259 250,277C325,257 371,218 371,163C371,110 325,74 266,74z" + id="glyph789" /> +<glyph + unicode="⁸" + horiz-adv-x="346" + d="M175,838C97,838 37,795 37,729C37,695 58,662 98,644l0,-2C53,623 23,592 23,545C23,495 68,438 170,438C263,438 323,487 323,555C323,605 285,637 246,651l0,2C286,671 306,704 306,739C306,788 265,838 176,838M173,499C135,499 112,525 112,554C112,583 131,604 166,615C202,605 233,588 233,550C233,520 209,499 174,499M173,779C208,779 225,754 225,729C225,703 205,684 180,676C143,687 119,704 119,732C119,758 138,779 172,779z" + id="glyph791" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M381,325C441,354 479,397 479,458C479,540 407,613 278,613C152,613 64,543 64,444C64,384 103,342 159,317l0,-4C89,282 41,230 41,159C41,60 132,-11 268,-11C402,-11 503,62 503,172C503,246 448,295 381,321M277,534C323,534 367,505 367,450C367,403 331,371 289,357C223,375 184,407 184,452C184,500 222,534 276,534M270,74C206,74 164,115 164,170C164,219 200,259 255,277C330,257 376,218 376,163C376,110 330,74 271,74z" + id="glyph793" /> +<glyph + unicode="…" + horiz-adv-x="1000" + d="M166,-11C212,-11 243,23 243,68C242,115 212,147 167,147C122,147 91,114 91,68C90,23 122,-11 165,-11M500,-11C546,-11 576,23 576,68C576,115 545,147 500,147C456,147 424,114 424,68C424,23 455,-11 499,-11M834,-11C880,-11 910,23 910,68C909,115 879,147 834,147C789,147 758,114 758,68C757,23 789,-11 833,-11z" + id="glyph795" /> +<glyph + unicode="ē" + horiz-adv-x="516" + d="M142,655l0,-71l239,0l0,71M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph797" /> +<glyph + unicode="—" + horiz-adv-x="1000" + d="M30,293l0,-78l940,0l0,78z" + id="glyph799" /> +<glyph + unicode="" + horiz-adv-x="1000" + d="M30,368l0,-78l940,0l0,78z" + id="glyph801" /> +<glyph + unicode="–" + horiz-adv-x="500" + d="M30,293l0,-78l440,0l0,78z" + id="glyph803" /> +<glyph + unicode="" + horiz-adv-x="500" + d="M30,368l0,-78l440,0l0,78z" + id="glyph805" /> +<glyph + unicode="ŋ" + horiz-adv-x="572" + d="M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-233C384,-39 352,-78 284,-91l24,-97C419,-174 507,-108 507,59l0,230C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342z" + id="glyph807" /> +<glyph + unicode="ę" + horiz-adv-x="516" + d="M405,-124C391,-130 373,-134 359,-134C340,-134 322,-123 322,-97C322,-60 374,-13 412,4C422,8 441,14 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209l326,0C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,88 127,-10 282,-11C289,-11 295,-10 302,-10l0,-1C272,-34 232,-79 232,-130C232,-182 269,-212 328,-212C364,-212 398,-202 424,-183M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph809" /> +<glyph + unicode="ε" + horiz-adv-x="455" + d="M149,258C85,246 36,198 36,138C36,28 147,-11 250,-11C314,-11 390,4 435,33l-18,82C385,98 328,81 280,81C220,81 167,105 167,151C167,198 226,214 304,215l54,0l0,82l-53,0C232,298 183,314 183,353C183,387 220,411 279,411C325,411 373,396 397,382l23,79C381,485 331,498 264,498C125,498 59,430 59,364C59,316 91,278 149,262z" + id="glyph811" /> +<glyph + unicode="έ" + horiz-adv-x="455" + d="M250,694l-49,-148l81,0l82,148M149,257C85,245 36,198 36,138C36,28 147,-11 250,-11C314,-11 390,4 435,33l-18,81C385,98 328,81 280,81C220,81 167,105 167,151C167,198 226,214 304,215l54,0l0,82l-53,0C232,298 183,314 183,353C183,387 220,411 279,411C325,411 373,396 397,382l23,79C380,485 331,498 264,498C125,498 59,430 59,364C59,316 90,278 149,262z" + id="glyph813" /> +<glyph + unicode="=" + horiz-adv-x="596" + d="M556,330l0,79l-516,0l0,-79M556,128l0,78l-516,0l0,-78z" + id="glyph815" /> +<glyph + unicode="℮" + horiz-adv-x="817" + d="M782,316C782,319 782,322 782,325C782,511 615,661 409,661C203,661 35,511 35,325C35,139 203,-11 409,-11C529,-11 637,41 705,120l-55,0C592,52 506,9 410,9C320,9 238,47 181,108C176,114 172,121 172,129l0,183C172,315 174,316 177,316M646,340C646,337 644,335 641,335l-464,0C174,335 172,337 172,340l0,180C172,529 176,537 182,543C240,603 320,641 410,641C498,641 578,604 636,546C642,540 646,533 646,524z" + id="glyph817" /> +<glyph + unicode="η" + horiz-adv-x="572" + d="M176,414C175,442 170,469 161,487l-110,0C63,451 66,398 66,342l0,-342l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-298C384,-88 387,-171 400,-198l118,0C507,-159 507,-80 507,-22l0,311C507,443 419,498 334,498C253,498 200,452 179,414z" + id="glyph819" /> +<glyph + unicode="ή" + horiz-adv-x="572" + d="M294,694l-49,-148l81,0l82,148M176,414C175,442 170,469 161,487l-110,0C63,451 66,398 66,342l0,-342l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-298C384,-88 387,-171 400,-198l118,0C507,-159 507,-80 507,-22l0,311C507,443 419,498 334,498C253,498 200,452 179,414z" + id="glyph821" /> +<glyph + unicode="ð" + horiz-adv-x="560" + d="M278,78C207,78 162,150 162,238C162,307 193,400 281,400C321,400 349,383 368,361C388,335 396,299 396,242C396,146 353,78 279,78M111,491l143,64C296,525 329,490 355,451l-2,-3C323,473 294,479 263,478C142,476 35,386 35,237C35,85 140,-11 278,-11C400,-11 523,70 523,255C523,327 509,390 481,445C452,504 410,556 357,599l105,48l-26,52l-133,-60C256,670 203,699 153,719l-54,-72C134,632 168,614 198,594l-110,-50z" + id="glyph823" /> +<glyph + unicode="!" + horiz-adv-x="251" + d="M172,208l18,466l-130,0l18,-466M125,-11C171,-11 201,22 201,67C200,113 171,145 125,145C81,145 50,113 50,67C49,22 80,-11 124,-11z" + id="glyph825" /> +<glyph + unicode="¡" + horiz-adv-x="251" + d="M191,-192l-18,463l-94,0l-18,-463M126,491C81,491 50,459 50,413C50,369 81,335 126,335C172,335 201,369 201,413C201,459 171,491 127,491z" + id="glyph827" /> +<glyph + unicode="" + horiz-adv-x="251" + d="M191,0l-18,463l-94,0l-18,-463M126,683C81,683 50,651 50,605C50,561 81,527 126,527C172,527 201,561 201,605C201,651 171,683 127,683z" + id="glyph829" /> +<glyph + unicode="f" + horiz-adv-x="319" + d="M203,0l0,395l106,0l0,92l-107,0l0,25C202,572 223,623 284,623C306,623 323,619 336,614l9,95C325,716 299,721 266,721C224,721 175,708 139,674C97,634 80,571 80,508l0,-21l-66,0l0,-92l66,0l0,-395z" + id="glyph831" /> +<glyph + unicode="ff" + horiz-adv-x="620" + d="M504,0l0,395l105,0l0,92l-106,0l0,25C503,572 523,623 584,623C607,623 623,619 637,614l8,95C626,716 600,721 567,721C524,721 476,708 440,674C397,634 380,571 380,508l0,-21l-178,0l0,17C202,565 219,612 275,612C291,612 307,608 319,603l20,91C324,701 298,708 272,708C210,708 165,689 135,658C95,618 80,560 80,497l0,-10l-66,0l0,-92l66,0l0,-395l123,0l0,395l177,0l0,-395z" + id="glyph833" /> +<glyph + unicode="ffi" + horiz-adv-x="861" + d="M747,693C718,709 664,721 618,721C474,721 382,641 382,510l0,-23l-180,0l0,15C202,536 209,571 229,592C241,605 258,613 281,613C300,613 320,607 333,600l29,89C343,698 311,708 277,708C216,708 171,688 141,659C97,617 80,558 80,499l0,-12l-66,0l0,-92l66,0l0,-395l123,0l0,395l179,0l0,-395l124,0l0,395l166,0l0,-395l123,0l0,487l-290,0l0,20C505,571 533,623 618,623C659,623 695,612 718,600z" + id="glyph835" /> +<glyph + unicode="" + horiz-adv-x="852" + d="M747,693C718,709 664,721 618,721C474,721 382,641 382,510l0,-23l-180,0l0,15C202,536 209,571 229,592C241,605 258,613 281,613C300,613 320,607 333,600l29,89C343,698 311,708 277,708C216,708 171,688 141,659C97,617 80,558 80,499l0,-12l-66,0l0,-92l66,0l0,-395l123,0l0,395l179,0l0,-395l124,0l0,395l166,0l0,-308C672,-19 663,-58 640,-82C620,-104 587,-114 548,-117l12,-96C621,-213 689,-196 731,-156C776,-112 795,-49 795,52l0,435l-290,0l0,20C505,571 533,623 618,623C659,623 695,612 718,600z" + id="glyph837" /> +<glyph + unicode="ffl" + horiz-adv-x="861" + d="M363,692C347,699 316,708 285,708C225,708 174,690 139,656C98,617 80,559 80,495l0,-8l-66,0l0,-92l66,0l0,-395l123,0l0,395l179,0l0,-395l124,0l0,395l99,0l0,92l-100,0l0,22C505,574 533,628 608,628C638,628 659,624 672,617l0,-617l123,0l0,684C752,708 692,721 626,721C553,721 497,704 454,669C408,630 382,569 382,508l0,-21l-180,0l0,16C202,538 209,571 229,591C242,605 259,613 283,613C302,613 320,608 335,601z" + id="glyph839" /> +<glyph + unicode="" + horiz-adv-x="559" + d="M203,0l0,395l166,0l0,-395l124,0l0,487l-291,0l0,20C202,571 230,623 316,623C362,623 400,610 423,597l27,93C422,708 364,721 316,721C171,721 80,641 80,510l0,-23l-66,0l0,-92l66,0l0,-395z" + id="glyph841" /> +<glyph + unicode="" + horiz-adv-x="557" + d="M203,0l0,395l166,0l0,-308C369,-19 360,-58 337,-82C318,-104 285,-114 245,-117l12,-96C318,-213 386,-196 429,-156C473,-112 493,-49 493,52l0,435l-291,0l0,20C202,571 230,623 316,623C362,623 400,610 423,597l27,93C422,708 364,721 316,721C171,721 80,641 80,510l0,-23l-66,0l0,-92l66,0l0,-395z" + id="glyph843" /> +<glyph + unicode="" + horiz-adv-x="559" + d="M203,0l0,395l99,0l0,92l-100,0l0,22C202,574 230,628 305,628C336,628 356,624 369,617l0,-617l124,0l0,684C449,708 389,721 324,721C250,721 195,704 152,669C105,630 80,569 80,508l0,-21l-66,0l0,-92l66,0l0,-395z" + id="glyph845" /> +<glyph + unicode="5" + horiz-adv-x="536" + d="M456,650l-336,0l-43,-328C103,326 130,328 166,328C296,328 351,279 351,205C351,127 282,86 210,86C151,86 95,105 67,120l-25,-93C76,8 139,-11 214,-11C374,-11 477,90 477,214C477,293 441,348 392,379C348,408 291,421 234,421C215,421 202,420 187,418l19,130l250,0z" + id="glyph847" /> +<glyph + unicode="" + horiz-adv-x="330" + d="M285,389l-219,0l-26,-201C56,189 75,192 97,192C179,192 210,165 210,124C210,82 167,62 128,62C91,62 54,74 36,82l-16,-65C42,6 88,-6 132,-6C235,-6 300,53 300,130C300,176 278,210 249,228C221,247 182,253 145,253C135,253 124,253 117,252l10,65l158,0z" + id="glyph849" /> +<glyph + unicode="" + horiz-adv-x="485" + d="M427,650l-336,0l-43,-328C74,326 101,328 137,328C267,328 322,279 322,205C322,127 253,86 181,86C122,86 66,105 38,120l-25,-93C47,8 110,-11 185,-11C345,-11 448,90 448,214C448,293 412,348 363,379C319,408 262,421 205,421C186,421 173,420 158,418l19,130l250,0z" + id="glyph851" /> +<glyph + unicode="₅" + horiz-adv-x="330" + d="M285,242l-219,0l-26,-201C56,42 75,45 97,45C179,45 210,18 210,-23C210,-65 167,-85 128,-85C91,-85 54,-73 36,-65l-16,-65C42,-141 88,-153 132,-153C235,-153 300,-94 300,-17C300,29 278,63 249,81C221,100 182,106 145,106C135,106 124,106 117,105l10,65l158,0z" + id="glyph853" /> +<glyph + unicode="" + horiz-adv-x="330" + d="M285,655l-219,0l-26,-201C56,455 75,458 97,458C179,458 210,431 210,390C210,348 167,328 128,328C91,328 54,340 36,348l-16,-65C42,272 88,260 132,260C235,260 300,319 300,396C300,442 278,476 249,494C221,513 182,519 145,519C135,519 124,519 117,518l10,65l158,0z" + id="glyph855" /> +<glyph + unicode="" + horiz-adv-x="455" + d="M29,-102C55,-112 105,-121 156,-121C320,-121 419,-32 419,89C419,135 403,175 373,205C323,253 243,268 159,268l23,120l214,0l14,99l-309,0l-53,-306C168,186 235,174 268,141C284,125 293,104 293,80C293,10 225,-28 147,-28C108,-28 65,-22 43,-15z" + id="glyph857" /> +<glyph + unicode="⁵" + horiz-adv-x="330" + d="M285,832l-219,0l-26,-201C56,632 75,635 97,635C179,635 210,608 210,567C210,525 167,505 128,505C91,505 54,517 36,525l-16,-65C42,449 88,437 132,437C235,437 300,496 300,573C300,619 278,653 249,671C221,690 182,696 145,696C135,696 124,696 117,695l10,65l158,0z" + id="glyph859" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M73,-102C99,-112 149,-121 200,-121C364,-121 463,-32 463,89C463,135 447,175 417,205C367,253 287,268 203,268l23,120l214,0l14,99l-309,0l-53,-306C212,186 279,174 312,141C328,125 337,104 337,80C337,10 269,-28 191,-28C152,-28 109,-22 87,-15z" + id="glyph861" /> +<glyph + unicode="ƒ" + horiz-adv-x="536" + d="M113,320l84,0l-19,-165C165,46 145,6 90,6C71,6 55,9 39,15l-19,-86C36,-80 67,-87 105,-88C236,-88 277,5 292,130l23,190l121,0l0,87l-110,0l6,48C339,518 365,567 423,567C443,567 459,563 473,558l20,87C477,654 449,660 417,661C261,661 221,527 212,446l-5,-39l-94,0z" + id="glyph863" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M124,270l84,0l-17,-150C178,12 158,-28 103,-29C85,-29 68,-26 52,-19l-19,-87C49,-114 81,-122 118,-122C249,-123 290,-30 305,96l22,174l121,0l0,75l-112,0l7,61C351,469 377,518 435,518C455,518 471,514 484,509l21,88C489,605 461,613 429,613C273,613 233,478 224,397l-6,-52l-94,0z" + id="glyph865" /> +<glyph + unicode="4" + horiz-adv-x="536" + d="M430,0l0,165l84,0l0,93l-84,0l0,392l-145,0l-265,-405l0,-80l294,0l0,-165M137,258l1,2l119,178C277,474 294,507 314,548l4,0C315,510 314,472 314,435l0,-177z" + id="glyph867" /> +<glyph + unicode="" + horiz-adv-x="372" + d="M299,0l0,96l53,0l0,63l-53,0l0,232l-110,0l-171,-242l0,-53l191,0l0,-96M209,159l-100,0l-2,1l68,97C187,276 198,296 210,321l3,-1C211,298 209,275 209,251z" + id="glyph869" /> +<glyph + unicode="" + horiz-adv-x="527" + d="M425,0l0,165l84,0l0,93l-84,0l0,392l-145,0l-265,-405l0,-80l294,0l0,-165M132,258l1,2l119,178C272,474 289,507 309,548l4,0C310,510 309,472 309,435l0,-177z" + id="glyph871" /> +<glyph + unicode="₄" + horiz-adv-x="372" + d="M299,-147l0,96l53,0l0,63l-53,0l0,232l-110,0l-171,-242l0,-53l191,0l0,-96M209,12l-100,0l-2,1l68,97C187,129 198,149 210,174l3,-1C211,151 209,128 209,104z" + id="glyph873" /> +<glyph + unicode="" + horiz-adv-x="372" + d="M299,266l0,96l53,0l0,63l-53,0l0,232l-110,0l-171,-242l0,-53l191,0l0,-96M209,425l-100,0l-2,1l68,97C187,542 198,562 210,587l3,-1C211,564 209,541 209,517z" + id="glyph875" /> +<glyph + unicode="" + horiz-adv-x="526" + d="M507,40l0,91l-84,0l0,361l-145,0l-257,-373l0,-79l288,0l0,-161l114,0l0,161M309,131l-171,0l-2,3l113,157C270,323 290,362 307,397l5,-1C309,362 309,327 309,292z" + id="glyph877" /> +<glyph + unicode="⁴" + horiz-adv-x="372" + d="M299,443l0,96l53,0l0,63l-53,0l0,232l-110,0l-171,-242l0,-53l191,0l0,-96M209,602l-100,0l-2,1l68,97C187,719 198,739 210,764l3,-1C211,741 209,718 209,694z" + id="glyph879" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M516,40l0,91l-84,0l0,361l-145,0l-257,-373l0,-79l288,0l0,-161l114,0l0,161M318,131l-171,0l-2,3l113,157C279,323 299,362 316,397l5,-1C318,362 318,327 318,292z" + id="glyph881" /> +<glyph + unicode="⁄" + horiz-adv-x="124" + d="M-90,-11l374,672l-71,0l-374,-672z" + id="glyph883" /> +<glyph + unicode="g" + horiz-adv-x="573" + d="M507,347C507,414 509,454 511,487l-107,0l-5,-65l-2,0C374,462 330,498 256,498C139,498 35,401 35,241C35,103 121,3 242,3C306,3 356,32 384,75l2,0l0,-42C386,-72 326,-114 246,-114C187,-114 135,-95 104,-77l-27,-93C119,-196 185,-209 247,-209C314,-209 384,-195 435,-150C486,-104 507,-31 507,71M384,213C384,199 382,182 379,169C365,125 325,95 279,95C202,95 160,162 160,245C160,344 211,404 280,404C332,404 367,370 380,325C383,315 384,303 384,292z" + id="glyph885" /> +<glyph + unicode="" + horiz-adv-x="573" + d="M500,347C500,414 502,454 504,487l-107,0l-5,-65l-2,0C367,462 323,498 249,498C132,498 28,401 28,241C28,103 114,3 235,3C299,3 349,32 377,75l2,0l0,-42C379,-72 319,-114 239,-114C180,-114 128,-95 97,-77l-27,-93C112,-196 178,-209 240,-209C307,-209 377,-195 428,-150C479,-104 500,-31 500,71M377,213C377,199 375,182 372,169C358,125 318,95 272,95C195,95 153,162 153,245C153,344 204,404 273,404C325,404 360,370 373,325C376,315 377,303 377,292M215,562C216,591 225,607 240,607C252,607 261,601 280,590C301,579 321,569 346,569C396,569 424,605 421,685l-59,0C359,651 351,642 335,642C323,642 309,651 293,660C272,671 253,681 229,681C182,681 151,638 153,562z" + id="glyph887" /> +<glyph + unicode="γ" + horiz-adv-x="492" + d="M297,-198C296,-131 296,-51 294,-2C294,7 295,14 300,22C398,166 472,327 472,451C472,463 472,478 470,487l-117,0C353,477 354,467 354,448C354,338 300,211 262,133l-4,0C231,275 167,427 128,487l-138,0C42,405 125,245 160,83C181,-12 180,-104 177,-198z" + id="glyph889" /> +<glyph + unicode="ğ" + horiz-adv-x="573" + d="M151,688C151,616 192,553 283,553C366,553 415,608 415,688l-67,0C346,655 326,623 282,623C245,623 223,651 218,688M507,347C507,414 509,454 511,487l-107,0l-5,-65l-2,0C374,462 330,498 256,498C139,498 35,401 35,241C35,103 121,3 242,3C306,3 356,32 384,75l2,0l0,-42C386,-72 326,-114 246,-114C187,-114 135,-95 104,-77l-27,-93C119,-196 185,-209 247,-209C314,-209 384,-195 435,-150C486,-104 507,-31 507,71M384,213C384,199 382,182 379,169C365,125 325,95 279,95C202,95 160,162 160,245C160,344 211,404 280,404C332,404 367,370 380,325C383,315 384,303 384,292z" + id="glyph891" /> +<glyph + unicode="ĝ" + horiz-adv-x="573" + d="M245,700l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M507,347C507,414 509,454 511,487l-107,0l-5,-65l-2,0C374,462 330,498 256,498C139,498 35,401 35,241C35,103 121,3 242,3C306,3 356,32 384,75l2,0l0,-42C386,-72 326,-114 246,-114C187,-114 135,-95 104,-77l-27,-93C119,-196 185,-209 247,-209C314,-209 384,-195 435,-150C486,-104 507,-31 507,71M384,213C384,199 382,182 379,169C365,125 325,95 279,95C202,95 160,162 160,245C160,344 211,404 280,404C332,404 367,370 380,325C383,315 384,303 384,292z" + id="glyph893" /> +<glyph + unicode="ģ" + horiz-adv-x="573" + d="M507,347C507,414 509,454 511,487l-107,0l-5,-65l-2,0C374,462 330,498 256,498C139,498 35,401 35,241C35,103 121,3 242,3C306,3 356,32 384,75l2,0l0,-42C386,-72 326,-114 246,-114C187,-114 135,-95 104,-77l-27,-93C119,-196 185,-209 247,-209C314,-209 384,-195 435,-150C486,-104 507,-31 507,71M384,213C384,199 382,182 379,169C365,125 325,95 279,95C202,95 160,162 160,245C160,344 211,404 280,404C332,404 367,370 380,325C383,315 384,303 384,292M353,717C278,714 208,683 208,609C208,578 228,551 248,537l83,15C315,566 299,586 299,609C299,646 332,665 373,671z" + id="glyph895" /> +<glyph + unicode="ġ" + horiz-adv-x="573" + d="M285,565C320,565 345,593 345,627C345,662 319,689 285,689C250,689 223,662 223,627C223,593 249,565 284,565M507,347C507,414 509,454 511,487l-107,0l-5,-65l-2,0C374,462 330,498 256,498C139,498 35,401 35,241C35,103 121,3 242,3C306,3 356,32 384,75l2,0l0,-42C386,-72 326,-114 246,-114C187,-114 135,-95 104,-77l-27,-93C119,-196 185,-209 247,-209C314,-209 384,-195 435,-150C486,-104 507,-31 507,71M384,213C384,199 382,182 379,169C365,125 325,95 279,95C202,95 160,162 160,245C160,344 211,404 280,404C332,404 367,370 380,325C383,315 384,303 384,292z" + id="glyph897" /> +<glyph + unicode="ß" + horiz-adv-x="576" + d="M190,0l0,449C190,580 231,626 296,626C351,626 386,589 386,533C386,518 383,505 378,494C323,466 294,421 294,370C294,310 334,269 371,233C401,203 415,181 415,149C415,112 387,83 334,83C306,83 280,89 258,99l-17,-92C269,-5 310,-11 347,-11C468,-11 539,57 539,155C539,230 495,273 459,311C432,339 417,358 417,387C417,416 439,442 481,461C494,479 504,511 504,543C504,654 415,721 306,721C236,721 181,701 141,663C94,620 66,554 66,440l0,-440z" + id="glyph899" /> +<glyph + unicode="`" + horiz-adv-x="300" + d="M12,698l122,-148l86,0l-87,148z" + id="glyph901" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M-8,830l124,-119l99,0l-88,119z" + id="glyph903" /> +<glyph + unicode=">" + horiz-adv-x="596" + d="M535,304l-474,228l0,-89l382,-176l0,-2l-382,-176l0,-89l474,228z" + id="glyph905" /> +<glyph + unicode="≥" + horiz-adv-x="596" + d="M534,293l0,77l-472,183l0,-85l364,-136l0,-2l-364,-135l0,-86M533,0l0,74l-471,0l0,-74z" + id="glyph907" /> +<glyph + unicode="«" + horiz-adv-x="444" + d="M241,440l-91,0l-123,-188l124,-188l90,0l-126,188M421,440l-90,0l-123,-188l123,-188l90,0l-125,188z" + id="glyph909" /> +<glyph + unicode="" + horiz-adv-x="444" + d="M241,515l-91,0l-123,-188l124,-188l90,0l-126,188M421,515l-90,0l-123,-188l123,-188l90,0l-125,188z" + id="glyph911" /> +<glyph + unicode="»" + horiz-adv-x="444" + d="M149,252l-126,-188l89,0l124,188l-124,188l-89,0M328,252l-126,-188l89,0l124,188l-124,188l-89,0z" + id="glyph913" /> +<glyph + unicode="" + horiz-adv-x="444" + d="M149,327l-126,-188l89,0l124,188l-124,188l-89,0M328,327l-126,-188l89,0l124,188l-124,188l-89,0z" + id="glyph915" /> +<glyph + unicode="‹" + horiz-adv-x="264" + d="M241,440l-91,0l-123,-188l124,-188l90,0l-126,188z" + id="glyph917" /> +<glyph + unicode="" + horiz-adv-x="264" + d="M241,515l-91,0l-123,-188l124,-188l90,0l-126,188z" + id="glyph919" /> +<glyph + unicode="›" + horiz-adv-x="264" + d="M148,252l-125,-188l89,0l124,188l-124,188l-89,0z" + id="glyph921" /> +<glyph + unicode="" + horiz-adv-x="264" + d="M148,327l-125,-188l89,0l124,188l-124,188l-89,0z" + id="glyph923" /> +<glyph + unicode="h" + horiz-adv-x="572" + d="M66,0l124,0l0,291C190,305 191,318 195,328C208,365 242,397 290,397C358,397 384,344 384,273l0,-273l123,0l0,287C507,443 420,498 337,498C306,498 277,490 253,476C228,463 207,443 192,420l-2,0l0,290l-124,0z" + id="glyph925" /> +<glyph + unicode="ħ" + horiz-adv-x="572" + d="M507,0l0,287C507,443 420,498 337,498C306,498 277,489 253,476C228,463 207,443 192,420l-2,0l0,125l187,0l0,71l-187,0l0,94l-124,0l0,-94l-78,0l0,-71l78,0l0,-545l124,0l0,291C190,305 191,318 195,328C208,365 242,397 290,397C358,397 384,344 384,273l0,-273z" + id="glyph927" /> +<glyph + unicode="ĥ" + horiz-adv-x="572" + d="M66,0l124,0l0,291C190,305 191,318 195,328C208,365 242,397 290,397C358,397 384,344 384,273l0,-273l123,0l0,287C507,443 420,498 337,498C306,498 277,490 253,476C228,463 207,443 192,420l-2,0l0,290l-124,0M86,851l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119z" + id="glyph929" /> +<glyph + unicode="˝" + horiz-adv-x="300" + d="M89,697l-71,-143l72,0l104,143M249,697l-70,-143l71,0l104,143z" + id="glyph931" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M92,831l-72,-114l87,0l106,114M258,831l-72,-114l87,0l106,114z" + id="glyph933" /> +<glyph + unicode="-" + horiz-adv-x="315" + d="M30,309l0,-85l255,0l0,85z" + id="glyph935" /> +<glyph + unicode="" + horiz-adv-x="315" + d="M30,384l0,-85l255,0l0,85z" + id="glyph937" /> +<glyph + unicode="" + horiz-adv-x="199" + d="M18,215l0,-60l163,0l0,60z" + id="glyph939" /> +<glyph + unicode="" + horiz-adv-x="199" + d="M18,68l0,-60l163,0l0,60z" + id="glyph941" /> +<glyph + unicode="" + horiz-adv-x="199" + d="M18,481l0,-60l163,0l0,60z" + id="glyph943" /> +<glyph + unicode="" + horiz-adv-x="199" + d="M18,658l0,-60l163,0l0,60z" + id="glyph945" /> +<glyph + unicode="i" + horiz-adv-x="256" + d="M190,0l0,487l-124,0l0,-487M128,690C87,690 59,661 59,623C59,586 86,557 127,557C170,557 197,586 197,623C196,661 170,690 128,690z" + id="glyph947" /> +<glyph + unicode="í" + horiz-adv-x="256" + d="M157,698l-87,-148l85,0l123,148M190,0l0,487l-124,0l0,-487z" + id="glyph949" /> +<glyph + unicode="ĭ" + horiz-adv-x="256" + d="M-2,688C-2,616 39,553 130,553C213,553 262,608 262,688l-67,0C193,655 173,623 129,623C92,623 70,651 65,688M190,0l0,487l-124,0l0,-487z" + id="glyph951" /> +<glyph + unicode="î" + horiz-adv-x="256" + d="M86,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M190,0l0,487l-124,0l0,-487z" + id="glyph953" /> +<glyph + unicode="ï" + horiz-adv-x="256" + d="M29,563C64,563 89,591 89,624C89,660 63,686 30,686C-6,686 -33,659 -33,624C-33,591 -7,563 28,563M228,563C263,563 288,591 288,624C288,660 263,686 228,686C193,686 167,659 167,624C167,591 192,563 227,563M190,0l0,487l-124,0l0,-487z" + id="glyph955" /> +<glyph + unicode="ì" + horiz-adv-x="256" + d="M-29,698l122,-148l86,0l-87,148M190,0l0,487l-124,0l0,-487z" + id="glyph957" /> +<glyph + unicode="ij" + horiz-adv-x="526" + d="M190,0l0,487l-124,0l0,-487M128,690C87,690 59,661 59,623C59,586 86,557 127,557C170,557 197,586 197,623C196,661 170,690 128,690M226,-213C287,-213 355,-196 398,-156C442,-112 461,-49 461,52l0,435l-123,0l0,-400C338,-19 329,-58 306,-82C286,-104 254,-114 214,-117M400,690C358,690 331,661 331,623C331,587 357,557 398,557C442,557 468,587 468,623C468,661 442,690 400,690z" + id="glyph959" /> +<glyph + unicode="ī" + horiz-adv-x="256" + d="M9,655l0,-71l239,0l0,71M190,0l0,487l-124,0l0,-487z" + id="glyph961" /> +<glyph + unicode="∞" + horiz-adv-x="741" + d="M549,106C645,106 710,179 710,264C710,355 648,422 557,422C473,422 422,368 371,318C322,366 266,422 192,422C96,422 31,349 31,264C31,173 93,106 184,106C268,106 318,160 370,210C419,162 475,106 548,106M548,183C496,183 457,232 422,264C458,301 500,345 551,345C595,345 628,311 628,264C628,220 593,183 549,183M190,183C146,183 113,217 113,264C113,308 148,345 193,345C245,345 284,296 318,263C282,227 241,183 191,183z" + id="glyph963" /> +<glyph + unicode="∫" + horiz-adv-x="339" + d="M331,795C320,799 300,802 279,802C240,802 203,789 177,760C138,719 120,660 120,555C120,401 133,251 133,88C133,3 121,-35 105,-55C94,-70 79,-77 62,-77C47,-77 34,-74 25,-71l-15,-77C22,-154 48,-159 73,-159C120,-159 163,-139 189,-95C211,-60 226,1 226,91C226,252 213,398 213,547C213,631 221,675 238,699C248,715 266,724 285,724C297,724 308,722 318,720z" + id="glyph965" /> +<glyph + unicode="į" + horiz-adv-x="256" + d="M128,560C172,560 199,590 199,628C199,667 171,696 128,696C85,696 57,666 57,628C57,590 85,560 127,560M190,487l-124,0l0,-487C53,-23 27,-70 27,-118C27,-182 68,-215 130,-215C160,-215 198,-206 223,-187l-18,59C195,-133 180,-136 161,-136C130,-136 108,-116 108,-82C108,-52 126,-19 138,0l52,0z" + id="glyph967" /> +<glyph + unicode="ι" + horiz-adv-x="260" + d="M190,487l-124,0l0,-362C66,17 110,-11 176,-11C196,-11 218,-8 229,-3l7,86C202,81 190,97 190,137z" + id="glyph969" /> +<glyph + unicode="ϊ" + horiz-adv-x="260" + d="M35,552C69,552 93,579 93,612C93,647 68,673 35,673C0,673 -26,646 -26,612C-26,579 -1,552 34,552M230,552C265,552 289,579 289,612C289,647 264,673 230,673C195,673 170,646 170,612C170,579 194,552 229,552M190,487l-123,0l0,-362C67,17 110,-11 176,-11C196,-11 218,-8 229,-3l7,86C202,81 190,97 190,137z" + id="glyph971" /> +<glyph + unicode="ΐ" + horiz-adv-x="260" + d="M190,487l-123,0l0,-362C67,17 110,-11 176,-11C196,-11 218,-8 229,-3l7,86C202,81 190,97 190,137M109,708l-22,-165l64,0l53,165M3,557C33,557 56,582 56,612C56,643 32,667 3,667C-28,667 -53,643 -53,612C-53,582 -29,557 2,557M272,557C304,557 326,582 326,612C326,643 303,667 273,667C242,667 218,643 218,612C218,582 241,557 271,557z" + id="glyph973" /> +<glyph + unicode="ί" + horiz-adv-x="260" + d="M131,694l-49,-147l82,0l82,147M190,487l-124,0l0,-362C66,17 110,-11 176,-11C196,-11 218,-8 229,-3l7,86C202,81 190,97 190,137z" + id="glyph975" /> +<glyph + unicode="ĩ" + horiz-adv-x="256" + d="M56,562C57,591 66,607 81,607C93,607 102,601 121,590C142,579 162,569 187,569C237,569 265,605 262,685l-59,0C200,651 192,642 176,642C164,642 150,651 134,660C113,671 94,681 70,681C23,681 -8,638 -6,562M190,0l0,487l-124,0l0,-487z" + id="glyph977" /> +<glyph + unicode="j" + horiz-adv-x="270" + d="M-30,-213C31,-213 99,-196 142,-156C186,-112 205,-49 205,52l0,435l-123,0l0,-400C82,-19 73,-58 50,-82C30,-104 -2,-114 -42,-117M144,690C102,690 75,661 75,623C75,587 101,557 142,557C186,557 212,587 212,623C212,661 186,690 144,690z" + id="glyph979" /> +<glyph + unicode="ĵ" + horiz-adv-x="270" + d="M-30,-213C31,-213 99,-196 142,-156C186,-112 205,-49 205,52l0,435l-123,0l0,-400C82,-19 73,-58 50,-82C30,-104 -2,-114 -42,-117M94,694l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148z" + id="glyph981" /> +<glyph + unicode="k" + horiz-adv-x="509" + d="M189,710l-123,0l0,-710l123,0l0,166l42,49l140,-215l151,0l-206,288l180,199l-148,0l-118,-157C217,313 203,292 191,273l-2,0z" + id="glyph983" /> +<glyph + unicode="κ" + horiz-adv-x="523" + d="M302,265C377,342 447,416 510,482l0,5l-140,0C322,429 262,352 193,278l-3,0l0,124C190,445 183,477 174,487l-120,0C62,470 66,415 66,371l0,-371l124,0l0,207l7,0C236,205 270,162 312,100l63,-100l142,0l-83,128C392,192 349,249 302,261z" + id="glyph985" /> +<glyph + unicode="ķ" + horiz-adv-x="509" + d="M189,710l-123,0l0,-710l123,0l0,166l42,49l140,-215l151,0l-206,288l180,199l-148,0l-118,-157C217,313 203,292 191,273l-2,0M191,-233C267,-229 337,-195 337,-116C337,-82 317,-54 297,-40l-87,-16C225,-70 241,-92 241,-116C241,-152 207,-173 172,-179z" + id="glyph987" /> +<glyph + unicode="ĸ" + horiz-adv-x="509" + d="M189,487l-123,0l0,-487l123,0l0,166l42,49l140,-215l151,0l-206,288l180,199l-148,0l-118,-157C217,313 203,292 191,273l-2,0z" + id="glyph989" /> +<glyph + unicode="l" + horiz-adv-x="257" + d="M66,0l124,0l0,710l-124,0z" + id="glyph991" /> +<glyph + unicode="ĺ" + horiz-adv-x="257" + d="M66,0l124,0l0,710l-124,0M149,860l-88,-120l99,0l124,120z" + id="glyph993" /> +<glyph + unicode="λ" + horiz-adv-x="497" + d="M357,0l131,0l-185,493C254,624 203,718 99,718C84,718 65,717 56,714l-11,-94C52,620 58,620 64,620C114,620 152,568 178,499C182,488 183,479 183,473C183,468 183,460 178,449l-173,-449l129,0l80,249C224,281 234,317 241,351l5,0C256,310 266,278 276,246z" + id="glyph995" /> +<glyph + unicode="ľ" + horiz-adv-x="266" + d="M66,0l124,0l0,710l-124,0M284,507C300,523 349,564 349,643C349,679 332,709 315,720l-81,-15C248,689 259,664 259,639C259,599 243,567 228,543z" + id="glyph997" /> +<glyph + unicode="ļ" + horiz-adv-x="257" + d="M66,0l124,0l0,710l-124,0M55,-233C131,-229 201,-195 201,-116C201,-82 181,-54 161,-40l-87,-16C89,-70 105,-92 105,-116C105,-152 71,-173 36,-179z" + id="glyph999" /> +<glyph + unicode="ŀ" + horiz-adv-x="317" + d="M66,0l124,0l0,710l-124,0M288,335C319,335 343,362 343,395C343,429 319,455 288,455C255,455 230,429 230,395C230,362 254,335 287,335z" + id="glyph1001" /> +<glyph + unicode="<" + horiz-adv-x="596" + d="M61,230l474,-230l0,89l-376,176l0,2l376,175l0,90l-474,-230z" + id="glyph1003" /> +<glyph + unicode="≤" + horiz-adv-x="596" + d="M534,109l0,86l-364,135l0,2l364,136l0,85l-472,-183l0,-77M534,0l0,74l-471,0l0,-74z" + id="glyph1005" /> +<glyph + unicode="¬" + horiz-adv-x="596" + d="M40,412l0,-78l434,0l0,-221l82,0l0,299z" + id="glyph1007" /> +<glyph + unicode="◊" + horiz-adv-x="550" + d="M508,325l-187,359l-92,0l-187,-359l187,-360l92,0M407,322l-117,-240C286,75 281,63 277,56l-2,0C273,64 267,75 263,82l-120,245l117,239C264,574 269,585 272,594l2,0C277,585 282,574 286,566z" + id="glyph1009" /> +<glyph + unicode="ł" + horiz-adv-x="267" + d="M195,0l0,348l71,59l0,91l-71,-58l0,270l-123,0l0,-363l-67,-55l0,-91l67,55l0,-256z" + id="glyph1011" /> +<glyph + unicode="m" + horiz-adv-x="848" + d="M66,0l120,0l0,288C186,302 188,316 193,328C204,362 235,398 281,398C338,398 365,350 365,282l0,-282l120,0l0,292C485,306 488,321 491,333C504,368 535,398 577,398C636,398 664,350 664,269l0,-269l120,0l0,287C784,443 702,498 621,498C581,498 550,488 523,469C500,455 480,435 463,408l-2,0C440,462 390,498 326,498C244,498 200,453 176,415l-3,0l-5,72l-106,0C65,445 66,398 66,342z" + id="glyph1013" /> +<glyph + unicode="" + horiz-adv-x="848" + d="M382,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M66,0l120,0l0,288C186,302 188,316 193,328C204,362 235,398 281,398C338,398 365,350 365,282l0,-282l120,0l0,292C485,306 488,321 491,333C504,368 535,398 577,398C636,398 664,350 664,269l0,-269l120,0l0,287C784,443 702,498 621,498C581,498 550,488 523,469C500,455 480,435 463,408l-2,0C440,462 390,498 326,498C244,498 200,453 176,415l-3,0l-5,72l-106,0C65,445 66,398 66,342z" + id="glyph1015" /> +<glyph + unicode="¯" + horiz-adv-x="300" + d="M31,655l0,-71l239,0l0,71z" + id="glyph1017" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M25,803l0,-69l251,0l0,69z" + id="glyph1019" /> +<glyph + unicode="−" + horiz-adv-x="596" + d="M40,306l0,-78l516,0l0,78z" + id="glyph1021" /> +<glyph + unicode="µ" + horiz-adv-x="570" + d="M396,71C408,15 445,-11 496,-11C515,-11 528,-9 540,-4l7,86C516,84 504,100 504,148l0,339l-123,0l0,-294C381,179 379,165 374,155C362,123 327,89 279,89C217,89 189,138 189,208l0,279l-123,0l0,-508C66,-95 70,-171 80,-198l109,0C181,-159 179,-71 179,-18l0,50C197,5 230,-8 265,-8C331,-8 375,34 393,71z" + id="glyph1023" /> +<glyph + unicode="×" + horiz-adv-x="596" + d="M40,470l202,-207l-202,-207l56,-56l202,206l202,-206l56,56l-202,207l202,207l-56,56l-202,-207l-203,207z" + id="glyph1025" /> +<glyph + unicode="n" + horiz-adv-x="572" + d="M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342z" + id="glyph1027" /> +<glyph + unicode="" + horiz-adv-x="572" + d="M244,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342z" + id="glyph1029" /> +<glyph + unicode="ń" + horiz-adv-x="572" + d="M311,698l-87,-148l85,0l123,148M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342z" + id="glyph1031" /> +<glyph + unicode="ʼn" + horiz-adv-x="572" + d="M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342M67,530C85,544 137,585 137,654C137,691 119,718 104,728l-83,-21C35,693 46,672 46,650C46,615 30,589 13,565z" + id="glyph1033" /> +<glyph + unicode="ň" + horiz-adv-x="572" + d="M329,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342z" + id="glyph1035" /> +<glyph + unicode="ņ" + horiz-adv-x="572" + d="M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342M222,-232C298,-228 368,-194 368,-115C368,-81 348,-53 328,-39l-87,-16C256,-69 272,-91 272,-115C272,-151 238,-172 203,-178z" + id="glyph1037" /> +<glyph + unicode="9" + horiz-adv-x="536" + d="M93,-9C111,-10 139,-10 167,-7C254,-1 332,29 389,82C456,146 502,245 502,384C502,542 421,661 270,661C131,661 35,553 35,429C35,309 119,227 233,227C295,227 341,248 374,285l2,-1C364,223 335,174 293,140C257,111 209,94 161,90C131,87 112,86 93,88M264,570C344,570 377,494 376,406C376,389 373,379 369,371C350,340 314,317 264,317C196,317 158,369 157,436C157,515 202,570 263,570z" + id="glyph1039" /> +<glyph + unicode="" + horiz-adv-x="344" + d="M58,-4C68,-4 85,-5 100,-3C162,0 215,16 254,51C295,88 322,148 322,228C322,318 273,395 171,395C85,395 22,333 22,256C22,181 78,135 147,135C184,135 213,148 230,166l2,-1C225,134 207,107 181,89C158,74 128,66 98,63C84,62 69,62 58,64M170,330C215,330 234,290 234,245C234,236 232,229 229,224C219,208 199,195 171,195C132,195 110,225 110,260C110,301 136,330 169,330z" + id="glyph1041" /> +<glyph + unicode="" + horiz-adv-x="539" + d="M93,-9C111,-10 139,-10 167,-7C254,-1 332,29 389,82C456,146 502,245 502,384C502,542 421,661 270,661C131,661 35,553 35,429C35,309 119,227 233,227C295,227 341,248 374,285l2,-1C364,223 335,174 293,140C257,111 209,94 161,90C131,87 112,86 93,88M264,570C344,570 377,494 376,406C376,389 373,379 369,371C350,340 314,317 264,317C196,317 158,369 157,436C157,515 202,570 263,570z" + id="glyph1043" /> +<glyph + unicode="₉" + horiz-adv-x="344" + d="M58,-151C68,-151 85,-152 100,-150C162,-147 215,-131 254,-96C295,-59 322,1 322,81C322,171 273,248 171,248C85,248 22,186 22,109C22,34 78,-12 147,-12C184,-12 213,1 230,19l2,-1C225,-13 207,-40 181,-58C158,-73 128,-81 98,-84C84,-85 69,-85 58,-83M170,183C215,183 234,143 234,98C234,89 232,82 229,77C219,61 199,48 171,48C132,48 110,78 110,113C110,154 136,183 169,183z" + id="glyph1045" /> +<glyph + unicode="" + horiz-adv-x="344" + d="M58,262C68,262 85,261 100,263C162,266 215,282 254,317C295,354 322,414 322,494C322,584 273,661 171,661C85,661 22,599 22,522C22,447 78,401 147,401C184,401 213,414 230,432l2,-1C225,400 207,373 181,355C158,340 128,332 98,329C84,328 69,328 58,330M170,596C215,596 234,556 234,511C234,502 232,495 229,490C219,474 199,461 171,461C132,461 110,491 110,526C110,567 136,596 169,596z" + id="glyph1047" /> +<glyph + unicode="" + horiz-adv-x="537" + d="M82,-118C314,-115 496,10 496,253C496,403 403,502 269,502C139,502 38,409 38,282C38,166 121,90 227,90C288,90 339,119 360,147l2,-1C334,46 243,-23 74,-29M264,174C207,174 160,213 160,290C160,369 213,409 265,409C326,409 374,356 374,281C374,258 369,238 361,227C344,197 304,174 265,174z" + id="glyph1049" /> +<glyph + unicode="⁹" + horiz-adv-x="344" + d="M58,439C68,439 85,438 100,440C162,443 215,459 254,494C295,531 322,591 322,671C322,761 273,838 171,838C85,838 22,776 22,699C22,624 78,578 147,578C184,578 213,591 230,609l2,-1C225,577 207,550 181,532C158,517 128,509 98,506C84,505 69,505 58,507M170,773C215,773 234,733 234,688C234,679 232,672 229,667C219,651 199,638 171,638C132,638 110,668 110,703C110,744 136,773 169,773z" + id="glyph1051" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M87,-118C319,-115 501,10 501,253C501,403 408,502 274,502C144,502 43,409 43,282C43,166 126,90 232,90C293,90 344,119 365,147l2,-1C339,46 248,-23 79,-29M269,174C212,174 165,213 165,290C165,369 218,409 270,409C331,409 379,356 379,281C379,258 374,238 366,227C349,197 309,174 270,174z" + id="glyph1053" /> +<glyph + unicode="≠" + horiz-adv-x="596" + d="M556,126l0,78l-259,0l81,128l178,0l0,78l-128,0l71,111l-83,0l-68,-111l-308,0l0,-78l258,0l-82,-128l-176,0l0,-78l126,0l-71,-110l83,0l69,110z" + id="glyph1055" /> +<glyph + unicode="ñ" + horiz-adv-x="572" + d="M214,562C215,591 224,607 239,607C251,607 260,601 279,590C300,579 320,569 345,569C395,569 423,605 420,685l-59,0C358,651 350,642 334,642C322,642 308,651 292,660C271,671 252,681 228,681C181,681 150,638 152,562M66,0l124,0l0,286C190,300 191,315 195,326C208,363 242,398 291,398C358,398 384,345 384,275l0,-275l123,0l0,289C507,443 419,498 334,498C253,498 200,452 179,414l-3,0l-6,73l-108,0C65,445 66,398 66,342z" + id="glyph1057" /> +<glyph + unicode="ν" + horiz-adv-x="492" + d="M1,487l173,-487l119,0C382,149 470,312 470,450C470,463 469,476 467,487l-115,0C352,473 352,460 352,445C352,338 290,198 250,116l-4,0C234,158 225,197 211,240l-78,247z" + id="glyph1059" /> +<glyph + unicode="#" + horiz-adv-x="526" + d="M207,264l17,127l97,0l-17,-127M169,0l26,187l96,0l-26,-187l77,0l25,187l97,0l0,77l-84,0l17,127l93,0l0,77l-81,0l25,182l-75,0l-26,-182l-97,0l25,182l-75,0l-26,-182l-97,0l0,-77l86,0l-18,-127l-94,0l0,-78l82,0l-26,-186z" + id="glyph1061" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M219,235l17,127l97,0l-17,-127M185,-6l22,164l96,0l-22,-164l76,0l22,164l97,0l0,77l-84,0l17,127l93,0l0,77l-81,0l22,159l-76,0l-22,-159l-97,0l21,159l-75,0l-22,-159l-97,0l0,-77l86,0l-18,-127l-94,0l0,-77l82,0l-23,-164z" + id="glyph1063" /> +<glyph + unicode="o" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1065" /> +<glyph + unicode="ó" + horiz-adv-x="564" + d="M308,698l-87,-148l85,0l123,148M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1067" /> +<glyph + unicode="ŏ" + horiz-adv-x="564" + d="M150,688C150,616 191,553 282,553C365,553 414,608 414,688l-67,0C345,655 325,623 281,623C244,623 222,651 217,688M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1069" /> +<glyph + unicode="ô" + horiz-adv-x="564" + d="M240,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1071" /> +<glyph + unicode="ö" + horiz-adv-x="564" + d="M183,563C218,563 243,591 243,624C243,660 217,686 184,686C148,686 121,659 121,624C121,591 147,563 182,563M382,563C417,563 442,591 442,624C442,660 417,686 382,686C347,686 321,659 321,624C321,591 346,563 381,563M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1073" /> +<glyph + unicode="œ" + horiz-adv-x="866" + d="M830,211C832,221 834,241 834,262C834,366 784,498 629,498C557,498 493,464 455,398l-2,0C421,463 352,498 276,498C138,498 35,397 35,242C35,85 138,-11 268,-11C342,-11 414,20 453,88l3,0C497,17 564,-11 641,-11C691,-11 762,-2 810,24l-19,85C754,94 717,82 658,82C577,82 514,129 512,211M281,408C362,408 397,327 397,244C397,145 353,78 280,78C208,78 162,150 162,243C162,318 191,408 280,408M511,296C514,345 545,414 618,414C702,414 719,340 718,296z" + id="glyph1075" /> +<glyph + unicode="˛" + horiz-adv-x="300" + d="M100,3C82,-19 52,-68 52,-115C52,-180 93,-213 156,-213C185,-213 224,-204 248,-185l-18,60C219,-130 203,-134 185,-134C153,-134 133,-113 133,-83C133,-50 159,-15 171,3z" + id="glyph1077" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M100,3C82,-19 52,-68 52,-115C52,-180 93,-213 156,-213C185,-213 224,-204 248,-185l-18,60C219,-130 203,-134 185,-134C153,-134 133,-113 133,-83C133,-50 159,-15 171,3z" + id="glyph1079" /> +<glyph + unicode="ò" + horiz-adv-x="564" + d="M138,698l122,-148l86,0l-87,148M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1081" /> +<glyph + unicode="ơ" + horiz-adv-x="573" + d="M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M474,561C485,541 493,519 493,496C493,470 481,458 456,458C429,458 398,477 373,485C347,493 319,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,307 514,356 488,394C549,396 583,433 583,491C583,523 575,553 563,576z" + id="glyph1083" /> +<glyph + unicode="ő" + horiz-adv-x="564" + d="M217,697l-71,-143l72,0l104,143M377,697l-70,-143l71,0l104,143M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1085" /> +<glyph + unicode="ō" + horiz-adv-x="564" + d="M163,655l0,-71l239,0l0,71M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1087" /> +<glyph + unicode="ω" + horiz-adv-x="716" + d="M415,369l-116,0l2,-184C302,133 276,92 239,92C200,92 155,139 155,254C155,350 187,437 230,488l0,3l-112,0C77,447 35,359 35,248C35,69 130,-11 223,-11C283,-11 328,22 353,70l4,0C378,20 428,-11 486,-11C589,-11 680,81 680,259C680,368 639,455 600,491l-114,0l0,-3C532,440 560,351 560,263C560,150 520,92 476,92C437,92 412,133 412,185z" + id="glyph1089" /> +<glyph + unicode="ώ" + horiz-adv-x="716" + d="M360,694l-49,-148l81,0l82,148M415,369l-116,0l2,-184C302,133 276,92 239,92C200,92 155,139 155,254C155,350 187,437 230,488l0,3l-112,0C77,447 35,359 35,248C35,69 130,-11 223,-11C283,-11 328,22 353,70l4,0C378,20 428,-11 486,-11C589,-11 681,81 681,259C681,368 640,455 600,491l-114,0l0,-3C532,440 560,351 560,263C560,150 520,92 476,92C438,92 412,133 412,185z" + id="glyph1091" /> +<glyph + unicode="ο" + horiz-adv-x="564" + d="M280,-11C403,-11 529,69 529,248C529,396 432,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11M282,78C208,78 162,149 162,243C162,324 197,408 284,408C368,408 402,321 402,245C402,147 353,78 283,78z" + id="glyph1093" /> +<glyph + unicode="ό" + horiz-adv-x="564" + d="M285,694l-49,-148l81,0l82,148M280,-11C403,-11 529,69 529,248C529,396 432,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11M282,78C208,78 162,149 162,243C162,324 197,408 284,408C368,408 402,321 402,245C402,147 353,78 283,78z" + id="glyph1095" /> +<glyph + unicode="1" + horiz-adv-x="536" + d="M236,0l119,0l0,650l-103,0l-158,-78l21,-93l119,60l2,0z" + id="glyph1097" /> +<glyph + unicode="" + horiz-adv-x="275" + d="M209,0l0,390l-84,0l-104,-46l14,-69l76,35l2,0l0,-310z" + id="glyph1099" /> +<glyph + unicode="" + horiz-adv-x="356" + d="M154,0l119,0l0,650l-103,0l-158,-78l21,-93l119,60l2,0z" + id="glyph1101" /> +<glyph + unicode="₁" + horiz-adv-x="275" + d="M209,-147l0,390l-84,0l-104,-46l14,-69l76,35l2,0l0,-310z" + id="glyph1103" /> +<glyph + unicode="" + horiz-adv-x="275" + d="M209,266l0,390l-84,0l-104,-46l14,-69l76,35l2,0l0,-310z" + id="glyph1105" /> +<glyph + unicode="" + horiz-adv-x="390" + d="M298,0l0,487l-100,0l-172,-76l21,-91l130,60l2,0l0,-380z" + id="glyph1107" /> +<glyph + unicode="¹" + horiz-adv-x="275" + d="M209,443l0,390l-84,0l-104,-46l14,-69l76,35l2,0l0,-310z" + id="glyph1109" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M392,0l0,487l-101,0l-217,-87l18,-91l179,75l2,0l0,-384z" + id="glyph1111" /> +<glyph + unicode="½" + horiz-adv-x="799" + d="M213,266l0,390l-84,0l-103,-46l13,-69l77,35l1,0l0,-310M207,-11l373,672l-71,0l-373,-672M465,0l290,0l0,72l-157,0l0,3l28,23C692,155 748,205 748,278C748,339 702,395 605,395C549,395 500,376 470,353l26,-63C516,305 547,320 581,320C625,320 647,295 647,262C647,215 606,176 527,104l-62,-56z" + id="glyph1113" /> +<glyph + unicode="¼" + horiz-adv-x="799" + d="M226,266l0,390l-83,0l-104,-46l14,-69l76,35l1,0l0,-310M228,-11l374,672l-71,0l-373,-672M707,0l0,96l56,0l0,63l-56,0l0,235l-106,0l-181,-246l0,-52l198,0l0,-96M618,159l-106,0l-2,1l74,101C596,279 606,299 619,322l2,0C620,300 618,277 618,252z" + id="glyph1115" /> +<glyph + unicode="ª" + horiz-adv-x="363" + d="M325,266C321,287 319,315 319,344l0,107C319,524 286,591 181,591C129,591 84,576 57,559l19,-55C98,520 130,530 165,530C224,530 232,493 232,481l0,-8C109,474 33,432 33,358C33,304 72,261 138,261C182,261 216,280 238,303l2,0l6,-37M234,377C234,370 232,361 227,353C217,335 194,320 169,320C142,320 119,337 119,364C119,409 176,422 234,421z" + id="glyph1117" /> +<glyph + unicode="º" + horiz-adv-x="372" + d="M187,591C94,591 21,525 21,423C21,325 93,261 185,261C267,261 350,314 351,429C351,522 287,591 188,591M185,528C237,528 261,472 261,426C261,366 231,324 186,324C142,324 110,368 110,425C110,471 132,528 184,528z" + id="glyph1119" /> +<glyph + unicode="ø" + horiz-adv-x="564" + d="M283,498C140,498 35,399 35,242C35,159 65,94 116,51l-44,-63l45,-36l46,66C198,-2 239,-11 283,-11C404,-11 529,70 529,246C529,325 501,390 451,435l44,65l-47,33l-44,-65C368,488 327,498 284,498M182,145l-2,0C161,174 154,209 154,243C154,320 190,408 281,408C309,408 332,400 349,385M383,338l3,0C404,307 410,270 410,243C410,146 360,78 281,78C257,78 235,85 217,101z" + id="glyph1121" /> +<glyph + unicode="ǿ" + horiz-adv-x="564" + d="M308,698l-87,-148l85,0l123,148M283,498C140,498 35,399 35,242C35,159 65,94 116,51l-44,-63l45,-36l46,66C198,-2 239,-11 283,-11C404,-11 529,70 529,246C529,325 501,390 451,435l44,65l-47,33l-44,-65C368,488 327,498 284,498M182,145l-2,0C161,174 154,209 154,243C154,320 190,408 281,408C309,408 332,400 349,385M383,338l3,0C404,307 410,270 410,243C410,146 360,78 281,78C257,78 235,85 217,101z" + id="glyph1123" /> +<glyph + unicode="õ" + horiz-adv-x="564" + d="M210,562C211,591 220,607 235,607C247,607 256,601 275,590C296,579 316,569 341,569C391,569 419,605 416,685l-59,0C354,651 346,642 330,642C318,642 304,651 288,660C267,671 248,681 224,681C177,681 146,638 148,562M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408z" + id="glyph1125" /> +<glyph + unicode="p" + horiz-adv-x="585" + d="M66,-198l123,0l0,252l2,0C216,15 267,-11 328,-11C439,-11 550,74 550,250C550,402 457,498 345,498C271,498 214,467 178,412l-2,0l-6,75l-108,0C64,441 66,389 66,325M189,279C189,289 191,300 194,310C206,364 253,401 304,401C383,401 425,331 425,245C425,149 379,84 301,84C249,84 205,120 193,170C190,181 189,192 189,204z" + id="glyph1127" /> +<glyph + unicode="¶" + horiz-adv-x="529" + d="M298,-48l0,646l71,0l0,-646l81,0l0,715C422,674 376,677 319,677C87,677 31,558 32,452C34,324 143,264 210,264l7,0l0,-312z" + id="glyph1129" /> +<glyph + unicode="(" + horiz-adv-x="301" + d="M183,693C126,612 63,481 62,286C62,92 126,-38 183,-118l90,0C210,-22 160,109 160,286C160,466 209,598 273,693z" + id="glyph1131" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M183,732C126,651 63,520 62,325C62,131 126,1 183,-79l90,0C210,17 160,148 160,325C160,505 209,637 273,732z" + id="glyph1133" /> +<glyph + unicode="" + horiz-adv-x="193" + d="M113,425C81,380 41,300 41,185C41,70 80,-9 113,-55l65,0C144,1 113,79 113,185C113,290 145,372 178,425z" + id="glyph1135" /> +<glyph + unicode="₍" + horiz-adv-x="193" + d="M113,278C81,233 41,153 41,38C41,-77 80,-156 113,-202l65,0C144,-146 113,-68 113,38C113,143 145,225 178,278z" + id="glyph1137" /> +<glyph + unicode="" + horiz-adv-x="193" + d="M113,691C81,646 41,566 41,451C41,336 80,257 113,211l65,0C144,267 113,345 113,451C113,556 145,638 178,691z" + id="glyph1139" /> +<glyph + unicode="⁽" + horiz-adv-x="193" + d="M113,868C81,823 41,743 41,628C41,513 80,434 113,388l65,0C144,444 113,522 113,628C113,733 145,815 178,868z" + id="glyph1141" /> +<glyph + unicode=")" + horiz-adv-x="301" + d="M117,-118C175,-37 238,92 239,287C239,483 175,613 117,693l-89,0C91,597 140,466 140,288C140,110 91,-23 28,-118z" + id="glyph1143" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M117,-79C175,2 238,131 239,326C239,522 175,652 117,732l-89,0C91,636 140,505 140,327C140,149 91,16 28,-79z" + id="glyph1145" /> +<glyph + unicode="" + horiz-adv-x="193" + d="M80,-55C113,-7 153,71 153,185C153,301 113,379 80,425l-64,0C50,370 81,290 81,185C81,81 49,2 16,-55z" + id="glyph1147" /> +<glyph + unicode="₎" + horiz-adv-x="193" + d="M80,-202C113,-154 153,-76 153,38C153,154 113,232 80,278l-64,0C50,223 81,143 81,38C81,-66 49,-145 16,-202z" + id="glyph1149" /> +<glyph + unicode="" + horiz-adv-x="193" + d="M80,211C113,259 153,337 153,451C153,567 113,645 80,691l-64,0C50,636 81,556 81,451C81,347 49,268 16,211z" + id="glyph1151" /> +<glyph + unicode="⁾" + horiz-adv-x="193" + d="M80,388C113,436 153,514 153,628C153,744 113,822 80,868l-64,0C50,813 81,733 81,628C81,524 49,445 16,388z" + id="glyph1153" /> +<glyph + unicode="∂" + horiz-adv-x="375" + d="M89,591C113,607 158,629 224,629C322,629 400,541 400,396C400,388 400,379 400,371l-1,0C378,406 325,452 244,452C115,452 24,343 24,206C24,102 91,-11 235,-11C387,-11 520,120 520,372C520,597 392,729 238,729C145,729 83,695 52,674M247,88C189,88 148,132 148,206C148,291 201,356 270,356C331,356 374,312 387,272C376,177 322,88 248,88z" + id="glyph1155" /> +<glyph + unicode="%" + horiz-adv-x="840" + d="M199,661C99,661 28,580 28,457C29,332 103,261 194,261C283,261 363,331 363,468C363,584 302,661 200,661M197,592C250,592 270,534 270,462C270,383 246,331 197,331C148,331 121,386 122,461C122,534 147,592 196,592M265,-12l378,673l-70,0l-378,-673M647,392C548,392 477,310 477,188C477,62 552,-8 642,-8C732,-8 811,62 811,198C811,315 750,392 648,392M645,322C698,322 718,265 718,192C718,113 695,61 645,61C596,61 569,117 570,191C570,264 595,322 644,322z" + id="glyph1157" /> +<glyph + unicode="" + horiz-adv-x="858" + d="M194,610C94,610 23,529 23,407C24,281 98,210 189,210C278,210 358,280 358,417C358,534 296,610 195,610M191,541C245,541 265,483 265,411C265,332 241,280 192,280C143,280 116,335 117,410C117,483 141,541 190,541M289,-12l350,625l-70,0l-350,-625M670,392C571,392 500,310 500,188C500,62 575,-8 665,-8C755,-8 835,62 835,198C835,315 773,392 671,392M668,322C721,322 741,265 741,192C741,113 718,61 668,61C620,61 593,117 593,191C594,264 618,322 667,322z" + id="glyph1159" /> +<glyph + unicode="." + horiz-adv-x="236" + d="M127,-11C173,-11 203,23 203,68C203,115 172,147 127,147C83,147 51,114 51,68C51,23 82,-11 126,-11z" + id="glyph1161" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M76,-6C110,-6 132,18 132,50C132,83 110,105 77,105C44,105 22,83 22,50C22,18 44,-6 75,-6z" + id="glyph1163" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M76,-153C110,-153 132,-129 132,-97C132,-64 110,-42 77,-42C44,-42 22,-64 22,-97C22,-129 44,-153 75,-153z" + id="glyph1165" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M76,260C110,260 132,284 132,316C132,349 110,371 77,371C44,371 22,349 22,316C22,284 44,260 75,260z" + id="glyph1167" /> +<glyph + unicode="" + horiz-adv-x="153" + d="M76,437C110,437 132,461 132,493C132,526 110,548 77,548C44,548 22,526 22,493C22,461 44,437 75,437z" + id="glyph1169" /> +<glyph + unicode="·" + horiz-adv-x="236" + d="M118,182C164,182 194,215 194,261C194,308 163,340 118,340C74,340 42,307 42,261C42,215 73,182 117,182z" + id="glyph1171" /> +<glyph + unicode="" + horiz-adv-x="236" + d="M118,257C164,257 194,290 194,336C194,383 163,415 118,415C74,415 42,382 42,336C42,290 73,257 117,257z" + id="glyph1173" /> +<glyph + unicode="‰" + horiz-adv-x="1227" + d="M199,661C99,661 28,580 28,457C29,332 103,261 194,261C283,261 363,331 363,468C363,584 302,661 200,661M197,592C250,592 270,534 270,462C270,383 246,331 197,331C148,331 121,386 122,461C122,534 147,592 196,592M265,-12l378,673l-70,0l-378,-673M647,392C548,392 477,310 477,188C477,62 552,-8 642,-8C732,-8 811,62 811,198C811,315 750,392 648,392M645,322C698,322 718,265 718,192C718,113 695,61 645,61C596,61 569,117 570,191C570,264 595,322 644,322M1033,392C934,392 863,310 863,188C863,62 938,-8 1028,-8C1118,-8 1198,62 1198,198C1198,315 1136,392 1034,392M1031,322C1084,322 1104,265 1104,192C1104,113 1081,61 1031,61C982,61 955,117 956,191C956,264 981,322 1030,322z" + id="glyph1175" /> +<glyph + unicode="φ" + horiz-adv-x="680" + d="M208,500C139,475 35,387 35,247C35,76 162,0 287,-10l0,-188l111,0l0,188C519,2 644,89 644,255C644,403 538,495 433,497C358,499 287,452 287,336l0,-260C203,91 158,161 158,250C158,342 209,403 255,434M397,359C397,394 411,411 431,411C477,411 527,343 527,253C527,151 474,87 397,75z" + id="glyph1177" /> +<glyph + unicode="π" + horiz-adv-x="580" + d="M547,393l12,94l-393,0C78,487 29,475 8,457l14,-72C47,391 81,393 114,393C113,278 87,96 54,0l118,0C205,78 230,273 233,393l129,0l0,-254C362,70 368,17 379,0l118,0C490,28 484,77 484,149l0,244z" + id="glyph1179" /> +<glyph + unicode="+" + horiz-adv-x="596" + d="M257,532l0,-226l-217,0l0,-78l217,0l0,-228l82,0l0,228l217,0l0,78l-217,0l0,226z" + id="glyph1181" /> +<glyph + unicode="±" + horiz-adv-x="596" + d="M257,576l0,-183l-217,0l0,-78l217,0l0,-187l82,0l0,187l217,0l0,78l-217,0l0,183M40,78l0,-78l516,0l0,78z" + id="glyph1183" /> +<glyph + unicode="∏" + horiz-adv-x="642" + d="M631,549l0,101l-621,0l0,-101l93,0l0,-639l115,0l0,639l205,0l0,-639l115,0l0,639z" + id="glyph1185" /> +<glyph + unicode="ψ" + horiz-adv-x="694" + d="M299,-198l111,0l0,187C589,0 653,149 653,296C653,386 633,452 613,487l-111,0C514,465 536,387 536,287C536,170 489,79 409,76l0,538l-110,0l0,-538C235,81 179,124 179,259l0,135C179,443 175,475 166,487l-112,0C60,458 61,415 61,368l0,-119C61,55 173,-3 299,-11z" + id="glyph1187" /> +<glyph + unicode="q" + horiz-adv-x="581" + d="M392,-198l123,0l0,544C515,397 517,443 519,487l-119,0l-3,-65l-2,1C369,469 321,498 253,498C149,498 35,414 35,236C35,84 130,-11 239,-11C310,-11 361,20 390,67l2,0M392,205C392,193 390,179 386,167C372,118 331,86 281,86C203,86 160,153 160,242C160,334 205,401 284,401C339,401 378,362 389,316C391,307 392,296 392,286z" + id="glyph1189" /> +<glyph + unicode="?" + horiz-adv-x="428" + d="M248,207l-1,21C245,276 261,317 300,363C343,412 387,462 387,534C387,614 330,686 206,686C143,686 85,668 49,645l30,-88C104,576 144,587 179,587C235,586 261,558 261,516C261,477 236,442 198,396C151,340 133,285 138,232l2,-25M191,-11C237,-11 267,23 267,67C266,113 236,145 191,145C147,145 115,113 115,67C115,23 146,-11 190,-11z" + id="glyph1191" /> +<glyph + unicode="¿" + horiz-adv-x="428" + d="M243,491C198,491 167,459 167,413C167,368 198,335 243,335C289,335 319,368 319,413C319,459 288,491 244,491M187,274l0,-21C189,205 174,164 134,118C91,69 48,19 48,-53C48,-133 105,-205 229,-205C292,-205 350,-187 385,-165l-29,89C330,-95 290,-107 256,-107C200,-106 173,-77 173,-35C173,3 198,39 236,84C283,141 301,196 297,248l-2,26z" + id="glyph1193" /> +<glyph + unicode="" + horiz-adv-x="428" + d="M243,683C198,683 167,651 167,605C167,560 198,527 243,527C289,527 319,560 319,605C319,651 288,683 244,683M187,466l0,-21C189,397 174,356 134,310C91,261 48,211 48,139C48,59 105,-13 229,-13C292,-13 350,5 385,27l-29,89C330,97 290,85 256,85C200,86 173,115 173,157C173,195 198,231 236,276C283,333 301,388 297,440l-2,26z" + id="glyph1195" /> +<glyph + unicode=""" + horiz-adv-x="370" + d="M42,692l20,-257l73,0l20,257M215,692l20,-257l73,0l20,257z" + id="glyph1197" /> +<glyph + unicode="„" + horiz-adv-x="412" + d="M93,-103C131,-41 174,62 201,153l-119,-10C70,63 44,-37 17,-110M267,-103C305,-41 348,62 376,153l-119,-10C244,63 218,-37 192,-110z" + id="glyph1199" /> +<glyph + unicode="“" + horiz-adv-x="409" + d="M148,439C161,519 187,619 213,692l-75,-7C100,623 57,520 30,429M323,439C335,519 361,619 388,692l-76,-7C274,623 231,520 204,429z" + id="glyph1201" /> +<glyph + unicode="”" + horiz-adv-x="409" + d="M94,683C81,602 55,502 29,429l76,8C142,499 185,601 213,692M268,683C256,602 229,502 203,429l76,8C316,499 360,601 387,692z" + id="glyph1203" /> +<glyph + unicode="‘" + horiz-adv-x="236" + d="M149,439C162,519 188,619 214,692l-76,-7C101,623 58,520 30,429z" + id="glyph1205" /> +<glyph + unicode="’" + horiz-adv-x="236" + d="M94,683C82,602 56,502 30,429l75,8C143,499 186,601 213,692z" + id="glyph1207" /> +<glyph + unicode="‚" + horiz-adv-x="236" + d="M94,-103C131,-41 175,62 202,153l-119,-10C71,63 45,-37 18,-110z" + id="glyph1209" /> +<glyph + unicode="'" + horiz-adv-x="198" + d="M42,692l20,-257l73,0l20,257z" + id="glyph1211" /> +<glyph + unicode="r" + horiz-adv-x="356" + d="M66,0l123,0l0,252C189,266 191,279 193,290C204,345 244,383 303,383C318,383 329,381 339,379l0,116C329,497 322,498 310,498C258,498 201,463 177,394l-4,0l-4,93l-107,0C65,443 66,396 66,330z" + id="glyph1213" /> +<glyph + unicode="ŕ" + horiz-adv-x="356" + d="M214,698l-87,-148l85,0l123,148M66,0l123,0l0,252C189,266 191,279 193,290C204,345 244,383 303,383C318,383 329,381 339,379l0,116C329,497 322,498 310,498C258,498 201,463 177,394l-4,0l-4,93l-107,0C65,443 66,396 66,330z" + id="glyph1215" /> +<glyph + unicode="√" + horiz-adv-x="577" + d="M597,834l-86,0l-173,-726C331,80 326,52 322,32l-2,0C315,53 308,81 299,106l-114,319l-159,-65l22,-59l77,31l158,-418l85,0z" + id="glyph1217" /> +<glyph + unicode="ř" + horiz-adv-x="356" + d="M235,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148M66,0l123,0l0,252C189,266 191,279 193,290C204,345 244,383 303,383C318,383 329,381 339,379l0,116C329,497 322,498 310,498C258,498 201,463 177,394l-4,0l-4,93l-107,0C65,443 66,396 66,330z" + id="glyph1219" /> +<glyph + unicode="ŗ" + horiz-adv-x="356" + d="M66,0l123,0l0,252C189,266 191,279 193,290C204,345 244,383 303,383C318,383 329,381 339,379l0,116C329,497 322,498 310,498C258,498 201,463 177,394l-4,0l-4,93l-107,0C65,443 66,396 66,330M63,-233C139,-229 209,-195 209,-116C209,-82 189,-54 169,-40l-87,-16C97,-70 113,-92 113,-116C113,-152 79,-173 44,-179z" + id="glyph1221" /> +<glyph + unicode="®" + horiz-adv-x="441" + d="M197,493l17,0C232,493 241,486 244,469C248,452 251,438 256,432l49,0C302,438 298,448 293,471C288,494 279,506 262,512l0,2C283,520 298,534 298,551C298,567 291,579 280,586C269,594 254,599 220,599C190,599 166,595 153,593l0,-161l44,0M198,566C203,566 209,567 217,567C237,567 248,559 248,545C248,531 234,524 215,524l-17,0M221,684C125,684 47,609 47,516C47,421 125,346 221,346C318,346 394,421 394,516C394,609 318,684 222,684M221,646C293,646 346,587 346,515C346,443 293,384 221,385C149,385 95,443 95,515C95,587 149,646 220,646z" + id="glyph1223" /> +<glyph + unicode="ρ" + horiz-adv-x="579" + d="M187,47C212,11 261,-11 319,-11C431,-11 544,76 544,248C544,397 452,498 311,498C241,498 186,473 145,434C101,394 62,334 62,199l0,-223C62,-96 65,-171 73,-198l119,0C185,-159 185,-80 185,-22l0,69M185,231C185,343 241,397 303,397C376,397 419,331 419,245C419,153 374,84 296,84C244,84 200,120 188,161C186,172 185,181 185,193z" + id="glyph1225" /> +<glyph + unicode="˚" + horiz-adv-x="300" + d="M142,538C206,538 249,579 249,633C249,688 210,730 143,730C73,730 33,687 33,632C33,579 76,538 142,538M141,582C112,582 94,608 94,632C94,660 111,685 140,685C172,685 190,661 190,634C190,606 172,582 141,582z" + id="glyph1227" /> +<glyph + unicode="" + horiz-adv-x="400" + d="M201,886C132,886 93,843 93,790C93,739 134,698 201,698C267,698 309,739 309,791C309,844 271,886 202,886M200,842C229,842 246,818 246,792C246,764 229,742 200,742C172,742 154,766 154,790C154,817 170,842 199,842z" + id="glyph1229" /> +<glyph + unicode="s" + horiz-adv-x="417" + d="M36,24C72,4 125,-11 184,-11C313,-11 382,54 382,142C381,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C48,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph1231" /> +<glyph + unicode="ś" + horiz-adv-x="417" + d="M244,698l-87,-148l85,0l123,148M36,24C72,4 125,-11 184,-11C313,-11 382,54 382,142C381,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C48,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph1233" /> +<glyph + unicode="š" + horiz-adv-x="417" + d="M255,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148M36,24C72,4 125,-11 184,-11C313,-11 382,54 382,142C381,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C48,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph1235" /> +<glyph + unicode="ş" + horiz-adv-x="417" + d="M36,24C81,-1 136,-10 169,-10l2,0l-37,-71C185,-85 211,-98 211,-120C212,-141 192,-149 173,-149C156,-149 135,-143 118,-133l-17,-50C119,-195 147,-202 175,-202C236,-202 289,-180 289,-118C289,-78 255,-51 218,-47l24,42C334,12 382,69 382,142C382,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C49,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph1237" /> +<glyph + unicode="" + horiz-adv-x="268" + d="M36,24C81,-1 136,-10 169,-10l2,0l-37,-71C185,-85 211,-98 211,-120C212,-141 192,-149 173,-149C156,-149 135,-143 118,-133l-17,-50C119,-195 147,-202 175,-202C236,-202 289,-180 289,-118C289,-78 255,-51 218,-47l24,42C334,12 382,69 382,142C382,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C49,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph1239" /> +<glyph + unicode="ŝ" + horiz-adv-x="417" + d="M169,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M36,24C72,4 125,-11 184,-11C313,-11 382,54 382,142C381,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C48,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113z" + id="glyph1241" /> +<glyph + unicode="ș" + horiz-adv-x="417" + d="M36,24C72,4 125,-11 184,-11C313,-11 382,54 382,142C381,214 340,259 252,291C190,313 167,328 167,359C167,389 191,410 235,410C278,410 317,394 338,382l24,87C334,484 287,498 233,498C120,498 49,431 49,345C48,288 88,236 183,203C243,182 263,165 263,132C263,100 239,78 185,78C141,78 88,96 60,113M141,-233C217,-229 287,-195 287,-116C287,-82 267,-54 247,-40l-87,-16C175,-70 191,-92 191,-116C191,-152 157,-173 122,-179z" + id="glyph1243" /> +<glyph + unicode="§" + horiz-adv-x="542" + d="M148,347C148,373 163,394 188,410C219,394 274,376 315,360C371,340 393,312 393,279C393,254 381,230 360,216C337,229 292,244 244,261C175,285 148,312 148,346M443,653C402,673 351,684 294,684C181,684 91,630 91,534C92,498 109,463 130,447C75,416 51,368 51,327C51,233 134,195 239,161C322,133 352,112 352,73C352,30 310,8 252,8C192,8 136,32 109,49l-27,-76C129,-58 195,-70 256,-70C349,-70 464,-29 464,85C464,128 440,161 422,176C464,205 491,244 491,297C491,394 414,433 313,464C240,487 198,506 198,546C198,582 236,606 294,606C351,606 393,591 421,576z" + id="glyph1245" /> +<glyph + unicode=";" + horiz-adv-x="236" + d="M92,-113C131,-49 175,59 204,153l-129,-10C63,60 36,-45 8,-121M133,326C179,326 209,359 209,404C208,450 178,483 133,483C89,483 58,450 58,404C57,359 88,326 132,326z" + id="glyph1247" /> +<glyph + unicode="7" + horiz-adv-x="536" + d="M53,650l0,-102l306,0l0,-2l-274,-546l130,0l275,571l0,79z" + id="glyph1249" /> +<glyph + unicode="" + horiz-adv-x="316" + d="M22,389l0,-72l186,0l0,-1l-168,-316l94,0l168,334l0,55z" + id="glyph1251" /> +<glyph + unicode="" + horiz-adv-x="440" + d="M3,650l0,-102l306,0l0,-2l-274,-546l130,0l275,571l0,79z" + id="glyph1253" /> +<glyph + unicode="₇" + horiz-adv-x="316" + d="M22,242l0,-72l186,0l0,-1l-168,-316l94,0l168,334l0,55z" + id="glyph1255" /> +<glyph + unicode="" + horiz-adv-x="316" + d="M22,655l0,-72l186,0l0,-1l-168,-316l94,0l168,334l0,55z" + id="glyph1257" /> +<glyph + unicode="" + horiz-adv-x="454" + d="M430,487l-412,0l0,-101l285,0l0,-2C249,246 120,20 36,-103l118,-16C254,36 370,256 430,412z" + id="glyph1259" /> +<glyph + unicode="⁷" + horiz-adv-x="316" + d="M22,832l0,-72l186,0l0,-1l-168,-316l94,0l168,334l0,55z" + id="glyph1261" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M475,487l-412,0l0,-101l285,0l0,-2C294,246 165,20 81,-103l118,-16C299,36 415,256 475,412z" + id="glyph1263" /> +<glyph + unicode="σ" + horiz-adv-x="576" + d="M445,412C504,408 537,408 561,408l5,90C546,498 488,498 455,498l-66,0C376,498 324,498 296,498C138,498 35,398 35,238C35,82 141,-11 279,-11C399,-11 520,67 520,244C520,304 494,369 444,408M281,78C209,78 162,149 162,243C162,324 197,408 283,408C365,408 398,322 398,245C398,148 350,78 282,78z" + id="glyph1265" /> +<glyph + unicode="6" + horiz-adv-x="536" + d="M438,659C423,660 403,660 376,657C283,649 205,617 145,561C77,496 32,394 32,269C32,109 119,-11 277,-11C415,-11 506,97 506,221C506,353 419,433 307,433C242,433 193,407 161,370l-3,0C174,460 240,544 376,560C401,563 421,563 439,563M276,81C197,81 156,151 154,237C154,254 157,266 162,276C181,315 222,343 268,343C340,343 381,290 381,214C381,136 340,81 277,81z" + id="glyph1267" /> +<glyph + unicode="" + horiz-adv-x="349" + d="M282,394C273,395 258,394 243,393C177,389 129,371 91,335C49,297 22,236 22,164C22,73 74,-5 180,-5C260,-5 327,53 327,136C327,213 276,261 202,261C165,261 132,246 114,228l-2,0C124,279 167,319 244,326C257,328 272,327 282,326M179,61C133,61 109,99 109,142C109,150 111,157 113,162C123,184 146,201 173,201C216,201 238,170 238,131C238,91 214,61 180,61z" + id="glyph1269" /> +<glyph + unicode="" + horiz-adv-x="537" + d="M439,659C424,660 404,660 377,657C284,649 206,617 146,561C78,496 33,394 33,269C33,109 120,-11 278,-11C416,-11 507,97 507,221C507,353 420,433 308,433C243,433 194,407 162,370l-3,0C175,460 241,544 377,560C402,563 422,563 440,563M277,81C198,81 157,151 155,237C155,254 158,266 163,276C182,315 223,343 269,343C341,343 382,290 382,214C382,136 341,81 278,81z" + id="glyph1271" /> +<glyph + unicode="₆" + horiz-adv-x="349" + d="M282,247C273,248 258,247 243,246C177,242 129,224 91,188C49,150 22,89 22,17C22,-74 74,-152 180,-152C260,-152 327,-94 327,-11C327,66 276,114 202,114C165,114 132,99 114,81l-2,0C124,132 167,172 244,179C257,181 272,180 282,179M179,-86C133,-86 109,-48 109,-5C109,3 111,10 113,15C123,37 146,54 173,54C216,54 238,23 238,-16C238,-56 214,-86 180,-86z" + id="glyph1273" /> +<glyph + unicode="" + horiz-adv-x="349" + d="M282,660C273,661 258,660 243,659C177,655 129,637 91,601C49,563 22,502 22,430C22,339 74,261 180,261C260,261 327,319 327,402C327,479 276,527 202,527C165,527 132,512 114,494l-2,0C124,545 167,585 244,592C257,594 272,593 282,592M179,327C133,327 109,365 109,408C109,416 111,423 113,428C123,450 146,467 173,467C216,467 238,436 238,397C238,357 214,327 180,327z" + id="glyph1275" /> +<glyph + unicode="" + horiz-adv-x="537" + d="M449,611C192,604 40,452 40,242C40,91 131,-11 274,-11C392,-11 499,76 499,209C499,330 413,401 308,401C246,401 199,371 175,342l-2,1C193,433 279,514 457,523M275,81C205,81 162,138 162,214C162,240 168,261 185,279C203,300 234,319 274,319C335,319 377,273 377,202C377,120 325,81 276,81z" + id="glyph1277" /> +<glyph + unicode="⁶" + horiz-adv-x="349" + d="M282,837C273,838 258,837 243,836C177,832 129,814 91,778C49,740 22,679 22,607C22,516 74,438 180,438C260,438 327,496 327,579C327,656 276,704 202,704C165,704 132,689 114,671l-2,0C124,722 167,762 244,769C257,771 272,770 282,769M179,504C133,504 109,542 109,585C109,593 111,600 113,605C123,627 146,644 173,644C216,644 238,613 238,574C238,534 214,504 180,504z" + id="glyph1279" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M457,611C200,604 48,452 48,242C48,91 139,-11 282,-11C400,-11 507,76 507,209C507,330 421,401 316,401C254,401 207,371 183,342l-2,1C201,433 287,514 465,523M283,81C213,81 170,138 170,214C170,240 176,261 193,279C211,300 242,319 282,319C343,319 385,273 385,202C385,120 333,81 284,81z" + id="glyph1281" /> +<glyph + unicode="/" + horiz-adv-x="337" + d="M94,-40l244,725l-86,0l-244,-725z" + id="glyph1283" /> +<glyph + unicode=" " + horiz-adv-x="207" + id="glyph1285" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M151,-80C116,-80 89,-107 89,-142C89,-176 115,-204 150,-204C186,-204 211,-176 211,-142C211,-107 185,-80 151,-80z" + id="glyph1287" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M151,-201C185,-201 210,-172 210,-140C210,-107 185,-80 152,-80C117,-80 90,-107 90,-140C90,-172 117,-201 150,-201z" + id="glyph1289" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M166,528C188,576 244,594 244,655C244,703 203,737 151,737C96,737 61,705 41,663l42,-25C93,654 108,672 129,672C146,672 160,659 160,639C160,602 117,589 105,543z" + id="glyph1291" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M174,707C201,756 248,767 248,822C248,865 208,901 157,901C101,901 67,869 46,828l41,-26C97,818 114,837 134,837C152,837 165,825 165,806C165,774 130,765 115,722z" + id="glyph1293" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M-18,693l107,-146l72,0l-72,146M148,693l106,-146l72,0l-71,146z" + id="glyph1295" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M-71,832l125,-121l91,0l-87,121M128,832l124,-121l91,0l-86,121z" + id="glyph1297" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M154,423l42,0C270,423 311,466 311,521C311,552 302,581 290,604l-89,-15C214,569 221,547 221,527C221,503 209,487 170,487l-16,0z" + id="glyph1299" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M161,606l38,0C280,606 318,645 318,706C318,737 309,766 298,790l-89,-15C221,755 228,733 228,712C228,686 216,674 177,674l-16,0z" + id="glyph1301" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M86,-233C162,-229 232,-195 232,-116C232,-82 212,-54 192,-40l-87,-16C120,-70 136,-92 136,-116C136,-152 102,-173 67,-179z" + id="glyph1303" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M86,-232C162,-229 232,-195 232,-116C232,-82 212,-54 192,-40l-87,-16C120,-70 136,-92 136,-116C136,-152 102,-172 67,-179z" + id="glyph1305" /> +<glyph + unicode="£" + horiz-adv-x="536" + d="M491,0l0,101l-281,0l0,2C238,128 258,157 268,191C276,219 276,249 275,280l129,0l0,85l-140,0C258,394 253,421 253,455C253,513 280,563 354,563C393,563 422,554 441,543l22,93C441,649 403,661 349,661C225,661 138,581 138,458C138,426 143,393 148,365l-86,0l0,-85l100,0C165,261 166,245 166,226C166,155 113,95 53,67l0,-67z" + id="glyph1307" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M499,0l0,97l-279,0l0,2C249,119 275,155 278,203C278,210 278,219 277,226l133,0l0,71l-146,0C261,313 258,330 258,348C258,413 292,457 359,457C395,457 426,449 446,438l22,90C445,541 404,553 354,553C223,553 143,465 143,351C143,333 146,315 149,297l-79,0l0,-71l94,0C166,215 167,203 167,191C167,137 124,92 61,66l0,-66z" + id="glyph1309" /> +<glyph + unicode="∑" + horiz-adv-x="527" + d="M528,-90l0,101l-358,0l0,4l208,267l-197,263l0,4l318,0l0,101l-475,0l0,-81l221,-292l-233,-296l0,-71z" + id="glyph1311" /> +<glyph + unicode="t" + horiz-adv-x="351" + d="M87,577l0,-90l-70,0l0,-92l70,0l0,-229C87,102 99,58 125,30C148,5 186,-11 231,-11C270,-11 302,-5 320,2l-2,94C304,92 293,90 270,90C223,90 207,121 207,180l0,215l117,0l0,92l-117,0l0,125z" + id="glyph1313" /> +<glyph + unicode="τ" + horiz-adv-x="435" + d="M345,86C340,85 333,85 325,85C295,85 272,101 272,158l0,235l141,0l13,94l-280,0C49,487 11,471 -8,457l16,-72C29,389 54,393 103,393l46,0l0,-242C149,43 184,-11 279,-11C305,-11 328,-7 339,-1z" + id="glyph1315" /> +<glyph + unicode="ŧ" + horiz-adv-x="351" + d="M324,395l0,92l-117,0l0,125l-120,-35l0,-90l-70,0l0,-92l70,0l0,-90l-65,0l0,-69l65,0l0,-70C87,102 99,58 125,30C148,5 186,-11 231,-11C270,-11 302,-5 320,2l-2,94C304,92 293,90 270,90C227,90 207,117 207,172l0,64l90,0l0,69l-90,0l0,90z" + id="glyph1317" /> +<glyph + unicode="ť" + horiz-adv-x="357" + d="M87,577l0,-90l-70,0l0,-92l70,0l0,-229C87,102 99,58 125,30C148,5 186,-11 231,-11C270,-11 302,-5 320,2l-2,94C304,92 293,90 270,90C223,90 207,121 207,180l0,215l117,0l0,92l-117,0l0,125M305,539C338,549 400,586 400,658C400,688 383,716 367,727l-82,-15C298,698 308,677 308,656C308,625 291,598 273,585z" + id="glyph1319" /> +<glyph + unicode="ţ" + horiz-adv-x="351" + d="M87,577l0,-90l-70,0l0,-92l70,0l0,-229C87,102 99,58 125,30C148,5 186,-11 231,-11C270,-11 302,-5 320,2l-2,94C304,92 293,90 270,90C223,90 207,121 207,180l0,215l117,0l0,92l-117,0l0,125M123,-236C199,-232 269,-198 269,-119C269,-85 249,-57 229,-43l-87,-16C157,-73 173,-95 173,-119C173,-155 139,-176 104,-182z" + id="glyph1321" /> +<glyph + unicode="θ" + horiz-adv-x="554" + d="M278,-11C442,-11 519,137 519,360C519,574 445,721 283,721C121,721 35,565 35,360C35,116 126,-11 277,-11M157,408C157,530 201,634 278,634C357,634 397,534 397,408M398,319C398,176 361,76 280,76C205,76 158,168 158,319z" + id="glyph1323" /> +<glyph + unicode="þ" + horiz-adv-x="585" + d="M66,673l0,-871l123,0l0,253l2,0C219,15 268,-11 329,-11C434,-11 550,69 550,249C550,405 454,500 345,498C273,498 222,467 191,419l-2,0l0,254M189,282C189,297 194,315 199,327C217,373 259,401 306,401C381,401 425,332 425,247C425,147 376,84 302,84C258,84 218,110 201,150C194,163 189,182 189,196z" + id="glyph1325" /> +<glyph + unicode="3" + horiz-adv-x="536" + d="M40,34C75,11 143,-11 222,-11C383,-11 475,76 475,186C475,274 411,334 332,348l0,2C412,378 452,434 452,502C452,587 383,661 249,661C171,661 99,637 62,611l28,-89C117,540 170,563 223,563C294,563 327,526 327,482C327,417 255,391 198,391l-55,0l0,-90l57,0C275,301 347,268 347,191C348,140 310,87 216,87C154,87 93,112 68,127z" + id="glyph1327" /> +<glyph + unicode="" + horiz-adv-x="328" + d="M46,302C64,313 98,328 134,328C174,328 191,308 191,286C191,248 143,235 108,235l-28,0l0,-58l29,0C157,177 204,159 204,118C204,89 176,62 125,62C89,62 48,77 30,88l-21,-65C35,7 83,-5 135,-5C241,-5 305,46 305,113C305,164 264,200 210,207l0,2C257,224 290,257 290,298C290,350 247,395 155,395C101,395 53,380 26,364z" + id="glyph1329" /> +<glyph + unicode="" + horiz-adv-x="476" + d="M7,34C42,11 110,-11 189,-11C350,-11 442,76 442,186C442,274 378,334 299,348l0,2C379,378 419,434 419,502C419,587 350,661 216,661C138,661 66,637 29,611l28,-89C84,540 137,563 190,563C261,563 294,526 294,482C294,417 222,391 165,391l-55,0l0,-90l57,0C242,301 314,268 314,191C315,140 277,87 183,87C121,87 60,112 35,127z" + id="glyph1331" /> +<glyph + unicode="₃" + horiz-adv-x="328" + d="M46,155C64,166 98,181 134,181C174,181 191,161 191,139C191,101 143,88 108,88l-28,0l0,-58l29,0C157,30 204,12 204,-29C204,-58 176,-85 125,-85C89,-85 48,-70 30,-59l-21,-65C35,-140 83,-152 135,-152C241,-152 305,-101 305,-34C305,17 264,53 210,60l0,2C257,77 290,110 290,151C290,203 247,248 155,248C101,248 53,233 26,217z" + id="glyph1333" /> +<glyph + unicode="" + horiz-adv-x="328" + d="M46,568C64,579 98,594 134,594C174,594 191,574 191,552C191,514 143,501 108,501l-28,0l0,-58l29,0C157,443 204,425 204,384C204,355 176,328 125,328C89,328 48,343 30,354l-21,-65C35,273 83,261 135,261C241,261 305,312 305,379C305,430 264,466 210,473l0,2C257,490 290,523 290,564C290,616 247,661 155,661C101,661 53,646 26,630z" + id="glyph1335" /> +<glyph + unicode="" + horiz-adv-x="454" + d="M18,-99C44,-110 98,-121 158,-121C297,-121 420,-46 420,74C420,157 354,205 275,217l0,2C343,237 394,290 394,358C394,445 316,499 206,499C137,499 67,476 30,451l23,-81C80,384 129,404 178,404C236,404 272,381 272,336C272,284 207,255 124,249l-23,-2l1,-87C116,161 136,162 156,162C240,162 293,128 293,75C293,12 232,-26 145,-26C100,-26 58,-18 32,-9z" + id="glyph1337" /> +<glyph + unicode="³" + horiz-adv-x="328" + d="M46,745C64,756 98,771 134,771C174,771 191,751 191,729C191,691 143,678 108,678l-28,0l0,-58l29,0C157,620 204,602 204,561C204,532 176,505 125,505C89,505 48,520 30,531l-21,-65C35,450 83,438 135,438C241,438 305,489 305,556C305,607 264,643 210,650l0,2C257,667 290,700 290,741C290,793 247,838 155,838C101,838 53,823 26,807z" + id="glyph1339" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M60,-99C86,-110 140,-121 200,-121C339,-121 462,-46 462,74C462,157 396,205 317,217l0,2C385,237 436,290 436,358C436,445 358,499 248,499C179,499 109,476 72,451l23,-81C122,384 171,404 220,404C278,404 314,381 314,336C314,284 249,255 166,249l-23,-2l1,-87C158,161 178,162 198,162C282,162 335,128 335,75C335,12 274,-26 187,-26C142,-26 100,-18 74,-9z" + id="glyph1341" /> +<glyph + unicode="¾" + horiz-adv-x="799" + d="M65,568C83,579 117,594 153,594C194,594 210,574 210,552C210,514 162,501 127,501l-28,0l0,-58l29,0C176,443 223,425 223,384C224,355 195,328 145,328C108,328 67,343 49,354l-21,-65C54,273 102,261 154,261C260,261 324,312 324,379C324,430 284,466 229,473l0,2C276,490 309,523 309,564C309,616 266,661 174,661C120,661 72,646 45,629M263,-11l373,672l-71,0l-373,-672M717,0l0,96l57,0l0,63l-57,0l0,235l-106,0l-181,-246l0,-52l198,0l0,-96M628,159l-106,0l-2,1l75,101C607,278 617,298 629,322l3,0C630,300 628,277 628,252z" + id="glyph1343" /> +<glyph + unicode="˜" + horiz-adv-x="300" + d="M78,562C79,591 88,607 103,607C115,607 124,601 143,590C164,579 184,569 209,569C259,569 287,605 284,685l-59,0C222,651 214,642 198,642C186,642 172,651 156,660C135,671 116,681 92,681C45,681 14,638 16,562z" + id="glyph1345" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M77,715C79,743 88,758 102,758C116,758 128,750 146,740C166,729 185,721 210,721C259,721 285,756 283,833l-58,0C222,801 214,792 198,792C184,792 168,801 152,811C132,822 115,831 90,831C44,831 15,789 17,715z" + id="glyph1347" /> +<glyph + unicode="΄" + horiz-adv-x="302" + d="M133,698l-49,-148l80,0l82,148z" + id="glyph1349" /> +<glyph + unicode="™" + horiz-adv-x="636" + d="M28,674l0,-57l82,0l0,-216l70,0l0,216l82,0l0,57M624,401l-17,273l-101,0l-33,-97C467,553 461,527 455,498l-2,0C446,536 442,557 436,577l-31,97l-107,0l-18,-273l66,0l8,138C355,568 356,600 356,632l4,0C367,601 374,563 379,539l35,-133l68,0l38,131C527,565 536,601 543,632l4,0C548,596 548,565 548,539l8,-138z" + id="glyph1351" /> +<glyph + unicode="2" + horiz-adv-x="536" + d="M484,0l0,102l-267,0l0,2l57,49C387,258 469,350 469,464C469,570 399,661 255,661C173,661 102,632 55,594l36,-87C124,532 173,561 230,561C315,561 346,510 346,452C345,366 273,288 120,146l-77,-71l0,-75z" + id="glyph1353" /> +<glyph + unicode="" + horiz-adv-x="333" + d="M14,0l290,0l0,72l-157,0l0,3l28,23C241,155 297,205 297,278C297,339 251,395 154,395C98,395 49,376 19,353l26,-63C65,305 96,320 130,320C174,320 196,295 196,262C196,215 155,176 76,104l-62,-56z" + id="glyph1355" /> +<glyph + unicode="" + horiz-adv-x="472" + d="M444,0l0,102l-267,0l0,2l57,49C347,258 429,350 429,464C429,570 359,661 215,661C133,661 62,632 15,594l36,-87C84,532 133,561 190,561C275,561 306,510 306,452C305,366 233,288 80,146l-77,-71l0,-75z" + id="glyph1357" /> +<glyph + unicode="₂" + horiz-adv-x="333" + d="M14,-147l290,0l0,72l-157,0l0,3l28,23C241,8 297,58 297,131C297,192 251,248 154,248C98,248 49,229 19,206l26,-63C65,158 96,173 130,173C174,173 196,148 196,115C196,68 155,29 76,-43l-62,-56z" + id="glyph1359" /> +<glyph + unicode="" + horiz-adv-x="333" + d="M14,266l290,0l0,72l-157,0l0,3l28,23C241,421 297,471 297,544C297,605 251,661 154,661C98,661 49,642 19,619l26,-63C65,571 96,586 130,586C174,586 196,561 196,528C196,481 155,442 76,370l-62,-56z" + id="glyph1361" /> +<glyph + unicode="" + horiz-adv-x="463" + d="M437,0l0,96l-142,0C270,96 230,94 200,90l0,2l46,32C333,184 403,250 403,340C403,440 330,501 217,501C131,501 61,466 27,441l29,-79C89,383 136,403 182,403C240,403 279,375 279,319C279,250 215,191 78,104l-55,-37l0,-67z" + id="glyph1363" /> +<glyph + unicode="²" + horiz-adv-x="333" + d="M14,443l290,0l0,72l-157,0l0,3l28,23C241,598 297,648 297,721C297,782 251,838 154,838C98,838 49,819 19,796l26,-63C65,748 96,763 130,763C174,763 196,738 196,705C196,658 155,619 76,547l-62,-56z" + id="glyph1365" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M477,0l0,96l-142,0C310,96 270,94 240,90l0,2l46,32C373,184 443,250 443,340C443,440 370,501 257,501C171,501 101,466 67,441l29,-79C129,383 176,403 222,403C280,403 319,375 319,319C319,250 255,191 118,104l-55,-37l0,-67z" + id="glyph1367" /> +<glyph + unicode="u" + horiz-adv-x="569" + d="M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1369" /> +<glyph + unicode="ú" + horiz-adv-x="569" + d="M318,698l-87,-148l85,0l123,148M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1371" /> +<glyph + unicode="ŭ" + horiz-adv-x="569" + d="M152,688C152,616 193,553 284,553C367,553 416,608 416,688l-67,0C347,655 327,623 283,623C246,623 224,651 219,688M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1373" /> +<glyph + unicode="û" + horiz-adv-x="569" + d="M242,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1375" /> +<glyph + unicode="ü" + horiz-adv-x="569" + d="M188,563C223,563 248,591 248,624C248,660 222,686 189,686C153,686 126,659 126,624C126,591 152,563 187,563M387,563C422,563 447,591 447,624C447,660 422,686 387,686C352,686 326,659 326,624C326,591 351,563 386,563M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1377" /> +<glyph + unicode="ù" + horiz-adv-x="569" + d="M135,698l122,-148l86,0l-87,148M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1379" /> +<glyph + unicode="ư" + horiz-adv-x="569" + d="M491,589C503,569 511,547 511,527C511,503 499,487 461,487l-82,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146l0,281l9,1C562,432 600,466 600,522C600,552 592,581 580,604z" + id="glyph1381" /> +<glyph + unicode="ű" + horiz-adv-x="569" + d="M232,697l-71,-143l72,0l104,143M392,697l-70,-143l71,0l104,143M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1383" /> +<glyph + unicode="ū" + horiz-adv-x="569" + d="M165,655l0,-71l239,0l0,71M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1385" /> +<glyph + unicode="_" + horiz-adv-x="500" + d="M0,-75l0,-50l500,0l0,50z" + id="glyph1387" /> +<glyph + unicode=" " + horiz-adv-x="207" + id="glyph1389" /> +<glyph + unicode="" + horiz-adv-x="291" + d="M30,309l0,-85l255,0l0,85z" + id="glyph1391" /> +<glyph + unicode="Ǻ" + horiz-adv-x="636" + d="M321,692C383,692 420,728 420,776C420,823 386,861 322,861C258,861 221,822 221,776C221,730 259,692 321,692M320,737C294,737 278,755 278,776C278,798 292,818 319,818C348,818 364,799 364,777C364,753 348,737 320,737M364,968l-106,-89l104,0l145,89M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166z" + id="glyph1393" /> +<glyph + unicode="ǻ" + horiz-adv-x="508" + d="M305,849l-109,-112l85,0l145,112M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M254,523C318,523 361,564 361,618C361,673 322,715 255,715C185,715 145,672 145,617C145,564 188,523 254,523M253,567C224,567 206,593 206,617C206,645 223,670 252,670C284,670 302,646 302,619C302,591 284,567 253,567z" + id="glyph1395" /> +<glyph + unicode="Ț" + horiz-adv-x="525" + d="M200,0l123,0l0,571l194,0l0,103l-509,0l0,-103l192,0M186,-232C262,-229 332,-195 332,-116C332,-82 312,-54 292,-40l-87,-16C220,-70 236,-92 236,-116C236,-152 202,-172 167,-179z" + id="glyph1397" /> +<glyph + unicode="ț" + horiz-adv-x="351" + d="M87,577l0,-90l-70,0l0,-92l70,0l0,-229C87,102 99,58 125,30C148,5 186,-11 231,-11C270,-11 302,-5 320,2l-2,94C304,92 293,90 270,90C223,90 207,121 207,180l0,215l117,0l0,92l-117,0l0,125M123,-236C199,-232 269,-198 269,-119C269,-85 249,-57 229,-43l-87,-16C157,-73 173,-95 173,-119C173,-155 139,-176 104,-182z" + id="glyph1399" /> +<glyph + unicode="Ȝ" + horiz-adv-x="512" + d="M69,-121C300,-104 473,22 473,186C473,293 393,356 295,368l0,2C364,402 416,460 416,533C416,625 334,685 220,685C147,685 74,659 34,635l24,-81C87,569 142,589 194,589C253,589 293,557 293,511C293,435 201,379 99,358l14,-85C147,284 190,291 211,291C299,291 342,238 342,179C342,66 182,-10 55,-26z" + id="glyph1401" /> +<glyph + unicode="ȝ" + horiz-adv-x="453" + d="M47,-209C265,-198 425,-88 425,62C425,149 365,207 272,219l0,3C339,250 377,295 377,357C377,444 306,498 196,498C127,498 60,474 19,447l24,-80C70,381 116,403 164,403C222,403 257,377 257,333C257,269 179,233 81,211l17,-85C125,134 160,142 184,142C269,142 298,96 298,49C298,-87 83,-113 34,-115z" + id="glyph1403" /> +<glyph + unicode="Ȳ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M162,803l0,-69l251,0l0,69z" + id="glyph1405" /> +<glyph + unicode="ȳ" + horiz-adv-x="500" + d="M131,655l0,-71l239,0l0,71M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1407" /> +<glyph + unicode="ˉ" + horiz-adv-x="300" + d="M31,655l0,-71l239,0l0,71z" + id="glyph1409" /> +<glyph + unicode=";" + horiz-adv-x="250" + d="M109,-113C142,-49 182,59 204,143l-129,-4C69,60 47,-45 26,-121M134,302C183,302 214,338 214,385C214,434 182,468 135,468C87,468 55,433 55,385C55,338 87,302 133,302z" + id="glyph1411" /> +<glyph + unicode="Δ" + horiz-adv-x="636" + d="M20,0l592,0l0,65l-221,609l-146,0l-225,-610M153,101l116,333C284,480 301,537 310,571l3,0C324,528 343,469 358,423l114,-322z" + id="glyph1413" /> +<glyph + unicode="Ω" + horiz-adv-x="727" + d="M555,98C614,149 682,240 682,374C682,540 566,685 369,685C179,685 44,547 44,364C44,240 108,148 169,98l0,-3l-138,0l0,-95l279,0l0,74C235,120 172,233 172,350C172,475 245,587 366,587C488,587 555,469 555,351C555,223 492,127 418,74l0,-74l277,0l0,95l-140,0z" + id="glyph1415" /> +<glyph + unicode="μ" + horiz-adv-x="570" + d="M396,71C408,15 445,-11 496,-11C515,-11 528,-9 540,-4l7,86C516,84 504,100 504,148l0,339l-123,0l0,-294C381,179 379,165 374,155C362,123 327,89 279,89C217,89 189,138 189,208l0,279l-123,0l0,-508C66,-95 70,-171 80,-198l109,0C181,-159 179,-71 179,-18l0,50C197,5 230,-8 265,-8C331,-8 375,34 393,71z" + id="glyph1417" /> +<glyph + unicode="ς" + horiz-adv-x="439" + d="M428,475C400,489 357,498 311,498C133,498 35,365 35,231C35,96 120,27 223,-2C251,-10 282,-16 309,-21C307,-45 292,-102 280,-124l79,-25C390,-112 424,-16 424,47C424,55 421,60 415,61C367,69 327,76 280,90C206,113 162,158 162,240C162,332 223,404 314,404C350,404 379,397 403,387z" + id="glyph1419" /> +<glyph + unicode="Ạ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M313,-80C278,-80 251,-107 251,-142C251,-176 277,-204 312,-204C348,-204 373,-176 373,-142C373,-107 347,-80 313,-80z" + id="glyph1421" /> +<glyph + unicode="ạ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M254,-80C219,-80 192,-107 192,-142C192,-176 218,-204 253,-204C289,-204 314,-176 314,-142C314,-107 288,-80 254,-80z" + id="glyph1423" /> +<glyph + unicode="Ả" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M348,707C375,756 422,767 422,822C422,865 382,901 331,901C275,901 241,869 220,828l41,-26C271,818 288,837 308,837C326,837 339,825 339,806C339,774 304,765 289,722z" + id="glyph1425" /> +<glyph + unicode="ả" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M274,528C296,576 352,594 352,655C352,703 311,737 259,737C204,737 169,705 149,663l42,-25C201,654 216,672 237,672C254,672 268,659 268,639C268,602 225,589 213,543z" + id="glyph1427" /> +<glyph + unicode="Ấ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M277,829l-91,-119l84,0l50,66l2,0l49,-66l85,0l-92,119M487,908l-80,-110l63,0l128,110z" + id="glyph1429" /> +<glyph + unicode="ấ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M204,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M419,787l-73,-141l66,0l108,141z" + id="glyph1431" /> +<glyph + unicode="Ầ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M346,892l114,-109l68,0l-69,109M271,829l-91,-119l84,0l49,66l2,0l50,-66l85,0l-92,119z" + id="glyph1433" /> +<glyph + unicode="ầ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M288,788l100,-142l65,0l-66,142M204,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148z" + id="glyph1435" /> +<glyph + unicode="Ẩ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M274,829l-91,-119l84,0l49,66l2,0l49,-66l86,0l-93,119M487,756C513,805 557,814 557,865C557,907 521,940 472,940C420,940 388,910 368,871l39,-24C417,864 433,883 454,883C472,883 486,870 486,852C486,821 450,811 435,769z" + id="glyph1437" /> +<glyph + unicode="ẩ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M202,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M413,634C439,680 483,689 483,742C483,782 446,817 397,817C345,817 312,787 292,748l39,-24C341,739 357,758 376,758C393,758 406,745 406,728C406,697 373,688 358,647z" + id="glyph1439" /> +<glyph + unicode="Ẫ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M270,829l-101,-119l90,0l57,65l2,0l56,-65l92,0l-103,119M248,844C250,867 258,882 273,882C285,882 296,876 314,866C335,856 354,845 379,845C429,845 457,879 454,952l-59,0C393,920 384,911 368,911C356,911 342,919 326,928C305,939 286,948 262,948C215,948 185,908 187,844z" + id="glyph1441" /> +<glyph + unicode="ẫ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M209,675l-92,-125l85,0l50,72l2,0l50,-72l86,0l-93,125M184,689C186,713 194,727 209,727C221,727 232,722 250,712C271,701 290,691 315,691C365,691 393,725 391,797l-60,0C329,765 320,757 304,757C292,757 278,765 262,774C241,785 222,794 198,794C151,794 121,753 123,689z" + id="glyph1443" /> +<glyph + unicode="Ậ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M272,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119M311,-201C345,-201 370,-172 370,-140C370,-107 345,-80 312,-80C277,-80 250,-107 250,-140C250,-172 277,-201 310,-201z" + id="glyph1445" /> +<glyph + unicode="ậ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M211,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M254,-80C219,-80 192,-107 192,-142C192,-176 218,-204 253,-204C289,-204 314,-176 314,-142C314,-107 288,-80 254,-80z" + id="glyph1447" /> +<glyph + unicode="Ắ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M184,812C191,750 233,699 317,699C402,699 446,753 453,812l-57,0C388,784 367,765 319,765C272,765 250,788 244,812M346,912l-71,-110l69,0l118,110z" + id="glyph1449" /> +<glyph + unicode="ắ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M285,793l-83,-140l68,0l118,140M104,665C106,592 155,532 246,532C329,532 385,585 388,665l-62,0C323,632 296,601 246,601C202,601 172,628 167,665z" + id="glyph1451" /> +<glyph + unicode="Ằ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M182,912l118,-110l69,0l-71,110M187,812C194,750 235,699 320,699C404,699 449,753 456,812l-58,0C390,784 369,765 322,765C275,765 253,788 246,812z" + id="glyph1453" /> +<glyph + unicode="ằ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M112,793l118,-140l67,0l-82,140M110,665C112,592 160,532 251,532C335,532 391,585 393,665l-62,0C328,632 302,601 251,601C207,601 178,628 172,665z" + id="glyph1455" /> +<glyph + unicode="Ẳ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M340,789C366,835 411,844 411,898C411,938 374,972 326,972C273,972 241,942 221,903l39,-24C270,895 285,914 305,914C322,914 335,901 335,883C335,853 301,844 287,802M187,812C195,750 236,699 321,699C405,699 450,753 457,812l-58,0C391,784 370,765 322,765C275,765 254,788 247,812z" + id="glyph1457" /> +<glyph + unicode="ẳ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M268,643C293,689 338,698 338,751C338,792 300,826 252,826C199,826 167,796 147,757l39,-24C196,748 211,767 230,767C248,767 261,754 261,737C261,706 227,697 213,656M107,665C109,592 158,532 249,532C332,532 388,585 391,665l-62,0C326,632 299,601 249,601C205,601 175,628 170,665z" + id="glyph1459" /> +<glyph + unicode="Ẵ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M247,844C249,860 256,874 271,874C283,874 293,869 312,859C333,848 351,838 376,838C427,838 454,872 452,944l-59,0C390,912 382,904 366,904C354,904 339,912 323,921C302,932 284,941 260,941C213,941 185,901 184,844M181,812C188,750 230,699 314,699C399,699 443,753 450,812l-57,0C385,784 364,765 316,765C269,765 247,788 241,812z" + id="glyph1461" /> +<glyph + unicode="ẵ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M182,705C184,721 191,736 206,736C218,736 228,730 247,720C268,710 286,699 311,699C362,699 389,733 387,806l-59,0C325,774 317,765 301,765C289,765 274,773 258,782C237,793 219,802 195,802C148,802 120,762 120,705M114,665C116,593 161,532 252,532C335,532 388,585 390,665l-62,0C325,632 300,598 251,598C209,598 182,628 176,665z" + id="glyph1463" /> +<glyph + unicode="Ặ" + horiz-adv-x="636" + d="M420,191l61,-191l132,0l-219,674l-157,0l-216,-674l127,0l58,191M226,284l52,166C290,490 300,537 310,576l2,0C322,537 333,491 346,450l53,-166M183,841C185,774 223,723 315,723C407,723 447,774 449,841l-66,0C378,816 361,793 316,793C271,793 255,818 251,841M313,-201C347,-201 372,-172 372,-140C372,-107 347,-80 314,-80C279,-80 252,-107 252,-140C252,-172 279,-201 312,-201z" + id="glyph1465" /> +<glyph + unicode="ặ" + horiz-adv-x="508" + d="M444,293C444,399 399,498 243,498C166,498 103,477 67,455l24,-80C124,396 174,411 222,411C308,411 321,358 321,326l0,-8C141,319 32,257 32,134C32,60 87,-11 185,-11C248,-11 299,16 329,54l3,0l8,-54l111,0C446,30 444,73 444,117M324,171C324,163 323,153 320,144C309,109 273,77 224,77C185,77 154,99 154,147C154,221 237,240 324,238M122,688C122,616 163,553 254,553C337,553 386,608 386,688l-67,0C317,655 297,623 253,623C216,623 194,651 189,688M254,-80C219,-80 192,-107 192,-142C192,-176 218,-204 253,-204C289,-204 314,-176 314,-142C314,-107 288,-80 254,-80z" + id="glyph1467" /> +<glyph + unicode="Ẹ" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M258,-201C292,-201 317,-172 317,-140C317,-107 292,-80 259,-80C224,-80 197,-107 197,-140C197,-172 224,-201 257,-201z" + id="glyph1469" /> +<glyph + unicode="ẹ" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M258,-80C223,-80 196,-107 196,-142C196,-176 222,-204 257,-204C293,-204 318,-176 318,-142C318,-107 292,-80 258,-80z" + id="glyph1471" /> +<glyph + unicode="Ẻ" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M305,707C332,756 379,767 379,822C379,865 339,901 288,901C232,901 198,869 177,828l41,-26C228,818 245,837 265,837C283,837 296,825 296,806C296,774 261,765 246,722z" + id="glyph1473" /> +<glyph + unicode="ẻ" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M278,528C300,576 356,594 356,655C356,703 315,737 263,737C208,737 173,705 153,663l42,-25C205,654 220,672 241,672C258,672 272,659 272,639C272,602 229,589 217,543z" + id="glyph1475" /> +<glyph + unicode="Ẽ" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M203,715C205,743 214,758 228,758C242,758 254,750 272,740C292,729 311,721 336,721C385,721 411,756 409,833l-58,0C348,801 340,792 324,792C310,792 294,801 278,811C258,822 241,831 216,831C170,831 141,789 143,715z" + id="glyph1477" /> +<glyph + unicode="ẽ" + horiz-adv-x="516" + d="M185,562C186,591 195,607 210,607C222,607 231,601 250,590C271,579 291,569 316,569C366,569 394,605 391,685l-59,0C329,651 321,642 305,642C293,642 279,651 263,660C242,671 223,681 199,681C152,681 121,638 123,562M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295z" + id="glyph1479" /> +<glyph + unicode="Ế" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M227,829l-91,-119l84,0l50,66l2,0l49,-66l85,0l-92,119M437,908l-80,-110l63,0l128,110z" + id="glyph1481" /> +<glyph + unicode="ế" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M231,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M446,787l-73,-141l66,0l108,141z" + id="glyph1483" /> +<glyph + unicode="Ề" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M299,892l114,-109l68,0l-69,109M224,829l-91,-119l84,0l49,66l2,0l50,-66l85,0l-92,119z" + id="glyph1485" /> +<glyph + unicode="ề" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M311,788l100,-142l65,0l-66,142M227,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148z" + id="glyph1487" /> +<glyph + unicode="Ể" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M226,829l-91,-119l84,0l49,66l2,0l49,-66l86,0l-93,119M439,756C465,805 509,814 509,865C509,907 473,940 424,940C372,940 340,910 320,871l39,-24C369,864 385,883 406,883C424,883 438,870 438,852C438,821 402,811 387,769z" + id="glyph1489" /> +<glyph + unicode="ể" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M223,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M434,634C460,680 504,689 504,742C504,782 467,817 418,817C366,817 333,787 313,748l39,-24C362,739 378,758 397,758C414,758 427,745 427,728C427,697 394,688 379,647z" + id="glyph1491" /> +<glyph + unicode="Ễ" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M233,829l-101,-119l90,0l57,65l2,0l56,-65l92,0l-103,119M211,844C213,867 221,882 236,882C248,882 259,876 277,866C298,856 317,845 342,845C392,845 420,879 417,952l-59,0C356,920 347,911 331,911C319,911 305,919 289,928C268,939 249,948 225,948C178,948 148,908 150,844z" + id="glyph1493" /> +<glyph + unicode="ễ" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M224,675l-92,-125l85,0l50,72l2,0l50,-72l86,0l-93,125M199,689C201,713 209,727 224,727C236,727 247,722 265,712C286,701 305,691 330,691C380,691 408,725 406,797l-60,0C344,765 335,757 319,757C307,757 293,765 277,774C256,785 237,794 213,794C166,794 136,753 138,689z" + id="glyph1495" /> +<glyph + unicode="Ệ" + horiz-adv-x="515" + d="M448,399l-254,0l0,174l269,0l0,101l-392,0l0,-674l407,0l0,101l-284,0l0,198l254,0M223,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119M270,-201C304,-201 329,-172 329,-140C329,-107 304,-80 271,-80C236,-80 209,-107 209,-140C209,-172 236,-201 269,-201z" + id="glyph1497" /> +<glyph + unicode="ệ" + horiz-adv-x="516" + d="M479,209C481,220 483,238 483,261C483,368 432,498 273,498C117,498 35,371 35,236C35,87 128,-11 286,-11C356,-11 414,3 456,20l-18,85C401,91 360,83 303,83C225,83 156,121 153,209M153,295C158,345 190,414 266,414C348,414 368,340 367,295M215,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M258,-80C223,-80 196,-107 196,-142C196,-176 222,-204 257,-204C293,-204 318,-176 318,-142C318,-107 292,-80 258,-80z" + id="glyph1499" /> +<glyph + unicode="Ỉ" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M162,707C189,756 236,767 236,822C236,865 196,901 145,901C89,901 55,869 34,828l41,-26C85,818 102,837 122,837C140,837 153,825 153,806C153,774 118,765 103,722z" + id="glyph1501" /> +<glyph + unicode="ỉ" + horiz-adv-x="256" + d="M190,0l0,487l-124,0l0,-487M148,528C170,576 226,594 226,655C226,703 185,737 133,737C78,737 43,705 23,663l42,-25C75,654 90,672 111,672C128,672 142,659 142,639C142,602 99,589 87,543z" + id="glyph1503" /> +<glyph + unicode="Ị" + horiz-adv-x="264" + d="M71,674l0,-674l123,0l0,674M133,-201C167,-201 192,-172 192,-140C192,-107 167,-80 134,-80C99,-80 72,-107 72,-140C72,-172 99,-201 132,-201z" + id="glyph1505" /> +<glyph + unicode="ị" + horiz-adv-x="256" + d="M190,0l0,487l-124,0l0,-487M128,690C87,690 59,661 59,623C59,586 86,557 127,557C170,557 197,586 197,623C196,661 170,690 128,690M129,-80C94,-80 67,-107 67,-142C67,-176 93,-204 128,-204C164,-204 189,-176 189,-142C189,-107 163,-80 129,-80z" + id="glyph1507" /> +<glyph + unicode="Ọ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M353,-201C387,-201 412,-172 412,-140C412,-107 387,-80 354,-80C319,-80 292,-107 292,-140C292,-172 319,-201 352,-201z" + id="glyph1509" /> +<glyph + unicode="ọ" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M283,-80C248,-80 221,-107 221,-142C221,-176 247,-204 282,-204C318,-204 343,-176 343,-142C343,-107 317,-80 283,-80z" + id="glyph1511" /> +<glyph + unicode="Ỏ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M382,707C409,756 456,767 456,822C456,865 416,901 365,901C309,901 275,869 254,828l41,-26C305,818 322,837 342,837C360,837 373,825 373,806C373,774 338,765 323,722z" + id="glyph1513" /> +<glyph + unicode="ỏ" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M302,528C324,576 380,594 380,655C380,703 339,737 287,737C232,737 197,705 177,663l42,-25C229,654 244,672 265,672C282,672 296,659 296,639C296,602 253,589 241,543z" + id="glyph1515" /> +<glyph + unicode="Ố" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M317,829l-91,-119l84,0l50,66l2,0l49,-66l85,0l-92,119M527,908l-80,-110l63,0l128,110z" + id="glyph1517" /> +<glyph + unicode="ố" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M242,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M457,787l-73,-141l66,0l108,141z" + id="glyph1519" /> +<glyph + unicode="Ồ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M388,892l114,-109l68,0l-69,109M313,829l-91,-119l84,0l49,66l2,0l50,-66l85,0l-92,119z" + id="glyph1521" /> +<glyph + unicode="ồ" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M327,788l100,-142l65,0l-66,142M243,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148z" + id="glyph1523" /> +<glyph + unicode="Ổ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M318,829l-91,-119l84,0l49,66l2,0l49,-66l86,0l-93,119M531,756C557,805 601,814 601,865C601,907 565,940 516,940C464,940 432,910 412,871l39,-24C461,864 477,883 498,883C516,883 530,870 530,852C530,821 494,811 479,769z" + id="glyph1525" /> +<glyph + unicode="ổ" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M238,698l-87,-148l82,0l48,90l2,0l47,-90l83,0l-87,148M449,634C475,680 519,689 519,742C519,782 482,817 433,817C381,817 348,787 328,748l39,-24C377,739 393,758 412,758C429,758 442,745 442,728C442,697 409,688 394,647z" + id="glyph1527" /> +<glyph + unicode="Ỗ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M307,829l-101,-119l90,0l57,65l2,0l56,-65l92,0l-103,119M285,844C287,867 295,882 310,882C322,882 333,876 351,866C372,856 391,845 416,845C466,845 494,879 491,952l-59,0C430,920 421,911 405,911C393,911 379,919 363,928C342,939 323,948 299,948C252,948 222,908 224,844z" + id="glyph1529" /> +<glyph + unicode="ỗ" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M237,675l-92,-125l85,0l50,72l2,0l50,-72l86,0l-93,125M212,689C214,713 222,727 237,727C249,727 260,722 278,712C299,701 318,691 343,691C393,691 421,725 419,797l-60,0C357,765 348,757 332,757C320,757 306,765 290,774C269,785 250,794 226,794C179,794 149,753 151,689z" + id="glyph1531" /> +<glyph + unicode="Ộ" + horiz-adv-x="704" + d="M356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,536 553,685 356,685M353,587C476,587 539,466 539,339C539,198 472,87 353,87C235,87 166,197 166,334C166,473 231,587 353,587M305,829l-101,-119l90,0l56,65l2,0l57,-65l92,0l-103,119M353,-201C387,-201 412,-172 412,-140C412,-107 387,-80 354,-80C319,-80 292,-107 292,-140C292,-172 319,-201 352,-201z" + id="glyph1533" /> +<glyph + unicode="ộ" + horiz-adv-x="564" + d="M287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,396 432,498 287,498M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M239,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M283,-80C248,-80 221,-107 221,-142C221,-176 247,-204 282,-204C318,-204 343,-176 343,-142C343,-107 317,-80 283,-80z" + id="glyph1535" /> +<glyph + unicode="Ớ" + horiz-adv-x="704" + d="M353,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87M597,742C607,722 616,698 616,673C616,645 604,631 575,631C546,631 503,655 471,666C443,676 403,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,439 641,513 603,564C675,563 708,607 708,665C708,703 698,734 687,758M385,830l-88,-120l99,0l124,120z" + id="glyph1537" /> +<glyph + unicode="ớ" + horiz-adv-x="573" + d="M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M474,561C485,541 493,519 493,496C493,470 481,458 456,458C429,458 398,477 373,485C347,493 319,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,307 514,356 488,394C549,396 583,433 583,491C583,523 575,553 563,576M311,698l-87,-148l85,0l123,148z" + id="glyph1539" /> +<glyph + unicode="Ờ" + horiz-adv-x="704" + d="M353,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87M597,742C607,722 616,698 616,673C616,645 604,631 575,631C546,631 503,655 471,666C443,676 403,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,439 641,513 603,564C675,563 708,607 708,665C708,703 698,734 687,758M201,830l124,-119l99,0l-88,119z" + id="glyph1541" /> +<glyph + unicode="ờ" + horiz-adv-x="573" + d="M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M474,561C485,541 493,519 493,496C493,470 481,458 456,458C429,458 398,477 373,485C347,493 319,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,307 514,356 488,394C549,396 583,433 583,491C583,523 575,553 563,576M141,698l122,-148l86,0l-87,148z" + id="glyph1543" /> +<glyph + unicode="Ở" + horiz-adv-x="704" + d="M353,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87M597,742C607,722 616,698 616,673C616,645 604,631 575,631C546,631 503,655 471,666C443,676 403,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,439 641,513 603,564C675,563 708,607 708,665C708,703 698,734 687,758M382,707C409,756 456,767 456,822C456,865 416,901 365,901C309,901 275,869 254,828l41,-26C305,818 322,837 342,837C360,837 373,825 373,806C373,774 338,765 323,722z" + id="glyph1545" /> +<glyph + unicode="ở" + horiz-adv-x="573" + d="M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M474,561C485,541 493,519 493,496C493,470 481,458 456,458C429,458 398,477 373,485C347,493 319,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,307 514,356 488,394C549,396 583,433 583,491C583,523 575,553 563,576M302,528C324,576 380,594 380,655C380,703 339,737 287,737C232,737 197,705 177,663l42,-25C229,654 244,672 265,672C282,672 296,659 296,639C296,602 253,589 241,543z" + id="glyph1547" /> +<glyph + unicode="Ỡ" + horiz-adv-x="704" + d="M353,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87M597,742C607,722 616,698 616,673C616,645 604,631 575,631C546,631 503,655 471,666C443,676 403,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,439 641,513 603,564C675,563 708,607 708,665C708,703 698,734 687,758M286,715C288,743 297,758 311,758C325,758 337,750 355,740C375,729 394,721 419,721C468,721 494,756 492,833l-58,0C431,801 423,792 407,792C393,792 377,801 361,811C341,822 324,831 299,831C253,831 224,789 226,715z" + id="glyph1549" /> +<glyph + unicode="ỡ" + horiz-adv-x="573" + d="M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M474,561C485,541 493,519 493,496C493,470 481,458 456,458C429,458 398,477 373,485C347,493 319,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,307 514,356 488,394C549,396 583,433 583,491C583,523 575,553 563,576M210,562C211,591 220,607 235,607C247,607 256,601 275,590C296,579 316,569 341,569C391,569 419,605 416,685l-59,0C354,651 346,642 330,642C318,642 304,651 288,660C267,671 248,681 224,681C177,681 146,638 148,562z" + id="glyph1551" /> +<glyph + unicode="Ợ" + horiz-adv-x="704" + d="M353,87C234,87 165,201 166,335C165,469 230,587 353,587C476,587 539,466 539,339C539,198 472,87 353,87M597,742C607,722 616,698 616,673C616,645 604,631 575,631C546,631 503,655 471,666C443,676 403,685 356,685C166,685 36,539 36,332C36,134 156,-11 346,-11C532,-11 669,118 669,344C669,439 641,513 603,564C675,563 708,607 708,665C708,703 698,734 687,758M353,-201C387,-201 412,-172 412,-140C412,-107 387,-80 354,-80C319,-80 292,-107 292,-140C292,-172 319,-201 352,-201z" + id="glyph1553" /> +<glyph + unicode="ợ" + horiz-adv-x="573" + d="M284,408C368,408 402,321 402,245C402,147 353,78 283,78C208,78 162,149 162,243C162,324 197,408 284,408M474,561C485,541 493,519 493,496C493,470 481,458 456,458C429,458 398,477 373,485C347,493 319,498 287,498C140,498 35,400 35,240C35,84 141,-11 279,-11C403,-11 529,69 529,248C529,307 514,356 488,394C549,396 583,433 583,491C583,523 575,553 563,576M285,-80C250,-80 223,-107 223,-142C223,-176 249,-204 284,-204C320,-204 345,-176 345,-142C345,-107 319,-80 285,-80z" + id="glyph1555" /> +<glyph + unicode="Ụ" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M334,-201C368,-201 393,-172 393,-140C393,-107 368,-80 335,-80C300,-80 273,-107 273,-140C273,-172 300,-201 333,-201z" + id="glyph1557" /> +<glyph + unicode="ụ" + horiz-adv-x="569" + d="M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146M285,-80C250,-80 223,-107 223,-142C223,-176 249,-204 284,-204C320,-204 345,-176 345,-142C345,-107 319,-80 285,-80z" + id="glyph1559" /> +<glyph + unicode="Ủ" + horiz-adv-x="666" + d="M70,674l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,385l-123,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393M363,707C390,756 437,767 437,822C437,865 397,901 346,901C290,901 256,869 235,828l41,-26C286,818 303,837 323,837C341,837 354,825 354,806C354,774 319,765 304,722z" + id="glyph1561" /> +<glyph + unicode="ủ" + horiz-adv-x="569" + d="M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146M305,528C327,576 383,594 383,655C383,703 342,737 290,737C235,737 200,705 180,663l42,-25C232,654 247,672 268,672C285,672 299,659 299,639C299,602 256,589 244,543z" + id="glyph1563" /> +<glyph + unicode="Ứ" + horiz-adv-x="705" + d="M603,775C615,755 622,733 622,713C622,687 610,674 572,674l-99,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,317l16,1C680,612 712,650 712,705C712,737 704,766 692,790M357,830l-88,-120l99,0l124,120z" + id="glyph1565" /> +<glyph + unicode="ứ" + horiz-adv-x="569" + d="M491,589C503,569 511,547 511,527C511,503 499,487 461,487l-82,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146l0,281l9,1C562,432 600,466 600,522C600,552 592,581 580,604M327,698l-87,-148l85,0l123,148z" + id="glyph1567" /> +<glyph + unicode="Ừ" + horiz-adv-x="705" + d="M603,775C615,755 622,733 622,713C622,687 610,674 572,674l-99,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,317l16,1C680,612 712,650 712,705C712,737 704,766 692,790M179,830l124,-119l99,0l-88,119z" + id="glyph1569" /> +<glyph + unicode="ừ" + horiz-adv-x="569" + d="M491,589C503,569 511,547 511,527C511,503 499,487 461,487l-82,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146l0,281l9,1C562,432 600,466 600,522C600,552 592,581 580,604M144,698l122,-148l86,0l-87,148z" + id="glyph1571" /> +<glyph + unicode="Ử" + horiz-adv-x="705" + d="M603,775C615,755 622,733 622,713C622,687 610,674 572,674l-99,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,317l16,1C680,612 712,650 712,705C712,737 704,766 692,790M382,707C409,756 456,767 456,822C456,865 416,901 365,901C309,901 275,869 254,828l41,-26C305,818 322,837 342,837C360,837 373,825 373,806C373,774 338,765 323,722z" + id="glyph1573" /> +<glyph + unicode="ử" + horiz-adv-x="569" + d="M491,589C503,569 511,547 511,527C511,503 499,487 461,487l-82,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146l0,281l9,1C562,432 600,466 600,522C600,552 592,581 580,604M312,528C334,576 390,594 390,655C390,703 349,737 297,737C242,737 207,705 187,663l42,-25C239,654 254,672 275,672C292,672 306,659 306,639C306,602 263,589 251,543z" + id="glyph1575" /> +<glyph + unicode="Ữ" + horiz-adv-x="705" + d="M603,775C615,755 622,733 622,713C622,687 610,674 572,674l-99,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,317l16,1C680,612 712,650 712,705C712,737 704,766 692,790M279,715C281,743 290,758 304,758C318,758 330,750 348,740C368,729 387,721 412,721C461,721 487,756 485,833l-58,0C424,801 416,792 400,792C386,792 370,801 354,811C334,822 317,831 292,831C246,831 217,789 219,715z" + id="glyph1577" /> +<glyph + unicode="ữ" + horiz-adv-x="569" + d="M491,589C503,569 511,547 511,527C511,503 499,487 461,487l-82,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146l0,281l9,1C562,432 600,466 600,522C600,552 592,581 580,604M214,562C215,591 224,607 239,607C251,607 260,601 279,590C300,579 320,569 345,569C395,569 423,605 420,685l-59,0C358,651 350,642 334,642C322,642 308,651 292,660C271,671 252,681 228,681C181,681 150,638 152,562z" + id="glyph1579" /> +<glyph + unicode="Ự" + horiz-adv-x="705" + d="M603,775C615,755 622,733 622,713C622,687 610,674 572,674l-99,0l0,-393C473,149 420,88 331,88C248,88 193,149 193,281l0,393l-123,0l0,-386C70,76 171,-11 327,-11C489,-11 596,81 596,289l0,317l16,1C680,612 712,650 712,705C712,737 704,766 692,790M353,-80C318,-80 291,-107 291,-142C291,-176 317,-204 352,-204C388,-204 413,-176 413,-142C413,-107 387,-80 353,-80z" + id="glyph1581" /> +<glyph + unicode="ự" + horiz-adv-x="569" + d="M491,589C503,569 511,547 511,527C511,503 499,487 461,487l-82,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146l0,281l9,1C562,432 600,466 600,522C600,552 592,581 580,604M283,-80C248,-80 221,-107 221,-142C221,-176 247,-204 282,-204C318,-204 343,-176 343,-142C343,-107 317,-80 283,-80z" + id="glyph1583" /> +<glyph + unicode="Ỵ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M288,-201C322,-201 347,-172 347,-140C347,-107 322,-80 289,-80C254,-80 227,-107 227,-140C227,-172 254,-201 287,-201z" + id="glyph1585" /> +<glyph + unicode="ỵ" + horiz-adv-x="500" + d="M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261M370,-80C335,-80 308,-107 308,-142C308,-176 334,-204 369,-204C405,-204 430,-176 430,-142C430,-107 404,-80 370,-80z" + id="glyph1587" /> +<glyph + unicode="Ỷ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M317,707C344,756 391,767 391,822C391,865 351,901 300,901C244,901 210,869 189,828l41,-26C240,818 257,837 277,837C295,837 308,825 308,806C308,774 273,765 258,722z" + id="glyph1589" /> +<glyph + unicode="ỷ" + horiz-adv-x="500" + d="M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261M281,528C303,576 359,594 359,655C359,703 318,737 266,737C211,737 176,705 156,663l42,-25C208,654 223,672 244,672C261,672 275,659 275,639C275,602 232,589 220,543z" + id="glyph1591" /> +<glyph + unicode="Ỹ" + horiz-adv-x="575" + d="M347,0l0,284l224,390l-139,0l-81,-173C328,452 309,410 292,367l-2,0C272,413 255,451 232,501l-81,173l-140,0l213,-393l0,-281M214,715C216,743 225,758 239,758C253,758 265,750 283,740C303,729 322,721 347,721C396,721 422,756 420,833l-58,0C359,801 351,792 335,792C321,792 305,801 289,811C269,822 252,831 227,831C181,831 152,789 154,715z" + id="glyph1593" /> +<glyph + unicode="ỹ" + horiz-adv-x="500" + d="M186,562C187,591 196,607 211,607C223,607 232,601 251,590C272,579 292,569 317,569C367,569 395,605 392,685l-59,0C330,651 322,642 306,642C294,642 280,651 264,660C243,671 224,681 200,681C153,681 122,638 124,562M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1595" /> +<glyph + unicode="∕" + horiz-adv-x="124" + d="M-90,-11l374,672l-71,0l-374,-672z" + id="glyph1597" /> +<glyph + unicode="∙" + horiz-adv-x="236" + d="M118,182C164,182 194,215 194,261C194,308 163,340 118,340C74,340 42,307 42,261C42,215 73,182 117,182z" + id="glyph1599" /> +<glyph + unicode="ų" + horiz-adv-x="569" + d="M502,487l-123,0l0,-294C379,179 377,166 372,155C360,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-72C385,-21 359,-68 359,-116C359,-180 399,-213 462,-213C492,-213 530,-204 555,-186l-18,60C527,-131 512,-134 493,-134C462,-134 440,-114 440,-80C440,-50 458,-17 470,2l36,-2C504,39 502,88 502,146z" + id="glyph1601" /> +<glyph + unicode="υ" + horiz-adv-x="543" + d="M349,487C360,460 380,378 380,290C380,174 347,89 274,89C227,89 182,125 182,204l0,190C182,448 178,475 167,487l-119,0C57,457 59,402 59,324l0,-131C59,55 150,-11 264,-11C447,-11 501,151 501,299C501,382 481,454 464,487z" + id="glyph1603" /> +<glyph + unicode="ϋ" + horiz-adv-x="543" + d="M168,552C202,552 226,579 226,612C226,647 201,673 168,673C134,673 107,646 107,612C107,579 132,552 167,552M363,552C398,552 422,579 422,612C422,647 397,673 363,673C329,673 303,646 303,612C303,579 328,552 362,552M349,487C360,460 380,378 380,290C380,174 347,89 274,89C227,89 182,125 182,204l0,190C182,448 178,475 167,487l-119,0C57,457 59,402 59,324l0,-131C59,55 150,-11 264,-11C447,-11 501,151 501,299C501,382 481,454 464,487z" + id="glyph1605" /> +<glyph + unicode="ΰ" + horiz-adv-x="543" + d="M349,487C360,460 380,378 380,290C380,174 347,89 274,89C227,89 182,125 182,204l0,190C182,448 178,475 167,487l-119,0C57,457 59,402 59,324l0,-131C59,55 150,-11 264,-11C447,-11 501,151 501,299C501,382 481,454 464,487M241,707l-22,-165l64,0l53,165M134,556C165,556 187,581 187,611C187,643 164,666 134,666C103,666 79,642 79,611C79,581 102,556 133,556M405,556C437,556 459,581 459,611C459,643 436,666 405,666C374,666 350,642 350,611C350,581 373,556 404,556z" + id="glyph1607" /> +<glyph + unicode="ύ" + horiz-adv-x="543" + d="M269,694l-49,-147l82,0l82,147M349,487C360,460 380,378 380,290C380,174 347,89 274,89C227,89 182,125 182,204l0,190C182,448 178,475 167,487l-119,0C57,457 59,402 59,324l0,-131C59,55 150,-11 264,-11C447,-11 501,151 501,299C501,382 481,454 464,487z" + id="glyph1609" /> +<glyph + unicode="ů" + horiz-adv-x="569" + d="M285,538C349,538 392,579 392,633C392,688 353,730 286,730C216,730 176,687 176,632C176,579 219,538 285,538M284,582C255,582 237,608 237,632C237,660 254,685 283,685C315,685 333,661 333,634C333,606 315,582 284,582M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1611" /> +<glyph + unicode="ũ" + horiz-adv-x="569" + d="M212,562C213,591 222,607 237,607C249,607 258,601 277,590C298,579 318,569 343,569C393,569 421,605 418,685l-59,0C356,651 348,642 332,642C320,642 306,651 290,660C269,671 250,681 226,681C179,681 148,638 150,562M502,487l-123,0l0,-294C379,179 376,166 372,155C359,124 327,89 278,89C213,89 187,141 187,222l0,265l-123,0l0,-286C64,42 145,-11 234,-11C321,-11 369,39 390,74l2,0l6,-74l108,0C504,40 502,88 502,146z" + id="glyph1613" /> +<glyph + unicode="v" + horiz-adv-x="508" + d="M11,487l181,-487l121,0l186,487l-130,0l-77,-247C279,196 268,158 259,118l-3,0C247,158 237,197 223,240l-79,247z" + id="glyph1615" /> +<glyph + unicode="w" + horiz-adv-x="749" + d="M14,487l144,-487l115,0l62,198C349,246 362,294 372,354l2,0C385,295 396,249 411,198l58,-198l114,0l153,487l-121,0l-54,-216C548,214 537,161 529,108l-2,0C516,161 503,214 488,271l-61,216l-100,0l-63,-222C250,215 234,161 224,108l-2,0C213,161 202,214 191,266l-51,221z" + id="glyph1617" /> +<glyph + unicode="ẃ" + horiz-adv-x="749" + d="M403,698l-87,-148l85,0l123,148M14,487l144,-487l115,0l62,198C349,246 362,294 372,354l2,0C385,295 396,249 411,198l58,-198l114,0l153,487l-121,0l-54,-216C548,214 537,161 529,108l-2,0C516,161 503,214 488,271l-61,216l-100,0l-63,-222C250,215 234,161 224,108l-2,0C213,161 202,214 191,266l-51,221z" + id="glyph1619" /> +<glyph + unicode="ŵ" + horiz-adv-x="749" + d="M332,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M14,487l144,-487l115,0l62,198C349,246 362,294 372,354l2,0C385,295 396,249 411,198l58,-198l114,0l153,487l-121,0l-54,-216C548,214 537,161 529,108l-2,0C516,161 503,214 488,271l-61,216l-100,0l-63,-222C250,215 234,161 224,108l-2,0C213,161 202,214 191,266l-51,221z" + id="glyph1621" /> +<glyph + unicode="ẅ" + horiz-adv-x="749" + d="M275,563C310,563 335,591 335,624C335,660 309,686 276,686C240,686 213,659 213,624C213,591 239,563 274,563M474,563C509,563 534,591 534,624C534,660 509,686 474,686C439,686 413,659 413,624C413,591 438,563 473,563M14,487l144,-487l115,0l62,198C349,246 362,294 372,354l2,0C385,295 396,249 411,198l58,-198l114,0l153,487l-121,0l-54,-216C548,214 537,161 529,108l-2,0C516,161 503,214 488,271l-61,216l-100,0l-63,-222C250,215 234,161 224,108l-2,0C213,161 202,214 191,266l-51,221z" + id="glyph1623" /> +<glyph + unicode="ẁ" + horiz-adv-x="749" + d="M232,698l122,-148l86,0l-87,148M14,487l144,-487l115,0l62,198C349,246 362,294 372,354l2,0C385,295 396,249 411,198l58,-198l114,0l153,487l-121,0l-54,-216C548,214 537,161 529,108l-2,0C516,161 503,214 488,271l-61,216l-100,0l-63,-222C250,215 234,161 224,108l-2,0C213,161 202,214 191,266l-51,221z" + id="glyph1625" /> +<glyph + unicode="x" + horiz-adv-x="494" + d="M11,487l163,-239l-169,-248l136,0l56,97C213,124 227,148 241,176l2,0C257,149 271,123 287,97l60,-97l139,0l-166,253l163,234l-134,0l-54,-90C281,372 267,347 253,320l-3,0C236,345 222,369 206,395l-58,92z" + id="glyph1627" /> +<glyph + unicode="ξ" + horiz-adv-x="454" + d="M380,435C366,435 352,436 340,436C263,441 206,473 206,536C206,587 243,633 319,633C356,633 391,623 413,613l22,81C403,710 362,721 303,721C156,721 82,636 82,548C82,483 118,431 190,406l0,-5C102,376 35,302 35,206C35,37 206,-2 330,-20C329,-45 315,-102 303,-124l78,-24C414,-106 448,-9 448,48C448,54 444,59 437,60C392,66 351,73 307,84C212,109 165,150 165,218C165,294 228,350 353,350l27,0z" + id="glyph1629" /> +<glyph + unicode="y" + horiz-adv-x="500" + d="M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1631" /> +<glyph + unicode="ý" + horiz-adv-x="500" + d="M292,698l-87,-148l85,0l123,148M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1633" /> +<glyph + unicode="ŷ" + horiz-adv-x="500" + d="M212,698l-97,-148l84,0l54,91l2,0l54,-91l84,0l-97,148M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1635" /> +<glyph + unicode="ÿ" + horiz-adv-x="500" + d="M162,563C197,563 222,591 222,624C222,660 196,686 163,686C127,686 100,659 100,624C100,591 126,563 161,563M361,563C396,563 421,591 421,624C421,660 396,686 361,686C326,686 300,659 300,624C300,591 325,563 360,563M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1637" /> +<glyph + unicode="¥" + horiz-adv-x="536" + d="M320,0l0,167l157,0l0,61l-157,0l0,68l157,0l0,61l-124,0l175,293l-130,0l-87,-179C294,434 281,399 269,369l-2,0C254,401 243,432 225,469l-84,181l-133,0l166,-293l-125,0l0,-61l156,0l0,-68l-156,0l0,-61l156,0l0,-167z" + id="glyph1639" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M329,0l0,109l152,0l0,62l-152,0l0,55l152,0l0,61l-121,0l157,241l-125,0l-73,-137C303,358 289,329 276,299l-2,0C262,329 250,355 234,390l-70,138l-129,0l148,-241l-121,0l0,-61l151,0l0,-55l-151,0l0,-62l151,0l0,-109z" + id="glyph1641" /> +<glyph + unicode="ỳ" + horiz-adv-x="500" + d="M112,698l122,-148l86,0l-87,148M8,487l178,-447C192,28 193,21 193,16C193,10 190,3 185,-6C168,-39 141,-68 118,-83C93,-101 67,-112 46,-117l28,-104C105,-217 155,-201 203,-158C261,-108 306,-27 373,155l122,332l-132,0l-76,-260C278,195 269,159 261,131l-3,0C251,159 241,196 231,226l-88,261z" + id="glyph1643" /> +<glyph + unicode="z" + horiz-adv-x="450" + d="M20,0l411,0l0,98l-257,0l0,2C199,126 222,154 246,183l179,229l0,75l-387,0l0,-98l237,0l0,-2C249,359 228,335 203,305l-183,-234z" + id="glyph1645" /> +<glyph + unicode="ź" + horiz-adv-x="450" + d="M259,698l-87,-148l85,0l123,148M20,0l411,0l0,98l-257,0l0,2C199,126 222,154 246,183l179,229l0,75l-387,0l0,-98l237,0l0,-2C249,359 228,335 203,305l-183,-234z" + id="glyph1647" /> +<glyph + unicode="ž" + horiz-adv-x="450" + d="M277,550l97,148l-84,0l-55,-91l-2,0l-54,91l-85,0l98,-148M20,0l411,0l0,98l-257,0l0,2C199,126 222,154 246,183l179,229l0,75l-387,0l0,-98l237,0l0,-2C249,359 228,335 203,305l-183,-234z" + id="glyph1649" /> +<glyph + unicode="ż" + horiz-adv-x="450" + d="M229,563C264,563 289,591 289,625C289,660 263,687 229,687C194,687 167,660 167,625C167,591 193,563 228,563M20,0l411,0l0,98l-257,0l0,2C199,126 222,154 246,183l179,229l0,75l-387,0l0,-98l237,0l0,-2C249,359 228,335 203,305l-183,-234z" + id="glyph1651" /> +<glyph + unicode="0" + horiz-adv-x="536" + d="M272,661C120,661 34,525 34,323C35,125 115,-11 265,-11C420,-11 502,120 502,329C502,521 429,661 272,661M269,567C343,567 379,478 379,327C379,171 341,83 269,83C201,83 157,165 158,323C157,486 203,567 269,567z" + id="glyph1653" /> +<glyph + unicode="" + horiz-adv-x="359" + d="M178,-5C288,-5 337,84 337,196C337,312 285,395 180,395C75,395 22,310 22,195C22,78 75,-5 177,-5M179,61C140,61 115,107 115,196C115,283 140,329 179,329C221,329 244,284 244,196C244,107 221,61 180,61z" + id="glyph1655" /> +<glyph + unicode="" + horiz-adv-x="536" + d="M272,661C120,661 34,525 34,323C35,125 115,-11 265,-11C420,-11 502,120 502,329C502,521 429,661 272,661M269,567C343,567 379,478 379,327C379,171 341,83 269,83C201,83 157,165 158,323C157,486 203,567 269,567z" + id="glyph1657" /> +<glyph + unicode="₀" + horiz-adv-x="359" + d="M178,-152C288,-152 337,-63 337,49C337,165 285,248 180,248C75,248 22,163 22,48C22,-69 75,-152 177,-152M179,-86C140,-86 115,-40 115,49C115,136 140,182 179,182C221,182 244,137 244,49C244,-40 221,-86 180,-86z" + id="glyph1659" /> +<glyph + unicode="" + horiz-adv-x="359" + d="M178,261C288,261 337,350 337,462C337,578 285,661 180,661C75,661 22,576 22,461C22,344 75,261 177,261M179,327C140,327 115,373 115,462C115,549 140,595 179,595C221,595 244,550 244,462C244,373 221,327 180,327z" + id="glyph1661" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M274,-11C418,-11 512,96 512,244C512,393 418,499 274,499C129,499 36,391 36,244C36,94 129,-11 273,-11M274,80C198,80 153,146 153,244C153,342 198,408 274,408C349,408 394,342 394,244C394,146 349,80 275,80z" + id="glyph1663" /> +<glyph + unicode="" + horiz-adv-x="536" + d="M168,208C157,242 152,282 152,327C152,476 197,567 268,567C304,567 330,549 346,521M371,444C380,407 384,369 384,327C384,171 342,83 267,83C234,83 211,98 192,127M499,644l-53,36l-41,-66C371,645 326,661 272,661C120,661 33,527 34,323C34,228 53,145 90,88l-49,-74l52,-37l38,61C167,6 213,-11 266,-11C420,-11 502,120 502,329C502,423 485,506 447,565z" + id="glyph1665" /> +<glyph + unicode="" + horiz-adv-x="536" + d="M168,208C157,242 152,282 152,327C152,476 197,567 268,567C304,567 330,549 346,521M371,444C380,407 384,369 384,327C384,171 342,83 267,83C234,83 211,98 192,127M499,644l-53,36l-41,-66C371,645 326,661 272,661C120,661 33,527 34,323C34,228 53,145 90,88l-49,-74l52,-37l38,61C167,6 213,-11 266,-11C420,-11 502,120 502,329C502,423 485,506 447,565z" + id="glyph1667" /> +<glyph + unicode="⁰" + horiz-adv-x="359" + d="M178,438C288,438 337,527 337,639C337,755 285,838 180,838C75,838 22,753 22,638C22,521 75,438 177,438M179,504C140,504 115,550 115,639C115,726 140,772 179,772C221,772 244,727 244,639C244,550 221,504 180,504z" + id="glyph1669" /> +<glyph + unicode="" + horiz-adv-x="547" + d="M274,-11C418,-11 512,96 512,244C512,393 418,499 274,499C129,499 36,391 36,244C36,94 129,-11 273,-11M274,80C198,80 153,146 153,244C153,342 198,408 274,408C349,408 394,342 394,244C394,146 349,80 275,80z" + id="glyph1671" /> +<glyph + unicode="ζ" + horiz-adv-x="427" + d="M351,-148C385,-106 419,-16 419,48C419,54 415,59 409,60C361,67 309,74 265,94C204,119 162,161 162,251C162,394 280,555 425,624l-18,83l-178,0C178,707 130,707 98,710l-12,-79C114,622 154,619 200,619l58,0l0,-4C119,511 37,361 37,228C37,100 108,37 189,8C229,-7 271,-15 303,-20C302,-45 286,-102 274,-124z" + id="glyph1673" /> +</font> + + <font + horiz-adv-x="1000" + id="font1675" + horiz-origin-x="0" + horiz-origin-y="0" + vert-origin-x="0" + vert-origin-y="0" + vert-adv-y="0"> +<!-- Copyright 2000, 2004 Adobe Systems Incorporated. All Rights Reserved. U.S. Patent D454,582.Myriad is a registered trademark of Adobe Systems Incorporated. --> +<font-face + font-family="MyriadPro-Regular" + units-per-em="1000" + underline-position="-100" + underline-thickness="50" + id="font-face1677" /> +<missing-glyph + horiz-adv-x="500" + d="M0,0l500,0l0,700l-500,0M250,395l-170,255l340,0M280,350l170,255l0,-510M80,50l170,255l170,-255M50,605l170,-255l-170,-255z" + id="missing-glyph1679" /> +<glyph + unicode="A" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194z" + id="glyph1681" /> +<glyph + unicode="Æ" + horiz-adv-x="788" + d="M89,0l108,238l207,0l30,-238l321,0l1,73l-247,0l-30,241l240,0l0,72l-251,0l-26,215l287,0l0,73l-422,0l-308,-674M226,308l87,197C328,541 344,579 356,611l4,0C362,577 368,534 372,502l22,-194z" + id="glyph1683" /> +<glyph + unicode="Ǽ" + horiz-adv-x="788" + d="M89,0l108,238l207,0l30,-238l321,0l1,73l-247,0l-30,241l240,0l0,72l-251,0l-26,215l287,0l0,73l-422,0l-308,-674M226,308l87,197C328,541 344,579 356,611l4,0C362,577 368,534 372,502l22,-194M488,827l-93,-117l72,0l127,117z" + id="glyph1685" /> +<glyph + unicode="Á" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M348,827l-93,-117l72,0l127,117z" + id="glyph1687" /> +<glyph + unicode="Ă" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M181,817C183,757 219,711 304,711C390,711 427,758 429,817l-51,0C373,791 354,768 305,768C256,768 238,794 233,817z" + id="glyph1689" /> +<glyph + unicode="Â" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M277,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1691" /> +<glyph + unicode="Ä" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M218,719C247,719 266,742 266,770C266,798 246,820 218,820C190,820 169,797 169,770C169,742 188,719 217,719M403,719C433,719 452,742 452,770C452,798 432,820 404,820C375,820 354,797 354,770C354,742 374,719 402,719z" + id="glyph1693" /> +<glyph + unicode="À" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M161,827l127,-116l72,0l-93,116z" + id="glyph1695" /> +<glyph + unicode="Α" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194z" + id="glyph1697" /> +<glyph + unicode="Ά" + horiz-adv-x="615" + d="M427,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M206,280l66,195C285,516 296,557 306,597l2,0C318,558 328,518 343,474l66,-194M70,674l-8,-155l52,0l35,155z" + id="glyph1699" /> +<glyph + unicode="Ā" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M185,788l0,-55l239,0l0,55z" + id="glyph1701" /> +<glyph + unicode="Ą" + horiz-adv-x="612" + d="M359,674l-105,0l-229,-674l90,0l70,212l239,0l71,-208C471,-23 436,-70 436,-119C436,-175 473,-205 529,-205C554,-205 589,-198 610,-182l-14,45C584,-143 568,-147 551,-147C521,-147 500,-127 500,-95C500,-59 537,-18 553,-2l36,2M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194z" + id="glyph1703" /> +<glyph + unicode="Å" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M309,879C247,879 208,838 208,788C208,739 248,700 309,700C367,700 408,738 408,788C408,839 370,879 310,879M307,842C337,842 356,818 356,789C356,760 337,737 307,737C279,737 260,762 260,788C260,817 277,842 306,842z" + id="glyph1705" /> +<glyph + unicode="Ã" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M233,715C235,742 243,759 259,759C273,759 287,750 306,740C328,729 346,721 367,721C413,721 435,755 434,818l-46,0C385,786 376,779 361,779C345,779 329,788 313,797C292,808 276,817 254,817C210,817 184,776 186,715z" + id="glyph1707" /> +<glyph + unicode="B" + horiz-adv-x="542" + d="M76,2C105,-2 151,-6 211,-6C321,-6 397,14 444,57C478,90 501,134 501,192C501,292 426,345 362,361l0,2C433,389 476,446 476,511C476,564 455,604 420,630C378,664 322,679 235,679C174,679 114,673 76,665M163,606C177,609 200,612 240,612C328,612 388,581 388,502C388,437 334,389 242,389l-79,0M163,323l72,0C330,323 409,285 409,193C409,95 326,62 236,62C205,62 180,63 163,66z" + id="glyph1709" /> +<glyph + unicode="Β" + horiz-adv-x="542" + d="M76,2C105,-2 151,-6 211,-6C321,-6 397,14 444,57C478,90 501,134 501,192C501,292 426,345 362,361l0,2C433,389 476,446 476,511C476,564 455,604 420,630C378,664 322,679 235,679C174,679 114,673 76,665M163,606C177,609 200,612 240,612C328,612 388,581 388,502C388,437 334,389 242,389l-79,0M163,323l72,0C330,323 409,285 409,193C409,95 326,62 236,62C205,62 180,63 163,66z" + id="glyph1711" /> +<glyph + unicode="C" + horiz-adv-x="580" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,110 179,-11 369,-11C451,-11 515,6 547,22z" + id="glyph1713" /> +<glyph + unicode="Ć" + horiz-adv-x="580" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,110 179,-11 369,-11C451,-11 515,6 547,22M400,829l-93,-117l72,0l127,117z" + id="glyph1715" /> +<glyph + unicode="Č" + horiz-adv-x="580" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,110 179,-11 369,-11C451,-11 515,6 547,22M386,713l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1717" /> +<glyph + unicode="Ç" + horiz-adv-x="585" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,128 155,12 319,-7l-42,-77C324,-89 355,-100 355,-126C355,-148 335,-157 312,-157C292,-157 272,-152 257,-144l-14,-45C260,-199 288,-206 314,-206C366,-206 417,-183 417,-125C417,-84 384,-57 347,-50l24,40C455,-9 516,6 547,22z" + id="glyph1719" /> +<glyph + unicode="Ĉ" + horiz-adv-x="580" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,110 179,-11 369,-11C451,-11 515,6 547,22M314,828l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1721" /> +<glyph + unicode="Ċ" + horiz-adv-x="580" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,110 179,-11 369,-11C451,-11 515,6 547,22M348,719C376,719 397,744 397,770C397,796 376,820 349,820C320,820 298,796 298,770C298,744 320,719 347,719z" + id="glyph1723" /> +<glyph + unicode="Χ" + horiz-adv-x="571" + d="M546,0l-210,346l205,328l-100,0l-92,-158C324,473 307,442 288,402l-3,0C267,438 248,472 223,516l-89,158l-101,0l198,-333l-206,-341l100,0l81,148C241,207 260,243 279,282l2,0C302,243 324,206 359,149l86,-149z" + id="glyph1725" /> +<glyph + unicode="D" + horiz-adv-x="666" + d="M76,2C120,-3 172,-6 234,-6C365,-6 469,28 532,91C595,153 629,243 629,353C629,462 594,540 534,595C475,650 386,679 261,679C192,679 129,673 76,665M163,601C186,606 220,610 265,610C449,610 539,509 538,350C538,168 437,64 251,64C217,64 185,65 163,69z" + id="glyph1727" /> +<glyph + unicode="Ď" + horiz-adv-x="666" + d="M76,2C120,-3 172,-6 234,-6C365,-6 469,28 532,91C595,153 629,243 629,353C629,462 594,540 534,595C475,650 386,679 261,679C192,679 129,673 76,665M163,601C186,606 220,610 265,610C449,610 539,509 538,350C538,168 437,64 251,64C217,64 185,65 163,69M342,712l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1729" /> +<glyph + unicode="Đ" + horiz-adv-x="671" + d="M-2,307l83,0l0,-307C125,-5 176,-7 239,-7C369,-7 474,27 537,90C600,153 634,242 634,353C634,464 599,543 539,598C480,653 390,682 266,682C197,682 134,676 81,667l0,-290l-83,0M348,377l-180,0l0,226C191,608 225,612 270,612C454,612 544,513 543,352C543,166 443,63 255,63C222,63 190,64 168,67l0,240l180,0z" + id="glyph1731" /> +<glyph + unicode="∆" + horiz-adv-x="569" + d="M544,0l0,50l-209,606l-98,0l-211,-607l0,-49M451,72l-337,0l123,361C253,480 271,542 280,577l3,0C295,534 316,468 332,419z" + id="glyph1733" /> +<glyph + unicode="E" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0z" + id="glyph1735" /> +<glyph + unicode="É" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M302,827l-93,-117l72,0l127,117z" + id="glyph1737" /> +<glyph + unicode="Ĕ" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M130,825C132,765 168,719 253,719C339,719 376,766 378,825l-51,0C322,799 303,776 254,776C205,776 187,802 182,825z" + id="glyph1739" /> +<glyph + unicode="Ě" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M291,710l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1741" /> +<glyph + unicode="Ê" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M224,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1743" /> +<glyph + unicode="Ë" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M163,721C192,721 211,744 211,772C211,800 191,822 163,822C135,822 114,799 114,772C114,744 133,721 162,721M348,721C378,721 397,744 397,772C397,800 377,822 349,822C320,822 299,799 299,772C299,744 319,721 347,721z" + id="glyph1745" /> +<glyph + unicode="Ė" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M251,721C279,721 300,746 300,772C300,798 279,822 252,822C223,822 201,798 201,772C201,746 223,721 250,721z" + id="glyph1747" /> +<glyph + unicode="È" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M119,827l127,-116l72,0l-93,116z" + id="glyph1749" /> +<glyph + unicode="Ē" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M140,788l0,-55l239,0l0,55z" + id="glyph1751" /> +<glyph + unicode="Ŋ" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l218,-348C491,-77 451,-115 371,-131l18,-68C517,-180 582,-105 582,38l0,636l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674z" + id="glyph1753" /> +<glyph + unicode="Ę" + horiz-adv-x="492" + d="M447,-138C432,-144 418,-146 403,-146C376,-146 357,-132 357,-106C357,-69 398,-27 440,-1C445,-1 451,0 455,0l0,73l-292,0l0,243l262,0l0,72l-262,0l0,213l277,0l0,73l-364,0l0,-674l292,0C332,-24 288,-71 288,-125C288,-177 323,-206 380,-206C410,-206 437,-199 460,-185z" + id="glyph1755" /> +<glyph + unicode="Ε" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0z" + id="glyph1757" /> +<glyph + unicode="Έ" + horiz-adv-x="561" + d="M494,388l-261,0l0,213l277,0l0,73l-365,0l0,-674l380,0l0,73l-292,0l0,243l261,0M7,674l-7,-155l52,0l35,155z" + id="glyph1759" /> +<glyph + unicode="Η" + horiz-adv-x="652" + d="M76,674l0,-674l87,0l0,316l326,0l0,-316l88,0l0,674l-88,0l0,-282l-326,0l0,282z" + id="glyph1761" /> +<glyph + unicode="Ή" + horiz-adv-x="722" + d="M145,674l0,-674l88,0l0,316l325,0l0,-316l88,0l0,674l-88,0l0,-282l-325,0l0,282M7,674l-7,-155l52,0l35,155z" + id="glyph1763" /> +<glyph + unicode="Ð" + horiz-adv-x="671" + d="M-2,307l83,0l0,-307C125,-5 176,-7 239,-7C369,-7 474,27 537,90C600,153 634,242 634,353C634,464 599,543 539,598C480,653 390,682 266,682C197,682 134,676 81,667l0,-290l-83,0M348,377l-180,0l0,226C191,608 225,612 270,612C454,612 544,513 543,352C543,166 443,63 255,63C222,63 190,64 168,67l0,240l180,0z" + id="glyph1765" /> +<glyph + unicode="€" + horiz-adv-x="513" + d="M479,94C456,81 410,61 357,61C303,61 253,80 216,119C189,149 169,192 161,250l283,0l0,49l-290,0C154,306 154,311 154,317C154,331 155,346 156,359l288,0l0,50l-279,0C175,462 195,507 225,536C259,572 303,591 357,591C402,591 444,577 469,563l20,66C461,644 415,661 356,661C274,661 209,631 158,578C118,537 88,479 76,409l-66,0l0,-50l59,0C68,345 67,330 67,314C67,309 67,304 67,299l-57,0l0,-49l62,0C81,178 106,120 144,78C194,20 266,-11 347,-11C413,-11 465,10 496,31z" + id="glyph1767" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M466,83C443,71 402,57 353,57C303,57 263,71 227,106C209,124 195,149 188,178l250,0l0,49l-260,0C177,239 177,248 177,257C177,264 178,275 179,286l259,0l0,48l-249,0C194,365 210,392 230,412C265,446 309,460 353,460C401,460 434,448 457,436l20,62C451,511 413,526 354,526C285,526 216,502 165,451C135,421 113,380 102,334l-64,0l0,-48l57,0C93,273 92,261 92,251C92,245 93,238 94,227l-56,0l0,-49l63,0C110,131 132,89 162,59C210,11 275,-11 341,-11C402,-11 455,7 478,22z" + id="glyph1769" /> +<glyph + unicode="F" + horiz-adv-x="487" + d="M76,0l87,0l0,305l255,0l0,72l-255,0l0,224l276,0l0,73l-363,0z" + id="glyph1771" /> +<glyph + unicode="G" + horiz-adv-x="646" + d="M589,354l-222,0l0,-70l137,0l0,-201C484,73 445,65 388,65C231,65 128,166 128,337C128,506 235,608 399,608C467,608 512,595 548,579l21,71C540,664 479,681 401,681C175,681 37,534 36,333C36,228 72,138 130,82C196,19 280,-7 382,-7C473,-7 550,16 589,30z" + id="glyph1773" /> +<glyph + unicode="" + horiz-adv-x="646" + d="M589,354l-222,0l0,-70l137,0l0,-201C484,73 445,65 388,65C231,65 128,166 128,337C128,506 235,608 399,608C467,608 512,595 548,579l21,71C540,664 479,681 401,681C175,681 37,534 36,333C36,228 72,138 130,82C196,19 280,-7 382,-7C473,-7 550,16 589,30M291,715C293,742 301,759 317,759C331,759 345,750 364,740C386,729 404,721 425,721C471,721 493,755 492,818l-46,0C443,786 434,779 419,779C403,779 387,788 371,797C350,808 334,817 312,817C268,817 242,776 244,715z" + id="glyph1775" /> +<glyph + unicode="Γ" + horiz-adv-x="451" + d="M76,674l0,-674l87,0l0,601l274,0l0,73z" + id="glyph1777" /> +<glyph + unicode="Ğ" + horiz-adv-x="646" + d="M589,354l-222,0l0,-70l137,0l0,-201C484,73 445,65 388,65C231,65 128,166 128,337C128,506 235,608 399,608C467,608 512,595 548,579l21,71C540,664 479,681 401,681C175,681 37,534 36,333C36,228 72,138 130,82C196,19 280,-7 382,-7C473,-7 550,16 589,30M230,827C232,767 268,721 353,721C439,721 476,768 478,827l-51,0C422,801 403,778 354,778C305,778 287,804 282,827z" + id="glyph1779" /> +<glyph + unicode="Ĝ" + horiz-adv-x="646" + d="M589,354l-222,0l0,-70l137,0l0,-201C484,73 445,65 388,65C231,65 128,166 128,337C128,506 235,608 399,608C467,608 512,595 548,579l21,71C540,664 479,681 401,681C175,681 37,534 36,333C36,228 72,138 130,82C196,19 280,-7 382,-7C473,-7 550,16 589,30M326,827l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1781" /> +<glyph + unicode="Ģ" + horiz-adv-x="646" + d="M589,354l-222,0l0,-70l137,0l0,-201C484,73 445,65 388,65C231,65 128,166 128,337C128,506 235,608 399,608C467,608 512,595 548,579l21,71C540,664 479,681 401,681C175,681 37,534 36,333C36,228 72,138 130,82C196,19 280,-7 382,-7C473,-7 550,16 589,30M298,-222C360,-218 427,-188 427,-114C427,-81 407,-54 387,-41l-65,-14C340,-69 356,-92 356,-115C356,-155 320,-174 281,-181z" + id="glyph1783" /> +<glyph + unicode="Ġ" + horiz-adv-x="646" + d="M589,354l-222,0l0,-70l137,0l0,-201C484,73 445,65 388,65C231,65 128,166 128,337C128,506 235,608 399,608C467,608 512,595 548,579l21,71C540,664 479,681 401,681C175,681 37,534 36,333C36,228 72,138 130,82C196,19 280,-7 382,-7C473,-7 550,16 589,30M362,719C390,719 411,744 411,770C411,796 390,820 363,820C334,820 312,796 312,770C312,744 334,719 361,719z" + id="glyph1785" /> +<glyph + unicode="H" + horiz-adv-x="652" + d="M76,674l0,-674l87,0l0,316l326,0l0,-316l88,0l0,674l-88,0l0,-282l-326,0l0,282z" + id="glyph1787" /> +<glyph + unicode="Ħ" + horiz-adv-x="657" + d="M636,479l0,58l-57,0l0,137l-88,0l0,-137l-326,0l0,137l-87,0l0,-137l-57,0l0,-58l57,0l0,-479l87,0l0,302l326,0l0,-302l88,0l0,479M491,377l-326,0l0,102l326,0z" + id="glyph1789" /> +<glyph + unicode="Ĥ" + horiz-adv-x="652" + d="M76,674l0,-674l87,0l0,316l326,0l0,-316l88,0l0,674l-88,0l0,-282l-326,0l0,282M293,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1791" /> +<glyph + unicode="I" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674z" + id="glyph1793" /> +<glyph + unicode="IJ" + horiz-adv-x="609" + d="M76,674l0,-674l87,0l0,674M453,230C453,98 407,63 329,63C300,63 274,69 256,76l-13,-71C265,-4 304,-11 336,-11C452,-11 540,44 540,223l0,451l-87,0z" + id="glyph1795" /> +<glyph + unicode="Í" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M160,827l-93,-117l72,0l127,117z" + id="glyph1797" /> +<glyph + unicode="Ĭ" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M-4,823C-2,763 34,717 119,717C205,717 242,764 244,823l-51,0C188,797 169,774 120,774C71,774 53,800 48,823z" + id="glyph1799" /> +<glyph + unicode="Î" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M86,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1801" /> +<glyph + unicode="Ï" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M28,721C57,721 76,744 76,772C76,800 56,822 28,822C0,822 -21,799 -21,772C-21,744 -2,721 27,721M213,721C243,721 262,744 262,772C262,800 242,822 214,822C185,822 164,799 164,772C164,744 184,721 212,721z" + id="glyph1803" /> +<glyph + unicode="İ" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M121,721C149,721 170,746 170,772C170,798 149,822 122,822C93,822 71,798 71,772C71,746 93,721 120,721z" + id="glyph1805" /> +<glyph + unicode="Ì" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M-25,827l127,-116l72,0l-93,116z" + id="glyph1807" /> +<glyph + unicode="Ī" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M1,788l0,-55l239,0l0,55z" + id="glyph1809" /> +<glyph + unicode="Į" + horiz-adv-x="239" + d="M163,674l-87,0l0,-674C58,-27 28,-74 28,-123C28,-179 66,-209 121,-209C146,-209 181,-202 202,-186l-14,45C177,-146 162,-150 143,-150C114,-150 92,-132 92,-98C92,-62 118,-21 133,0l30,0z" + id="glyph1811" /> +<glyph + unicode="Ι" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674z" + id="glyph1813" /> +<glyph + unicode="Ϊ" + horiz-adv-x="239" + d="M28,711C56,711 76,735 76,761C76,790 55,812 28,812C0,812 -22,789 -22,761C-22,735 -2,711 27,711M211,711C240,711 260,735 260,761C260,790 239,812 212,812C183,812 162,789 162,761C162,735 182,711 210,711M76,674l0,-674l87,0l0,674z" + id="glyph1815" /> +<glyph + unicode="Ί" + horiz-adv-x="309" + d="M145,674l0,-674l88,0l0,674M7,674l-7,-155l52,0l35,155z" + id="glyph1817" /> +<glyph + unicode="Ĩ" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M43,715C45,742 53,759 69,759C83,759 97,750 116,740C138,729 156,721 177,721C223,721 245,755 244,818l-46,0C195,786 186,779 171,779C155,779 139,788 123,797C102,808 86,817 64,817C20,817 -6,776 -4,715z" + id="glyph1819" /> +<glyph + unicode="J" + horiz-adv-x="370" + d="M214,230C214,98 168,63 90,63C61,63 35,69 17,76l-13,-71C26,-4 65,-11 97,-11C213,-11 301,44 301,223l0,451l-87,0z" + id="glyph1821" /> +<glyph + unicode="Ĵ" + horiz-adv-x="370" + d="M214,230C214,98 168,63 90,63C61,63 35,69 17,76l-13,-71C26,-4 65,-11 97,-11C213,-11 301,44 301,223l0,451l-87,0M220,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1823" /> +<glyph + unicode="K" + horiz-adv-x="542" + d="M76,0l87,0l0,257l64,74l222,-331l103,0l-263,388l244,286l-108,0l-206,-253C202,399 184,375 166,349l-3,0l0,325l-87,0z" + id="glyph1825" /> +<glyph + unicode="Κ" + horiz-adv-x="542" + d="M76,0l87,0l0,257l64,74l222,-331l103,0l-263,388l244,286l-108,0l-206,-253C202,399 184,375 166,349l-3,0l0,325l-87,0z" + id="glyph1827" /> +<glyph + unicode="Ķ" + horiz-adv-x="542" + d="M76,0l87,0l0,257l64,74l222,-331l103,0l-263,388l244,286l-108,0l-206,-253C202,399 184,375 166,349l-3,0l0,325l-87,0M215,-220C277,-216 344,-186 344,-112C344,-79 324,-52 304,-39l-65,-14C257,-67 273,-90 273,-113C273,-153 237,-172 198,-179z" + id="glyph1829" /> +<glyph + unicode="L" + horiz-adv-x="472" + d="M76,0l375,0l0,73l-288,0l0,601l-87,0z" + id="glyph1831" /> +<glyph + unicode="Ĺ" + horiz-adv-x="472" + d="M76,0l375,0l0,73l-288,0l0,601l-87,0M179,827l-93,-117l72,0l127,117z" + id="glyph1833" /> +<glyph + unicode="Λ" + horiz-adv-x="609" + d="M590,0l-232,674l-104,0l-229,-674l91,0l153,465C282,509 292,550 302,592l3,0C315,551 325,511 340,464l156,-464z" + id="glyph1835" /> +<glyph + unicode="Ľ" + horiz-adv-x="472" + d="M76,0l375,0l0,73l-288,0l0,601l-87,0M260,504C322,508 389,538 389,612C389,645 369,672 349,685l-65,-14C302,657 318,634 318,611C318,571 282,552 243,545z" + id="glyph1837" /> +<glyph + unicode="Ļ" + horiz-adv-x="472" + d="M76,0l375,0l0,73l-288,0l0,601l-87,0M187,-222C249,-218 316,-188 316,-114C316,-81 296,-54 276,-41l-65,-14C229,-69 245,-92 245,-115C245,-155 209,-174 170,-181z" + id="glyph1839" /> +<glyph + unicode="Ŀ" + horiz-adv-x="472" + d="M76,0l375,0l0,73l-288,0l0,601l-87,0M341,320C369,320 390,345 390,371C390,397 369,421 342,421C313,421 291,397 291,371C291,345 313,320 340,320z" + id="glyph1841" /> +<glyph + unicode="Ł" + horiz-adv-x="476" + d="M455,0l0,73l-288,0l0,256l134,96l0,69l-134,-97l0,277l-87,0l0,-337l-84,-61l0,-68l84,62l0,-270z" + id="glyph1843" /> +<glyph + unicode="M" + horiz-adv-x="804" + d="M661,0l85,0l-42,674l-111,0l-120,-326C443,263 419,189 402,121l-3,0C382,191 359,265 331,348l-115,326l-111,0l-47,-674l83,0l18,289C165,390 170,503 172,587l2,0C193,507 220,420 252,325l109,-321l66,0l119,327C580,423 608,508 631,587l3,0C633,503 639,390 644,296z" + id="glyph1845" /> +<glyph + unicode="" + horiz-adv-x="804" + d="M661,0l85,0l-42,674l-111,0l-120,-326C443,263 419,189 402,121l-3,0C382,191 359,265 331,348l-115,326l-111,0l-47,-674l83,0l18,289C165,390 170,503 172,587l2,0C193,507 220,420 252,325l109,-321l66,0l119,327C580,423 608,508 631,587l3,0C633,503 639,390 644,296M368,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1847" /> +<glyph + unicode="Μ" + horiz-adv-x="804" + d="M661,0l85,0l-42,674l-111,0l-120,-326C443,263 419,189 402,121l-3,0C382,191 359,265 331,348l-115,326l-111,0l-47,-674l83,0l18,289C165,390 170,503 172,587l2,0C193,507 220,420 252,325l109,-321l66,0l119,327C580,423 608,508 631,587l3,0C633,503 639,390 644,296z" + id="glyph1849" /> +<glyph + unicode="N" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674z" + id="glyph1851" /> +<glyph + unicode="" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674M295,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1853" /> +<glyph + unicode="Ń" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674M371,827l-93,-117l72,0l127,117z" + id="glyph1855" /> +<glyph + unicode="Ň" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674M364,705l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1857" /> +<glyph + unicode="Ņ" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674M270,-221C332,-217 399,-187 399,-113C399,-80 379,-53 359,-40l-65,-14C312,-68 328,-91 328,-114C328,-154 292,-173 253,-180z" + id="glyph1859" /> +<glyph + unicode="Ñ" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674M257,715C259,742 267,759 283,759C297,759 311,750 330,740C352,729 370,721 391,721C437,721 459,755 458,818l-46,0C409,786 400,779 385,779C369,779 353,788 337,797C316,808 300,817 278,817C234,817 208,776 210,715z" + id="glyph1861" /> +<glyph + unicode="Ν" + horiz-adv-x="658" + d="M158,0l0,288C158,400 156,481 151,566l3,1C188,494 233,417 280,342l214,-342l88,0l0,674l-82,0l0,-282C500,287 502,205 510,115l-2,-1C476,183 437,254 387,333l-216,341l-95,0l0,-674z" + id="glyph1863" /> +<glyph + unicode="O" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614z" + id="glyph1865" /> +<glyph + unicode="Œ" + horiz-adv-x="894" + d="M857,73l-292,0l0,242l261,0l0,73l-261,0l0,213l277,0l0,73l-322,0C502,674 481,677 456,680C431,682 403,685 368,685C174,685 36,554 36,332C36,139 154,-11 363,-11C395,-11 421,-8 446,-6C470,-3 492,0 512,0l345,0M477,73C455,65 416,60 379,60C221,60 128,172 129,336C128,501 220,614 371,614C419,614 451,608 477,599z" + id="glyph1867" /> +<glyph + unicode="Ó" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M384,830l-93,-117l72,0l127,117z" + id="glyph1869" /> +<glyph + unicode="Ŏ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M220,830C222,770 258,724 343,724C429,724 466,771 468,830l-51,0C412,804 393,781 344,781C295,781 277,807 272,830z" + id="glyph1871" /> +<glyph + unicode="Ô" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M310,827l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1873" /> +<glyph + unicode="Ö" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M252,721C281,721 300,744 300,772C300,800 280,822 252,822C224,822 203,799 203,772C203,744 222,721 251,721M437,721C467,721 486,744 486,772C486,800 466,822 438,822C409,822 388,799 388,772C388,744 408,721 436,721z" + id="glyph1875" /> +<glyph + unicode="Ò" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M199,830l127,-116l72,0l-93,116z" + id="glyph1877" /> +<glyph + unicode="Ơ" + horiz-adv-x="689" + d="M344,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60M593,740C602,722 609,700 609,676C609,647 597,632 564,632C532,632 484,658 453,668C427,677 389,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,449 619,530 574,582C646,575 679,616 679,671C679,705 671,731 661,753z" + id="glyph1879" /> +<glyph + unicode="Ő" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M319,830l-80,-112l64,0l111,112M466,830l-80,-112l64,0l112,112z" + id="glyph1881" /> +<glyph + unicode="Ō" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M225,793l0,-55l239,0l0,55z" + id="glyph1883" /> +<glyph + unicode="Ω" + horiz-adv-x="705" + d="M528,73C595,129 661,228 661,366C661,544 544,685 357,685C178,685 43,550 43,358C43,228 108,128 175,73l0,-4l-145,0l0,-69l254,0l0,55C204,105 134,225 134,351C134,483 213,614 355,614C498,614 571,475 571,352C571,217 502,110 422,55l0,-55l253,0l0,69l-147,0z" + id="glyph1885" /> +<glyph + unicode="Ώ" + horiz-adv-x="742" + d="M565,73C633,127 698,226 698,365C698,543 580,685 394,685C215,685 80,549 80,357C80,227 144,128 212,73l0,-4l-146,0l0,-69l254,0l0,55C240,104 171,220 171,350C171,482 250,614 392,614C535,614 607,474 608,351C608,213 539,109 458,55l0,-55l253,0l0,69l-146,0M7,674l-7,-155l52,0l35,155z" + id="glyph1887" /> +<glyph + unicode="Ο" + horiz-adv-x="689" + d="M340,-11C511,-11 652,112 652,344C652,544 533,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11M343,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60z" + id="glyph1889" /> +<glyph + unicode="Ό" + horiz-adv-x="728" + d="M380,-11C551,-11 692,112 692,344C692,544 572,685 388,685C208,685 76,545 76,331C76,127 200,-11 379,-11M383,60C246,60 168,191 168,333C168,479 240,614 385,614C530,614 600,474 600,340C600,187 522,60 384,60M7,674l-7,-155l52,0l35,155z" + id="glyph1891" /> +<glyph + unicode="Ø" + horiz-adv-x="689" + d="M112,-43l63,86C223,8 282,-11 346,-11C507,-11 652,108 652,343C652,442 621,530 565,591l62,86l-53,36l-59,-81C467,667 409,685 347,685C171,685 36,546 36,333C36,233 69,144 125,84l-62,-88M176,158l-1,-1C145,209 125,263 125,338C125,479 199,615 346,615C396,615 438,597 472,568M513,515l3,0C551,458 563,397 563,340C563,188 485,59 344,59C290,59 251,76 218,107z" + id="glyph1893" /> +<glyph + unicode="Ǿ" + horiz-adv-x="689" + d="M112,-43l63,86C223,8 282,-11 346,-11C507,-11 652,108 652,343C652,442 621,530 565,591l62,86l-53,36l-59,-81C467,667 409,685 347,685C171,685 36,546 36,333C36,233 69,144 125,84l-62,-88M176,158l-1,-1C145,209 125,263 125,338C125,479 199,615 346,615C396,615 438,597 472,568M513,515l3,0C551,458 563,397 563,340C563,188 485,59 344,59C290,59 251,76 218,107M392,835l-93,-117l72,0l127,117z" + id="glyph1895" /> +<glyph + unicode="Õ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M267,717C269,744 277,761 293,761C307,761 321,752 340,742C362,731 380,723 401,723C447,723 469,757 468,820l-46,0C419,788 410,781 395,781C379,781 363,790 347,799C326,810 310,819 288,819C244,819 218,778 220,717z" + id="glyph1897" /> +<glyph + unicode="P" + horiz-adv-x="532" + d="M76,0l87,0l0,270C183,265 207,264 233,264C318,264 393,289 439,338C473,373 491,421 491,482C491,542 469,591 432,623C392,659 329,679 243,679C173,679 118,673 76,666M163,603C178,607 207,610 245,610C341,610 404,567 404,478C404,385 340,334 235,334C206,334 182,336 163,341z" + id="glyph1899" /> +<glyph + unicode="Φ" + horiz-adv-x="718" + d="M400,26C542,38 682,138 682,338C682,537 542,639 402,650l0,50l-84,0l0,-50C190,640 36,540 36,333C36,132 179,38 316,26l0,-52l84,0M317,89C219,101 126,187 126,335C126,497 230,581 317,587M401,587C496,578 592,496 592,336C592,182 497,98 401,89z" + id="glyph1901" /> +<glyph + unicode="Π" + horiz-adv-x="634" + d="M558,674l-482,0l0,-674l87,0l0,600l307,0l0,-600l88,0z" + id="glyph1903" /> +<glyph + unicode="Ψ" + horiz-adv-x="685" + d="M639,674l-86,0l0,-186C553,334 473,279 384,274l0,400l-82,0l0,-399C202,280 132,332 132,469l0,205l-85,0l0,-220C47,278 171,211 301,206l0,-206l83,0l0,206C533,214 639,304 639,479z" + id="glyph1905" /> +<glyph + unicode="Q" + horiz-adv-x="689" + d="M657,-26C600,-16 527,0 460,17l0,4C572,60 652,171 652,344C652,543 533,685 350,685C168,685 36,546 36,330C36,113 173,-5 333,-11C346,-11 360,-16 374,-21C452,-48 541,-75 632,-99M343,60C207,60 128,189 129,332C128,478 200,614 347,614C490,614 560,475 560,340C560,186 482,60 344,60z" + id="glyph1907" /> +<glyph + unicode="R" + horiz-adv-x="538" + d="M76,0l87,0l0,292l82,0C324,289 360,254 380,161C399,77 414,20 425,0l90,0C501,26 485,91 463,185C447,255 416,303 364,321l0,3C435,348 491,407 491,496C491,548 472,594 438,624C396,662 336,679 243,679C183,679 120,674 76,665M163,604C177,608 207,612 249,612C341,611 404,573 404,486C404,409 345,358 252,358l-89,0z" + id="glyph1909" /> +<glyph + unicode="Ŕ" + horiz-adv-x="538" + d="M76,0l87,0l0,292l82,0C324,289 360,254 380,161C399,77 414,20 425,0l90,0C501,26 485,91 463,185C447,255 416,303 364,321l0,3C435,348 491,407 491,496C491,548 472,594 438,624C396,662 336,679 243,679C183,679 120,674 76,665M163,604C177,608 207,612 249,612C341,611 404,573 404,486C404,409 345,358 252,358l-89,0M302,830l-93,-117l72,0l127,117z" + id="glyph1911" /> +<glyph + unicode="Ř" + horiz-adv-x="538" + d="M76,0l87,0l0,292l82,0C324,289 360,254 380,161C399,77 414,20 425,0l90,0C501,26 485,91 463,185C447,255 416,303 364,321l0,3C435,348 491,407 491,496C491,548 472,594 438,624C396,662 336,679 243,679C183,679 120,674 76,665M163,604C177,608 207,612 249,612C341,611 404,573 404,486C404,409 345,358 252,358l-89,0M297,713l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1913" /> +<glyph + unicode="Ŗ" + horiz-adv-x="538" + d="M76,0l87,0l0,292l82,0C324,289 360,254 380,161C399,77 414,20 425,0l90,0C501,26 485,91 463,185C447,255 416,303 364,321l0,3C435,348 491,407 491,496C491,548 472,594 438,624C396,662 336,679 243,679C183,679 120,674 76,665M163,604C177,608 207,612 249,612C341,611 404,573 404,486C404,409 345,358 252,358l-89,0M212,-215C274,-211 341,-181 341,-107C341,-74 321,-47 301,-34l-65,-14C254,-62 270,-85 270,-108C270,-148 234,-167 195,-174z" + id="glyph1915" /> +<glyph + unicode="Ρ" + horiz-adv-x="532" + d="M76,0l87,0l0,270C183,265 207,264 233,264C318,264 393,289 439,338C473,373 491,421 491,482C491,542 469,591 432,623C392,659 329,679 243,679C173,679 118,673 76,666M163,603C178,607 207,610 245,610C341,610 404,567 404,478C404,385 340,334 235,334C206,334 182,336 163,341z" + id="glyph1917" /> +<glyph + unicode="S" + horiz-adv-x="493" + d="M42,33C78,9 149,-11 214,-11C373,-11 450,80 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106z" + id="glyph1919" /> +<glyph + unicode="Ś" + horiz-adv-x="493" + d="M42,33C78,9 149,-11 214,-11C373,-11 450,80 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106M299,830l-93,-117l72,0l127,117z" + id="glyph1921" /> +<glyph + unicode="Š" + horiz-adv-x="493" + d="M42,33C78,9 149,-11 214,-11C373,-11 450,80 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106M292,713l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1923" /> +<glyph + unicode="Ş" + horiz-adv-x="493" + d="M42,33C80,8 153,-11 211,-11C213,-11 216,-11 218,-11l-40,-70C225,-86 256,-98 256,-123C256,-145 236,-155 214,-155C193,-155 175,-149 159,-140l-15,-43C161,-193 187,-200 214,-200C268,-200 317,-177 317,-120C317,-83 285,-55 247,-50l28,45C393,16 450,95 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106z" + id="glyph1925" /> +<glyph + unicode="" + horiz-adv-x="340" + d="M42,33C80,8 153,-11 211,-11C213,-11 216,-11 218,-11l-40,-70C225,-86 256,-98 256,-123C256,-145 236,-155 214,-155C193,-155 175,-149 159,-140l-15,-43C161,-193 187,-200 214,-200C268,-200 317,-177 317,-120C317,-83 285,-55 247,-50l28,45C393,16 450,95 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106z" + id="glyph1927" /> +<glyph + unicode="Ŝ" + horiz-adv-x="493" + d="M42,33C78,9 149,-11 214,-11C373,-11 450,80 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106M214,827l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1929" /> +<glyph + unicode="Ș" + horiz-adv-x="493" + d="M42,33C78,9 149,-11 214,-11C373,-11 450,80 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106M185,-222C247,-218 314,-188 314,-114C314,-81 294,-54 274,-41l-65,-14C227,-69 243,-92 243,-115C243,-155 207,-174 168,-181z" + id="glyph1931" /> +<glyph + unicode="Σ" + horiz-adv-x="552" + d="M140,76l217,262l-206,260l0,4l341,0l0,72l-453,0l0,-59l221,-280l-234,-283l0,-52l498,0l0,73l-384,0z" + id="glyph1933" /> +<glyph + unicode="T" + horiz-adv-x="497" + d="M204,0l88,0l0,600l206,0l0,74l-499,0l0,-74l205,0z" + id="glyph1935" /> +<glyph + unicode="Τ" + horiz-adv-x="497" + d="M204,0l88,0l0,600l206,0l0,74l-499,0l0,-74l205,0z" + id="glyph1937" /> +<glyph + unicode="Ŧ" + horiz-adv-x="497" + d="M498,600l0,74l-499,0l0,-74l205,0l0,-222l-124,0l0,-58l124,0l0,-320l88,0l0,320l124,0l0,58l-124,0l0,222z" + id="glyph1939" /> +<glyph + unicode="Ť" + horiz-adv-x="497" + d="M204,0l88,0l0,600l206,0l0,74l-499,0l0,-74l205,0M282,710l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph1941" /> +<glyph + unicode="Ţ" + horiz-adv-x="497" + d="M204,0l88,0l0,600l206,0l0,74l-499,0l0,-74l205,0M180,-222C242,-218 309,-188 309,-114C309,-81 289,-54 269,-41l-65,-14C222,-69 238,-92 238,-115C238,-155 202,-174 163,-181z" + id="glyph1943" /> +<glyph + unicode="Θ" + horiz-adv-x="689" + d="M341,-11C512,-11 653,112 653,344C653,544 533,685 349,685C169,685 36,545 36,331C36,127 161,-11 340,-11M344,60C206,60 128,191 128,333C128,479 200,614 346,614C491,614 561,474 561,340C561,187 483,60 345,60M209,383l0,-72l271,0l0,72z" + id="glyph1945" /> +<glyph + unicode="Þ" + horiz-adv-x="531" + d="M76,0l86,0l0,156C183,153 210,151 238,151C384,151 491,222 491,364C491,416 471,462 436,495C395,531 337,550 258,550C222,550 189,547 162,542l0,132l-86,0M162,473C178,477 210,481 250,481C342,481 404,442 404,355C404,273 345,221 237,221C211,221 185,223 162,227z" + id="glyph1947" /> +<glyph + unicode="U" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399z" + id="glyph1949" /> +<glyph + unicode="Ú" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M367,827l-93,-117l72,0l127,117z" + id="glyph1951" /> +<glyph + unicode="Ŭ" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M199,827C201,767 237,721 322,721C408,721 445,768 447,827l-51,0C391,801 372,778 323,778C274,778 256,804 251,827z" + id="glyph1953" /> +<glyph + unicode="Û" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M296,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1955" /> +<glyph + unicode="Ü" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M234,721C263,721 282,744 282,772C282,800 262,822 234,822C206,822 185,799 185,772C185,744 204,721 233,721M419,721C449,721 468,744 468,772C468,800 448,822 420,822C391,822 370,799 370,772C370,744 390,721 418,721z" + id="glyph1957" /> +<glyph + unicode="Ù" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M183,827l127,-116l72,0l-93,116z" + id="glyph1959" /> +<glyph + unicode="Ư" + horiz-adv-x="677" + d="M593,770C603,752 609,731 609,712C609,686 597,674 563,674l-79,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,340l15,1C647,624 677,658 677,707C677,735 670,760 660,782z" + id="glyph1961" /> +<glyph + unicode="Ű" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M283,827l-80,-112l64,0l111,112M430,827l-80,-112l64,0l112,112z" + id="glyph1963" /> +<glyph + unicode="Ū" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M204,788l0,-55l239,0l0,55z" + id="glyph1965" /> +<glyph + unicode="Ų" + horiz-adv-x="647" + d="M416,-135C402,-141 386,-145 370,-145C345,-145 325,-130 325,-102C325,-66 359,-26 386,-1C495,21 572,109 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,72 175,-6 308,-11C310,-11 312,-10 314,-10C287,-39 257,-80 257,-124C257,-174 291,-204 347,-204C374,-204 408,-195 429,-180z" + id="glyph1967" /> +<glyph + unicode="Υ" + horiz-adv-x="588" + d="M10,609C99,589 193,491 233,310C242,269 247,224 247,174l0,-174l87,0l0,173C334,225 343,271 353,311C399,485 510,581 584,609l-15,71C524,673 462,637 415,587C367,534 315,450 298,342l-4,0C282,448 241,532 190,589C144,643 73,677 26,680z" + id="glyph1969" /> +<glyph + unicode="Ϋ" + horiz-adv-x="588" + d="M206,697C235,697 254,720 254,747C254,775 234,797 207,797C179,797 157,775 157,747C157,720 177,697 205,697M390,697C419,697 438,720 438,747C438,775 418,797 390,797C362,797 341,775 341,747C341,720 361,697 389,697M10,609C99,589 193,491 233,310C242,269 247,224 247,174l0,-174l87,0l0,173C334,225 343,271 353,311C399,485 510,581 584,609l-15,71C524,673 462,637 415,587C367,534 315,450 298,342l-4,0C282,448 241,532 190,589C144,643 73,677 26,680z" + id="glyph1971" /> +<glyph + unicode="Ύ" + horiz-adv-x="707" + d="M128,609C216,589 310,491 351,310C359,269 365,224 365,174l0,-174l87,0l0,173C452,225 460,271 470,311C516,485 627,581 702,609l-16,71C642,673 580,637 533,587C485,534 433,450 416,342l-4,0C399,448 359,532 308,589C262,643 191,677 144,680M7,674l-7,-155l52,0l35,155z" + id="glyph1973" /> +<glyph + unicode="Ů" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M325,873C263,873 224,832 224,782C224,733 264,694 325,694C383,694 424,732 424,782C424,833 386,873 326,873M323,836C353,836 372,812 372,783C372,754 353,731 323,731C295,731 276,756 276,782C276,811 293,836 322,836z" + id="glyph1975" /> +<glyph + unicode="Ũ" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M246,715C248,742 256,759 272,759C286,759 300,750 319,740C341,729 359,721 380,721C426,721 448,755 447,818l-46,0C398,786 389,779 374,779C358,779 342,788 326,797C305,808 289,817 267,817C223,817 197,776 199,715z" + id="glyph1977" /> +<glyph + unicode="V" + horiz-adv-x="558" + d="M320,0l241,674l-93,0l-114,-333C324,253 296,168 277,90l-2,0C257,169 232,251 203,342l-105,332l-94,0l220,-674z" + id="glyph1979" /> +<glyph + unicode="W" + horiz-adv-x="846" + d="M277,0l96,351C398,438 413,504 425,571l2,0C436,503 450,437 471,351l85,-351l91,0l191,674l-89,0l-89,-340C639,250 620,175 606,101l-2,0C594,172 576,252 557,332l-82,342l-91,0l-90,-340C271,247 250,167 239,100l-2,0C225,165 207,249 187,333l-80,341l-92,0l171,-674z" + id="glyph1981" /> +<glyph + unicode="Ẃ" + horiz-adv-x="846" + d="M277,0l96,351C398,438 413,504 425,571l2,0C436,503 450,437 471,351l85,-351l91,0l191,674l-89,0l-89,-340C639,250 620,175 606,101l-2,0C594,172 576,252 557,332l-82,342l-91,0l-90,-340C271,247 250,167 239,100l-2,0C225,165 207,249 187,333l-80,341l-92,0l171,-674M477,827l-93,-117l72,0l127,117z" + id="glyph1983" /> +<glyph + unicode="Ŵ" + horiz-adv-x="846" + d="M277,0l96,351C398,438 413,504 425,571l2,0C436,503 450,437 471,351l85,-351l91,0l191,674l-89,0l-89,-340C639,250 620,175 606,101l-2,0C594,172 576,252 557,332l-82,342l-91,0l-90,-340C271,247 250,167 239,100l-2,0C225,165 207,249 187,333l-80,341l-92,0l171,-674M391,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1985" /> +<glyph + unicode="Ẅ" + horiz-adv-x="846" + d="M277,0l96,351C398,438 413,504 425,571l2,0C436,503 450,437 471,351l85,-351l91,0l191,674l-89,0l-89,-340C639,250 620,175 606,101l-2,0C594,172 576,252 557,332l-82,342l-91,0l-90,-340C271,247 250,167 239,100l-2,0C225,165 207,249 187,333l-80,341l-92,0l171,-674M334,721C363,721 382,744 382,772C382,800 362,822 334,822C306,822 285,799 285,772C285,744 304,721 333,721M519,721C549,721 568,744 568,772C568,800 548,822 520,822C491,822 470,799 470,772C470,744 490,721 518,721z" + id="glyph1987" /> +<glyph + unicode="Ẁ" + horiz-adv-x="846" + d="M277,0l96,351C398,438 413,504 425,571l2,0C436,503 450,437 471,351l85,-351l91,0l191,674l-89,0l-89,-340C639,250 620,175 606,101l-2,0C594,172 576,252 557,332l-82,342l-91,0l-90,-340C271,247 250,167 239,100l-2,0C225,165 207,249 187,333l-80,341l-92,0l171,-674M273,827l127,-116l72,0l-93,116z" + id="glyph1989" /> +<glyph + unicode="X" + horiz-adv-x="571" + d="M546,0l-210,346l205,328l-100,0l-92,-158C324,473 307,442 288,402l-3,0C267,438 248,472 223,516l-89,158l-101,0l198,-333l-206,-341l100,0l81,148C241,207 260,243 279,282l2,0C302,243 324,206 359,149l86,-149z" + id="glyph1991" /> +<glyph + unicode="Ξ" + horiz-adv-x="540" + d="M54,674l0,-72l441,0l0,72M87,385l0,-72l374,0l0,72M43,73l0,-73l462,0l0,73z" + id="glyph1993" /> +<glyph + unicode="Y" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286z" + id="glyph1995" /> +<glyph + unicode="Ý" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M320,822l-93,-117l72,0l127,117z" + id="glyph1997" /> +<glyph + unicode="Ŷ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M237,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph1999" /> +<glyph + unicode="Ÿ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M187,714C216,714 235,737 235,765C235,793 215,815 187,815C159,815 138,792 138,765C138,737 157,714 186,714M372,714C402,714 421,737 421,765C421,793 401,815 373,815C344,815 323,792 323,765C323,737 343,714 371,714z" + id="glyph2001" /> +<glyph + unicode="Ỳ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M131,820l127,-116l72,0l-93,116z" + id="glyph2003" /> +<glyph + unicode="Z" + horiz-adv-x="553" + d="M30,0l492,0l0,73l-377,0l0,3l372,545l0,53l-455,0l0,-73l342,0l0,-3l-374,-547z" + id="glyph2005" /> +<glyph + unicode="Ź" + horiz-adv-x="553" + d="M30,0l492,0l0,73l-377,0l0,3l372,545l0,53l-455,0l0,-73l342,0l0,-3l-374,-547M325,827l-93,-117l72,0l127,117z" + id="glyph2007" /> +<glyph + unicode="Ž" + horiz-adv-x="553" + d="M30,0l492,0l0,73l-377,0l0,3l372,545l0,53l-455,0l0,-73l342,0l0,-3l-374,-547M320,710l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph2009" /> +<glyph + unicode="Ż" + horiz-adv-x="553" + d="M30,0l492,0l0,73l-377,0l0,3l372,545l0,53l-455,0l0,-73l342,0l0,-3l-374,-547M281,720C309,720 330,745 330,771C330,797 309,821 282,821C253,821 231,797 231,771C231,745 253,720 280,720z" + id="glyph2011" /> +<glyph + unicode="Ζ" + horiz-adv-x="553" + d="M30,0l492,0l0,73l-377,0l0,3l372,545l0,53l-455,0l0,-73l342,0l0,-3l-374,-547z" + id="glyph2013" /> +<glyph + unicode="a" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2015" /> +<glyph + unicode="á" + horiz-adv-x="482" + d="M275,693l-88,-143l63,0l122,143M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2017" /> +<glyph + unicode="ă" + horiz-adv-x="482" + d="M117,682C117,620 153,558 241,558C316,558 365,608 365,682l-52,0C310,649 288,613 241,613C200,613 175,643 169,682M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2019" /> +<glyph + unicode="â" + horiz-adv-x="482" + d="M206,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2021" /> +<glyph + unicode="´" + horiz-adv-x="300" + d="M189,693l-88,-143l63,0l122,143z" + id="glyph2023" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M198,827l-93,-117l72,0l127,117z" + id="glyph2025" /> +<glyph + unicode="ä" + horiz-adv-x="482" + d="M149,570C178,570 198,594 198,621C198,650 177,672 149,672C121,672 98,649 98,621C98,594 119,570 148,570M334,570C363,570 383,594 383,621C383,650 362,672 334,672C306,672 284,649 284,621C284,594 304,570 333,570M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2027" /> +<glyph + unicode="æ" + horiz-adv-x="773" + d="M734,233C736,242 737,257 737,272C737,352 696,495 548,495C472,495 412,456 379,393l-2,0C357,454 308,495 221,495C173,495 113,478 70,451l21,-58C120,413 169,430 215,430C313,430 328,346 328,322l0,-20C140,304 36,239 36,127C36,57 86,-11 187,-11C270,-11 336,29 367,96l2,0C397,28 471,-11 553,-11C617,-11 673,2 712,23l-16,62C669,72 631,57 564,57C473,57 407,118 410,233M329,172C329,160 328,148 324,137C310,93 270,54 207,54C161,54 123,83 123,134C123,229 236,245 329,243M409,296C412,351 452,432 539,432C628,432 655,345 652,296z" + id="glyph2029" /> +<glyph + unicode="ǽ" + horiz-adv-x="773" + d="M429,693l-88,-143l63,0l122,143M734,233C736,242 737,257 737,272C737,352 696,495 548,495C472,495 412,456 379,393l-2,0C357,454 308,495 221,495C173,495 113,478 70,451l21,-58C120,413 169,430 215,430C313,430 328,346 328,322l0,-20C140,304 36,239 36,127C36,57 86,-11 187,-11C270,-11 336,29 367,96l2,0C397,28 471,-11 553,-11C617,-11 673,2 712,23l-16,62C669,72 631,57 564,57C473,57 407,118 410,233M329,172C329,160 328,148 324,137C310,93 270,54 207,54C161,54 123,83 123,134C123,229 236,245 329,243M409,296C412,351 452,432 539,432C628,432 655,345 652,296z" + id="glyph2031" /> +<glyph + unicode="А" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194z" + id="glyph2033" /> +<glyph + unicode="Б" + horiz-adv-x="546" + d="M76,674l0,-672C105,-2 151,-6 212,-6C304,-6 395,15 450,69C485,104 506,152 506,215C506,294 471,347 420,380C373,411 310,425 246,425C224,425 184,423 163,420l0,181l291,0l0,73M163,353C185,355 212,357 232,357C280,357 325,347 360,323C393,300 415,265 415,213C415,171 401,138 378,114C345,79 290,62 236,62C206,62 183,63 163,67z" + id="glyph2035" /> +<glyph + unicode="В" + horiz-adv-x="542" + d="M76,2C105,-2 151,-6 211,-6C321,-6 397,14 444,57C478,90 501,134 501,192C501,292 426,345 362,361l0,2C433,389 476,446 476,511C476,564 455,604 420,630C378,664 322,679 235,679C174,679 114,673 76,665M163,606C177,609 200,612 240,612C328,612 388,581 388,502C388,437 334,389 242,389l-79,0M163,323l72,0C330,323 409,285 409,193C409,95 326,62 236,62C205,62 180,63 163,66z" + id="glyph2037" /> +<glyph + unicode="Г" + horiz-adv-x="433" + d="M76,674l0,-674l87,0l0,601l271,0l0,73z" + id="glyph2039" /> +<glyph + unicode="Д" + horiz-adv-x="630" + d="M160,674l0,-171C160,387 151,297 128,219C112,166 87,117 62,71l-51,-2l6,-231l68,0l6,162l425,0l6,-162l68,0l6,231l-59,2l0,603M241,602l209,0l0,-530l-298,0C172,108 193,153 207,200C231,276 241,367 241,475z" + id="glyph2041" /> +<glyph + unicode="Е" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0z" + id="glyph2043" /> +<glyph + unicode="Ё" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M163,721C192,721 211,744 211,772C211,800 191,822 163,822C135,822 114,799 114,772C114,744 133,721 162,721M348,721C378,721 397,744 397,772C397,800 377,822 349,822C320,822 299,799 299,772C299,744 319,721 347,721z" + id="glyph2045" /> +<glyph + unicode="Ж" + horiz-adv-x="801" + d="M21,674l239,-310C155,351 112,285 78,190C56,127 40,62 11,0l89,0C122,45 138,110 154,158C186,250 220,309 328,309l30,0l0,-309l85,0l0,309l29,0C580,309 614,250 645,158C662,110 678,45 701,0l89,0C762,62 746,127 723,191C690,287 647,353 541,364l239,310l-102,0l-208,-299l-27,0l0,299l-85,0l0,-299l-26,0l-209,299z" + id="glyph2047" /> +<glyph + unicode="З" + horiz-adv-x="492" + d="M74,570C110,593 163,614 214,614C287,614 335,576 335,508C335,439 266,384 177,384l-40,0l0,-66l40,0C263,318 360,289 360,188C360,114 305,60 205,60C150,60 90,80 54,101l-23,-68C82,2 146,-11 207,-11C330,-11 451,45 451,185C451,282 382,344 287,354l0,2C366,374 426,437 426,520C426,624 345,685 228,685C155,685 96,664 50,634z" + id="glyph2049" /> +<glyph + unicode="И" + horiz-adv-x="658" + d="M76,674l0,-674l93,0l211,335C426,410 472,488 505,562l3,-1C501,476 500,396 500,287l0,-287l82,0l0,674l-87,0l-218,-347C228,247 186,174 152,104l-2,1C156,195 158,284 158,390l0,284z" + id="glyph2051" /> +<glyph + unicode="Й" + horiz-adv-x="658" + d="M76,674l0,-674l93,0l211,335C426,410 472,488 505,562l3,-1C501,476 500,396 500,287l0,-287l82,0l0,674l-87,0l-218,-347C228,247 186,174 152,104l-2,1C156,195 158,284 158,390l0,284M201,816C206,750 245,708 327,708C412,708 452,749 457,816l-67,0C386,783 375,753 329,753C282,753 272,784 268,816z" + id="glyph2053" /> +<glyph + unicode="К" + horiz-adv-x="542" + d="M76,674l0,-674l86,0l0,309l28,0C304,309 347,248 378,159C396,106 415,46 439,0l94,0C503,60 484,122 459,191C424,282 381,350 270,365l255,309l-105,0l-231,-299l-27,0l0,299z" + id="glyph2055" /> +<glyph + unicode="Л" + horiz-adv-x="594" + d="M123,674l0,-284C123,282 118,151 64,98C48,82 20,67 -7,62l11,-70C43,-8 82,8 107,24C202,88 208,251 208,397l0,204l223,0l0,-601l88,0l0,674z" + id="glyph2057" /> +<glyph + unicode="М" + horiz-adv-x="804" + d="M661,0l85,0l-42,674l-111,0l-120,-326C443,263 419,189 402,121l-3,0C382,191 359,265 331,348l-115,326l-111,0l-47,-674l83,0l18,289C165,390 170,503 172,587l2,0C193,507 220,420 252,325l109,-321l66,0l119,327C580,423 608,508 631,587l3,0C633,503 639,390 644,296z" + id="glyph2059" /> +<glyph + unicode="Н" + horiz-adv-x="652" + d="M76,674l0,-674l87,0l0,316l326,0l0,-316l88,0l0,674l-88,0l0,-282l-326,0l0,282z" + id="glyph2061" /> +<glyph + unicode="О" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614z" + id="glyph2063" /> +<glyph + unicode="П" + horiz-adv-x="639" + d="M76,674l0,-674l87,0l0,601l313,0l0,-601l87,0l0,674z" + id="glyph2065" /> +<glyph + unicode="Р" + horiz-adv-x="532" + d="M76,0l87,0l0,270C183,265 207,264 233,264C318,264 393,289 439,338C473,373 491,421 491,482C491,542 469,591 432,623C392,659 329,679 243,679C173,679 118,673 76,666M163,603C178,607 207,610 245,610C341,610 404,567 404,478C404,385 340,334 235,334C206,334 182,336 163,341z" + id="glyph2067" /> +<glyph + unicode="С" + horiz-adv-x="580" + d="M529,91C494,74 440,63 387,63C223,63 128,169 128,334C128,511 233,612 391,612C447,612 494,600 527,584l21,71C525,667 472,685 388,685C179,685 36,542 36,331C36,110 179,-11 369,-11C451,-11 515,6 547,22z" + id="glyph2069" /> +<glyph + unicode="Т" + horiz-adv-x="497" + d="M204,0l88,0l0,600l206,0l0,74l-499,0l0,-74l205,0z" + id="glyph2071" /> +<glyph + unicode="У" + horiz-adv-x="521" + d="M2,674l224,-456C234,202 236,193 229,178C208,129 167,62 97,62C82,62 72,63 65,65l-9,-71C67,-10 83,-12 105,-12C165,-12 214,16 258,71C305,133 347,236 384,327l141,347l-92,0l-105,-286C311,341 298,301 286,267l-2,0C268,306 252,346 235,386l-134,288z" + id="glyph2073" /> +<glyph + unicode="Ф" + horiz-adv-x="725" + d="M322,701l0,-60C192,633 36,554 36,333C36,120 179,45 320,36l0,-63l84,0l0,63C547,44 689,125 689,337C689,549 547,635 405,641l0,60M321,98C222,108 126,176 126,337C126,512 233,573 321,578M404,578C501,573 599,511 599,338C599,171 501,106 404,98z" + id="glyph2075" /> +<glyph + unicode="Х" + horiz-adv-x="571" + d="M546,0l-210,346l205,328l-100,0l-92,-158C324,473 307,442 288,402l-3,0C267,438 248,472 223,516l-89,158l-101,0l198,-333l-206,-341l100,0l81,148C241,207 260,243 279,282l2,0C302,243 324,206 359,149l86,-149z" + id="glyph2077" /> +<glyph + unicode="Ц" + horiz-adv-x="652" + d="M76,674l0,-674l462,0l6,-162l69,0l6,231l-59,2l0,603l-88,0l0,-601l-309,0l0,601z" + id="glyph2079" /> +<glyph + unicode="Ч" + horiz-adv-x="597" + d="M71,674l0,-229C71,280 181,242 270,242C329,242 385,257 432,284l2,0l0,-288l88,0l0,678l-88,0l0,-322C399,330 348,313 301,313C206,313 158,366 158,458l0,216z" + id="glyph2081" /> +<glyph + unicode="Ш" + horiz-adv-x="847" + d="M76,674l0,-674l696,0l0,674l-87,0l0,-601l-218,0l0,601l-86,0l0,-601l-219,0l0,601z" + id="glyph2083" /> +<glyph + unicode="Щ" + horiz-adv-x="864" + d="M76,674l0,-674l675,0l5,-162l69,0l6,231l-59,2l0,603l-87,0l0,-601l-218,0l0,601l-86,0l0,-601l-219,0l0,601z" + id="glyph2085" /> +<glyph + unicode="Ъ" + horiz-adv-x="631" + d="M-2,674l0,-72l173,0l0,-600C200,-2 246,-6 305,-6C395,-6 484,14 540,73C572,107 592,154 592,217C592,364 469,427 339,427C313,427 278,425 258,422l0,252M258,353C279,356 305,358 328,358C416,358 500,318 500,213C500,111 424,62 327,62C299,62 277,63 258,67z" + id="glyph2087" /> +<glyph + unicode="Ы" + horiz-adv-x="746" + d="M76,674l0,-672C105,-2 151,-6 211,-6C302,-6 396,15 451,73C483,108 503,154 503,216C503,366 382,427 243,427C221,427 183,425 163,422l0,252M163,353C174,355 200,358 232,358C322,358 412,318 412,213C412,111 329,62 233,62C203,62 181,63 163,67M578,674l0,-674l88,0l0,674z" + id="glyph2089" /> +<glyph + unicode="Ь" + horiz-adv-x="545" + d="M76,674l0,-672C105,-2 151,-6 212,-6C305,-6 399,15 454,73C486,108 506,154 506,217C506,366 384,427 246,427C223,427 183,425 163,422l0,252M163,353C185,356 212,358 234,358C330,358 415,318 415,212C415,109 332,62 235,62C205,62 183,63 163,67z" + id="glyph2091" /> +<glyph + unicode="Э" + horiz-adv-x="568" + d="M123,310l315,0C432,170 343,62 194,62C144,62 88,75 49,93l-19,-69C78,2 139,-11 206,-11C385,-11 531,115 531,336C531,532 423,685 211,685C147,685 89,667 41,646l21,-67C105,598 152,613 206,613C345,613 426,510 437,380l-314,0z" + id="glyph2093" /> +<glyph + unicode="Ю" + horiz-adv-x="867" + d="M76,674l0,-674l87,0l0,310l98,0C268,113 378,-11 541,-11C701,-11 831,110 831,344C831,544 725,685 550,685C391,685 281,571 263,383l-100,0l0,291M544,59C418,59 347,191 347,333C347,479 412,615 546,615C679,615 742,474 742,340C742,187 672,59 545,59z" + id="glyph2095" /> +<glyph + unicode="Я" + horiz-adv-x="543" + d="M108,0C118,18 123,32 130,49C154,113 173,212 219,259C243,283 274,292 316,292l64,0l0,-292l87,0l0,665C423,674 363,679 306,679C221,679 155,661 113,627C75,596 51,549 51,490C51,398 117,338 200,324l0,-3C180,315 160,303 144,287C89,232 71,138 42,61C34,39 25,18 15,0M380,359C363,358 335,358 306,358C211,358 139,405 139,486C139,579 212,612 298,612C337,612 365,607 380,603z" + id="glyph2097" /> +<glyph + unicode="Ґ" + horiz-adv-x="447" + d="M76,674l0,-674l87,0l0,601l274,0l11,195l-64,0l-16,-122z" + id="glyph2099" /> +<glyph + unicode="Ђ" + horiz-adv-x="665" + d="M-1,674l0,-73l187,0l0,-601l87,0l0,338C303,360 343,377 387,377C438,377 474,355 500,316C523,280 534,230 534,170C534,95 516,37 487,-1C458,-40 416,-61 377,-71l16,-68C437,-132 490,-110 532,-63C591,-13 625,75 625,178C625,246 611,314 574,366C537,418 485,450 411,450C359,450 308,429 276,409l-3,0l0,192l244,0l0,73z" + id="glyph2101" /> +<glyph + unicode="Ѓ" + horiz-adv-x="433" + d="M76,674l0,-674l87,0l0,601l271,0l0,73M283,827l-93,-117l72,0l127,117z" + id="glyph2103" /> +<glyph + unicode="Є" + horiz-adv-x="576" + d="M544,653C520,665 472,685 387,685C168,685 36,526 36,336C36,118 180,-11 371,-11C451,-11 510,6 543,22l-16,67C488,72 439,62 387,62C233,62 135,173 131,309l345,0l0,70l-346,0C144,509 237,613 390,613C442,613 490,601 523,585z" + id="glyph2105" /> +<glyph + unicode="Ѕ" + horiz-adv-x="493" + d="M42,33C78,9 149,-11 214,-11C373,-11 450,80 450,184C450,283 392,338 278,382C185,418 144,449 144,512C144,558 179,613 271,613C332,613 377,593 399,581l24,71C393,669 343,685 274,685C143,685 56,607 56,502C56,407 124,350 234,311C325,276 361,240 361,177C361,109 309,62 220,62C160,62 103,82 64,106z" + id="glyph2107" /> +<glyph + unicode="І" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674z" + id="glyph2109" /> +<glyph + unicode="Ї" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M28,721C57,721 76,744 76,772C76,800 56,822 28,822C0,822 -21,799 -21,772C-21,744 -2,721 27,721M213,721C243,721 262,744 262,772C262,800 242,822 214,822C185,822 164,799 164,772C164,744 184,721 212,721z" + id="glyph2111" /> +<glyph + unicode="Ј" + horiz-adv-x="370" + d="M214,230C214,98 168,63 90,63C61,63 35,69 17,76l-13,-71C26,-4 65,-11 97,-11C213,-11 301,44 301,223l0,451l-87,0z" + id="glyph2113" /> +<glyph + unicode="Љ" + horiz-adv-x="881" + d="M125,674l0,-284C125,282 120,151 66,99C50,83 22,67 -6,62l13,-70C43,-8 84,8 108,24C202,88 209,251 209,397l0,204l205,0l0,-599C443,-2 488,-6 549,-6C642,-6 730,15 786,69C820,104 842,152 842,215C842,364 718,425 583,425C561,425 522,423 501,420l0,254M501,353C523,355 550,357 571,357C665,357 751,318 751,213C751,111 668,62 571,62C543,62 522,63 501,66z" + id="glyph2115" /> +<glyph + unicode="Њ" + horiz-adv-x="897" + d="M76,674l0,-674l87,0l0,331l267,0l0,-329C459,-2 504,-6 566,-6C662,-6 746,13 801,68C836,102 858,149 858,214C858,356 741,416 609,416C574,416 538,414 517,411l0,263l-87,0l0,-269l-267,0l0,269M517,344C536,346 560,348 591,348C684,348 767,310 767,211C767,107 683,62 588,62C559,62 537,63 517,67z" + id="glyph2117" /> +<glyph + unicode="Ћ" + horiz-adv-x="675" + d="M-1,674l0,-73l184,0l0,-601l88,0l0,340C303,363 355,383 398,383C436,383 463,371 484,349C507,324 516,289 516,239l0,-239l88,0l0,251C604,318 587,371 551,408C522,437 482,454 426,454C369,454 311,431 273,408l-2,0l0,193l235,0l0,73z" + id="glyph2119" /> +<glyph + unicode="Ќ" + horiz-adv-x="542" + d="M76,674l0,-674l86,0l0,309l28,0C304,309 347,248 378,159C396,106 415,46 439,0l94,0C503,60 484,122 459,191C424,282 381,350 270,365l255,309l-105,0l-231,-299l-27,0l0,299M318,822l-93,-117l72,0l127,117z" + id="glyph2121" /> +<glyph + unicode="Ў" + horiz-adv-x="521" + d="M2,674l224,-456C234,202 236,193 229,178C208,129 167,62 97,62C82,62 72,63 65,65l-9,-71C67,-10 83,-12 105,-12C165,-12 214,16 258,71C305,133 347,236 384,327l141,347l-92,0l-105,-286C311,341 298,301 286,267l-2,0C268,306 252,346 235,386l-134,288M133,817C138,751 177,709 259,709C344,709 384,750 389,817l-67,0C318,784 307,754 261,754C214,754 204,785 200,817z" + id="glyph2123" /> +<glyph + unicode="а" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2125" /> +<glyph + unicode="б" + horiz-adv-x="528" + d="M443,721C410,709 362,701 323,695C229,679 162,648 115,581C63,513 40,423 40,313C40,152 96,-11 266,-11C408,-11 490,87 490,245C490,399 403,484 284,484C215,484 147,445 116,375l-3,0C117,426 135,491 166,535C203,588 260,614 338,626C370,631 414,639 439,648M401,238C401,149 365,54 268,54C172,54 133,170 133,250C133,289 141,324 157,353C177,392 214,419 266,419C370,419 401,316 401,239z" + id="glyph2127" /> +<glyph + unicode="в" + horiz-adv-x="500" + d="M71,0C97,-2 153,-6 207,-6C302,-6 462,10 462,137C462,207 410,247 339,257l0,2C394,271 440,308 440,368C440,476 306,489 225,489C168,489 105,484 71,478M155,424C176,426 194,428 223,428C311,428 353,405 353,357C353,300 286,281 221,281l-66,0M154,223l64,0C302,223 372,208 372,138C372,68 284,54 225,54C197,54 179,55 154,58z" + id="glyph2129" /> +<glyph + unicode="г" + horiz-adv-x="385" + d="M71,484l0,-484l87,0l0,413l210,0l0,71z" + id="glyph2131" /> +<glyph + unicode="д" + horiz-adv-x="530" + d="M125,484l0,-126C125,290 116,225 97,165C86,130 70,97 49,66l-41,-1l4,-219l69,0l4,154l335,0l4,-154l69,0l5,219l-50,1l0,418M203,417l159,0l0,-350l-223,0C154,94 168,124 178,157C194,211 203,273 203,337z" + id="glyph2133" /> +<glyph + unicode="е" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2135" /> +<glyph + unicode="ё" + horiz-adv-x="501" + d="M176,570C205,570 225,594 225,621C225,650 204,672 176,672C148,672 125,649 125,621C125,594 146,570 175,570M361,570C390,570 410,594 410,621C410,650 389,672 361,672C333,672 311,649 311,621C311,594 331,570 360,570M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2137" /> +<glyph + unicode="ж" + horiz-adv-x="667" + d="M13,484l192,-220C132,256 89,213 59,135C42,90 26,39 6,0l85,0C108,30 121,68 137,110C167,189 208,214 276,214l16,0l0,-214l84,0l0,214l15,0C454,214 499,190 529,111C543,69 557,29 573,0l88,0C641,38 625,89 608,136C579,213 536,256 463,264l191,220l-101,0l-160,-211l-17,0l0,211l-84,0l0,-211l-18,0l-160,211z" + id="glyph2139" /> +<glyph + unicode="з" + horiz-adv-x="431" + d="M113,221l44,0C230,221 298,203 298,138C298,90 252,55 179,55C133,55 86,69 51,88l-23,-59C71,5 128,-10 185,-10C287,-10 394,29 394,134C394,205 334,249 262,255l0,2C316,268 371,307 371,367C371,461 281,494 207,494C151,494 95,480 46,450l22,-54C102,417 146,430 185,430C240,430 279,403 279,360C279,315 229,281 155,281l-42,0z" + id="glyph2141" /> +<glyph + unicode="и" + horiz-adv-x="554" + d="M71,484l0,-484l101,0C224,85 277,178 323,253C351,302 372,341 403,403l3,0C402,325 400,284 400,205l0,-205l84,0l0,484l-103,0l-151,-250C198,178 181,144 152,86l-3,0C153,155 154,203 154,280l0,204z" + id="glyph2143" /> +<glyph + unicode="й" + horiz-adv-x="554" + d="M71,484l0,-484l101,0C224,85 277,178 323,253C351,302 372,341 403,403l3,0C402,325 400,284 400,205l0,-205l84,0l0,484l-103,0l-151,-250C198,178 181,144 152,86l-3,0C153,155 154,203 154,280l0,204M147,681C151,612 185,557 273,557C350,557 401,599 406,681l-64,0C338,637 318,605 275,605C234,605 213,638 211,681z" + id="glyph2145" /> +<glyph + unicode="к" + horiz-adv-x="479" + d="M73,484l0,-484l87,0l0,213l17,0C259,213 304,177 337,99C352,59 369,21 382,0l92,0C452,39 435,86 422,118C384,205 337,257 259,265l211,219l-107,0l-182,-210l-21,0l0,210z" + id="glyph2147" /> +<glyph + unicode="л" + horiz-adv-x="507" + d="M100,484l0,-193C100,183 95,115 50,83C38,73 19,65 2,63l9,-70C56,-7 83,4 107,24C170,65 183,155 183,291l0,123l166,0l0,-414l87,0l0,484z" + id="glyph2149" /> +<glyph + unicode="м" + horiz-adv-x="639" + d="M42,0l81,0l12,229C138,282 141,346 143,403l4,0C159,358 180,288 195,242l86,-239l64,0l93,244C465,321 478,360 491,404l3,0C496,345 500,283 503,231l11,-231l84,0l-34,484l-109,0l-78,-211C358,220 331,143 320,107l-2,0C307,145 286,204 274,240l-88,244l-108,0z" + id="glyph2151" /> +<glyph + unicode="н" + horiz-adv-x="546" + d="M71,484l0,-484l87,0l0,219l230,0l0,-219l87,0l0,484l-87,0l0,-195l-230,0l0,195z" + id="glyph2153" /> +<glyph + unicode="о" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2155" /> +<glyph + unicode="п" + horiz-adv-x="541" + d="M71,484l0,-484l87,0l0,414l225,0l0,-414l88,0l0,484z" + id="glyph2157" /> +<glyph + unicode="р" + horiz-adv-x="569" + d="M73,-198l87,0l0,263l2,0C191,17 247,-11 311,-11C425,-11 531,75 531,249C531,396 443,495 326,495C247,495 190,460 154,401l-2,0l-4,83l-79,0C71,438 73,388 73,326M160,280C160,292 163,305 166,316C183,382 239,425 299,425C392,425 443,342 443,245C443,134 389,58 296,58C233,58 180,100 164,161C162,172 160,184 160,197z" + id="glyph2159" /> +<glyph + unicode="с" + horiz-adv-x="448" + d="M403,84C378,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,89 133,-11 279,-11C344,-11 395,6 418,18z" + id="glyph2161" /> +<glyph + unicode="т" + horiz-adv-x="411" + d="M11,484l0,-70l151,0l0,-414l87,0l0,414l151,0l0,70z" + id="glyph2163" /> +<glyph + unicode="у" + horiz-adv-x="471" + d="M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph2165" /> +<glyph + unicode="ф" + horiz-adv-x="641" + d="M280,708l1,-215C145,482 38,398 38,238C38,87 141,-1 281,-10l-1,-188l82,0l-1,189C496,0 604,84 604,243C604,394 502,483 361,493l1,215M281,51C185,63 125,141 125,241C125,340 183,420 281,433M361,433C458,419 517,343 517,243C517,144 458,64 361,51z" + id="glyph2167" /> +<glyph + unicode="х" + horiz-adv-x="463" + d="M16,484l164,-237l-172,-247l97,0l70,109C193,138 210,163 226,193l2,0C245,164 261,137 280,109l71,-109l100,0l-170,250l165,234l-95,0l-68,-103C267,355 251,330 235,301l-3,0C216,328 201,353 183,380l-69,104z" + id="glyph2169" /> +<glyph + unicode="ц" + horiz-adv-x="550" + d="M71,484l0,-484l370,0l4,-154l68,0l5,219l-50,1l0,418l-87,0l0,-415l-223,0l0,415z" + id="glyph2171" /> +<glyph + unicode="ч" + horiz-adv-x="515" + d="M65,484l0,-174C65,200 132,153 224,153C270,153 322,168 355,190l2,0l0,-196l87,0l0,490l-87,0l0,-233C330,232 291,218 253,218C176,218 153,260 153,321l0,163z" + id="glyph2173" /> +<glyph + unicode="ш" + horiz-adv-x="738" + d="M71,484l0,-484l597,0l0,484l-87,0l0,-415l-169,0l0,415l-86,0l0,-415l-169,0l0,415z" + id="glyph2175" /> +<glyph + unicode="щ" + horiz-adv-x="749" + d="M71,484l0,-484l569,0l4,-154l68,0l5,219l-49,1l0,418l-87,0l0,-415l-169,0l0,415l-86,0l0,-415l-169,0l0,415z" + id="glyph2177" /> +<glyph + unicode="ъ" + horiz-adv-x="579" + d="M14,484l0,-69l143,0l0,-413C194,-2 239,-5 287,-5C358,-5 439,5 492,50C523,76 543,111 543,162C543,287 428,325 328,325C298,325 268,324 245,321l0,163M244,257C265,260 284,262 306,262C376,262 455,242 455,159C455,82 383,57 310,57C281,57 262,59 244,62z" + id="glyph2179" /> +<glyph + unicode="ы" + horiz-adv-x="665" + d="M71,484l0,-482C107,-2 152,-5 201,-5C265,-5 343,6 396,51C427,77 446,112 446,163C446,282 341,325 237,325C205,325 177,323 158,319l0,165M158,257C176,260 198,262 223,262C289,262 359,238 359,160C359,85 286,57 219,57C194,57 175,59 158,61M507,484l0,-484l87,0l0,484z" + id="glyph2181" /> +<glyph + unicode="ь" + horiz-adv-x="498" + d="M71,484l0,-482C107,-2 152,-5 201,-5C272,-5 358,6 411,51C442,77 462,112 462,162C462,291 347,325 238,325C212,325 179,324 158,320l0,164M158,257C178,260 198,262 221,262C286,262 374,244 374,160C374,79 289,57 223,57C194,57 175,59 158,61z" + id="glyph2183" /> +<glyph + unicode="э" + horiz-adv-x="464" + d="M93,216l243,0C328,120 263,59 166,59C109,59 68,75 45,85l-17,-63C67,2 117,-11 169,-11C333,-11 426,99 426,240C426,319 401,383 360,426C317,471 254,495 184,495C133,495 77,481 35,457l19,-57C80,412 119,427 170,427C278,427 329,359 337,276l-244,0z" + id="glyph2185" /> +<glyph + unicode="ю" + horiz-adv-x="705" + d="M71,484l0,-484l86,0l0,213l81,0C248,75 333,-11 449,-11C571,-11 667,89 667,246C667,395 581,495 455,495C339,495 255,408 240,280l-83,0l0,204M450,54C365,54 322,145 322,239C322,337 367,429 452,429C538,429 579,335 579,244C579,152 539,54 451,54z" + id="glyph2187" /> +<glyph + unicode="я" + horiz-adv-x="496" + d="M426,480C384,485 328,490 270,490C207,490 148,480 108,457C71,436 44,402 44,352C44,278 109,239 172,232l0,-3C150,225 132,214 117,201C74,165 62,102 41,55C32,34 22,16 11,0l94,0C112,12 119,25 125,40C144,87 157,147 193,176C212,193 237,203 274,203l65,0l0,-203l87,0M339,263C319,263 300,263 264,263C212,263 135,285 135,347C135,414 206,431 266,431C297,431 317,429 339,426z" + id="glyph2189" /> +<glyph + unicode="ґ" + horiz-adv-x="392" + d="M71,484l0,-484l87,0l0,414l211,0l6,179l-63,0l-12,-109z" + id="glyph2191" /> +<glyph + unicode="ђ" + horiz-adv-x="557" + d="M7,625l0,-63l82,0l0,-562l88,0l0,305C177,329 182,346 192,360C220,394 260,418 299,418C339,418 371,396 391,364C421,314 429,254 429,194C429,89 405,15 370,-25C343,-55 314,-67 276,-75l17,-69C339,-139 383,-118 418,-88C480,-36 517,64 517,199C517,279 503,355 464,411C433,460 386,489 326,489C267,489 214,459 179,420l-2,0l0,142l226,0l0,63l-226,0l0,85l-88,0l0,-85z" + id="glyph2193" /> +<glyph + unicode="ѓ" + horiz-adv-x="385" + d="M71,484l0,-484l87,0l0,413l210,0l0,71M250,693l-88,-143l63,0l122,143z" + id="glyph2195" /> +<glyph + unicode="є" + horiz-adv-x="460" + d="M432,463C409,476 360,495 302,495C148,495 38,392 38,237C38,97 129,-11 285,-11C354,-11 408,8 432,21l-18,62C391,73 351,58 299,58C203,58 136,120 129,216l244,0l0,60l-245,0C138,359 195,427 301,427C352,427 388,411 411,400z" + id="glyph2197" /> +<glyph + unicode="ѕ" + horiz-adv-x="396" + d="M39,23C73,3 122,-11 175,-11C290,-11 356,50 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2199" /> +<glyph + unicode="і" + horiz-adv-x="234" + d="M161,0l0,484l-88,0l0,-484M117,675C85,675 62,651 62,620C62,590 84,566 115,566C150,566 172,590 171,620C171,651 150,675 117,675z" + id="glyph2201" /> +<glyph + unicode="ї" + horiz-adv-x="234" + d="M25,570C54,570 74,594 74,621C74,650 53,672 25,672C-3,672 -26,649 -26,621C-26,594 -5,570 24,570M210,570C239,570 259,594 259,621C259,650 238,672 210,672C182,672 160,649 160,621C160,594 180,570 209,570M161,0l0,484l-88,0l0,-484z" + id="glyph2203" /> +<glyph + unicode="ј" + horiz-adv-x="243" + d="M-36,-211C11,-211 75,-195 114,-156C157,-112 172,-51 172,43l0,441l-88,0l0,-407C84,-39 75,-78 51,-105C30,-128 -5,-139 -45,-142M129,675C96,675 73,651 73,620C73,591 94,566 127,566C162,566 183,591 182,620C182,651 161,675 129,675z" + id="glyph2205" /> +<glyph + unicode="љ" + horiz-adv-x="775" + d="M105,484l0,-193C105,185 96,114 50,82C39,73 19,65 2,63l9,-70C56,-7 83,5 106,23C169,65 187,155 187,291l0,123l162,0l0,-412C386,-2 433,-5 480,-5C550,-5 638,6 690,51C720,77 739,112 739,161C739,287 624,324 516,324C491,324 458,323 437,320l0,164M436,256C457,259 478,262 506,262C568,262 651,242 651,159C651,79 566,57 501,57C473,57 454,59 436,61z" + id="glyph2207" /> +<glyph + unicode="њ" + horiz-adv-x="782" + d="M71,484l0,-484l87,0l0,236l204,0l0,-235C398,-2 445,-5 493,-5C563,-5 645,4 697,49C727,75 746,110 746,160C746,281 631,321 532,321C504,321 470,318 450,315l0,169l-88,0l0,-180l-204,0l0,180M449,253C469,256 490,258 515,258C581,258 659,238 659,157C659,79 578,57 515,57C485,57 466,59 449,61z" + id="glyph2209" /> +<glyph + unicode="ћ" + horiz-adv-x="571" + d="M7,625l0,-63l82,0l0,-562l88,0l0,283C177,300 179,317 183,329C199,374 242,417 301,417C384,417 413,351 413,273l0,-273l88,0l0,283C501,418 433,489 332,489C297,489 266,481 239,465C216,450 195,430 179,406l-2,0l0,156l202,0l0,63l-202,0l0,85l-88,0l0,-85z" + id="glyph2211" /> +<glyph + unicode="ќ" + horiz-adv-x="479" + d="M73,484l0,-484l87,0l0,213l17,0C259,213 304,177 337,99C352,59 369,21 382,0l92,0C452,39 435,86 422,118C384,205 337,257 259,265l211,219l-107,0l-182,-210l-21,0l0,210M280,693l-88,-143l63,0l122,143z" + id="glyph2213" /> +<glyph + unicode="ў" + horiz-adv-x="471" + d="M106,681C110,612 144,557 232,557C309,557 360,599 365,681l-64,0C297,637 277,605 234,605C193,605 172,638 170,681M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph2215" /> +<glyph + unicode="Џ" + horiz-adv-x="635" + d="M76,674l0,-674l199,0l7,-174l72,0l8,174l199,0l0,674l-87,0l0,-601l-311,0l0,601z" + id="glyph2217" /> +<glyph + unicode="џ" + horiz-adv-x="538" + d="M71,484l0,-484l157,0l7,-157l70,0l6,157l157,0l0,484l-88,0l0,-415l-222,0l0,415z" + id="glyph2219" /> +<glyph + unicode="ә" + horiz-adv-x="328" + d="M78,400C113,415 151,426 214,426C304,426 376,377 378,257l-339,0C37,248 36,234 36,216C36,127 77,-11 235,-11C376,-11 462,104 462,250C462,395 373,495 225,495C148,495 94,477 63,462M377,194C370,133 333,53 242,53C144,53 119,139 120,194z" + id="glyph2221" /> +<glyph + unicode="ℓ" + horiz-adv-x="504" + d="M194,511C194,611 227,652 263,652C299,652 325,625 325,559C325,470 276,380 194,296M432,151C407,115 361,64 296,64C237,64 196,98 194,198l0,14C326,329 398,438 398,560C398,653 348,714 268,714C179,714 110,651 110,495l0,-264C85,210 58,191 31,172l26,-49C75,135 93,147 112,160C112,158 112,155 112,152C122,58 172,-10 275,-10C361,-10 428,29 475,111z" + id="glyph2223" /> +<glyph + unicode="№" + horiz-adv-x="916" + d="M728,640C642,640 569,576 569,463C569,354 638,292 727,292C807,292 885,343 885,468C885,572 825,640 729,640M727,590C789,590 813,524 813,466C813,393 780,341 727,341C680,341 641,393 641,464C641,524 668,590 726,590M859,197l0,52l-263,0l0,-52M152,0l0,269C152,384 150,463 146,547l3,1C179,474 214,400 251,328l172,-328l89,0l0,654l-78,0l0,-266C434,284 436,204 443,113l-1,-1C414,180 380,252 340,329l-170,325l-96,0l0,-654z" + id="glyph2225" /> +<glyph + unicode="à" + horiz-adv-x="482" + d="M95,693l122,-143l62,0l-87,143M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2227" /> +<glyph + unicode="α" + horiz-adv-x="551" + d="M416,484C412,460 409,436 407,398l-4,0C377,459 324,495 254,495C142,495 38,390 38,232C38,85 121,-11 236,-11C313,-11 369,33 401,91l3,0C409,17 444,-11 487,-11C497,-11 511,-10 517,-7l7,63C490,57 476,81 476,138C476,267 484,433 489,484M127,235C127,345 183,426 265,426C329,426 377,367 389,302C393,285 393,269 393,250C393,223 392,204 387,182C372,118 319,59 258,59C174,59 127,143 127,234z" + id="glyph2229" /> +<glyph + unicode="ά" + horiz-adv-x="551" + d="M286,686l-54,-143l59,0l83,143M416,484C412,460 409,436 407,398l-4,0C377,459 324,495 254,495C142,495 38,390 38,232C38,85 121,-11 236,-11C313,-11 369,33 401,91l3,0C409,17 444,-11 487,-11C497,-11 511,-10 517,-7l7,63C490,57 476,81 476,138C476,267 484,433 489,484M127,235C127,345 183,426 265,426C329,426 377,367 389,302C393,285 393,269 393,250C393,223 392,204 387,182C372,118 319,59 258,59C174,59 127,143 127,234z" + id="glyph2231" /> +<glyph + unicode="ā" + horiz-adv-x="482" + d="M129,643l0,-57l225,0l0,57M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2233" /> +<glyph + unicode="&" + horiz-adv-x="605" + d="M602,0C562,43 527,81 491,119C541,178 570,262 588,368l-80,0C495,283 474,218 443,171C400,219 336,293 279,359l0,3C386,415 427,471 427,542C427,629 362,685 278,685C166,685 107,601 107,519C107,471 128,422 163,378l0,-3C86,333 31,270 31,178C31,77 107,-11 238,-11C312,-11 377,12 435,66C461,38 479,19 498,0M253,55C172,55 115,114 115,191C115,262 166,305 202,328C277,242 352,160 392,116C360,81 311,55 254,55M272,625C327,625 350,579 350,538C350,481 306,445 238,406C209,442 187,479 187,527C187,579 216,625 271,625z" + id="glyph2235" /> +<glyph + unicode="·" + horiz-adv-x="217" + d="M108,251C147,251 172,281 172,319C172,359 147,387 109,387C71,387 45,359 45,319C44,281 71,251 107,251z" + id="glyph2237" /> +<glyph + unicode="ą" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-58C323,-25 293,-71 293,-121C293,-176 331,-206 382,-206C409,-206 442,-199 465,-182l-14,46C438,-142 424,-146 405,-146C377,-146 357,-129 357,-96C357,-61 381,-20 396,0l25,0C415,32 413,71 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2239" /> +<glyph + unicode="≈" + horiz-adv-x="596" + d="M533,416C508,390 463,358 417,358C367,358 334,379 304,392C271,405 235,428 176,428C113,428 55,389 23,351l35,-42C85,337 130,368 176,368C226,368 260,346 290,334C322,320 360,297 418,297C480,297 538,335 570,375M538,218C513,189 468,159 421,159C372,159 338,181 308,193C275,207 239,229 180,229C118,229 59,191 28,153l34,-43C89,139 135,169 179,169C230,169 264,147 294,135C327,122 364,99 421,99C483,99 542,137 574,178z" + id="glyph2241" /> +<glyph + unicode="å" + horiz-adv-x="482" + d="M240,537C297,537 340,576 340,629C340,682 301,723 241,723C178,723 139,681 139,628C139,577 180,537 240,537M239,574C210,574 190,602 190,628C190,659 208,685 239,685C271,685 291,660 291,630C291,600 271,574 239,574M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2243" /> +<glyph + unicode="^" + horiz-adv-x="596" + d="M536,189l-207,461l-62,0l-207,-461l70,0l167,388l2,0l167,-388z" + id="glyph2245" /> +<glyph + unicode="~" + horiz-adv-x="596" + d="M108,210C110,270 132,295 168,295C204,295 241,274 288,251C357,219 393,207 433,207C495,207 553,246 548,358l-60,0C487,303 470,274 434,274C397,274 360,292 309,316C242,348 210,361 168,361C99,361 45,310 49,210z" + id="glyph2247" /> +<glyph + unicode="*" + horiz-adv-x="415" + d="M269,685l-61,-140l-2,0l-62,139l-61,-35l94,-122l0,-2l-147,19l0,-68l148,19l0,-2l-95,-122l57,-36l65,141l2,0l60,-140l63,36l-96,120l0,2l151,-18l0,68l-151,-20l0,2l95,125z" + id="glyph2249" /> +<glyph + unicode="@" + horiz-adv-x="737" + d="M448,255C437,193 384,119 326,119C282,119 260,151 260,195C260,292 331,375 419,375C442,375 459,371 469,368M508,-22C467,-45 414,-57 357,-57C209,-57 100,47 100,214C100,417 237,547 400,547C555,547 642,442 642,297C642,181 585,113 534,114C501,115 489,150 504,226l34,181C512,419 474,428 431,428C292,428 194,315 194,191C194,112 244,65 302,65C362,65 408,94 443,153l4,0C444,92 481,65 521,65C615,65 699,153 699,303C699,470 582,593 408,593C186,593 43,414 43,208C43,15 182,-105 346,-105C413,-105 469,-94 524,-65z" + id="glyph2251" /> +<glyph + unicode="" + horiz-adv-x="737" + d="M448,348C437,286 384,212 326,212C282,212 260,244 260,288C260,385 331,468 419,468C442,468 459,464 469,461M508,71C467,48 414,36 357,36C209,36 100,140 100,307C100,510 237,640 400,640C555,640 642,535 642,390C642,274 585,206 534,207C501,208 489,243 504,319l34,181C512,512 474,521 431,521C292,521 194,408 194,284C194,205 244,158 302,158C362,158 408,187 443,246l4,0C444,185 481,158 521,158C615,158 699,246 699,396C699,563 582,686 408,686C186,686 43,507 43,301C43,108 182,-12 346,-12C413,-12 469,-1 524,28z" + id="glyph2253" /> +<glyph + unicode="ã" + horiz-adv-x="482" + d="M163,567C165,595 173,612 189,612C201,612 212,606 232,594C253,583 272,574 293,574C340,574 363,608 362,675l-47,0C313,641 303,632 287,632C275,632 261,640 245,649C222,662 205,671 183,671C140,671 113,632 115,567M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247z" + id="glyph2255" /> +<glyph + unicode="b" + horiz-adv-x="569" + d="M73,125C73,82 71,33 69,0l76,0l4,80l3,0C188,16 244,-11 314,-11C422,-11 531,75 531,248C532,395 447,495 327,495C249,495 193,460 162,406l-2,0l0,304l-87,0M160,281C160,295 163,307 165,317C183,384 239,425 299,425C393,425 443,342 443,245C443,134 388,59 296,59C232,59 181,101 164,162C162,172 160,183 160,194z" + id="glyph2257" /> +<glyph + unicode="\" + horiz-adv-x="341" + d="M342,-40l-272,725l-68,0l272,-725z" + id="glyph2259" /> +<glyph + unicode="|" + horiz-adv-x="239" + d="M86,750l0,-1000l67,0l0,1000z" + id="glyph2261" /> +<glyph + unicode="β" + horiz-adv-x="560" + d="M159,50C196,11 246,-11 313,-11C420,-11 522,62 522,196C522,307 450,371 374,387l0,4C420,414 463,471 463,547C463,651 387,721 288,721C224,721 180,700 146,668C108,632 71,571 71,433l0,-461C71,-98 74,-166 86,-198l82,0C158,-157 157,-74 157,-17l0,67M156,443C156,581 199,651 280,651C339,651 382,605 382,534C382,470 344,411 268,406C262,405 257,404 251,404l0,-60C260,344 269,345 278,344C366,341 434,292 434,196C434,116 377,57 297,57C228,57 179,100 156,137z" + id="glyph2263" /> +<glyph + unicode="{" + horiz-adv-x="284" + d="M28,262C99,262 109,219 109,188C109,161 105,134 101,107C97,80 93,52 93,25C93,-76 155,-112 238,-112l21,0l0,55l-18,0C185,-56 162,-26 162,30C162,54 165,77 169,102C173,126 176,151 176,178C177,242 149,276 104,287l0,2C149,301 177,333 176,397C176,424 173,449 169,473C165,497 162,521 162,545C162,599 182,630 241,631l18,0l0,55l-21,0C153,686 93,647 93,555C93,527 97,499 101,471C105,443 109,415 109,388C109,352 99,313 28,313z" + id="glyph2265" /> +<glyph + unicode="" + horiz-adv-x="284" + d="M28,301C99,301 109,258 109,227C109,200 105,173 101,146C97,119 93,91 93,64C93,-37 155,-73 238,-73l21,0l0,55l-18,0C185,-17 162,13 162,69C162,93 165,116 169,141C173,165 176,190 176,217C177,281 149,315 104,326l0,2C149,340 177,372 176,436C176,463 173,488 169,512C165,536 162,560 162,584C162,638 182,669 241,670l18,0l0,55l-21,0C153,725 93,686 93,594C93,566 97,538 101,510C105,482 109,454 109,427C109,391 99,352 28,352z" + id="glyph2267" /> +<glyph + unicode="}" + horiz-adv-x="284" + d="M256,313C185,313 175,352 175,388C175,415 179,443 183,471C187,499 191,527 191,555C191,647 130,686 45,686l-20,0l0,-55l18,0C101,630 122,599 122,545C122,521 118,497 115,473C111,449 107,424 107,397C107,333 135,301 179,289l0,-2C135,276 107,242 107,178C107,151 111,126 115,102C118,77 122,54 122,30C122,-26 98,-56 42,-57l-17,0l0,-55l21,0C128,-112 191,-76 191,25C191,52 187,80 183,107C179,134 175,161 175,188C175,219 185,262 256,262z" + id="glyph2269" /> +<glyph + unicode="" + horiz-adv-x="284" + d="M256,352C185,352 175,391 175,427C175,454 179,482 183,510C187,538 191,566 191,594C191,686 130,725 45,725l-20,0l0,-55l18,0C101,669 122,638 122,584C122,560 118,536 115,512C111,488 107,463 107,436C107,372 135,340 179,328l0,-2C135,315 107,281 107,217C107,190 111,165 115,141C118,116 122,93 122,69C122,13 98,-17 42,-18l-17,0l0,-55l21,0C128,-73 191,-37 191,64C191,91 187,119 183,146C179,173 175,200 175,227C175,258 185,301 256,301z" + id="glyph2271" /> +<glyph + unicode="[" + horiz-adv-x="284" + d="M265,-112l0,55l-115,0l0,688l115,0l0,55l-183,0l0,-798z" + id="glyph2273" /> +<glyph + unicode="" + horiz-adv-x="284" + d="M265,-73l0,55l-115,0l0,688l115,0l0,55l-183,0l0,-798z" + id="glyph2275" /> +<glyph + unicode="]" + horiz-adv-x="284" + d="M19,686l0,-55l115,0l0,-688l-115,0l0,-55l183,0l0,798z" + id="glyph2277" /> +<glyph + unicode="" + horiz-adv-x="284" + d="M19,725l0,-55l115,0l0,-688l-115,0l0,-55l183,0l0,798z" + id="glyph2279" /> +<glyph + unicode="˘" + horiz-adv-x="300" + d="M26,682C26,620 62,558 150,558C225,558 274,608 274,682l-52,0C219,649 197,613 150,613C109,613 84,643 78,682z" + id="glyph2281" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M26,832C28,772 64,726 149,726C235,726 272,773 274,832l-51,0C218,806 199,783 150,783C101,783 83,809 78,832z" + id="glyph2283" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M21,681C25,612 59,557 147,557C224,557 275,599 280,681l-64,0C212,637 192,605 149,605C108,605 87,638 85,681z" + id="glyph2285" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M22,834C27,768 66,726 148,726C233,726 273,767 278,834l-67,0C207,801 196,771 150,771C103,771 93,802 89,834z" + id="glyph2287" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M192,773l-79,-131l53,0l111,131M21,657C22,594 62,533 150,533C225,533 278,582 279,657l-50,0C226,623 201,588 150,588C106,588 77,618 71,657z" + id="glyph2289" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M29,802C36,747 74,699 152,699C231,699 272,749 278,802l-46,0C224,775 205,753 153,753C104,753 83,778 77,802M191,896l-74,-105l54,0l112,105z" + id="glyph2291" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M22,773l110,-131l54,0l-80,131M17,657C18,594 58,533 146,533C222,533 274,582 275,657l-49,0C223,623 197,588 146,588C102,588 73,618 67,657z" + id="glyph2293" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M29,896l112,-105l54,0l-74,105M30,802C37,747 75,699 153,699C232,699 273,749 279,802l-46,0C225,775 205,753 154,753C105,753 84,778 78,802z" + id="glyph2295" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M163,634C189,679 232,688 232,737C232,776 197,808 152,808C103,808 74,778 57,744l32,-20C100,741 116,760 136,760C156,760 170,746 170,727C170,696 135,687 119,645M21,657C22,594 62,533 150,533C225,533 278,582 279,657l-50,0C226,623 201,588 150,588C106,588 77,618 71,657z" + id="glyph2297" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M168,778C195,823 237,833 237,882C237,921 203,952 158,952C109,952 80,923 63,888l32,-19C106,886 122,904 143,904C162,904 176,891 176,872C176,841 141,831 125,790M30,802C37,747 76,699 153,699C232,699 273,749 280,802l-47,0C225,775 206,753 155,753C105,753 84,778 78,802z" + id="glyph2299" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M78,698C80,712 87,728 102,728C114,728 126,722 146,712C166,700 185,691 206,691C253,691 276,724 275,787l-47,0C226,755 216,746 200,746C188,746 174,754 158,762C135,774 118,784 96,784C53,784 30,747 28,698M23,657C24,594 62,533 150,533C225,533 276,582 277,657l-49,0C225,623 200,587 150,587C107,587 78,618 72,657z" + id="glyph2301" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M78,836C81,851 87,867 103,867C115,867 126,861 146,850C167,839 186,829 207,829C254,829 276,863 275,925l-46,0C226,893 217,885 201,885C189,885 175,892 159,901C135,913 119,922 97,922C54,922 31,886 29,836M26,802C32,747 71,699 149,699C228,699 269,749 275,802l-47,0C220,775 201,753 150,753C101,753 80,778 73,802z" + id="glyph2303" /> +<glyph + unicode="¦" + horiz-adv-x="239" + d="M86,174l0,-350l67,0l0,350M86,674l0,-350l67,0l0,350z" + id="glyph2305" /> +<glyph + unicode="•" + horiz-adv-x="282" + d="M141,157C200,157 246,203 246,262C246,320 199,367 141,367C83,367 35,320 36,261C36,202 83,157 140,157z" + id="glyph2307" /> +<glyph + unicode="c" + horiz-adv-x="448" + d="M403,84C378,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,89 133,-11 279,-11C344,-11 395,6 418,18z" + id="glyph2309" /> +<glyph + unicode="ć" + horiz-adv-x="448" + d="M302,693l-88,-143l63,0l122,143M403,84C378,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,89 133,-11 279,-11C344,-11 395,6 418,18z" + id="glyph2311" /> +<glyph + unicode="ˇ" + horiz-adv-x="300" + d="M183,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143z" + id="glyph2313" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M184,710l103,114l-72,0l-63,-69l-2,0l-64,69l-74,0l105,-114z" + id="glyph2315" /> +<glyph + unicode="č" + horiz-adv-x="448" + d="M294,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143M403,84C378,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,89 133,-11 279,-11C344,-11 395,6 418,18z" + id="glyph2317" /> +<glyph + unicode="ç" + horiz-adv-x="447" + d="M403,84C379,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,103 117,8 240,-7l-42,-77C245,-89 275,-99 276,-126C276,-150 256,-157 233,-157C213,-157 193,-153 178,-144l-15,-45C182,-200 210,-206 233,-206C287,-206 337,-183 337,-123C337,-82 303,-57 267,-50l25,40C352,-9 398,7 419,18z" + id="glyph2319" /> +<glyph + unicode="ĉ" + horiz-adv-x="448" + d="M229,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M403,84C378,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,89 133,-11 279,-11C344,-11 395,6 418,18z" + id="glyph2321" /> +<glyph + unicode="ċ" + horiz-adv-x="448" + d="M265,570C294,570 314,594 314,621C314,650 293,673 265,673C236,673 214,650 214,621C214,594 235,570 264,570M403,84C378,73 345,60 295,60C199,60 127,129 127,241C127,342 187,424 298,424C346,424 379,413 400,401l20,68C396,481 350,494 298,494C140,494 38,386 38,237C38,89 133,-11 279,-11C344,-11 395,6 418,18z" + id="glyph2323" /> +<glyph + unicode="¸" + horiz-adv-x="300" + d="M136,2l-47,-83C136,-85 166,-97 167,-123C167,-147 146,-155 124,-155C106,-155 86,-150 70,-140l-15,-43C72,-193 98,-200 123,-200C178,-200 227,-178 227,-119C227,-81 194,-55 157,-50l33,52z" + id="glyph2325" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M136,2l-46,-86C137,-89 168,-100 168,-127C168,-148 149,-157 126,-157C104,-157 85,-152 70,-144l-14,-45C73,-199 101,-206 127,-206C179,-206 230,-183 230,-125C230,-84 197,-57 159,-50l32,52z" + id="glyph2327" /> +<glyph + unicode="¢" + horiz-adv-x="513" + d="M330,-14l0,101C379,89 421,105 441,116l-16,63C401,168 366,154 311,154C223,154 155,219 155,330C155,428 216,508 315,508C365,508 399,495 419,483l20,67C416,562 375,575 330,575l0,98l-62,0l0,-100C146,553 67,455 67,326C67,253 89,195 123,157C159,118 204,95 268,87l0,-101z" + id="glyph2329" /> +<glyph + unicode="" + horiz-adv-x="315" + d="M206,-8l0,59C234,53 259,61 272,68l-11,47C246,109 225,102 194,102C144,102 103,136 103,199C103,254 139,296 195,296C225,296 244,288 257,282l13,49C254,339 231,346 206,346l0,57l-46,0l0,-59C82,332 36,271 36,195C36,154 49,119 70,96C92,72 120,57 160,52l0,-60z" + id="glyph2331" /> +<glyph + unicode="" + horiz-adv-x="315" + d="M206,-155l0,59C234,-94 259,-86 272,-79l-11,47C246,-38 225,-45 194,-45C144,-45 103,-11 103,52C103,107 139,149 195,149C225,149 244,141 257,135l13,49C254,192 231,199 206,199l0,57l-46,0l0,-59C82,185 36,124 36,48C36,7 49,-28 70,-51C92,-75 120,-90 160,-95l0,-60z" + id="glyph2333" /> +<glyph + unicode="" + horiz-adv-x="315" + d="M206,258l0,59C234,319 259,327 272,334l-11,47C246,375 225,368 194,368C144,368 103,402 103,465C103,520 139,562 195,562C225,562 244,554 257,548l13,49C254,605 231,612 206,612l0,57l-46,0l0,-59C82,598 36,537 36,461C36,420 49,385 70,362C92,338 120,323 160,318l0,-60z" + id="glyph2335" /> +<glyph + unicode="" + horiz-adv-x="315" + d="M206,435l0,59C234,496 259,504 272,511l-11,47C246,552 225,545 194,545C144,545 103,579 103,642C103,697 139,739 195,739C225,739 244,731 257,725l13,49C254,782 231,789 206,789l0,57l-46,0l0,-59C82,775 36,714 36,638C36,597 49,562 70,539C92,515 120,500 160,495l0,-60z" + id="glyph2337" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M403,129C382,120 348,109 301,109C226,109 165,157 165,248C165,332 221,389 305,389C346,389 379,382 399,371l16,60C395,440 357,451 317,451l0,66l-55,0l0,-68C154,433 81,352 81,244C81,130 154,59 262,47l0,-73l55,0l0,72C361,48 400,61 417,70z" + id="glyph2339" /> +<glyph + unicode="χ" + horiz-adv-x="457" + d="M18,484l166,-328l-173,-346l75,-23l91,193C195,19 210,54 223,83l4,0l144,-296l82,22l-176,348l171,327l-86,0l-72,-154C269,288 253,254 239,223l-4,0l-123,261z" + id="glyph2341" /> +<glyph + unicode="ˆ" + horiz-adv-x="300" + d="M119,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143z" + id="glyph2343" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M116,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph2345" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M82,563C87,606 107,639 150,639C190,639 211,606 214,563l64,0C274,632 240,687 152,687C75,687 24,645 19,563z" + id="glyph2347" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M278,726C274,788 234,831 152,831C68,831 27,789 22,726l67,-1C93,755 104,785 150,785C197,785 207,753 211,725z" + id="glyph2349" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M117,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M309,775l-69,-132l51,0l99,132z" + id="glyph2351" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M121,824l-92,-114l67,0l56,70l2,0l55,-70l70,0l-95,114M315,900l-84,-105l52,0l121,105z" + id="glyph2353" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M189,775l93,-132l51,0l-64,132M117,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143z" + id="glyph2355" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M180,881l109,-105l54,0l-72,105M117,824l-92,-114l68,0l55,70l2,0l55,-70l70,0l-94,114z" + id="glyph2357" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M117,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M297,635C323,680 366,689 366,738C366,777 331,809 286,809C237,809 208,779 191,745l32,-20C234,742 250,761 271,761C290,761 304,747 304,728C304,697 269,688 253,646z" + id="glyph2359" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M120,824l-93,-114l68,0l55,70l2,0l56,-70l70,0l-95,114M302,754C328,800 371,810 371,858C371,898 337,928 292,928C243,928 214,899 197,864l32,-19C240,862 257,882 278,882C297,882 312,868 312,849C312,817 276,807 260,766z" + id="glyph2361" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M117,668l-89,-118l67,0l53,73l2,0l54,-73l69,0l-92,118M78,680C79,703 88,719 103,719C116,719 127,713 147,702C168,691 186,681 208,681C255,681 277,715 276,777l-47,0C227,745 217,737 202,737C189,737 175,744 159,753C136,765 119,775 97,775C55,775 28,737 29,680z" + id="glyph2363" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M117,824l-103,-114l72,0l63,69l2,0l64,-69l73,0l-104,114M79,837C80,861 89,877 104,877C117,877 128,871 148,860C169,849 187,839 209,839C256,839 278,873 277,935l-47,0C228,903 218,894 203,894C190,894 176,902 160,910C137,923 120,932 98,932C56,932 29,894 30,837z" + id="glyph2365" /> +<glyph + unicode=":" + horiz-adv-x="207" + d="M111,342C148,342 171,369 171,404C170,441 147,468 112,468C77,468 52,441 52,404C52,369 76,342 110,342M111,-11C148,-11 171,16 171,51C170,88 147,114 112,114C77,114 52,88 52,51C52,16 76,-11 110,-11z" + id="glyph2367" /> +<glyph + unicode="," + horiz-adv-x="207" + d="M79,-117C107,-70 151,41 174,126l-98,-10C65,43 38,-64 17,-123z" + id="glyph2369" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M48,-67C70,-31 97,34 113,89l-74,-5C32,35 16,-28 0,-71z" + id="glyph2371" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M48,-214C70,-178 97,-113 113,-58l-74,-5C32,-112 16,-175 0,-218z" + id="glyph2373" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M48,199C70,235 97,300 113,355l-74,-5C32,301 16,238 0,195z" + id="glyph2375" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M48,376C70,412 97,477 113,532l-74,-5C32,478 16,415 0,372z" + id="glyph2377" /> +<glyph + unicode="©" + horiz-adv-x="677" + d="M340,648C170,648 36,510 36,340C36,168 170,30 340,30C509,30 641,168 641,340C641,510 509,648 341,648M340,607C481,607 591,489 591,340C591,188 481,70 340,71C196,72 86,188 86,337C86,489 196,607 339,607M474,493C461,505 416,520 364,520C263,520 173,448 173,334C173,233 240,153 356,153C398,153 444,161 479,184l-12,38C444,207 406,196 366,196C274,196 225,259 225,337C225,417 271,477 365,477C412,477 449,461 462,454z" + id="glyph2379" /> +<glyph + unicode="¤" + horiz-adv-x="513" + d="M256,534C212,534 170,518 142,493l-69,76l-44,-46l75,-71C83,424 65,381 65,329C65,280 82,235 102,209l-72,-71l43,-44l67,74C167,143 213,128 254,128C294,128 341,142 371,171l68,-77l45,44l-75,74C427,237 444,280 444,335C444,383 428,425 409,452l78,71l-44,46l-71,-75C344,518 303,534 257,534M255,471C332,471 373,403 373,334C373,231 310,191 254,191C203,191 136,233 136,330C136,408 181,471 254,471z" + id="glyph2381" /> +<glyph + unicode="d" + horiz-adv-x="564" + d="M403,710l0,-289l-2,0C379,460 329,495 255,495C137,495 37,396 38,235C38,88 128,-11 245,-11C324,-11 383,30 410,84l2,0l4,-84l79,0C492,33 491,82 491,125l0,585M403,203C403,189 402,177 399,165C383,99 329,60 270,60C175,60 127,141 127,239C127,346 181,426 272,426C338,426 386,380 399,324C402,313 403,298 403,287z" + id="glyph2383" /> +<glyph + unicode="†" + horiz-adv-x="500" + d="M209,674l8,-211l-174,7l0,-72l174,8l-8,-456l82,0l-8,456l174,-8l0,72l-174,-7l8,211z" + id="glyph2385" /> +<glyph + unicode="‡" + horiz-adv-x="500" + d="M210,674l6,-201l-173,8l0,-72l173,7l0,-204l-173,8l0,-72l172,7l-5,-205l81,0l-7,205l173,-7l0,72l-173,-8l0,204l173,-7l0,72l-173,-8l7,201z" + id="glyph2387" /> +<glyph + unicode="ď" + horiz-adv-x="574" + d="M403,710l0,-289l-2,0C379,460 329,495 255,495C137,495 37,396 38,235C38,88 128,-11 245,-11C324,-11 383,30 410,84l2,0l4,-84l79,0C492,33 491,82 491,125l0,585M403,203C403,189 402,177 399,165C383,99 329,60 270,60C175,60 127,141 127,239C127,346 181,426 272,426C338,426 386,380 399,324C402,313 403,298 403,287M572,522C587,534 639,571 639,646C639,679 621,708 603,719l-63,-14C557,690 571,665 571,640C571,599 551,570 535,551z" + id="glyph2389" /> +<glyph + unicode="đ" + horiz-adv-x="564" + d="M568,552l0,55l-77,0l0,103l-88,0l0,-103l-197,0l0,-55l197,0l0,-131l-2,0C379,460 329,495 255,495C137,495 37,396 38,235C38,88 128,-11 245,-11C324,-11 383,30 410,84l2,0l4,-84l79,0C492,33 491,82 491,125l0,427M403,203C403,189 402,177 399,165C383,99 329,60 270,60C175,60 127,141 127,239C127,346 181,426 272,426C338,426 386,380 399,324C402,313 403,298 403,287z" + id="glyph2391" /> +<glyph + unicode="°" + horiz-adv-x="318" + d="M162,686C88,686 28,627 29,550C29,481 82,422 160,422C229,422 294,475 294,556C294,624 244,686 163,686M161,639C218,639 240,592 240,554C240,505 206,469 161,469C117,469 83,505 83,551C83,593 109,639 160,639z" + id="glyph2393" /> +<glyph + unicode="δ" + horiz-adv-x="546" + d="M466,673C437,696 376,721 306,721C193,721 138,662 138,608C138,562 176,527 199,508l23,-18l0,-5C123,438 38,356 38,232C38,77 148,-11 273,-11C403,-11 508,86 508,236C508,332 460,402 380,463l-82,66C253,565 226,584 226,610C226,634 253,655 308,655C363,655 419,630 440,615M326,402C388,354 420,298 420,230C420,120 355,55 278,55C202,55 127,120 127,233C127,339 197,404 273,444z" + id="glyph2395" /> +<glyph + unicode="¨" + horiz-adv-x="300" + d="M58,570C87,570 107,594 107,621C107,650 86,672 58,672C30,672 7,649 7,621C7,594 28,570 57,570M243,570C272,570 292,594 292,621C292,650 271,672 243,672C215,672 193,649 193,621C193,594 213,570 242,570z" + id="glyph2397" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M58,721C87,721 106,744 106,772C106,800 86,822 58,822C30,822 9,799 9,772C9,744 28,721 57,721M243,721C273,721 292,744 292,772C292,800 272,822 244,822C215,822 194,799 194,772C194,744 214,721 242,721z" + id="glyph2399" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M24,573C52,573 72,594 72,622C72,650 52,672 25,672C-3,672 -24,649 -24,622C-24,596 -4,573 23,573M271,573C299,573 319,594 319,622C319,650 299,672 272,672C244,672 223,649 223,622C223,596 243,573 270,573M151,701l-77,-143l58,0l111,143z" + id="glyph2401" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M13,721C42,721 61,742 61,770C61,798 42,819 15,819C-12,819 -33,797 -33,770C-33,743 -14,721 12,721M287,721C316,721 336,742 336,770C336,798 316,819 289,819C261,819 241,797 241,770C241,743 260,721 286,721M142,828l-81,-117l67,0l116,117z" + id="glyph2403" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M28,573C56,573 76,594 76,622C76,650 56,672 29,672C2,672 -20,649 -20,622C-20,596 0,573 27,573M269,573C298,573 318,594 318,622C318,650 298,672 270,672C243,672 222,649 222,622C222,596 241,573 268,573M56,701l112,-143l57,0l-77,143z" + id="glyph2405" /> +<glyph + unicode="" + horiz-adv-x="301" + d="M13,721C42,721 61,742 61,770C61,798 42,819 15,819C-13,819 -33,797 -33,770C-33,743 -14,721 12,721M287,721C316,721 335,742 335,770C335,798 316,819 289,819C261,819 241,797 241,770C241,743 260,721 286,721M57,828l116,-117l67,0l-81,117z" + id="glyph2407" /> +<glyph + unicode="΅" + horiz-adv-x="346" + d="M153,710l-22,-157l48,0l49,157M51,573C78,573 97,595 97,621C97,648 77,668 52,668C25,668 4,647 4,621C4,595 23,573 50,573M293,573C321,573 339,595 339,621C339,648 320,668 293,668C267,668 246,647 246,621C246,595 265,573 292,573z" + id="glyph2409" /> +<glyph + unicode="÷" + horiz-adv-x="596" + d="M298,387C331,387 351,411 351,442C351,475 330,498 299,498C267,498 245,475 245,442C245,411 266,387 297,387M556,237l0,60l-516,0l0,-60M298,36C331,36 351,60 351,91C351,124 330,147 299,147C267,147 245,124 245,91C245,60 266,36 297,36z" + id="glyph2411" /> +<glyph + unicode="$" + horiz-adv-x="513" + d="M281,-86l0,104C384,35 439,109 439,188C439,277 387,329 284,371C197,407 160,433 160,485C160,525 189,571 266,571C330,571 371,549 392,537l25,66C388,620 347,636 287,638l0,97l-62,0l0,-101C133,619 74,556 74,473C74,386 137,340 239,300C313,270 352,236 352,181C352,124 304,83 233,83C177,83 125,102 89,126l-24,-67C100,34 160,16 218,15l0,-101z" + id="glyph2413" /> +<glyph + unicode="" + horiz-adv-x="317" + d="M179,-51l0,61C245,21 280,66 280,115C280,169 249,201 182,225C126,246 106,260 106,287C106,309 121,333 167,333C208,333 236,319 249,311l16,49C247,370 219,381 182,382l0,56l-45,0l0,-59C77,369 41,330 41,279C41,224 80,197 146,173C193,155 214,139 214,109C214,79 187,58 145,58C109,58 76,70 52,85l-17,-51C58,20 96,8 134,8l0,-59z" + id="glyph2415" /> +<glyph + unicode="" + horiz-adv-x="317" + d="M179,-198l0,61C245,-126 280,-81 280,-32C280,22 249,54 182,78C126,99 106,113 106,140C106,162 121,186 167,186C208,186 236,172 249,164l16,49C247,223 219,234 182,235l0,56l-45,0l0,-59C77,222 41,183 41,132C41,77 80,50 146,26C193,8 214,-8 214,-38C214,-68 187,-89 145,-89C109,-89 76,-77 52,-62l-17,-51C58,-127 96,-139 134,-139l0,-59z" + id="glyph2417" /> +<glyph + unicode="" + horiz-adv-x="317" + d="M179,215l0,61C245,287 280,332 280,381C280,435 249,467 182,491C126,512 106,526 106,553C106,575 121,599 167,599C208,599 236,585 249,577l16,49C247,636 219,647 182,648l0,56l-45,0l0,-59C77,635 41,596 41,545C41,490 80,463 146,439C193,421 214,405 214,375C214,345 187,324 145,324C109,324 76,336 52,351l-17,-51C58,286 96,274 134,274l0,-59z" + id="glyph2419" /> +<glyph + unicode="" + horiz-adv-x="317" + d="M179,392l0,61C245,464 280,509 280,558C280,612 249,644 182,668C126,689 106,703 106,730C106,752 121,776 167,776C208,776 236,762 249,754l16,49C247,813 219,824 182,825l0,56l-45,0l0,-59C77,812 41,773 41,722C41,667 80,640 146,616C193,598 214,582 214,552C214,522 187,501 145,501C109,501 76,513 52,528l-17,-51C58,463 96,451 134,451l0,-59z" + id="glyph2421" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M295,-81l0,82C391,15 444,76 444,139C444,204 398,247 298,279C212,308 189,326 189,362C189,399 231,423 285,423C333,423 378,407 401,393l24,60C400,467 355,483 301,485l0,83l-57,0l0,-86C159,472 101,422 101,356C101,288 151,253 254,218C325,194 354,174 354,134C354,90 306,63 248,63C200,63 143,79 114,99l-24,-61C124,16 184,0 238,-1l0,-80z" + id="glyph2423" /> +<glyph + unicode="₫" + horiz-adv-x="513" + d="M89,98l0,-59l337,0l0,59M346,664l0,-68l-127,0l0,-49l127,0l0,-86l-2,0C325,489 288,512 236,512C143,512 67,442 68,327C68,223 136,153 226,153C286,153 329,180 351,218l2,0l3,-57l69,0C423,184 422,219 422,250l0,297l62,0l0,49l-62,0l0,68M346,311C346,302 344,292 342,284C331,240 290,213 246,213C182,213 147,265 147,331C147,402 185,453 250,453C297,453 333,421 343,384C345,377 346,366 346,358z" + id="glyph2425" /> +<glyph + unicode="" + horiz-adv-x="513" + d="M346,664l0,-68l-127,0l0,-49l127,0l0,-86l-2,0C325,489 288,512 236,512C143,512 67,442 68,327C68,223 136,153 226,153C286,153 329,180 351,218l2,0l3,-57l69,0C423,184 422,219 422,250l0,297l62,0l0,49l-62,0l0,68M346,311C346,302 344,292 342,284C331,240 290,213 246,213C182,213 147,265 147,331C147,402 185,453 250,453C297,453 333,421 343,384C345,377 346,366 346,358z" + id="glyph2427" /> +<glyph + unicode="˙" + horiz-adv-x="300" + d="M140,570C169,570 189,594 189,621C189,650 168,673 140,673C111,673 89,650 89,621C89,594 110,570 139,570z" + id="glyph2429" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M151,721C179,721 200,746 200,772C200,798 179,822 152,822C123,822 101,798 101,772C101,746 123,721 150,721z" + id="glyph2431" /> +<glyph + unicode="ı" + horiz-adv-x="234" + d="M161,0l0,484l-88,0l0,-484z" + id="glyph2433" /> +<glyph + unicode="e" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2435" /> +<glyph + unicode="é" + horiz-adv-x="501" + d="M304,693l-88,-143l63,0l122,143M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2437" /> +<glyph + unicode="ĕ" + horiz-adv-x="501" + d="M129,682C129,620 165,558 253,558C328,558 377,608 377,682l-52,0C322,649 300,613 253,613C212,613 187,643 181,682M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2439" /> +<glyph + unicode="ě" + horiz-adv-x="501" + d="M286,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2441" /> +<glyph + unicode="ê" + horiz-adv-x="501" + d="M232,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2443" /> +<glyph + unicode="ë" + horiz-adv-x="501" + d="M176,570C205,570 225,594 225,621C225,650 204,672 176,672C148,672 125,649 125,621C125,594 146,570 175,570M361,570C390,570 410,594 410,621C410,650 389,672 361,672C333,672 311,649 311,621C311,594 331,570 360,570M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2445" /> +<glyph + unicode="ė" + horiz-adv-x="501" + d="M255,570C284,570 304,594 304,621C304,650 283,673 255,673C226,673 204,650 204,621C204,594 225,570 254,570M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2447" /> +<glyph + unicode="è" + horiz-adv-x="501" + d="M135,693l122,-143l62,0l-87,143M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2449" /> +<glyph + unicode="8" + horiz-adv-x="513" + d="M166,339C86,305 37,247 37,165C37,70 119,-11 255,-11C379,-11 476,65 476,178C476,257 426,314 345,346l0,3C425,387 452,446 452,501C452,582 389,661 263,661C149,661 62,591 62,488C62,432 93,376 165,342M257,53C174,53 124,111 127,177C127,239 168,289 244,311C332,286 387,248 387,169C387,103 336,53 257,53M258,598C337,598 368,544 368,492C368,433 325,393 269,374C194,394 145,429 145,494C145,550 185,598 258,598z" + id="glyph2451" /> +<glyph + unicode="" + horiz-adv-x="328" + d="M167,395C94,395 38,352 38,290C38,255 58,223 100,203l0,-2C53,181 24,149 24,101C24,46 72,-5 162,-5C245,-5 304,41 304,110C304,158 271,191 226,208l0,2C271,230 289,265 289,298C289,347 249,395 168,395M164,42C118,42 91,74 91,109C91,142 115,169 157,181C204,168 236,148 236,105C236,69 208,42 165,42M165,349C208,349 226,319 226,291C226,259 202,237 171,228C128,239 101,258 101,293C101,323 123,349 164,349z" + id="glyph2453" /> +<glyph + unicode="" + horiz-adv-x="513" + d="M166,339C86,305 37,247 37,165C37,70 119,-11 255,-11C379,-11 476,65 476,178C476,257 426,314 345,346l0,3C425,387 452,446 452,501C452,582 389,661 263,661C149,661 62,591 62,488C62,432 93,376 165,342M257,53C174,53 124,111 127,177C127,239 168,289 244,311C332,286 387,248 387,169C387,103 336,53 257,53M258,598C337,598 368,544 368,492C368,433 325,393 269,374C194,394 145,429 145,494C145,550 185,598 258,598z" + id="glyph2455" /> +<glyph + unicode="₈" + horiz-adv-x="328" + d="M167,248C94,248 38,205 38,143C38,108 58,76 100,56l0,-2C53,34 24,2 24,-46C24,-101 72,-152 162,-152C245,-152 304,-106 304,-37C304,11 271,44 226,61l0,2C271,83 289,118 289,151C289,200 249,248 168,248M164,-105C118,-105 91,-73 91,-38C91,-5 115,22 157,34C204,21 236,1 236,-42C236,-78 208,-105 165,-105M165,202C208,202 226,172 226,144C226,112 202,90 171,81C128,92 101,111 101,146C101,176 123,202 164,202z" + id="glyph2457" /> +<glyph + unicode="" + horiz-adv-x="328" + d="M167,661C94,661 38,618 38,556C38,521 58,489 100,469l0,-2C53,447 24,415 24,367C24,312 72,261 162,261C245,261 304,307 304,376C304,424 271,457 226,474l0,2C271,496 289,531 289,564C289,613 249,661 168,661M164,308C118,308 91,340 91,375C91,408 115,435 157,447C204,434 236,414 236,371C236,335 208,308 165,308M165,615C208,615 226,585 226,557C226,525 202,503 171,494C128,505 101,524 101,559C101,589 123,615 164,615z" + id="glyph2459" /> +<glyph + unicode="" + horiz-adv-x="513" + d="M343,320C414,354 449,398 449,456C449,540 375,607 262,607C151,607 65,541 65,445C65,382 109,340 166,316l0,-3C92,280 41,228 41,155C41,58 128,-11 254,-11C374,-11 472,58 472,166C472,237 419,290 343,317M261,547C315,547 367,515 367,452C367,396 324,360 270,344C195,364 150,398 150,452C150,510 200,547 260,547M255,52C177,52 129,99 129,164C129,219 174,265 240,287C331,263 382,221 382,158C382,95 326,52 256,52z" + id="glyph2461" /> +<glyph + unicode="⁸" + horiz-adv-x="328" + d="M167,838C94,838 38,795 38,733C38,698 58,666 100,646l0,-2C53,624 24,592 24,544C24,489 72,438 162,438C245,438 304,484 304,553C304,601 271,634 226,651l0,2C271,673 289,708 289,741C289,790 249,838 168,838M164,485C118,485 91,517 91,552C91,585 115,612 157,624C204,611 236,591 236,548C236,512 208,485 165,485M165,792C208,792 226,762 226,734C226,702 202,680 171,671C128,682 101,701 101,736C101,766 123,792 164,792z" + id="glyph2463" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M350,320C421,354 456,398 456,456C456,540 382,607 269,607C158,607 72,541 72,445C72,382 116,340 173,316l0,-3C99,280 48,228 48,155C48,58 135,-11 261,-11C381,-11 479,58 479,166C479,237 426,290 350,317M268,547C322,547 374,515 374,452C374,396 331,360 277,344C202,364 157,398 157,452C157,510 207,547 267,547M262,52C184,52 136,99 136,164C136,219 181,265 247,287C338,263 389,221 389,158C389,95 333,52 263,52z" + id="glyph2465" /> +<glyph + unicode="…" + horiz-adv-x="1000" + d="M167,-11C203,-11 226,16 226,52C226,89 203,115 167,115C132,115 108,89 108,52C107,16 132,-11 166,-11M500,-11C536,-11 560,16 560,52C559,89 536,115 501,115C466,115 441,89 441,52C440,16 465,-11 499,-11M833,-11C870,-11 893,16 893,52C893,89 869,115 834,115C799,115 774,89 774,52C774,16 798,-11 832,-11z" + id="glyph2467" /> +<glyph + unicode="ē" + horiz-adv-x="501" + d="M141,643l0,-57l225,0l0,57M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2469" /> +<glyph + unicode="—" + horiz-adv-x="1000" + d="M30,284l0,-60l940,0l0,60z" + id="glyph2471" /> +<glyph + unicode="" + horiz-adv-x="1000" + d="M30,359l0,-60l940,0l0,60z" + id="glyph2473" /> +<glyph + unicode="–" + horiz-adv-x="500" + d="M30,284l0,-60l440,0l0,60z" + id="glyph2475" /> +<glyph + unicode="" + horiz-adv-x="500" + d="M30,359l0,-60l440,0l0,60z" + id="glyph2477" /> +<glyph + unicode="ŋ" + horiz-adv-x="555" + d="M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-228C397,-42 357,-93 278,-109l17,-70C402,-164 485,-95 485,59l0,230C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z" + id="glyph2479" /> +<glyph + unicode="ę" + horiz-adv-x="501" + d="M385,-136C371,-142 352,-146 338,-146C317,-146 297,-133 297,-104C297,-63 352,-14 391,3C400,6 422,13 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226l340,0C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,89 127,-10 274,-11C284,-11 292,-10 301,-9l0,-1C269,-33 228,-77 228,-126C228,-176 264,-205 318,-205C348,-205 377,-197 399,-181M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph2481" /> +<glyph + unicode="ε" + horiz-adv-x="440" + d="M159,258C88,246 39,196 39,136C39,27 146,-11 243,-11C311,-11 383,8 420,33l-16,60C375,76 315,56 262,56C191,56 131,87 131,144C131,202 196,226 283,226l61,0l0,63l-58,0C210,289 151,310 151,358C151,402 196,430 263,430C312,430 364,413 384,400l19,58C368,482 319,495 256,495C127,495 63,428 63,364C63,316 95,276 159,262z" + id="glyph2483" /> +<glyph + unicode="έ" + horiz-adv-x="440" + d="M256,686l-53,-143l58,0l84,143M159,257C88,246 39,196 39,136C39,27 146,-11 243,-11C311,-11 383,8 420,33l-16,60C375,76 315,56 262,56C191,56 131,87 131,144C131,202 196,226 283,226l61,0l0,63l-58,0C210,289 151,310 151,358C151,402 196,430 263,430C312,430 364,413 384,400l19,58C368,482 319,495 256,495C127,495 63,428 63,364C63,316 95,276 159,262z" + id="glyph2485" /> +<glyph + unicode="=" + horiz-adv-x="596" + d="M556,337l0,60l-516,0l0,-60M556,141l0,60l-516,0l0,-60z" + id="glyph2487" /> +<glyph + unicode="℮" + horiz-adv-x="817" + d="M782,316C782,319 782,322 782,325C782,511 615,661 409,661C203,661 35,511 35,325C35,139 203,-11 409,-11C529,-11 637,41 705,120l-55,0C592,52 506,9 410,9C320,9 238,47 181,108C176,114 172,121 172,129l0,183C172,315 174,316 177,316M646,340C646,337 644,335 641,335l-464,0C174,335 172,337 172,340l0,180C172,529 176,537 182,543C240,603 320,641 410,641C498,641 578,604 636,546C642,540 646,533 646,524z" + id="glyph2489" /> +<glyph + unicode="η" + horiz-adv-x="555" + d="M152,404C152,435 147,465 138,484l-79,0C70,451 73,402 73,353l0,-353l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-300C397,-85 400,-168 412,-198l83,0C485,-158 485,-74 485,-18l0,307C485,455 381,495 314,495C234,495 178,450 154,404z" + id="glyph2491" /> +<glyph + unicode="ή" + horiz-adv-x="555" + d="M301,686l-53,-143l58,0l84,143M152,404C152,435 147,465 138,484l-79,0C70,451 73,402 73,353l0,-353l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-300C397,-85 400,-168 412,-198l83,0C485,-158 485,-74 485,-18l0,307C485,455 381,495 314,495C234,495 178,450 154,404z" + id="glyph2493" /> +<glyph + unicode="ð" + horiz-adv-x="541" + d="M267,55C186,55 127,135 127,237C127,317 167,424 271,424C322,424 355,402 377,377C402,346 411,306 411,243C411,133 355,55 268,55M116,507l142,66C309,535 347,492 376,444l-2,-3C336,477 299,484 261,484C146,483 38,392 38,236C38,85 136,-11 267,-11C383,-11 502,67 501,253C501,327 486,391 459,447C430,507 386,561 331,606l106,50l-21,44l-128,-61C245,670 195,698 147,720l-38,-55C146,647 183,627 213,605l-116,-55z" + id="glyph2495" /> +<glyph + unicode="!" + horiz-adv-x="230" + d="M149,194l14,480l-96,0l14,-480M115,-11C151,-11 174,16 174,51C174,87 151,113 115,113C81,113 56,87 56,51C56,16 80,-11 114,-11z" + id="glyph2497" /> +<glyph + unicode="¡" + horiz-adv-x="230" + d="M164,-195l-15,477l-68,0l-14,-477M116,488C81,488 56,462 56,426C56,391 80,364 115,364C151,364 174,391 174,426C174,462 151,488 117,488z" + id="glyph2499" /> +<glyph + unicode="" + horiz-adv-x="230" + d="M164,0l-15,477l-68,0l-14,-477M116,683C81,683 56,657 56,621C56,586 80,559 115,559C151,559 174,586 174,621C174,657 151,683 117,683z" + id="glyph2501" /> +<glyph + unicode="f" + horiz-adv-x="292" + d="M169,0l0,417l117,0l0,67l-117,0l0,26C169,584 188,650 263,650C288,650 306,645 319,639l12,68C314,714 287,721 256,721C215,721 171,708 138,676C97,637 82,575 82,507l0,-23l-68,0l0,-67l68,0l0,-417z" + id="glyph2503" /> +<glyph + unicode="ff" + horiz-adv-x="583" + d="M460,0l0,417l116,0l0,67l-117,0l0,26C459,584 479,650 553,650C579,650 596,645 610,639l11,68C604,714 578,721 546,721C506,721 462,708 429,676C388,637 372,575 372,507l0,-23l-203,0l0,20C169,580 188,639 257,639C277,639 296,633 309,626l18,66C312,700 286,709 258,709C204,709 164,692 136,663C98,625 82,566 82,498l0,-14l-68,0l0,-67l68,0l0,-417l87,0l0,417l203,0l0,-417z" + id="glyph2505" /> +<glyph + unicode="ffi" + horiz-adv-x="815" + d="M705,690C679,708 629,721 585,721C449,721 373,638 373,510l0,-26l-204,0l0,17C169,544 177,588 201,614C216,630 236,639 264,639C287,639 310,631 325,622l24,65C331,697 297,709 264,709C210,709 168,690 140,662C98,621 82,561 82,501l0,-17l-68,0l0,-67l68,0l0,-417l87,0l0,417l204,0l0,-417l88,0l0,417l193,0l0,-417l88,0l0,484l-282,0l0,24C460,585 488,650 581,650C621,650 657,638 679,624z" + id="glyph2507" /> +<glyph + unicode="" + horiz-adv-x="809" + d="M705,690C679,708 629,721 585,721C449,721 373,638 373,510l0,-26l-204,0l0,17C169,544 177,588 201,614C216,630 236,639 264,639C287,639 310,631 325,622l24,65C331,697 297,709 264,709C210,709 168,690 140,662C98,621 82,561 82,501l0,-17l-68,0l0,-67l68,0l0,-417l87,0l0,417l204,0l0,-417l88,0l0,417l193,0l0,-340C654,-39 645,-78 621,-105C600,-128 565,-139 525,-142l9,-69C581,-211 645,-195 684,-156C727,-112 742,-51 742,43l0,441l-282,0l0,24C460,585 488,650 581,650C621,650 657,638 679,624z" + id="glyph2509" /> +<glyph + unicode="ffl" + horiz-adv-x="815" + d="M349,690C334,698 301,709 269,709C214,709 169,691 138,660C96,619 82,557 82,495l0,-11l-68,0l0,-67l68,0l0,-417l87,0l0,417l204,0l0,-417l88,0l0,417l113,0l0,67l-114,0l0,24C460,586 491,653 582,653C617,653 639,648 655,640l0,-640l87,0l0,689C705,709 653,721 597,721C529,721 479,704 442,672C397,632 373,572 373,505l0,-21l-204,0l0,18C169,547 177,588 201,613C216,630 237,639 266,639C287,639 308,634 326,624z" + id="glyph2511" /> +<glyph + unicode="" + horiz-adv-x="523" + d="M169,0l0,417l194,0l0,-417l87,0l0,484l-281,0l0,24C169,585 196,650 289,650C332,650 370,637 391,622l25,67C391,707 338,721 293,721C157,721 82,638 82,510l0,-26l-68,0l0,-67l68,0l0,-417z" + id="glyph2513" /> +<glyph + unicode="" + horiz-adv-x="522" + d="M169,0l0,417l194,0l0,-340C363,-39 353,-78 329,-105C308,-128 274,-139 233,-142l9,-69C289,-211 354,-195 392,-156C435,-112 450,-51 450,43l0,441l-281,0l0,24C169,585 196,650 289,650C332,650 370,637 391,622l25,67C391,707 338,721 293,721C157,721 82,638 82,510l0,-26l-68,0l0,-67l68,0l0,-417z" + id="glyph2515" /> +<glyph + unicode="" + horiz-adv-x="523" + d="M169,0l0,417l113,0l0,67l-113,0l0,24C169,586 199,653 291,653C325,653 348,648 363,640l0,-640l87,0l0,689C414,709 361,721 305,721C238,721 188,704 150,672C105,632 82,572 82,505l0,-21l-68,0l0,-67l68,0l0,-417z" + id="glyph2517" /> +<glyph + unicode="5" + horiz-adv-x="513" + d="M433,650l-311,0l-42,-312C104,341 133,345 170,345C300,345 356,285 357,201C357,114 286,60 203,60C144,60 91,80 64,96l-22,-67C73,9 133,-11 204,-11C345,-11 446,84 446,211C446,293 404,349 353,378C313,402 263,413 213,413C189,413 175,411 160,409l25,167l248,0z" + id="glyph2519" /> +<glyph + unicode="" + horiz-adv-x="308" + d="M268,388l-202,0l-26,-192C55,198 74,201 97,201C178,201 210,168 210,122C210,74 167,46 122,46C84,46 50,58 33,68l-14,-50C39,6 80,-6 123,-6C216,-6 279,51 279,128C279,177 254,211 223,228C197,244 164,250 131,250C118,250 108,249 100,248l13,85l155,0z" + id="glyph2521" /> +<glyph + unicode="" + horiz-adv-x="460" + d="M406,650l-311,0l-42,-312C77,341 106,345 143,345C273,345 329,285 330,201C330,114 259,60 176,60C117,60 64,80 37,96l-22,-67C46,9 106,-11 177,-11C318,-11 419,84 419,211C419,293 377,349 326,378C286,402 236,413 186,413C162,413 148,411 133,409l25,167l248,0z" + id="glyph2523" /> +<glyph + unicode="₅" + horiz-adv-x="308" + d="M268,241l-202,0l-26,-192C55,51 74,54 97,54C178,54 210,21 210,-25C210,-73 167,-101 122,-101C84,-101 50,-89 33,-79l-14,-50C39,-141 80,-153 123,-153C216,-153 279,-96 279,-19C279,30 254,64 223,81C197,97 164,103 131,103C118,103 108,102 100,101l13,85l155,0z" + id="glyph2525" /> +<glyph + unicode="" + horiz-adv-x="308" + d="M268,654l-202,0l-26,-192C55,464 74,467 97,467C178,467 210,434 210,388C210,340 167,312 122,312C84,312 50,324 33,334l-14,-50C39,272 80,260 123,260C216,260 279,317 279,394C279,443 254,477 223,494C197,510 164,516 131,516C118,516 108,515 100,514l13,85l155,0z" + id="glyph2527" /> +<glyph + unicode="" + horiz-adv-x="441" + d="M36,-97C61,-107 106,-115 150,-115C299,-115 402,-34 402,89C402,132 386,173 356,202C303,253 218,265 135,264l30,149l217,0l11,72l-287,0l-52,-287C164,206 243,194 282,155C301,136 313,111 313,82C313,-2 234,-47 149,-47C111,-47 70,-41 47,-34z" + id="glyph2529" /> +<glyph + unicode="⁵" + horiz-adv-x="308" + d="M268,831l-202,0l-26,-192C55,641 74,644 97,644C178,644 210,611 210,565C210,517 167,489 122,489C84,489 50,501 33,511l-14,-50C39,449 80,437 123,437C216,437 279,494 279,571C279,620 254,654 223,671C197,687 164,693 131,693C118,693 108,692 100,691l13,85l155,0z" + id="glyph2531" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M78,-97C103,-107 148,-115 192,-115C341,-115 444,-34 444,89C444,132 428,173 398,202C345,253 260,265 177,264l30,149l217,0l11,72l-287,0l-52,-287C206,206 285,194 324,155C343,136 355,111 355,82C355,-2 276,-47 191,-47C153,-47 112,-41 89,-34z" + id="glyph2533" /> +<glyph + unicode="ƒ" + horiz-adv-x="513" + d="M111,338l92,0l-23,-197C168,39 145,-16 83,-16C62,-16 44,-13 27,-5l-16,-63C28,-77 56,-84 89,-84C206,-84 247,4 263,133l24,205l128,0l0,63l-120,0l6,52C309,527 339,592 407,592C427,592 444,588 459,582l16,64C461,654 437,660 407,661C272,661 228,546 216,447l-6,-46l-99,0z" + id="glyph2535" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M128,282l92,0l-20,-174C188,5 164,-49 103,-50C82,-49 64,-46 47,-38l-16,-63C47,-110 75,-117 108,-117C226,-118 267,-29 282,100l22,182l128,0l0,58l-121,0l7,59C326,473 356,538 424,538C445,538 461,533 476,528l17,64C479,599 454,607 424,607C290,608 245,492 233,393l-6,-53l-99,0z" + id="glyph2537" /> +<glyph + unicode="4" + horiz-adv-x="513" + d="M400,0l0,177l91,0l0,69l-91,0l0,404l-95,0l-290,-415l0,-58l302,0l0,-177M104,246l0,2l159,221C281,499 297,527 317,565l3,0C318,531 317,497 317,463l0,-217z" + id="glyph2539" /> +<glyph + unicode="" + horiz-adv-x="347" + d="M272,0l0,103l56,0l0,49l-56,0l0,238l-79,0l-177,-247l0,-40l189,0l0,-103M205,152l-119,0l-1,2l87,118C184,290 194,308 206,331l2,-1C207,311 205,290 205,268z" + id="glyph2541" /> +<glyph + unicode="" + horiz-adv-x="503" + d="M396,0l0,177l91,0l0,69l-91,0l0,404l-95,0l-290,-415l0,-58l302,0l0,-177M100,246l0,2l159,221C277,499 293,527 313,565l3,0C314,531 313,497 313,463l0,-217z" + id="glyph2543" /> +<glyph + unicode="₄" + horiz-adv-x="347" + d="M272,-147l0,103l56,0l0,49l-56,0l0,238l-79,0l-177,-247l0,-40l189,0l0,-103M205,5l-119,0l-1,2l87,118C184,143 194,161 206,184l2,-1C207,164 205,143 205,121z" + id="glyph2545" /> +<glyph + unicode="" + horiz-adv-x="347" + d="M272,266l0,103l56,0l0,49l-56,0l0,238l-79,0l-177,-247l0,-40l189,0l0,-103M205,418l-119,0l-1,2l87,118C184,556 194,574 206,597l2,-1C207,577 205,556 205,534z" + id="glyph2547" /> +<glyph + unicode="" + horiz-adv-x="502" + d="M483,50l0,67l-91,0l0,375l-95,0l-279,-385l0,-57l292,0l0,-167l82,0l0,167M310,117l-203,0l-2,3l150,198C274,347 293,381 309,412l4,0C310,380 310,350 310,317z" + id="glyph2549" /> +<glyph + unicode="⁴" + horiz-adv-x="347" + d="M272,443l0,103l56,0l0,49l-56,0l0,238l-79,0l-177,-247l0,-40l189,0l0,-103M205,595l-119,0l-1,2l87,118C184,733 194,751 206,774l2,-1C207,754 205,733 205,711z" + id="glyph2551" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M497,50l0,67l-91,0l0,375l-95,0l-279,-385l0,-57l292,0l0,-167l82,0l0,167M324,117l-203,0l-2,3l150,198C288,347 307,381 323,412l4,0C324,380 324,350 324,317z" + id="glyph2553" /> +<glyph + unicode="⁄" + horiz-adv-x="121" + d="M-101,-11l380,672l-57,0l-379,-672z" + id="glyph2555" /> +<glyph + unicode="g" + horiz-adv-x="559" + d="M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293z" + id="glyph2557" /> +<glyph + unicode="" + horiz-adv-x="559" + d="M474,351C474,410 476,449 478,484l-77,0l-4,-73l-2,0C374,451 328,495 244,495C133,495 26,402 26,238C26,104 112,2 232,2C307,2 359,38 386,83l2,0l0,-54C388,-93 322,-140 232,-140C172,-140 122,-122 90,-102l-22,-67C107,-195 171,-209 229,-209C290,-209 358,-195 405,-151C452,-109 474,-41 474,70M387,206C387,191 385,174 380,159C361,103 312,69 258,69C163,69 115,148 115,243C115,355 175,426 259,426C323,426 366,384 382,333C386,321 387,308 387,293M205,567C207,595 215,612 231,612C243,612 254,606 274,594C295,583 314,574 335,574C382,574 405,608 404,675l-47,0C355,641 345,632 329,632C317,632 303,640 287,649C264,662 247,671 225,671C182,671 155,632 157,567z" + id="glyph2559" /> +<glyph + unicode="γ" + horiz-adv-x="464" + d="M269,-198C269,-130 269,-55 266,2C266,11 266,18 272,26C365,167 443,327 443,451C443,463 442,475 440,484l-83,0C357,475 358,465 358,450C358,331 288,188 244,107l-4,0C210,256 137,418 93,484l-99,0C43,409 129,246 165,86C188,-10 186,-106 183,-198z" + id="glyph2561" /> +<glyph + unicode="ğ" + horiz-adv-x="559" + d="M148,682C148,620 184,558 272,558C347,558 396,608 396,682l-52,0C341,649 319,613 272,613C231,613 206,643 200,682M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293z" + id="glyph2563" /> +<glyph + unicode="ĝ" + horiz-adv-x="559" + d="M248,696l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293z" + id="glyph2565" /> +<glyph + unicode="ģ" + horiz-adv-x="559" + d="M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293M338,709C277,706 209,677 209,606C209,575 229,548 249,535l63,13C294,563 277,584 277,607C277,647 314,666 355,672z" + id="glyph2567" /> +<glyph + unicode="ġ" + horiz-adv-x="559" + d="M279,573C308,573 328,597 328,624C328,653 307,676 279,676C250,676 228,653 228,624C228,597 249,573 278,573M486,351C486,410 488,449 490,484l-77,0l-4,-73l-2,0C386,451 340,495 256,495C145,495 38,402 38,238C38,104 124,2 244,2C319,2 371,38 398,83l2,0l0,-54C400,-93 334,-140 244,-140C184,-140 134,-122 102,-102l-22,-67C119,-195 183,-209 241,-209C302,-209 370,-195 417,-151C464,-109 486,-41 486,70M399,206C399,191 397,174 392,159C373,103 324,69 270,69C175,69 127,148 127,243C127,355 187,426 271,426C335,426 378,384 394,333C398,321 399,308 399,293z" + id="glyph2569" /> +<glyph + unicode="ß" + horiz-adv-x="548" + d="M161,0l0,456C161,598 204,652 281,652C343,652 385,612 385,543C385,526 381,512 376,500C319,472 287,428 287,377C287,321 323,284 365,244C401,211 419,183 419,141C419,95 385,57 321,57C291,57 263,64 237,77l-16,-67C250,-5 290,-11 327,-11C438,-11 506,54 506,148C506,220 465,262 425,302C394,334 374,355 374,389C374,423 401,451 446,470C457,486 469,517 469,552C469,661 386,721 291,721C230,721 182,704 144,670C100,630 73,566 73,452l0,-452z" + id="glyph2571" /> +<glyph + unicode="`" + horiz-adv-x="300" + d="M22,693l122,-143l62,0l-87,143z" + id="glyph2573" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M-2,827l127,-116l72,0l-93,116z" + id="glyph2575" /> +<glyph + unicode=">" + horiz-adv-x="596" + d="M530,294l-464,238l0,-69l394,-196l0,-2l-394,-196l0,-69l464,238z" + id="glyph2577" /> +<glyph + unicode="≥" + horiz-adv-x="596" + d="M530,292l0,59l-464,192l0,-66l382,-155l0,-2l-382,-154l0,-67M528,0l0,58l-462,0l0,-58z" + id="glyph2579" /> +<glyph + unicode="«" + horiz-adv-x="419" + d="M232,436l-71,0l-132,-184l132,-184l71,0l-134,184M396,436l-71,0l-132,-184l132,-184l71,0l-134,184z" + id="glyph2581" /> +<glyph + unicode="" + horiz-adv-x="419" + d="M232,511l-71,0l-132,-184l132,-184l71,0l-134,184M396,511l-71,0l-132,-184l132,-184l71,0l-134,184z" + id="glyph2583" /> +<glyph + unicode="»" + horiz-adv-x="419" + d="M157,252l-133,-184l70,0l133,184l-133,184l-71,0M320,252l-133,-184l70,0l133,184l-133,184l-71,0z" + id="glyph2585" /> +<glyph + unicode="" + horiz-adv-x="419" + d="M157,327l-133,-184l70,0l133,184l-133,184l-71,0M320,327l-133,-184l70,0l133,184l-133,184l-71,0z" + id="glyph2587" /> +<glyph + unicode="‹" + horiz-adv-x="255" + d="M232,436l-71,0l-132,-184l132,-184l71,0l-134,184z" + id="glyph2589" /> +<glyph + unicode="" + horiz-adv-x="255" + d="M232,511l-71,0l-132,-184l132,-184l71,0l-134,184z" + id="glyph2591" /> +<glyph + unicode="›" + horiz-adv-x="255" + d="M157,252l-134,-184l71,0l133,184l-134,184l-70,0z" + id="glyph2593" /> +<glyph + unicode="" + horiz-adv-x="255" + d="M157,327l-134,-184l71,0l133,184l-134,184l-70,0z" + id="glyph2595" /> +<glyph + unicode="h" + horiz-adv-x="555" + d="M73,0l88,0l0,292C161,309 162,322 167,334C183,382 228,422 285,422C368,422 397,356 397,278l0,-278l88,0l0,288C485,455 381,495 316,495C283,495 252,485 226,470C199,455 177,433 163,408l-2,0l0,302l-88,0z" + id="glyph2597" /> +<glyph + unicode="ħ" + horiz-adv-x="555" + d="M485,0l0,288C485,455 381,495 316,495C283,495 252,485 226,470C199,455 177,433 163,408l-2,0l0,144l193,0l0,55l-193,0l0,103l-88,0l0,-103l-77,0l0,-55l77,0l0,-552l88,0l0,292C161,309 162,322 167,334C183,382 228,422 285,422C368,422 397,356 397,278l0,-278z" + id="glyph2599" /> +<glyph + unicode="ĥ" + horiz-adv-x="555" + d="M73,0l88,0l0,292C161,309 162,322 167,334C183,382 228,422 285,422C368,422 397,356 397,278l0,-278l88,0l0,288C485,455 381,495 316,495C283,495 252,485 226,470C199,455 177,433 163,408l-2,0l0,302l-88,0M87,843l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114z" + id="glyph2601" /> +<glyph + unicode="˝" + horiz-adv-x="300" + d="M123,693l-79,-137l55,0l110,137M267,693l-79,-137l55,0l110,137z" + id="glyph2603" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M117,827l-80,-112l64,0l111,112M264,827l-80,-112l64,0l112,112z" + id="glyph2605" /> +<glyph + unicode="-" + horiz-adv-x="307" + d="M30,303l0,-65l247,0l0,65z" + id="glyph2607" /> +<glyph + unicode="" + horiz-adv-x="307" + d="M30,378l0,-65l247,0l0,65z" + id="glyph2609" /> +<glyph + unicode="" + horiz-adv-x="191" + d="M18,208l0,-48l155,0l0,48z" + id="glyph2611" /> +<glyph + unicode="" + horiz-adv-x="191" + d="M18,61l0,-48l155,0l0,48z" + id="glyph2613" /> +<glyph + unicode="" + horiz-adv-x="191" + d="M18,474l0,-48l155,0l0,48z" + id="glyph2615" /> +<glyph + unicode="" + horiz-adv-x="191" + d="M18,651l0,-48l155,0l0,48z" + id="glyph2617" /> +<glyph + unicode="i" + horiz-adv-x="234" + d="M161,0l0,484l-88,0l0,-484M117,675C85,675 62,651 62,620C62,590 84,566 115,566C150,566 172,590 171,620C171,651 150,675 117,675z" + id="glyph2619" /> +<glyph + unicode="í" + horiz-adv-x="234" + d="M155,693l-88,-143l63,0l122,143M161,0l0,484l-88,0l0,-484z" + id="glyph2621" /> +<glyph + unicode="ĭ" + horiz-adv-x="234" + d="M-4,682C-4,620 32,558 120,558C195,558 244,608 244,682l-52,0C189,649 167,613 120,613C79,613 54,643 48,682M161,0l0,484l-88,0l0,-484z" + id="glyph2623" /> +<glyph + unicode="î" + horiz-adv-x="234" + d="M86,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M161,0l0,484l-88,0l0,-484z" + id="glyph2625" /> +<glyph + unicode="ï" + horiz-adv-x="234" + d="M25,570C54,570 74,594 74,621C74,650 53,672 25,672C-3,672 -26,649 -26,621C-26,594 -5,570 24,570M210,570C239,570 259,594 259,621C259,650 238,672 210,672C182,672 160,649 160,621C160,594 180,570 209,570M161,0l0,484l-88,0l0,-484z" + id="glyph2627" /> +<glyph + unicode="ì" + horiz-adv-x="234" + d="M-27,693l122,-143l62,0l-87,143M161,0l0,484l-88,0l0,-484z" + id="glyph2629" /> +<glyph + unicode="ij" + horiz-adv-x="477" + d="M161,0l0,484l-88,0l0,-484M117,675C85,675 62,651 62,620C62,590 84,566 115,566C150,566 172,590 171,620C171,651 150,675 117,675M198,-211C245,-211 309,-195 348,-156C391,-112 406,-51 406,43l0,441l-88,0l0,-407C318,-39 309,-78 285,-105C264,-128 229,-139 189,-142M363,675C330,675 307,651 307,620C307,591 328,566 361,566C396,566 417,591 416,620C416,651 395,675 363,675z" + id="glyph2631" /> +<glyph + unicode="ī" + horiz-adv-x="234" + d="M5,643l0,-57l225,0l0,57M161,0l0,484l-88,0l0,-484z" + id="glyph2633" /> +<glyph + unicode="∞" + horiz-adv-x="728" + d="M541,116C629,116 693,185 693,264C693,350 634,412 549,412C468,412 415,356 364,305C316,353 261,412 187,412C99,412 35,343 35,264C35,178 95,116 179,116C260,116 313,172 364,223C412,175 467,116 540,116M542,176C484,176 443,228 404,264C442,303 488,352 545,352C594,352 630,315 630,264C630,217 592,176 543,176M183,176C134,176 98,213 98,264C98,311 136,352 186,352C244,352 285,300 324,264C286,225 240,176 184,176z" + id="glyph2635" /> +<glyph + unicode="∫" + horiz-adv-x="316" + d="M306,788C296,792 280,795 261,795C227,795 196,784 172,758C136,719 120,663 120,562C120,415 132,238 132,86C132,-10 118,-47 101,-68C89,-83 73,-89 57,-89C43,-89 30,-86 22,-83l-12,-58C19,-146 41,-150 64,-150C102,-150 137,-134 159,-102C183,-70 201,-12 201,86C201,237 189,411 189,556C189,645 199,689 219,714C230,729 247,736 265,736C275,736 286,734 295,732z" + id="glyph2637" /> +<glyph + unicode="į" + horiz-adv-x="234" + d="M116,570C151,570 173,596 173,626C173,657 151,682 117,682C84,682 61,657 61,626C61,596 83,570 115,570M161,484l-88,0l0,-484C55,-27 26,-74 26,-123C26,-179 63,-209 118,-209C144,-209 178,-202 199,-186l-13,45C174,-146 160,-150 140,-150C111,-150 89,-132 89,-98C89,-62 116,-21 131,0l30,0z" + id="glyph2639" /> +<glyph + unicode="ι" + horiz-adv-x="236" + d="M161,484l-88,0l0,-376C73,12 112,-11 163,-11C178,-11 193,-9 203,-6l5,64C170,56 161,74 161,117z" + id="glyph2641" /> +<glyph + unicode="ϊ" + horiz-adv-x="236" + d="M28,557C57,557 77,580 77,607C77,636 56,658 29,658C0,658 -21,635 -21,607C-21,580 -1,557 27,557M212,557C241,557 261,580 261,607C261,636 240,658 212,658C184,658 163,635 163,607C163,580 182,557 211,557M161,484l-88,0l0,-375C73,12 112,-11 163,-11C178,-11 193,-9 203,-6l5,64C170,56 161,74 161,117z" + id="glyph2643" /> +<glyph + unicode="ΐ" + horiz-adv-x="236" + d="M161,484l-88,0l0,-376C73,12 112,-11 163,-11C178,-11 193,-9 203,-6l5,64C170,56 161,74 161,117M107,696l-22,-156l47,0l50,156M5,561C32,561 50,582 50,607C50,633 31,653 6,653C-20,653 -41,633 -41,607C-41,582 -22,561 4,561M247,561C274,561 292,582 292,607C292,633 273,653 247,653C221,653 201,633 201,607C201,582 220,561 246,561z" + id="glyph2645" /> +<glyph + unicode="ί" + horiz-adv-x="236" + d="M133,687l-53,-143l59,0l83,143M161,484l-88,0l0,-376C73,12 112,-11 163,-11C178,-11 193,-9 203,-6l5,64C170,56 161,74 161,117z" + id="glyph2647" /> +<glyph + unicode="ĩ" + horiz-adv-x="234" + d="M42,567C44,595 52,612 68,612C80,612 91,606 111,594C132,583 151,574 172,574C219,574 242,608 241,675l-47,0C192,641 182,632 166,632C154,632 140,640 124,649C101,662 84,671 62,671C19,671 -8,632 -6,567M161,0l0,484l-88,0l0,-484z" + id="glyph2649" /> +<glyph + unicode="j" + horiz-adv-x="243" + d="M-36,-211C11,-211 75,-195 114,-156C157,-112 172,-51 172,43l0,441l-88,0l0,-407C84,-39 75,-78 51,-105C30,-128 -5,-139 -45,-142M129,675C96,675 73,651 73,620C73,591 94,566 127,566C162,566 183,591 182,620C182,651 161,675 129,675z" + id="glyph2651" /> +<glyph + unicode="ĵ" + horiz-adv-x="243" + d="M-36,-211C11,-211 75,-195 114,-156C157,-112 172,-51 172,43l0,441l-88,0l0,-407C84,-39 75,-78 51,-105C30,-128 -5,-139 -45,-142M91,686l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143z" + id="glyph2653" /> +<glyph + unicode="k" + horiz-adv-x="469" + d="M160,710l-87,0l0,-710l87,0l0,182l45,50l167,-232l107,0l-213,285l187,199l-106,0l-142,-167C191,300 174,279 162,262l-2,0z" + id="glyph2655" /> +<glyph + unicode="κ" + horiz-adv-x="493" + d="M250,262C335,342 412,417 480,480l0,4l-103,0C320,426 252,351 165,269l-4,0l0,133C161,442 155,473 147,484l-85,0C70,466 73,418 73,379l0,-379l88,0l0,215l8,0C215,214 260,167 309,103l76,-103l102,0l-88,122C350,186 298,248 250,259z" + id="glyph2657" /> +<glyph + unicode="ķ" + horiz-adv-x="469" + d="M160,710l-87,0l0,-710l87,0l0,182l45,50l167,-232l107,0l-213,285l187,199l-106,0l-142,-167C191,300 174,279 162,262l-2,0M177,-222C239,-218 306,-188 306,-114C306,-82 286,-54 266,-41l-65,-14C219,-69 235,-92 235,-115C235,-155 199,-175 160,-181z" + id="glyph2659" /> +<glyph + unicode="ĸ" + horiz-adv-x="469" + d="M160,484l-87,0l0,-484l87,0l0,182l45,50l167,-232l107,0l-213,285l187,199l-106,0l-142,-167C191,300 174,279 162,262l-2,0z" + id="glyph2661" /> +<glyph + unicode="l" + horiz-adv-x="236" + d="M73,0l88,0l0,710l-88,0z" + id="glyph2663" /> +<glyph + unicode="ĺ" + horiz-adv-x="236" + d="M73,0l88,0l0,710l-88,0M154,856l-93,-117l72,0l127,117z" + id="glyph2665" /> +<glyph + unicode="λ" + horiz-adv-x="472" + d="M364,0l93,0l-185,492C220,629 175,720 80,720C69,720 55,718 47,716l-9,-68C44,649 50,649 55,649C114,649 151,582 182,497C187,485 188,477 188,471C188,466 186,459 182,448l-175,-448l92,0l99,280C209,313 222,350 229,382l5,0C244,343 255,310 266,277z" + id="glyph2667" /> +<glyph + unicode="ľ" + horiz-adv-x="244" + d="M73,0l88,0l0,710l-88,0M240,522C255,535 304,571 304,644C304,679 286,708 269,719l-63,-14C223,690 237,665 237,640C237,599 217,571 201,551z" + id="glyph2669" /> +<glyph + unicode="ļ" + horiz-adv-x="236" + d="M73,0l88,0l0,710l-88,0M52,-222C114,-218 181,-188 181,-114C181,-82 161,-54 141,-41l-65,-14C94,-69 110,-92 110,-115C110,-155 74,-175 35,-181z" + id="glyph2671" /> +<glyph + unicode="ŀ" + horiz-adv-x="272" + d="M73,0l88,0l0,710l-88,0M253,341C280,341 299,365 299,391C299,420 279,441 253,441C226,441 205,419 205,391C205,365 225,341 252,341z" + id="glyph2673" /> +<glyph + unicode="<" + horiz-adv-x="596" + d="M66,239l464,-239l0,69l-391,196l0,2l391,196l0,69l-464,-239z" + id="glyph2675" /> +<glyph + unicode="≤" + horiz-adv-x="596" + d="M530,99l0,67l-382,154l0,2l382,155l0,66l-464,-192l0,-59M530,0l0,58l-462,0l0,-58z" + id="glyph2677" /> +<glyph + unicode="¬" + horiz-adv-x="596" + d="M40,399l0,-60l452,0l0,-218l64,0l0,278z" + id="glyph2679" /> +<glyph + unicode="◊" + horiz-adv-x="522" + d="M477,325l-182,355l-69,0l-181,-355l181,-356l69,0M401,323l-128,-263C270,53 266,44 263,37l-2,0C259,44 254,53 251,60l-131,266l128,264C252,596 256,605 258,613l2,0C263,605 267,596 271,590z" + id="glyph2681" /> +<glyph + unicode="ł" + horiz-adv-x="242" + d="M165,0l0,355l71,61l0,70l-71,-61l0,285l-88,0l0,-352l-68,-59l0,-69l68,58l0,-288z" + id="glyph2683" /> +<glyph + unicode="m" + horiz-adv-x="834" + d="M73,0l86,0l0,292C159,307 161,322 166,335C180,379 220,423 275,423C342,423 376,367 376,290l0,-290l86,0l0,299C462,315 465,331 469,343C484,386 523,423 573,423C644,423 678,367 678,274l0,-274l86,0l0,285C764,453 669,495 605,495C559,495 527,483 498,461C478,446 459,425 444,398l-2,0C421,455 371,495 305,495C225,495 180,452 153,406l-3,0l-4,78l-77,0C72,444 73,403 73,353z" + id="glyph2685" /> +<glyph + unicode="" + horiz-adv-x="834" + d="M386,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M73,0l86,0l0,292C159,307 161,322 166,335C180,379 220,423 275,423C342,423 376,367 376,290l0,-290l86,0l0,299C462,315 465,331 469,343C484,386 523,423 573,423C644,423 678,367 678,274l0,-274l86,0l0,285C764,453 669,495 605,495C559,495 527,483 498,461C478,446 459,425 444,398l-2,0C421,455 371,495 305,495C225,495 180,452 153,406l-3,0l-4,78l-77,0C72,444 73,403 73,353z" + id="glyph2687" /> +<glyph + unicode="¯" + horiz-adv-x="300" + d="M38,643l0,-57l225,0l0,57z" + id="glyph2689" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M31,788l0,-55l239,0l0,55z" + id="glyph2691" /> +<glyph + unicode="−" + horiz-adv-x="596" + d="M40,297l0,-60l516,0l0,60z" + id="glyph2693" /> +<glyph + unicode="µ" + horiz-adv-x="553" + d="M403,75C411,13 446,-11 489,-11C503,-11 509,-10 519,-7l7,64C491,59 480,80 480,133l0,351l-87,0l0,-297C393,171 390,155 385,142C369,103 327,61 269,61C192,61 160,123 160,198l0,286l-87,0l0,-504C73,-97 77,-168 87,-198l79,0C156,-158 155,-69 155,-16l0,60C176,7 217,-9 261,-9C333,-9 380,37 400,75z" + id="glyph2695" /> +<glyph + unicode="×" + horiz-adv-x="596" + d="M40,482l214,-219l-214,-219l44,-44l214,219l214,-219l44,44l-215,219l215,219l-44,44l-214,-219l-214,219z" + id="glyph2697" /> +<glyph + unicode="n" + horiz-adv-x="555" + d="M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z" + id="glyph2699" /> +<glyph + unicode="" + horiz-adv-x="555" + d="M246,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z" + id="glyph2701" /> +<glyph + unicode="ń" + horiz-adv-x="555" + d="M318,693l-88,-143l63,0l122,143M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z" + id="glyph2703" /> +<glyph + unicode="ʼn" + horiz-adv-x="555" + d="M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353M56,536C72,548 123,584 123,651C123,686 105,713 88,723l-64,-17C41,692 55,670 55,647C55,609 35,584 18,565z" + id="glyph2705" /> +<glyph + unicode="ň" + horiz-adv-x="555" + d="M310,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z" + id="glyph2707" /> +<glyph + unicode="ņ" + horiz-adv-x="555" + d="M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353M221,-221C283,-217 350,-187 350,-113C350,-81 330,-53 310,-40l-65,-14C263,-68 279,-91 279,-114C279,-154 243,-174 204,-180z" + id="glyph2709" /> +<glyph + unicode="9" + horiz-adv-x="513" + d="M96,-10C117,-11 145,-10 177,-5C247,3 316,33 366,82C430,144 478,244 478,387C478,553 396,661 263,661C130,661 41,553 41,430C41,319 116,231 233,231C298,231 350,257 386,301l3,0C377,224 346,167 303,128C266,93 218,72 167,65C137,62 115,60 96,62M256,594C348,594 389,509 389,398C389,384 386,374 381,366C358,327 312,298 253,298C177,298 128,355 128,436C128,528 181,594 255,594z" + id="glyph2711" /> +<glyph + unicode="" + horiz-adv-x="327" + d="M58,-4C69,-5 87,-5 104,-2C155,2 202,19 235,50C275,87 304,147 304,230C304,326 254,395 164,395C81,395 23,331 23,257C23,187 73,137 144,137C183,137 213,151 234,175l1,0C228,135 210,104 184,83C161,64 131,53 100,49C84,48 69,47 58,48M161,345C214,345 236,298 236,241C236,233 235,226 232,221C219,201 194,185 161,185C117,185 90,218 90,260C90,308 120,345 160,345z" + id="glyph2713" /> +<glyph + unicode="" + horiz-adv-x="517" + d="M96,-10C117,-11 145,-10 177,-5C247,3 316,33 366,82C430,144 478,244 478,387C478,553 396,661 263,661C130,661 41,553 41,430C41,319 116,231 233,231C298,231 350,257 386,301l3,0C377,224 346,167 303,128C266,93 218,72 167,65C137,62 115,60 96,62M256,594C348,594 389,509 389,398C389,384 386,374 381,366C358,327 312,298 253,298C177,298 128,355 128,436C128,528 181,594 255,594z" + id="glyph2715" /> +<glyph + unicode="₉" + horiz-adv-x="327" + d="M58,-151C69,-152 87,-152 104,-149C155,-145 202,-128 235,-97C275,-60 304,0 304,83C304,179 254,248 164,248C81,248 23,184 23,110C23,40 73,-10 144,-10C183,-10 213,4 234,28l1,0C228,-12 210,-43 184,-64C161,-83 131,-94 100,-98C84,-99 69,-100 58,-99M161,198C214,198 236,151 236,94C236,86 235,79 232,74C219,54 194,38 161,38C117,38 90,71 90,113C90,161 120,198 160,198z" + id="glyph2717" /> +<glyph + unicode="" + horiz-adv-x="327" + d="M58,262C69,261 87,261 104,264C155,268 202,285 235,316C275,353 304,413 304,496C304,592 254,661 164,661C81,661 23,597 23,523C23,453 73,403 144,403C183,403 213,417 234,441l1,0C228,401 210,370 184,349C161,330 131,319 100,315C84,314 69,313 58,314M161,611C214,611 236,564 236,507C236,499 235,492 232,487C219,467 194,451 161,451C117,451 90,484 90,526C90,574 120,611 160,611z" + id="glyph2719" /> +<glyph + unicode="" + horiz-adv-x="520" + d="M82,-114C313,-107 472,34 472,258C472,411 378,498 260,498C138,498 43,406 43,285C43,165 129,93 230,93C298,93 353,127 373,161l2,-1C348,42 247,-42 76,-49M252,155C187,155 131,199 131,291C131,385 195,430 256,430C326,430 385,371 385,280C385,256 379,234 370,220C351,185 303,155 253,155z" + id="glyph2721" /> +<glyph + unicode="⁹" + horiz-adv-x="327" + d="M58,439C69,438 87,438 104,441C155,445 202,462 235,493C275,530 304,590 304,673C304,769 254,838 164,838C81,838 23,774 23,700C23,630 73,580 144,580C183,580 213,594 234,618l1,0C228,578 210,547 184,526C161,507 131,496 100,492C84,491 69,490 58,491M161,788C214,788 236,741 236,684C236,676 235,669 232,664C219,644 194,628 161,628C117,628 90,661 90,703C90,751 120,788 160,788z" + id="glyph2723" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M89,-114C320,-107 479,34 479,258C479,411 385,498 267,498C145,498 50,406 50,285C50,165 136,93 237,93C305,93 360,127 380,161l2,-1C355,42 254,-42 83,-49M259,155C194,155 138,199 138,291C138,385 202,430 263,430C333,430 392,371 392,280C392,256 386,234 377,220C358,185 310,155 260,155z" + id="glyph2725" /> +<glyph + unicode="≠" + horiz-adv-x="596" + d="M556,140l0,60l-271,0l88,138l183,0l0,60l-145,0l79,122l-65,0l-76,-122l-309,0l0,-60l271,0l-89,-138l-182,0l0,-60l144,0l-79,-122l65,0l76,122z" + id="glyph2727" /> +<glyph + unicode="ñ" + horiz-adv-x="555" + d="M203,567C205,595 213,612 229,612C241,612 252,606 272,594C293,583 312,574 333,574C380,574 403,608 402,675l-47,0C353,641 343,632 327,632C315,632 301,640 285,649C262,662 245,671 223,671C180,671 153,632 155,567M73,0l88,0l0,291C161,306 163,321 167,332C182,381 227,422 285,422C368,422 397,357 397,279l0,-279l88,0l0,289C485,455 381,495 314,495C234,495 178,450 154,404l-2,0l-5,80l-78,0C72,444 73,403 73,353z" + id="glyph2729" /> +<glyph + unicode="ν" + horiz-adv-x="469" + d="M4,484l177,-484l83,0C352,147 445,311 445,448C445,461 443,474 442,484l-83,0C360,470 360,455 360,439C360,327 275,164 235,86l-3,0C218,128 206,169 190,212l-93,272z" + id="glyph2731" /> +<glyph + unicode="#" + horiz-adv-x="497" + d="M188,255l19,145l104,0l-19,-145M153,0l26,196l104,0l-26,-196l60,0l26,196l95,0l0,59l-86,0l18,145l91,0l0,59l-82,0l25,191l-59,0l-26,-191l-104,0l25,191l-58,0l-26,-191l-95,0l0,-59l86,0l-19,-145l-91,0l0,-59l82,0l-26,-196z" + id="glyph2733" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M205,226l19,145l103,0l-18,-145M174,-3l22,170l104,0l-23,-170l60,0l23,170l95,0l0,59l-86,0l18,145l91,0l0,59l-82,0l21,165l-59,0l-22,-165l-104,0l22,165l-59,0l-22,-165l-95,0l0,-59l86,0l-19,-145l-91,0l0,-59l82,0l-22,-170z" + id="glyph2735" /> +<glyph + unicode="o" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2737" /> +<glyph + unicode="ó" + horiz-adv-x="549" + d="M309,693l-88,-143l63,0l122,143M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2739" /> +<glyph + unicode="ŏ" + horiz-adv-x="549" + d="M150,682C150,620 186,558 274,558C349,558 398,608 398,682l-52,0C343,649 321,613 274,613C233,613 208,643 202,682M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2741" /> +<glyph + unicode="ô" + horiz-adv-x="549" + d="M243,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2743" /> +<glyph + unicode="ö" + horiz-adv-x="549" + d="M182,570C211,570 231,594 231,621C231,650 210,672 182,672C154,672 131,649 131,621C131,594 152,570 181,570M367,570C396,570 416,594 416,621C416,650 395,672 367,672C339,672 317,649 317,621C317,594 337,570 366,570M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2745" /> +<glyph + unicode="œ" + horiz-adv-x="863" + d="M824,227C826,236 827,251 827,268C827,354 786,495 631,495C556,495 491,456 455,379l-2,0C423,453 352,495 270,495C140,495 38,396 38,239C38,86 136,-11 264,-11C339,-11 415,25 451,103l3,0C492,24 561,-11 645,-11C698,-11 763,0 802,22l-16,63C753,71 716,58 656,58C564,58 496,117 495,227M271,429C371,429 413,334 413,242C413,131 356,55 271,55C187,55 127,136 127,241C127,324 165,429 270,429M495,290C500,351 537,431 624,431C720,431 743,344 742,290z" + id="glyph2747" /> +<glyph + unicode="˛" + horiz-adv-x="300" + d="M123,4C99,-22 63,-70 63,-119C63,-175 101,-205 157,-205C182,-205 217,-198 237,-182l-14,45C211,-142 195,-146 177,-146C148,-146 127,-127 127,-96C127,-58 165,-15 180,4z" + id="glyph2749" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M123,4C99,-22 63,-70 63,-119C63,-175 101,-205 157,-205C182,-205 217,-198 237,-182l-14,45C211,-142 195,-146 177,-146C148,-146 127,-127 127,-96C127,-58 165,-15 180,4z" + id="glyph2751" /> +<glyph + unicode="ò" + horiz-adv-x="549" + d="M145,693l122,-143l62,0l-87,143M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2753" /> +<glyph + unicode="ơ" + horiz-adv-x="553" + d="M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M470,554C479,537 486,517 486,495C486,469 472,455 446,455C415,455 385,474 361,481C334,490 308,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,311 493,366 461,406C522,403 554,439 554,493C554,521 547,545 537,566z" + id="glyph2755" /> +<glyph + unicode="ő" + horiz-adv-x="549" + d="M238,693l-79,-137l55,0l110,137M382,693l-79,-137l55,0l110,137M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2757" /> +<glyph + unicode="ō" + horiz-adv-x="549" + d="M162,643l0,-57l225,0l0,57M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2759" /> +<glyph + unicode="ω" + horiz-adv-x="694" + d="M388,363l-83,0l2,-191C307,111 272,62 227,62C181,62 124,114 124,249C124,352 159,439 204,488l0,2l-81,0C80,448 38,356 38,247C38,63 131,-11 215,-11C274,-11 320,22 343,75l4,0C367,22 414,-11 473,-11C565,-11 656,75 656,255C656,366 616,454 573,490l-83,0l0,-2C536,443 570,352 570,257C570,127 519,62 469,62C421,62 386,110 386,173z" + id="glyph2761" /> +<glyph + unicode="ώ" + horiz-adv-x="694" + d="M360,686l-53,-143l59,0l83,143M388,363l-83,0l2,-191C307,111 272,62 227,62C181,62 124,114 124,249C124,352 159,439 204,488l0,2l-81,0C80,448 38,356 38,247C38,63 131,-11 215,-11C274,-11 320,22 343,75l4,0C367,22 414,-11 474,-11C565,-11 656,75 656,255C656,366 616,454 573,490l-83,0l0,-2C537,443 570,352 570,257C570,127 519,62 469,62C421,62 386,110 386,173z" + id="glyph2763" /> +<glyph + unicode="ο" + horiz-adv-x="549" + d="M271,-11C387,-11 511,67 511,246C511,394 417,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11M273,55C188,55 127,135 127,241C127,333 172,429 276,429C380,429 421,325 421,243C421,134 358,55 274,55z" + id="glyph2765" /> +<glyph + unicode="ό" + horiz-adv-x="549" + d="M289,686l-53,-143l58,0l84,143M271,-11C387,-11 511,67 511,246C511,394 417,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11M273,55C188,55 127,135 127,241C127,333 172,429 276,429C380,429 421,325 421,243C421,134 358,55 274,55z" + id="glyph2767" /> +<glyph + unicode="1" + horiz-adv-x="513" + d="M236,0l85,0l0,650l-75,0l-142,-76l17,-67l113,61l2,0z" + id="glyph2769" /> +<glyph + unicode="" + horiz-adv-x="244" + d="M177,0l0,390l-61,0l-94,-44l11,-52l72,35l2,0l0,-329z" + id="glyph2771" /> +<glyph + unicode="" + horiz-adv-x="326" + d="M151,0l85,0l0,650l-75,0l-142,-76l17,-67l113,61l2,0z" + id="glyph2773" /> +<glyph + unicode="₁" + horiz-adv-x="244" + d="M177,-147l0,390l-61,0l-94,-44l11,-52l72,35l2,0l0,-329z" + id="glyph2775" /> +<glyph + unicode="" + horiz-adv-x="244" + d="M177,266l0,390l-61,0l-94,-44l11,-52l72,35l2,0l0,-329z" + id="glyph2777" /> +<glyph + unicode="" + horiz-adv-x="362" + d="M263,0l0,485l-73,0l-164,-74l16,-65l135,62l2,0l0,-408z" + id="glyph2779" /> +<glyph + unicode="¹" + horiz-adv-x="244" + d="M177,443l0,390l-61,0l-94,-44l11,-52l72,35l2,0l0,-329z" + id="glyph2781" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M366,0l0,485l-73,0l-215,-92l15,-65l186,82l2,0l0,-410z" + id="glyph2783" /> +<glyph + unicode="½" + horiz-adv-x="759" + d="M184,266l0,390l-62,0l-93,-44l11,-52l72,35l2,0l0,-329M175,-11l379,672l-56,0l-379,-672M440,0l270,0l0,55l-171,0l0,2l31,28C645,153 703,207 703,282C703,336 665,395 570,395C520,395 475,376 448,353l21,-45C488,321 518,339 553,339C605,339 629,307 629,269C629,218 586,171 503,96l-63,-59z" + id="glyph2785" /> +<glyph + unicode="¼" + horiz-adv-x="759" + d="M198,266l0,390l-61,0l-94,-44l11,-52l72,35l2,0l0,-329M195,-11l379,672l-56,0l-380,-672M664,0l0,103l60,0l0,49l-60,0l0,243l-74,0l-193,-253l0,-39l200,0l0,-103M597,152l-128,0l0,1l96,125C576,294 585,312 598,333l2,0C599,314 597,293 597,270z" + id="glyph2787" /> +<glyph + unicode="ª" + horiz-adv-x="346" + d="M305,266C301,290 299,317 299,344l0,107C299,523 272,591 172,591C129,591 87,579 58,558l16,-43C97,533 128,542 160,542C224,542 234,496 234,480l0,-10C106,473 33,428 33,356C33,304 70,261 136,261C183,261 218,284 237,308l2,0l6,-42M235,372C235,365 231,355 225,345C212,326 187,309 157,309C126,309 99,327 99,360C99,417 172,428 235,427z" + id="glyph2789" /> +<glyph + unicode="º" + horiz-adv-x="355" + d="M179,591C93,591 18,527 18,423C18,325 88,261 177,261C254,261 336,313 337,428C337,522 276,591 180,591M177,541C243,541 270,476 270,426C270,357 231,311 178,311C128,311 85,359 85,425C85,478 113,541 176,541z" + id="glyph2791" /> +<glyph + unicode="ø" + horiz-adv-x="549" + d="M276,495C144,495 38,400 38,239C38,160 66,96 113,52l-42,-60l37,-30l43,61C186,0 230,-11 275,-11C388,-11 511,68 511,246C511,321 485,386 438,431l42,62l-39,27l-42,-59C363,483 320,495 277,495M161,119l-2,1C134,155 123,199 123,241C123,329 166,429 274,429C309,429 337,418 359,400M389,361l2,0C417,323 425,276 425,243C425,134 364,55 272,55C242,55 214,63 191,83z" + id="glyph2793" /> +<glyph + unicode="ǿ" + horiz-adv-x="549" + d="M315,693l-88,-143l63,0l122,143M276,495C144,495 38,400 38,239C38,160 66,96 113,52l-42,-60l37,-30l43,61C186,0 230,-11 275,-11C388,-11 511,68 511,246C511,321 485,386 438,431l42,62l-39,27l-42,-59C363,483 320,495 277,495M161,119l-2,1C134,155 123,199 123,241C123,329 166,429 274,429C309,429 337,418 359,400M389,361l2,0C417,323 425,276 425,243C425,134 364,55 272,55C242,55 214,63 191,83z" + id="glyph2795" /> +<glyph + unicode="õ" + horiz-adv-x="549" + d="M199,567C201,595 209,612 225,612C237,612 248,606 268,594C289,583 308,574 329,574C376,574 399,608 398,675l-47,0C349,641 339,632 323,632C311,632 297,640 281,649C258,662 241,671 219,671C176,671 149,632 151,567M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429z" + id="glyph2797" /> +<glyph + unicode="p" + horiz-adv-x="569" + d="M73,-198l87,0l0,263l2,0C191,17 247,-11 311,-11C425,-11 531,75 531,249C531,396 443,495 326,495C247,495 190,460 154,401l-2,0l-4,83l-79,0C71,438 73,388 73,326M160,280C160,292 163,305 166,316C183,382 239,425 299,425C392,425 443,342 443,245C443,134 389,58 296,58C233,58 180,100 164,161C162,172 160,184 160,197z" + id="glyph2799" /> +<glyph + unicode="¶" + horiz-adv-x="512" + d="M293,-49l0,664l71,0l0,-664l62,0l0,716C401,673 362,676 309,676C89,676 32,558 34,454C36,326 144,264 223,264l7,0l0,-313z" + id="glyph2801" /> +<glyph + unicode="(" + horiz-adv-x="284" + d="M195,694C132,610 65,482 64,285C64,90 132,-38 195,-121l69,0C193,-21 138,107 138,285C138,466 191,596 264,694z" + id="glyph2803" /> +<glyph + unicode="" + horiz-adv-x="284" + d="M195,733C132,649 65,521 64,324C64,129 132,1 195,-82l69,0C193,18 138,146 138,324C138,505 191,635 264,733z" + id="glyph2805" /> +<glyph + unicode="" + horiz-adv-x="180" + d="M117,425C81,377 40,299 40,182C40,67 81,-10 117,-58l51,0C129,0 96,76 96,182C96,290 128,368 168,425z" + id="glyph2807" /> +<glyph + unicode="₍" + horiz-adv-x="180" + d="M117,278C81,230 40,152 40,35C40,-80 81,-157 117,-205l51,0C129,-147 96,-71 96,35C96,143 128,221 168,278z" + id="glyph2809" /> +<glyph + unicode="" + horiz-adv-x="180" + d="M117,691C81,643 40,565 40,448C40,333 81,256 117,208l51,0C129,266 96,342 96,448C96,556 128,634 168,691z" + id="glyph2811" /> +<glyph + unicode="⁽" + horiz-adv-x="180" + d="M117,868C81,820 40,742 40,625C40,510 81,433 117,385l51,0C129,443 96,519 96,625C96,733 128,811 168,868z" + id="glyph2813" /> +<glyph + unicode=")" + horiz-adv-x="284" + d="M88,-121C152,-36 219,91 219,287C219,484 152,612 88,694l-68,0C92,595 146,467 146,288C146,108 91,-22 20,-121z" + id="glyph2815" /> +<glyph + unicode="" + horiz-adv-x="284" + d="M88,-82C152,3 219,130 219,326C219,523 152,651 88,733l-68,0C92,634 146,506 146,327C146,147 91,17 20,-82z" + id="glyph2817" /> +<glyph + unicode="" + horiz-adv-x="180" + d="M64,-58C100,-9 141,68 141,184C141,300 100,377 64,425l-51,0C52,367 85,290 85,184C85,77 52,0 13,-58z" + id="glyph2819" /> +<glyph + unicode="₎" + horiz-adv-x="180" + d="M64,-205C100,-156 141,-79 141,37C141,153 100,230 64,278l-51,0C52,220 85,143 85,37C85,-70 52,-147 13,-205z" + id="glyph2821" /> +<glyph + unicode="" + horiz-adv-x="180" + d="M64,208C100,257 141,334 141,450C141,566 100,643 64,691l-51,0C52,633 85,556 85,450C85,343 52,266 13,208z" + id="glyph2823" /> +<glyph + unicode="⁾" + horiz-adv-x="180" + d="M64,385C100,434 141,511 141,627C141,743 100,820 64,868l-51,0C52,810 85,733 85,627C85,520 52,443 13,385z" + id="glyph2825" /> +<glyph + unicode="∂" + horiz-adv-x="375" + d="M86,614C113,633 158,654 223,654C317,654 410,565 410,389C410,378 410,366 409,356l-1,0C387,395 328,443 245,443C122,443 26,335 26,199C26,93 90,-11 225,-11C366,-11 494,118 494,372C494,602 369,726 231,726C146,726 91,697 58,673M232,61C162,61 114,113 114,201C114,299 179,373 259,373C328,373 381,326 400,277C386,163 322,61 233,61z" + id="glyph2827" /> +<glyph + unicode="%" + horiz-adv-x="792" + d="M187,661C101,661 30,588 30,458C31,328 101,262 182,262C266,262 338,330 338,467C338,591 278,661 188,661M184,608C245,608 268,542 268,463C268,377 242,315 184,315C129,315 98,379 100,461C100,543 129,608 183,608M231,-12l383,674l-56,0l-382,-674M611,392C525,392 454,319 454,188C455,58 525,-7 606,-7C689,-7 761,61 761,198C761,322 702,392 612,392M608,339C669,339 692,273 692,194C692,108 666,46 608,46C554,46 522,109 523,192C523,274 553,339 607,339z" + id="glyph2829" /> +<glyph + unicode="" + horiz-adv-x="818" + d="M184,603C98,603 27,530 27,400C28,269 98,204 179,204C263,204 335,272 335,409C335,533 275,603 185,603M182,550C243,550 265,484 265,405C265,319 240,257 181,257C127,257 96,321 97,403C97,485 127,550 181,550M261,-12l351,620l-55,0l-352,-620M640,392C554,392 482,319 483,188C483,58 554,-7 635,-7C718,-7 790,61 790,198C790,322 731,392 641,392M637,339C698,339 721,273 721,194C721,108 695,46 637,46C583,46 551,109 552,192C553,274 582,339 636,339z" + id="glyph2831" /> +<glyph + unicode="." + horiz-adv-x="207" + d="M111,-11C148,-11 171,16 171,52C171,89 147,115 112,115C77,115 52,89 52,52C52,16 76,-11 110,-11z" + id="glyph2833" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M65,-6C92,-6 109,14 109,39C109,66 92,84 66,84C40,84 21,66 21,39C21,14 39,-6 64,-6z" + id="glyph2835" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M65,-153C92,-153 109,-133 109,-108C109,-81 92,-63 66,-63C40,-63 21,-81 21,-108C21,-133 39,-153 64,-153z" + id="glyph2837" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M65,260C92,260 109,280 109,305C109,332 92,350 66,350C40,350 21,332 21,305C21,280 39,260 64,260z" + id="glyph2839" /> +<glyph + unicode="" + horiz-adv-x="131" + d="M65,437C92,437 109,457 109,482C109,509 92,527 66,527C40,527 21,509 21,482C21,457 39,437 64,437z" + id="glyph2841" /> +<glyph + unicode="·" + horiz-adv-x="207" + d="M103,200C139,200 163,227 163,263C162,300 139,326 104,326C69,326 44,299 44,263C43,227 68,200 102,200z" + id="glyph2843" /> +<glyph + unicode="" + horiz-adv-x="207" + d="M103,275C139,275 163,302 163,338C162,375 139,401 104,401C69,401 44,374 44,338C43,302 68,275 102,275z" + id="glyph2845" /> +<glyph + unicode="‰" + horiz-adv-x="1156" + d="M187,661C101,661 30,588 30,458C31,328 101,262 182,262C266,262 338,330 338,467C338,591 278,661 188,661M184,608C245,608 268,542 268,463C268,377 242,315 184,315C129,315 98,379 100,461C100,543 129,608 183,608M231,-12l383,674l-56,0l-383,-674M611,392C525,392 454,319 454,188C455,58 525,-7 606,-7C689,-7 761,61 761,198C761,322 702,392 612,392M608,339C669,339 692,273 692,194C692,108 666,46 608,46C553,46 522,109 523,192C523,274 553,339 607,339M975,392C889,392 818,319 818,188C819,58 889,-7 970,-7C1054,-7 1126,61 1126,198C1126,322 1066,392 976,392M973,339C1034,339 1056,273 1056,194C1056,108 1031,46 972,46C917,46 886,109 888,192C888,274 918,339 972,339z" + id="glyph2847" /> +<glyph + unicode="φ" + horiz-adv-x="642" + d="M204,498C138,470 38,385 38,245C38,74 162,-1 282,-10l0,-188l81,0l0,188C485,2 603,88 603,254C603,402 501,492 406,493C340,494 282,451 282,348l0,-294C177,71 125,150 125,247C125,351 187,417 238,449M363,361C363,407 380,429 405,429C461,429 519,351 519,253C519,140 458,67 363,54z" + id="glyph2849" /> +<glyph + unicode="π" + horiz-adv-x="555" + d="M520,415l10,69l-378,0C75,484 35,473 13,458l12,-52C50,413 85,415 124,415C120,287 92,95 56,0l84,0C175,80 204,288 208,415l159,0l0,-280C367,58 374,15 382,0l85,0C460,25 453,65 453,144l0,271z" + id="glyph2851" /> +<glyph + unicode="+" + horiz-adv-x="596" + d="M266,532l0,-235l-226,0l0,-60l226,0l0,-237l64,0l0,237l226,0l0,60l-226,0l0,235z" + id="glyph2853" /> +<glyph + unicode="±" + horiz-adv-x="596" + d="M266,572l0,-195l-226,0l0,-60l226,0l0,-200l64,0l0,200l226,0l0,60l-226,0l0,195M40,60l0,-60l516,0l0,60z" + id="glyph2855" /> +<glyph + unicode="∏" + horiz-adv-x="615" + d="M605,577l0,73l-596,0l0,-73l93,0l0,-667l83,0l0,667l244,0l0,-667l83,0l0,667z" + id="glyph2857" /> +<glyph + unicode="ψ" + horiz-adv-x="655" + d="M295,-198l81,0l0,187C548,-2 609,149 609,293C609,383 590,452 570,484l-81,0C502,463 525,386 525,286C525,147 463,57 376,53l0,558l-80,0l0,-558C220,58 152,106 152,248l0,145C152,443 148,473 140,484l-81,0C66,459 67,416 67,371l0,-131C67,53 172,-3 295,-11z" + id="glyph2859" /> +<glyph + unicode="q" + horiz-adv-x="563" + d="M403,-198l88,0l0,549C491,401 492,443 494,484l-83,0l-3,-73l-2,0C382,458 332,495 257,495C155,495 38,415 38,234C38,87 130,-11 243,-11C321,-11 375,27 401,75l2,0M403,197C403,184 400,167 396,155C376,94 327,59 270,59C173,59 127,143 127,239C127,348 182,425 273,425C339,425 386,379 399,325C402,314 403,300 403,288z" + id="glyph2861" /> +<glyph + unicode="?" + horiz-adv-x="406" + d="M219,192l-1,25C215,268 231,313 275,365C323,421 361,471 361,539C361,615 309,686 194,686C141,686 85,670 51,646l24,-63C100,602 140,614 176,614C239,613 271,579 271,528C271,483 246,444 201,390C151,331 133,271 139,218l2,-26M178,-11C215,-11 238,16 238,51C238,88 214,114 179,114C144,114 119,88 119,51C119,16 143,-11 177,-11z" + id="glyph2863" /> +<glyph + unicode="¿" + horiz-adv-x="406" + d="M237,488C202,488 178,462 178,425C178,390 201,363 237,363C273,363 296,390 296,425C296,462 273,488 238,488M197,286l1,-26C201,209 185,165 142,112C93,56 55,6 55,-61C55,-138 108,-209 222,-209C275,-209 332,-192 366,-169l-24,64C316,-125 276,-137 241,-137C177,-136 146,-102 146,-50C146,-6 170,33 215,87C265,147 284,206 278,260l-3,26z" + id="glyph2865" /> +<glyph + unicode="" + horiz-adv-x="406" + d="M237,683C202,683 178,657 178,620C178,585 201,558 237,558C273,558 296,585 296,620C296,657 273,683 238,683M197,481l1,-26C201,404 185,360 142,307C93,251 55,201 55,134C55,57 108,-14 222,-14C275,-14 332,3 366,26l-24,64C316,70 276,58 241,58C177,59 146,93 146,145C146,189 170,228 215,282C265,342 284,401 278,455l-3,26z" + id="glyph2867" /> +<glyph + unicode=""" + horiz-adv-x="337" + d="M51,692l16,-240l54,0l17,240M200,692l17,-240l53,0l17,240z" + id="glyph2869" /> +<glyph + unicode="„" + horiz-adv-x="356" + d="M76,-111C103,-65 147,42 169,126l-90,-10C68,45 42,-59 20,-118M224,-111C252,-65 295,42 318,126l-91,-10C216,45 190,-59 168,-118z" + id="glyph2871" /> +<glyph + unicode="“" + horiz-adv-x="354" + d="M125,458C136,530 162,634 184,692l-56,-6C100,640 57,532 34,448M273,458C284,530 310,634 332,692l-56,-6C249,640 205,532 183,448z" + id="glyph2873" /> +<glyph + unicode="”" + horiz-adv-x="354" + d="M93,682C82,611 55,507 34,449l56,6C117,501 160,609 183,692M241,682C230,611 204,507 182,449l56,6C265,501 308,609 331,692z" + id="glyph2875" /> +<glyph + unicode="‘" + horiz-adv-x="207" + d="M125,458C136,530 163,634 184,692l-56,-6C101,640 58,532 35,448z" + id="glyph2877" /> +<glyph + unicode="’" + horiz-adv-x="207" + d="M93,682C82,611 56,507 34,449l56,6C118,501 161,609 184,692z" + id="glyph2879" /> +<glyph + unicode="‚" + horiz-adv-x="207" + d="M76,-111C104,-65 147,42 170,126l-91,-10C69,45 42,-59 21,-118z" + id="glyph2881" /> +<glyph + unicode="'" + horiz-adv-x="188" + d="M51,692l16,-240l54,0l17,240z" + id="glyph2883" /> +<glyph + unicode="r" + horiz-adv-x="327" + d="M73,0l87,0l0,258C160,273 162,287 164,299C176,365 220,412 282,412C294,412 303,411 312,409l0,83C304,494 297,495 287,495C228,495 175,454 153,389l-4,0l-3,95l-77,0C72,439 73,390 73,333z" + id="glyph2885" /> +<glyph + unicode="ŕ" + horiz-adv-x="327" + d="M216,693l-88,-143l63,0l122,143M73,0l87,0l0,258C160,273 162,287 164,299C176,365 220,412 282,412C294,412 303,411 312,409l0,83C304,494 297,495 287,495C228,495 175,454 153,389l-4,0l-3,95l-77,0C72,439 73,390 73,333z" + id="glyph2887" /> +<glyph + unicode="√" + horiz-adv-x="562" + d="M579,827l-66,0l-186,-766C322,39 317,13 314,-4l-2,0C308,15 301,39 293,60l-123,346l-138,-58l19,-47l75,32l159,-428l64,0z" + id="glyph2889" /> +<glyph + unicode="ř" + horiz-adv-x="327" + d="M214,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143M73,0l87,0l0,258C160,273 162,287 164,299C176,365 220,412 282,412C294,412 303,411 312,409l0,83C304,494 297,495 287,495C228,495 175,454 153,389l-4,0l-3,95l-77,0C72,439 73,390 73,333z" + id="glyph2891" /> +<glyph + unicode="ŗ" + horiz-adv-x="327" + d="M73,0l87,0l0,258C160,273 162,287 164,299C176,365 220,412 282,412C294,412 303,411 312,409l0,83C304,494 297,495 287,495C228,495 175,454 153,389l-4,0l-3,95l-77,0C72,439 73,390 73,333M60,-222C122,-218 189,-188 189,-114C189,-82 169,-54 149,-41l-65,-14C102,-69 118,-92 118,-115C118,-155 82,-175 43,-181z" + id="glyph2893" /> +<glyph + unicode="®" + horiz-adv-x="419" + d="M184,499l18,0C223,499 233,491 236,473C241,454 244,440 249,434l41,0C286,439 283,449 278,474C273,496 264,509 248,514l0,2C268,522 282,536 282,555C282,570 275,583 266,590C254,598 240,603 209,603C182,603 161,600 146,597l0,-163l38,0M185,573C189,574 196,575 206,575C230,575 241,565 241,550C241,533 224,526 203,526l-18,0M210,683C116,683 41,610 41,519C41,426 116,354 210,354C305,354 378,426 378,519C378,610 305,683 211,683M210,650C283,650 336,591 336,519C336,446 283,387 211,388C137,388 83,446 83,519C83,591 137,650 209,650z" + id="glyph2895" /> +<glyph + unicode="ρ" + horiz-adv-x="563" + d="M158,60C186,15 241,-11 304,-11C418,-11 525,78 525,249C525,394 439,495 304,495C231,495 175,463 138,423C100,383 68,327 68,202l0,-224C68,-97 70,-168 79,-198l84,0C156,-158 155,-74 155,-18l0,78M155,231C155,365 228,422 297,422C387,422 437,342 437,247C437,139 383,58 290,58C228,58 175,100 159,155C156,166 155,178 155,190z" + id="glyph2897" /> +<glyph + unicode="˚" + horiz-adv-x="300" + d="M142,537C199,537 242,576 242,629C242,682 203,723 143,723C80,723 41,681 41,628C41,577 82,537 142,537M141,574C112,574 92,602 92,628C92,659 110,685 141,685C173,685 193,660 193,630C193,600 173,574 141,574z" + id="glyph2899" /> +<glyph + unicode="" + horiz-adv-x="400" + d="M202,877C140,877 101,836 101,786C101,737 141,698 202,698C260,698 301,736 301,786C301,837 263,877 203,877M200,840C230,840 249,816 249,787C249,758 230,735 200,735C172,735 153,760 153,786C153,815 170,840 199,840z" + id="glyph2901" /> +<glyph + unicode="s" + horiz-adv-x="396" + d="M39,23C73,3 122,-11 175,-11C290,-11 356,50 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2903" /> +<glyph + unicode="ś" + horiz-adv-x="396" + d="M244,693l-88,-143l63,0l122,143M39,23C73,3 122,-11 175,-11C290,-11 356,50 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2905" /> +<glyph + unicode="š" + horiz-adv-x="396" + d="M235,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143M39,23C73,3 122,-11 175,-11C290,-11 356,50 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2907" /> +<glyph + unicode="ş" + horiz-adv-x="396" + d="M39,23C78,1 128,-10 167,-10l4,0l-40,-71C178,-85 208,-97 209,-123C209,-147 188,-155 166,-155C148,-155 128,-150 112,-140l-15,-43C114,-193 141,-200 165,-200C221,-200 269,-178 269,-119C269,-81 236,-55 199,-50l28,44C310,10 356,64 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2909" /> +<glyph + unicode="" + horiz-adv-x="268" + d="M39,23C78,1 128,-10 167,-10l4,0l-40,-71C178,-85 208,-97 209,-123C209,-147 188,-155 166,-155C148,-155 128,-150 112,-140l-15,-43C114,-193 141,-200 165,-200C221,-200 269,-178 269,-119C269,-81 236,-55 199,-50l28,44C310,10 356,64 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2911" /> +<glyph + unicode="ŝ" + horiz-adv-x="396" + d="M169,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M39,23C73,3 122,-11 175,-11C290,-11 356,50 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90z" + id="glyph2913" /> +<glyph + unicode="ș" + horiz-adv-x="396" + d="M39,23C73,3 122,-11 175,-11C290,-11 356,50 356,135C356,207 313,249 229,281C166,305 137,323 137,363C137,399 166,429 218,429C263,429 298,413 317,401l22,64C312,481 269,495 220,495C116,495 53,431 53,353C53,295 94,247 181,216C246,192 271,169 271,127C271,87 241,55 177,55C133,55 87,73 61,90M136,-222C198,-218 265,-188 265,-114C265,-82 245,-54 225,-41l-65,-14C178,-69 194,-92 194,-115C194,-155 158,-175 119,-181z" + id="glyph2915" /> +<glyph + unicode="§" + horiz-adv-x="519" + d="M130,351C130,381 144,406 176,429C208,409 259,393 299,378C364,355 388,323 388,285C388,254 375,226 350,208C327,223 287,238 236,256C157,285 130,315 130,350M418,650C384,669 336,683 280,683C177,683 95,629 95,541C96,507 112,473 132,457C79,423 59,378 59,340C59,254 130,217 233,182C326,150 355,125 355,78C355,25 307,-4 243,-4C184,-4 135,20 111,37l-24,-58C125,-48 183,-62 244,-62C337,-62 434,-17 434,88C434,129 413,162 396,176C436,207 461,245 461,297C461,387 390,425 298,455C223,480 173,499 173,551C173,596 218,625 277,625C331,625 371,609 397,593z" + id="glyph2917" /> +<glyph + unicode=";" + horiz-adv-x="207" + d="M78,-117C106,-70 150,41 174,126l-97,-10C66,43 39,-64 17,-123M119,342C155,342 178,369 178,404C178,441 155,468 119,468C85,468 60,441 60,404C60,369 84,342 118,342z" + id="glyph2919" /> +<glyph + unicode="7" + horiz-adv-x="513" + d="M57,650l0,-73l318,0l0,-2l-282,-575l91,0l283,592l0,58z" + id="glyph2921" /> +<glyph + unicode="" + horiz-adv-x="294" + d="M19,388l0,-55l192,0l0,-1l-171,-332l71,0l171,345l0,43z" + id="glyph2923" /> +<glyph + unicode="" + horiz-adv-x="412" + d="M1,650l0,-73l318,0l0,-2l-282,-575l91,0l283,592l0,58z" + id="glyph2925" /> +<glyph + unicode="₇" + horiz-adv-x="294" + d="M19,241l0,-55l192,0l0,-1l-171,-332l71,0l171,345l0,43z" + id="glyph2927" /> +<glyph + unicode="" + horiz-adv-x="294" + d="M19,654l0,-55l192,0l0,-1l-171,-332l71,0l171,345l0,43z" + id="glyph2929" /> +<glyph + unicode="" + horiz-adv-x="428" + d="M405,485l-387,0l0,-73l297,0l0,-2C262,270 122,20 40,-98l82,-16C223,43 343,266 405,429z" + id="glyph2931" /> +<glyph + unicode="⁷" + horiz-adv-x="294" + d="M19,831l0,-55l192,0l0,-1l-171,-332l71,0l171,345l0,43z" + id="glyph2933" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M454,485l-387,0l0,-73l297,0l0,-2C311,270 171,20 89,-98l82,-16C272,43 392,266 454,429z" + id="glyph2935" /> +<glyph + unicode="σ" + horiz-adv-x="554" + d="M412,432C483,429 516,428 539,428l3,67C524,494 466,495 433,495l-53,0C366,495 314,495 287,495C141,495 38,399 38,237C38,84 139,-11 270,-11C380,-11 502,67 502,245C502,318 469,389 412,428M272,55C188,55 127,135 127,241C127,333 172,429 273,429C373,429 415,327 415,245C415,135 352,55 273,55z" + id="glyph2937" /> +<glyph + unicode="6" + horiz-adv-x="513" + d="M416,660C400,660 378,659 352,655C270,645 198,612 144,559C80,495 34,394 34,266C34,97 125,-11 264,-11C399,-11 480,99 480,218C480,345 399,428 284,428C212,428 158,394 127,353l-3,0C139,460 212,561 350,584C375,588 398,589 416,588M264,57C172,57 123,136 121,243C121,259 125,272 131,282C155,329 204,362 257,362C338,362 391,306 391,213C391,120 340,57 265,57z" + id="glyph2939" /> +<glyph + unicode="" + horiz-adv-x="332" + d="M267,394C257,395 243,394 227,392C171,387 125,368 91,335C50,297 23,236 23,162C23,65 78,-5 171,-5C251,-5 309,57 309,134C309,209 259,259 187,259C145,259 112,240 93,218l-1,0C102,278 147,329 227,340C241,342 256,342 267,341M170,46C117,46 89,91 89,145C89,154 91,160 94,166C107,192 134,211 165,211C212,211 241,179 241,130C241,82 212,46 171,46z" + id="glyph2941" /> +<glyph + unicode="" + horiz-adv-x="515" + d="M418,660C402,660 380,659 354,655C272,645 200,612 146,559C82,495 36,394 36,266C36,97 127,-11 266,-11C401,-11 482,99 482,218C482,345 401,428 286,428C214,428 160,394 129,353l-3,0C141,460 214,561 352,584C377,588 400,589 418,588M266,57C174,57 125,136 123,243C123,259 127,272 133,282C157,329 206,362 259,362C340,362 393,306 393,213C393,120 342,57 267,57z" + id="glyph2943" /> +<glyph + unicode="₆" + horiz-adv-x="332" + d="M267,247C257,248 243,247 227,245C171,240 125,221 91,188C50,150 23,89 23,15C23,-82 78,-152 171,-152C251,-152 309,-90 309,-13C309,62 259,112 187,112C145,112 112,93 93,71l-1,0C102,131 147,182 227,193C241,195 256,195 267,194M170,-101C117,-101 89,-56 89,-2C89,7 91,13 94,19C107,45 134,64 165,64C212,64 241,32 241,-17C241,-65 212,-101 171,-101z" + id="glyph2945" /> +<glyph + unicode="" + horiz-adv-x="332" + d="M267,660C257,661 243,660 227,658C171,653 125,634 91,601C50,563 23,502 23,428C23,331 78,261 171,261C251,261 309,323 309,400C309,475 259,525 187,525C145,525 112,506 93,484l-1,0C102,544 147,595 227,606C241,608 256,608 267,607M170,312C117,312 89,357 89,411C89,420 91,426 94,432C107,458 134,477 165,477C212,477 241,445 241,396C241,348 212,312 171,312z" + id="glyph2947" /> +<glyph + unicode="" + horiz-adv-x="520" + d="M432,606C192,593 47,442 47,235C47,88 136,-11 269,-11C373,-11 477,68 477,202C477,321 392,394 290,394C221,394 169,357 144,321l-3,0C164,435 258,530 438,541M269,56C188,56 135,125 135,206C135,239 143,263 168,288C186,308 223,331 268,331C341,331 390,279 390,198C390,103 328,56 270,56z" + id="glyph2949" /> +<glyph + unicode="⁶" + horiz-adv-x="332" + d="M267,837C257,838 243,837 227,835C171,830 125,811 91,778C50,740 23,679 23,605C23,508 78,438 171,438C251,438 309,500 309,577C309,652 259,702 187,702C145,702 112,683 93,661l-1,0C102,721 147,772 227,783C241,785 256,785 267,784M170,489C117,489 89,534 89,588C89,597 91,603 94,609C107,635 134,654 165,654C212,654 241,622 241,573C241,525 212,489 171,489z" + id="glyph2951" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M441,606C201,593 56,442 56,235C56,88 145,-11 278,-11C382,-11 486,68 486,202C486,321 401,394 299,394C230,394 178,357 153,321l-3,0C173,435 267,530 447,541M278,56C197,56 144,125 144,206C144,239 152,263 177,288C195,308 232,331 277,331C350,331 399,279 399,198C399,103 337,56 279,56z" + id="glyph2953" /> +<glyph + unicode="/" + horiz-adv-x="343" + d="M66,-40l280,725l-68,0l-279,-725z" + id="glyph2955" /> +<glyph + unicode=" " + horiz-adv-x="212" + id="glyph2957" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M151,-80C122,-80 99,-103 99,-131C99,-159 121,-183 149,-183C179,-183 200,-159 200,-131C200,-103 178,-80 151,-80z" + id="glyph2959" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M151,-181C179,-181 200,-156 200,-130C200,-104 179,-80 152,-80C123,-80 101,-104 101,-130C101,-156 123,-181 150,-181z" + id="glyph2961" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M161,530C184,576 236,591 236,647C236,691 199,722 152,722C102,722 72,691 54,655l34,-20C100,653 115,670 137,670C155,670 170,657 170,636C170,599 126,586 114,542z" + id="glyph2963" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M169,709C195,755 240,766 240,817C240,858 203,889 157,889C106,889 76,858 58,822l34,-20C103,819 120,837 141,837C161,837 174,825 174,806C174,773 138,765 122,722z" + id="glyph2965" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M-2,685l116,-141l56,0l-81,141M149,685l116,-141l55,0l-80,141z" + id="glyph2967" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M-45,828l128,-117l67,0l-92,117M129,828l127,-117l67,0l-91,117z" + id="glyph2969" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M173,433l31,0C266,433 303,470 303,520C303,547 296,572 285,592l-67,-11C229,563 235,542 235,523C235,498 223,484 188,484l-15,0z" + id="glyph2971" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M173,621l30,0C270,621 303,656 303,707C303,736 296,760 286,782l-68,-12C229,752 235,731 235,712C235,686 223,674 188,674l-15,0z" + id="glyph2973" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M93,-222C155,-218 222,-188 222,-114C222,-82 202,-54 182,-41l-65,-14C135,-69 151,-92 151,-115C151,-155 115,-175 76,-181z" + id="glyph2975" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M93,-222C155,-218 222,-188 222,-114C222,-81 202,-54 182,-41l-65,-14C135,-69 151,-92 151,-115C151,-155 115,-174 76,-181z" + id="glyph2977" /> +<glyph + unicode="£" + horiz-adv-x="513" + d="M471,0l0,73l-298,0l0,2C207,104 231,140 242,183C251,219 249,257 246,295l136,0l0,63l-144,0C232,390 226,422 226,465C226,536 258,590 336,590C378,590 407,580 425,570l20,66C424,649 388,661 336,661C225,661 144,584 144,466C144,427 150,388 155,358l-93,0l0,-63l103,0C169,269 170,249 170,225C170,150 123,84 56,50l0,-50z" + id="glyph2979" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M479,0l0,71l-292,0l0,2C223,97 249,137 250,189C250,199 249,211 248,222l138,0l0,55l-149,0C233,296 230,317 230,338C230,415 266,464 339,464C379,464 411,454 429,443l19,65C426,521 389,533 339,533C224,533 147,454 147,339C147,319 151,298 155,277l-86,0l0,-55l97,0C169,207 170,192 170,177C170,124 129,78 63,50l0,-50z" + id="glyph2981" /> +<glyph + unicode="∑" + horiz-adv-x="507" + d="M514,-90l0,73l-392,0l0,3l234,295l-222,293l0,4l349,0l0,72l-461,0l0,-59l238,-313l-251,-316l0,-52z" + id="glyph2983" /> +<glyph + unicode="t" + horiz-adv-x="331" + d="M93,573l0,-89l-75,0l0,-67l75,0l0,-264C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C196,62 179,96 179,156l0,261l126,0l0,67l-126,0l0,116z" + id="glyph2985" /> +<glyph + unicode="τ" + horiz-adv-x="413" + d="M321,60C315,59 308,59 300,59C270,59 244,75 244,141l0,274l150,0l11,69l-279,0C47,484 11,470 -6,458l12,-51C27,411 53,415 104,415l53,0l0,-276C157,37 191,-11 269,-11C287,-11 307,-8 317,-3z" + id="glyph2987" /> +<glyph + unicode="ŧ" + horiz-adv-x="331" + d="M305,417l0,67l-126,0l0,116l-86,-27l0,-89l-75,0l0,-67l75,0l0,-115l-67,0l0,-54l67,0l0,-95C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C198,62 179,94 179,152l0,96l98,0l0,54l-98,0l0,115z" + id="glyph2989" /> +<glyph + unicode="ť" + horiz-adv-x="339" + d="M93,573l0,-89l-75,0l0,-67l75,0l0,-264C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C196,62 179,96 179,156l0,261l126,0l0,67l-126,0l0,116M277,541C301,551 357,585 357,653C357,685 339,711 322,722l-63,-13C275,695 289,673 289,650C289,614 268,589 251,576z" + id="glyph2991" /> +<glyph + unicode="ţ" + horiz-adv-x="331" + d="M93,573l0,-89l-75,0l0,-67l75,0l0,-264C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C196,62 179,96 179,156l0,261l126,0l0,67l-126,0l0,116M122,-227C184,-223 251,-193 251,-119C251,-87 231,-59 211,-46l-65,-14C164,-74 180,-97 180,-120C180,-160 144,-180 105,-186z" + id="glyph2993" /> +<glyph + unicode="θ" + horiz-adv-x="534" + d="M269,-11C424,-11 497,136 497,361C497,576 426,721 272,721C121,721 38,565 38,361C38,114 126,-11 268,-11M125,399C125,535 178,656 267,656C364,656 409,541 410,399M410,333C410,170 365,54 271,54C185,54 125,155 125,333z" + id="glyph2995" /> +<glyph + unicode="þ" + horiz-adv-x="569" + d="M73,669l0,-867l87,0l0,263l2,0C193,17 247,-11 311,-11C421,-11 531,71 531,248C531,398 441,496 327,495C248,495 195,460 162,407l-2,0l0,262M160,280C160,296 165,315 170,329C191,388 244,425 300,425C390,425 443,343 443,247C443,131 385,58 298,58C240,58 189,92 170,147C164,160 160,177 160,191z" + id="glyph2997" /> +<glyph + unicode="3" + horiz-adv-x="513" + d="M42,33C74,12 138,-11 211,-11C367,-11 448,80 448,184C448,275 383,335 303,351l0,2C383,382 423,439 423,506C423,585 365,661 237,661C167,661 101,636 67,611l23,-64C118,567 168,590 220,590C301,590 334,544 334,492C334,415 253,382 189,382l-49,0l0,-66l49,0C274,316 356,277 357,186C358,132 323,60 210,60C149,60 91,85 66,101z" + id="glyph2999" /> +<glyph + unicode="" + horiz-adv-x="305" + d="M45,317C61,328 94,343 130,343C175,343 193,318 193,294C192,247 139,230 102,230l-28,0l0,-46l27,0C154,184 207,164 207,114C208,83 181,47 120,47C82,47 43,62 27,73l-18,-50C31,8 75,-5 123,-5C225,-5 281,50 281,112C281,163 243,201 188,209l0,2C236,227 265,261 265,301C265,349 229,395 143,395C94,395 51,379 28,364z" + id="glyph3001" /> +<glyph + unicode="" + horiz-adv-x="450" + d="M7,33C39,12 103,-11 176,-11C332,-11 413,80 413,184C413,275 348,335 268,351l0,2C348,382 388,439 388,506C388,585 330,661 202,661C132,661 66,636 32,611l23,-64C83,567 133,590 185,590C266,590 299,544 299,492C299,415 218,382 154,382l-49,0l0,-66l49,0C239,316 321,277 322,186C323,132 288,60 175,60C114,60 56,85 31,101z" + id="glyph3003" /> +<glyph + unicode="₃" + horiz-adv-x="305" + d="M45,170C61,181 94,196 130,196C175,196 193,171 193,147C192,100 139,83 102,83l-28,0l0,-46l27,0C154,37 207,17 207,-33C208,-64 181,-100 120,-100C82,-100 43,-85 27,-74l-18,-50C31,-139 75,-152 123,-152C225,-152 281,-97 281,-35C281,16 243,54 188,62l0,2C236,80 265,114 265,154C265,202 229,248 143,248C94,248 51,232 28,217z" + id="glyph3005" /> +<glyph + unicode="" + horiz-adv-x="305" + d="M45,583C61,594 94,609 130,609C175,609 193,584 193,560C192,513 139,496 102,496l-28,0l0,-46l27,0C154,450 207,430 207,380C208,349 181,313 120,313C82,313 43,328 27,339l-18,-50C31,274 75,261 123,261C225,261 281,316 281,378C281,429 243,467 188,475l0,2C236,493 265,527 265,567C265,615 229,661 143,661C94,661 51,645 28,630z" + id="glyph3007" /> +<glyph + unicode="" + horiz-adv-x="433" + d="M17,-95C40,-104 92,-115 144,-115C279,-115 396,-39 396,76C396,161 328,210 249,221l0,2C317,243 367,294 367,362C367,445 295,496 193,496C127,496 64,472 33,451l18,-58C75,406 123,427 174,427C239,427 279,401 279,347C279,285 209,248 116,241l-19,-1l2,-65C111,176 131,177 154,177C246,177 306,138 306,75C306,2 236,-46 137,-46C95,-46 53,-38 28,-29z" + id="glyph3009" /> +<glyph + unicode="³" + horiz-adv-x="305" + d="M45,760C61,771 94,786 130,786C175,786 193,761 193,737C192,690 139,673 102,673l-28,0l0,-46l27,0C154,627 207,607 207,557C208,526 181,490 120,490C82,490 43,505 27,516l-18,-50C31,451 75,438 123,438C225,438 281,493 281,555C281,606 243,644 188,652l0,2C236,670 265,704 265,744C265,792 229,838 143,838C94,838 51,822 28,807z" + id="glyph3011" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M61,-95C84,-104 136,-115 188,-115C323,-115 440,-39 440,76C440,161 372,210 293,221l0,2C361,243 411,294 411,362C411,445 339,496 237,496C171,496 108,472 77,451l18,-58C119,406 167,427 218,427C283,427 323,401 323,347C323,285 253,248 160,241l-19,-1l2,-65C155,176 175,177 198,177C290,177 350,138 350,75C350,2 280,-46 181,-46C139,-46 97,-38 72,-29z" + id="glyph3013" /> +<glyph + unicode="¾" + horiz-adv-x="759" + d="M66,583C82,593 114,609 150,609C196,609 214,584 214,560C214,513 160,497 123,496l-28,0l0,-46l28,0C175,450 228,430 229,380C229,349 203,313 141,313C103,313 64,328 48,339l-18,-50C53,274 96,261 145,261C247,261 302,316 302,378C302,429 264,467 210,475l0,2C257,493 286,527 286,567C286,615 250,661 163,661C114,661 72,645 49,629M232,-11l379,672l-56,0l-380,-672M674,0l0,103l61,0l0,49l-61,0l0,243l-73,0l-193,-253l0,-39l200,0l0,-103M608,152l-128,0l-1,1l97,125C587,294 596,311 609,333l2,0C610,314 608,293 608,270z" + id="glyph3015" /> +<glyph + unicode="˜" + horiz-adv-x="300" + d="M75,567C77,595 85,612 101,612C113,612 124,606 144,594C165,583 184,574 205,574C252,574 275,608 274,675l-47,0C225,641 215,632 199,632C187,632 173,640 157,649C134,662 117,671 95,671C52,671 25,632 27,567z" + id="glyph3017" /> +<glyph + unicode="" + horiz-adv-x="300" + d="M73,715C75,742 83,759 99,759C113,759 127,750 146,740C168,729 186,721 207,721C253,721 275,755 274,818l-46,0C225,786 216,779 201,779C185,779 169,788 153,797C132,808 116,817 94,817C50,817 24,776 26,715z" + id="glyph3019" /> +<glyph + unicode="΄" + horiz-adv-x="301" + d="M137,694l-53,-143l58,0l84,143z" + id="glyph3021" /> +<glyph + unicode="™" + horiz-adv-x="619" + d="M30,674l0,-45l87,0l0,-227l54,0l0,227l87,0l0,45M606,402l-21,272l-78,0l-47,-134C454,520 448,497 441,472l-1,0C433,500 428,520 422,541l-47,133l-81,0l-20,-272l52,0l10,160C337,587 338,613 338,638l3,0C347,614 355,582 360,561l49,-155l56,0l49,151C522,582 530,614 537,638l3,0C541,609 541,583 542,562l10,-160z" + id="glyph3023" /> +<glyph + unicode="2" + horiz-adv-x="513" + d="M460,0l0,73l-292,0l0,2l52,48C357,255 444,352 444,472C444,565 385,661 245,661C170,661 106,633 61,595l28,-62C119,558 168,588 227,588C324,588 356,527 356,461C355,363 280,279 114,121l-69,-67l0,-54z" + id="glyph3025" /> +<glyph + unicode="" + horiz-adv-x="311" + d="M12,0l270,0l0,55l-171,0l0,2l31,28C218,152 275,207 275,282C275,336 236,395 142,395C92,395 47,376 20,353l21,-45C60,321 90,339 125,339C177,339 201,307 201,269C201,218 158,171 75,96l-63,-59z" + id="glyph3027" /> +<glyph + unicode="" + horiz-adv-x="447" + d="M418,0l0,73l-292,0l0,2l52,48C315,255 402,352 402,472C402,565 343,661 203,661C128,661 64,633 19,595l28,-62C77,558 126,588 185,588C282,588 314,527 314,461C313,363 238,279 72,121l-69,-67l0,-54z" + id="glyph3029" /> +<glyph + unicode="₂" + horiz-adv-x="311" + d="M12,-147l270,0l0,55l-171,0l0,2l31,28C218,5 275,60 275,135C275,189 236,248 142,248C92,248 47,229 20,206l21,-45C60,174 90,192 125,192C177,192 201,160 201,122C201,71 158,24 75,-51l-63,-59z" + id="glyph3031" /> +<glyph + unicode="" + horiz-adv-x="311" + d="M12,266l270,0l0,55l-171,0l0,2l31,28C218,418 275,473 275,548C275,602 236,661 142,661C92,661 47,642 20,619l21,-45C60,587 90,605 125,605C177,605 201,573 201,535C201,484 158,437 75,362l-63,-59z" + id="glyph3033" /> +<glyph + unicode="" + horiz-adv-x="448" + d="M420,0l0,70l-182,0C213,70 181,69 157,66l0,3l51,37C313,180 385,249 385,342C385,441 313,498 211,498C131,498 65,463 30,437l23,-57C88,404 137,428 189,428C251,428 297,395 297,330C297,249 223,181 72,82l-48,-34l0,-48z" + id="glyph3035" /> +<glyph + unicode="²" + horiz-adv-x="311" + d="M12,443l270,0l0,55l-171,0l0,2l31,28C218,595 275,650 275,725C275,779 236,838 142,838C92,838 47,819 20,796l21,-45C60,764 90,782 125,782C177,782 201,750 201,712C201,661 158,614 75,539l-63,-59z" + id="glyph3037" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M462,0l0,70l-182,0C255,70 223,69 199,66l0,3l51,37C355,180 427,249 427,342C427,441 355,498 253,498C173,498 107,463 72,437l23,-57C130,404 179,428 231,428C293,428 339,395 339,330C339,249 265,181 114,82l-48,-34l0,-48z" + id="glyph3039" /> +<glyph + unicode="u" + horiz-adv-x="551" + d="M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3041" /> +<glyph + unicode="ú" + horiz-adv-x="551" + d="M321,693l-88,-143l63,0l122,143M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3043" /> +<glyph + unicode="ŭ" + horiz-adv-x="551" + d="M151,682C151,620 187,558 275,558C350,558 399,608 399,682l-52,0C344,649 322,613 275,613C234,613 209,643 203,682M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3045" /> +<glyph + unicode="û" + horiz-adv-x="551" + d="M244,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3047" /> +<glyph + unicode="ü" + horiz-adv-x="551" + d="M186,570C215,570 235,594 235,621C235,650 214,672 186,672C158,672 135,649 135,621C135,594 156,570 185,570M371,570C400,570 420,594 420,621C420,650 399,672 371,672C343,672 321,649 321,621C321,594 341,570 370,570M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3049" /> +<glyph + unicode="ù" + horiz-adv-x="551" + d="M140,693l122,-143l62,0l-87,143M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3051" /> +<glyph + unicode="ư" + horiz-adv-x="551" + d="M489,581C499,563 506,542 506,523C506,498 494,484 459,484l-69,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132l0,303l11,1C538,438 574,470 574,520C574,547 567,572 556,592z" + id="glyph3053" /> +<glyph + unicode="ű" + horiz-adv-x="551" + d="M246,693l-79,-137l55,0l110,137M390,693l-79,-137l55,0l110,137M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3055" /> +<glyph + unicode="ū" + horiz-adv-x="551" + d="M163,643l0,-57l225,0l0,57M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3057" /> +<glyph + unicode="_" + horiz-adv-x="500" + d="M0,-75l0,-50l500,0l0,50z" + id="glyph3059" /> +<glyph + unicode=" " + horiz-adv-x="212" + id="glyph3061" /> +<glyph + unicode="" + horiz-adv-x="291" + d="M30,303l0,-65l247,0l0,65z" + id="glyph3063" /> +<glyph + unicode="Ǻ" + horiz-adv-x="612" + d="M312,692C366,692 402,725 402,769C402,813 369,848 313,848C255,848 221,812 221,769C221,726 256,692 312,692M311,729C285,729 268,747 268,769C268,792 283,812 310,812C338,812 355,793 355,770C355,745 338,729 311,729M366,952l-106,-86l77,0l143,86M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194z" + id="glyph3065" /> +<glyph + unicode="ǻ" + horiz-adv-x="482" + d="M297,834l-107,-106l62,0l143,106M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M241,520C298,520 341,559 341,612C341,665 302,706 242,706C179,706 140,664 140,611C140,560 181,520 241,520M240,557C211,557 191,585 191,611C191,642 209,668 240,668C272,668 292,643 292,613C292,583 272,557 240,557z" + id="glyph3067" /> +<glyph + unicode="Ț" + horiz-adv-x="497" + d="M204,0l88,0l0,600l206,0l0,74l-499,0l0,-74l205,0M180,-222C242,-218 309,-188 309,-114C309,-81 289,-54 269,-41l-65,-14C222,-69 238,-92 238,-115C238,-155 202,-174 163,-181z" + id="glyph3069" /> +<glyph + unicode="ț" + horiz-adv-x="331" + d="M93,573l0,-89l-75,0l0,-67l75,0l0,-264C93,96 102,53 127,27C148,3 181,-11 222,-11C256,-11 283,-5 300,2l-4,66C283,64 269,62 245,62C196,62 179,96 179,156l0,261l126,0l0,67l-126,0l0,116M122,-227C184,-223 251,-193 251,-119C251,-87 231,-59 211,-46l-65,-14C164,-74 180,-97 180,-120C180,-160 144,-180 105,-186z" + id="glyph3071" /> +<glyph + unicode="Ȝ" + horiz-adv-x="480" + d="M63,-115C293,-88 441,34 441,188C441,295 363,361 262,371l0,3C330,405 385,463 385,538C385,625 311,685 202,685C134,685 63,658 31,638l18,-59C74,592 132,615 186,615C251,615 297,578 297,524C297,445 209,384 96,358l12,-62C142,307 181,314 202,314C296,315 349,256 349,182C349,64 212,-22 52,-46z" + id="glyph3073" /> +<glyph + unicode="ȝ" + horiz-adv-x="427" + d="M44,-209C276,-190 400,-73 400,57C400,145 337,204 245,214l0,3C308,242 351,291 351,357C351,441 284,495 183,495C118,495 57,470 20,446l18,-58C62,401 111,426 162,426C226,426 266,394 266,342C266,269 188,229 81,204l13,-63C121,149 154,156 182,156C271,156 310,106 310,47C310,-108 73,-138 35,-141z" + id="glyph3075" /> +<glyph + unicode="Ȳ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M151,788l0,-55l239,0l0,55z" + id="glyph3077" /> +<glyph + unicode="ȳ" + horiz-adv-x="471" + d="M124,643l0,-57l225,0l0,57M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3079" /> +<glyph + unicode="ˉ" + horiz-adv-x="300" + d="M38,643l0,-57l225,0l0,57z" + id="glyph3081" /> +<glyph + unicode=";" + horiz-adv-x="217" + d="M91,-117C115,-70 155,41 173,119l-98,-5C69,43 47,-64 30,-123M119,323C157,323 181,352 181,388C181,427 157,455 119,455C83,455 57,427 57,388C57,352 82,323 118,323z" + id="glyph3083" /> +<glyph + unicode="Δ" + horiz-adv-x="620" + d="M25,0l566,0l0,50l-232,624l-99,0l-235,-625M118,73l141,384C276,504 294,560 304,594l3,0C319,551 340,491 357,444l137,-371z" + id="glyph3085" /> +<glyph + unicode="Ω" + horiz-adv-x="705" + d="M528,73C595,129 661,228 661,366C661,544 544,685 357,685C178,685 43,550 43,358C43,228 108,128 175,73l0,-4l-145,0l0,-69l254,0l0,55C204,105 134,225 134,351C134,483 213,614 355,614C498,614 571,475 571,352C571,217 502,110 422,55l0,-55l253,0l0,69l-147,0z" + id="glyph3087" /> +<glyph + unicode="μ" + horiz-adv-x="553" + d="M403,75C411,13 446,-11 489,-11C503,-11 509,-10 519,-7l7,64C491,59 480,80 480,133l0,351l-87,0l0,-297C393,171 390,155 385,142C369,103 327,61 269,61C192,61 160,123 160,198l0,286l-87,0l0,-504C73,-97 77,-168 87,-198l79,0C156,-158 155,-69 155,-16l0,60C176,7 217,-9 261,-9C333,-9 380,37 400,75z" + id="glyph3089" /> +<glyph + unicode="ς" + horiz-adv-x="417" + d="M408,473C383,487 342,495 299,495C131,495 38,364 38,234C38,104 119,33 220,4C250,-4 286,-11 319,-17C317,-41 300,-103 286,-126l59,-18C371,-111 403,-26 403,29C403,39 400,42 393,44C347,51 312,57 264,70C177,96 127,147 127,242C127,341 195,427 299,427C337,427 364,419 388,409z" + id="glyph3091" /> +<glyph + unicode="Ạ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M304,-80C275,-80 252,-103 252,-131C252,-159 274,-183 302,-183C332,-183 353,-159 353,-131C353,-103 331,-80 304,-80z" + id="glyph3093" /> +<glyph + unicode="ạ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M241,-80C212,-80 189,-103 189,-131C189,-159 211,-183 239,-183C269,-183 290,-159 290,-131C290,-103 268,-80 241,-80z" + id="glyph3095" /> +<glyph + unicode="Ả" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M331,709C357,755 402,766 402,817C402,858 365,889 319,889C268,889 238,858 220,822l34,-20C265,819 282,837 303,837C323,837 336,825 336,806C336,773 300,765 284,722z" + id="glyph3097" /> +<glyph + unicode="ả" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M255,530C278,576 330,591 330,647C330,691 293,722 246,722C196,722 166,691 148,655l34,-20C194,653 209,670 231,670C249,670 264,657 264,636C264,599 220,586 208,542z" + id="glyph3099" /> +<glyph + unicode="Ấ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M280,824l-92,-114l67,0l56,70l2,0l55,-70l70,0l-95,114M474,900l-84,-105l52,0l121,105z" + id="glyph3101" /> +<glyph + unicode="ấ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M201,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M393,775l-69,-132l51,0l99,132z" + id="glyph3103" /> +<glyph + unicode="Ầ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M336,881l109,-105l54,0l-72,105M273,824l-92,-114l68,0l55,70l2,0l55,-70l70,0l-94,114z" + id="glyph3105" /> +<glyph + unicode="ầ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M272,775l93,-132l51,0l-64,132M200,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143z" + id="glyph3107" /> +<glyph + unicode="Ẩ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M277,824l-93,-114l68,0l55,70l2,0l56,-70l70,0l-95,114M459,754C485,800 528,810 528,858C528,898 494,928 449,928C400,928 371,899 354,864l32,-19C397,862 414,882 435,882C454,882 469,868 469,849C469,817 433,807 417,766z" + id="glyph3109" /> +<glyph + unicode="ẩ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M198,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M378,635C404,680 447,689 447,738C447,777 412,809 367,809C318,809 289,779 272,745l32,-20C315,742 331,761 352,761C371,761 385,747 385,728C385,697 350,688 334,646z" + id="glyph3111" /> +<glyph + unicode="Ẫ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M274,824l-103,-114l72,0l63,69l2,0l64,-69l73,0l-104,114M236,837C237,861 246,877 261,877C274,877 285,871 305,860C326,849 344,839 366,839C413,839 435,873 434,935l-47,0C385,903 375,894 360,894C347,894 333,902 317,910C294,923 277,932 255,932C213,932 186,894 187,837z" + id="glyph3113" /> +<glyph + unicode="ẫ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M208,668l-89,-118l67,0l53,73l2,0l54,-73l69,0l-92,118M169,680C170,703 179,719 194,719C207,719 218,713 238,702C259,691 277,681 299,681C346,681 368,715 367,777l-47,0C318,745 308,737 293,737C280,737 266,744 250,753C227,765 210,775 188,775C146,775 119,737 120,680z" + id="glyph3115" /> +<glyph + unicode="Ậ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M275,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114M302,-181C330,-181 351,-156 351,-130C351,-104 330,-80 303,-80C274,-80 252,-104 252,-130C252,-156 274,-181 301,-181z" + id="glyph3117" /> +<glyph + unicode="ậ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M210,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M241,-80C212,-80 189,-103 189,-131C189,-159 211,-183 239,-183C269,-183 290,-159 290,-131C290,-103 268,-80 241,-80z" + id="glyph3119" /> +<glyph + unicode="Ắ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M185,802C192,747 230,699 308,699C387,699 428,749 434,802l-46,0C380,775 361,753 309,753C260,753 239,778 233,802M347,896l-74,-105l54,0l112,105z" + id="glyph3121" /> +<glyph + unicode="ắ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M279,773l-79,-131l53,0l111,131M108,657C109,594 149,533 237,533C312,533 365,582 366,657l-50,0C313,623 288,588 237,588C193,588 164,618 158,657z" + id="glyph3123" /> +<glyph + unicode="Ằ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M185,896l112,-105l54,0l-74,105M186,802C193,747 231,699 309,699C388,699 429,749 435,802l-46,0C381,775 361,753 310,753C261,753 240,778 234,802z" + id="glyph3125" /> +<glyph + unicode="ằ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M113,773l110,-131l54,0l-80,131M108,657C109,594 149,533 237,533C313,533 365,582 366,657l-49,0C314,623 288,588 237,588C193,588 164,618 158,657z" + id="glyph3127" /> +<glyph + unicode="Ẳ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M324,778C351,823 393,833 393,882C393,921 359,952 314,952C265,952 236,923 219,888l32,-19C262,886 278,904 299,904C318,904 332,891 332,872C332,841 297,831 281,790M186,802C193,747 232,699 309,699C388,699 429,749 436,802l-47,0C381,775 362,753 311,753C261,753 240,778 234,802z" + id="glyph3129" /> +<glyph + unicode="ẳ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M249,634C275,679 318,688 318,737C318,776 283,808 238,808C189,808 160,778 143,744l32,-20C186,741 202,760 222,760C242,760 256,746 256,727C256,696 221,687 205,645M107,657C108,594 148,533 236,533C311,533 364,582 365,657l-50,0C312,623 287,588 236,588C192,588 163,618 157,657z" + id="glyph3131" /> +<glyph + unicode="Ẵ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M232,836C235,851 241,867 257,867C269,867 280,861 300,850C321,839 340,829 361,829C408,829 430,863 429,925l-46,0C380,893 371,885 355,885C343,885 329,892 313,901C289,913 273,922 251,922C208,922 185,886 183,836M180,802C186,747 225,699 303,699C382,699 423,749 429,802l-47,0C374,775 355,753 304,753C255,753 234,778 227,802z" + id="glyph3133" /> +<glyph + unicode="ẵ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M168,698C170,712 177,728 192,728C204,728 216,722 236,712C256,700 275,691 296,691C343,691 366,724 365,787l-47,0C316,755 306,746 290,746C278,746 264,754 248,762C225,774 208,784 186,784C143,784 120,747 118,698M113,657C114,594 152,533 240,533C315,533 366,582 367,657l-49,0C315,623 290,587 240,587C197,587 168,618 162,657z" + id="glyph3135" /> +<glyph + unicode="Ặ" + horiz-adv-x="612" + d="M424,212l72,-212l93,0l-230,674l-105,0l-229,-674l90,0l70,212M203,280l66,195C282,516 293,557 303,597l2,0C315,558 325,518 340,474l66,-194M182,832C184,772 220,726 305,726C391,726 428,773 430,832l-51,0C374,806 355,783 306,783C257,783 239,809 234,832M305,-181C333,-181 354,-156 354,-130C354,-104 333,-80 306,-80C277,-80 255,-104 255,-130C255,-156 277,-181 304,-181z" + id="glyph3137" /> +<glyph + unicode="ặ" + horiz-adv-x="482" + d="M413,297C413,394 377,495 229,495C168,495 110,478 70,452l20,-58C124,416 171,430 216,430C315,430 326,358 326,318l0,-10C139,309 35,245 35,128C35,58 85,-11 183,-11C252,-11 304,23 331,61l3,0l7,-61l80,0C415,33 413,74 413,116M328,163C328,154 326,144 323,135C309,94 269,54 206,54C161,54 123,81 123,138C123,232 232,249 328,247M117,682C117,620 153,558 241,558C316,558 365,608 365,682l-52,0C310,649 288,613 241,613C200,613 175,643 169,682M241,-80C212,-80 189,-103 189,-131C189,-159 211,-183 239,-183C269,-183 290,-159 290,-131C290,-103 268,-80 241,-80z" + id="glyph3139" /> +<glyph + unicode="Ẹ" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M246,-181C274,-181 295,-156 295,-130C295,-104 274,-80 247,-80C218,-80 196,-104 196,-130C196,-156 218,-181 245,-181z" + id="glyph3141" /> +<glyph + unicode="ẹ" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M251,-80C222,-80 199,-103 199,-131C199,-159 221,-183 249,-183C279,-183 300,-159 300,-131C300,-103 278,-80 251,-80z" + id="glyph3143" /> +<glyph + unicode="Ẻ" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M291,709C317,755 362,766 362,817C362,858 325,889 279,889C228,889 198,858 180,822l34,-20C225,819 242,837 263,837C283,837 296,825 296,806C296,773 260,765 244,722z" + id="glyph3145" /> +<glyph + unicode="ẻ" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M264,530C287,576 339,591 339,647C339,691 302,722 255,722C205,722 175,691 157,655l34,-20C203,653 218,670 240,670C258,670 273,657 273,636C273,599 229,586 217,542z" + id="glyph3147" /> +<glyph + unicode="Ẽ" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M189,715C191,742 199,759 215,759C229,759 243,750 262,740C284,729 302,721 323,721C369,721 391,755 390,818l-46,0C341,786 332,779 317,779C301,779 285,788 269,797C248,808 232,817 210,817C166,817 140,776 142,715z" + id="glyph3149" /> +<glyph + unicode="ẽ" + horiz-adv-x="501" + d="M175,567C177,595 185,612 201,612C213,612 224,606 244,594C265,583 284,574 305,574C352,574 375,608 374,675l-47,0C325,641 315,632 299,632C287,632 273,640 257,649C234,662 217,671 195,671C152,671 125,632 127,567M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289z" + id="glyph3151" /> +<glyph + unicode="Ế" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M230,824l-92,-114l67,0l56,70l2,0l55,-70l70,0l-95,114M424,900l-84,-105l52,0l121,105z" + id="glyph3153" /> +<glyph + unicode="ế" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M234,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M426,775l-69,-132l51,0l99,132z" + id="glyph3155" /> +<glyph + unicode="Ề" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M289,881l109,-105l54,0l-72,105M226,824l-92,-114l68,0l55,70l2,0l55,-70l70,0l-94,114z" + id="glyph3157" /> +<glyph + unicode="ề" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M303,775l93,-132l51,0l-64,132M231,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143z" + id="glyph3159" /> +<glyph + unicode="Ể" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M226,824l-93,-114l68,0l55,70l2,0l56,-70l70,0l-95,114M408,754C434,800 477,810 477,858C477,898 443,928 398,928C349,928 320,899 303,864l32,-19C346,862 363,882 384,882C403,882 418,868 418,849C418,817 382,807 366,766z" + id="glyph3161" /> +<glyph + unicode="ể" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M226,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M406,635C432,680 475,689 475,738C475,777 440,809 395,809C346,809 317,779 300,745l32,-20C343,742 359,761 380,761C399,761 413,747 413,728C413,697 378,688 362,646z" + id="glyph3163" /> +<glyph + unicode="Ễ" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M235,824l-103,-114l72,0l63,69l2,0l64,-69l73,0l-104,114M197,837C198,861 207,877 222,877C235,877 246,871 266,860C287,849 305,839 327,839C374,839 396,873 395,935l-47,0C346,903 336,894 321,894C308,894 294,902 278,910C255,923 238,932 216,932C174,932 147,894 148,837z" + id="glyph3165" /> +<glyph + unicode="ễ" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M228,668l-89,-118l67,0l53,73l2,0l54,-73l69,0l-92,118M189,680C190,703 199,719 214,719C227,719 238,713 258,702C279,691 297,681 319,681C366,681 388,715 387,777l-47,0C338,745 328,737 313,737C300,737 286,744 270,753C247,765 230,775 208,775C166,775 139,737 140,680z" + id="glyph3167" /> +<glyph + unicode="Ệ" + horiz-adv-x="492" + d="M425,388l-262,0l0,213l277,0l0,73l-364,0l0,-674l379,0l0,73l-292,0l0,243l262,0M228,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114M261,-181C289,-181 310,-156 310,-130C310,-104 289,-80 262,-80C233,-80 211,-104 211,-130C211,-156 233,-181 260,-181z" + id="glyph3169" /> +<glyph + unicode="ệ" + horiz-adv-x="501" + d="M462,226C463,235 465,249 465,267C465,356 423,495 265,495C124,495 38,380 38,234C38,88 127,-11 276,-11C353,-11 406,6 437,20l-15,63C389,69 351,58 288,58C200,58 124,107 122,226M123,289C130,350 169,432 258,432C357,432 381,345 380,289M219,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M251,-80C222,-80 199,-103 199,-131C199,-159 221,-183 249,-183C279,-183 300,-159 300,-131C300,-103 278,-80 251,-80z" + id="glyph3171" /> +<glyph + unicode="Ỉ" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M144,709C170,755 215,766 215,817C215,858 178,889 132,889C81,889 51,858 33,822l34,-20C78,819 95,837 116,837C136,837 149,825 149,806C149,773 113,765 97,722z" + id="glyph3173" /> +<glyph + unicode="ỉ" + horiz-adv-x="234" + d="M161,0l0,484l-88,0l0,-484M131,530C154,576 206,591 206,647C206,691 169,722 122,722C72,722 42,691 24,655l34,-20C70,653 85,670 107,670C125,670 140,657 140,636C140,599 96,586 84,542z" + id="glyph3175" /> +<glyph + unicode="Ị" + horiz-adv-x="239" + d="M76,674l0,-674l87,0l0,674M120,-181C148,-181 169,-156 169,-130C169,-104 148,-80 121,-80C92,-80 70,-104 70,-130C70,-156 92,-181 119,-181z" + id="glyph3177" /> +<glyph + unicode="ị" + horiz-adv-x="234" + d="M161,0l0,484l-88,0l0,-484M117,675C85,675 62,651 62,620C62,590 84,566 115,566C150,566 172,590 171,620C171,651 150,675 117,675M117,-80C88,-80 65,-103 65,-131C65,-159 87,-183 115,-183C145,-183 166,-159 166,-131C166,-103 144,-80 117,-80z" + id="glyph3179" /> +<glyph + unicode="Ọ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M345,-181C373,-181 394,-156 394,-130C394,-104 373,-80 346,-80C317,-80 295,-104 295,-130C295,-156 317,-181 344,-181z" + id="glyph3181" /> +<glyph + unicode="ọ" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M275,-80C246,-80 223,-103 223,-131C223,-159 245,-183 273,-183C303,-183 324,-159 324,-131C324,-103 302,-80 275,-80z" + id="glyph3183" /> +<glyph + unicode="Ỏ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M368,709C394,755 439,766 439,817C439,858 402,889 356,889C305,889 275,858 257,822l34,-20C302,819 319,837 340,837C360,837 373,825 373,806C373,773 337,765 321,722z" + id="glyph3185" /> +<glyph + unicode="ỏ" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M288,530C311,576 363,591 363,647C363,691 326,722 279,722C229,722 199,691 181,655l34,-20C227,653 242,670 264,670C282,670 297,657 297,636C297,599 253,586 241,542z" + id="glyph3187" /> +<glyph + unicode="Ố" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M321,824l-92,-114l67,0l56,70l2,0l55,-70l70,0l-95,114M515,900l-84,-105l52,0l121,105z" + id="glyph3189" /> +<glyph + unicode="ố" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M246,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M438,775l-69,-132l51,0l99,132z" + id="glyph3191" /> +<glyph + unicode="Ồ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M379,881l109,-105l54,0l-72,105M316,824l-92,-114l68,0l55,70l2,0l55,-70l70,0l-94,114z" + id="glyph3193" /> +<glyph + unicode="ồ" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M318,775l93,-132l51,0l-64,132M246,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143z" + id="glyph3195" /> +<glyph + unicode="Ổ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M318,824l-93,-114l68,0l55,70l2,0l56,-70l70,0l-95,114M500,754C526,800 569,810 569,858C569,898 535,928 490,928C441,928 412,899 395,864l32,-19C438,862 455,882 476,882C495,882 510,868 510,849C510,817 474,807 458,766z" + id="glyph3197" /> +<glyph + unicode="ổ" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M241,693l-84,-143l64,0l51,94l2,0l51,-94l67,0l-86,143M421,635C447,680 490,689 490,738C490,777 455,809 410,809C361,809 332,779 315,745l32,-20C358,742 374,761 395,761C414,761 428,747 428,728C428,697 393,688 377,646z" + id="glyph3199" /> +<glyph + unicode="Ỗ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M311,824l-103,-114l72,0l63,69l2,0l64,-69l73,0l-104,114M273,837C274,861 283,877 298,877C311,877 322,871 342,860C363,849 381,839 403,839C450,839 472,873 471,935l-47,0C422,903 412,894 397,894C384,894 370,902 354,910C331,923 314,932 292,932C250,932 223,894 224,837z" + id="glyph3201" /> +<glyph + unicode="ỗ" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M241,668l-89,-118l67,0l53,73l2,0l54,-73l69,0l-92,118M202,680C203,703 212,719 227,719C240,719 251,713 271,702C292,691 310,681 332,681C379,681 401,715 400,777l-47,0C351,745 341,737 326,737C313,737 299,744 283,753C260,765 243,775 221,775C179,775 152,737 153,680z" + id="glyph3203" /> +<glyph + unicode="Ộ" + horiz-adv-x="689" + d="M349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,544 533,685 349,685M345,614C490,614 560,474 560,340C560,187 482,60 344,60C207,60 129,189 129,333C129,481 201,614 345,614M310,824l-103,-114l71,0l64,69l2,0l64,-69l73,0l-104,114M345,-181C373,-181 394,-156 394,-130C394,-104 373,-80 346,-80C317,-80 295,-104 295,-130C295,-156 317,-181 344,-181z" + id="glyph3205" /> +<glyph + unicode="ộ" + horiz-adv-x="549" + d="M278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,394 417,495 278,495M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M243,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M275,-80C246,-80 223,-103 223,-131C223,-159 245,-183 273,-183C303,-183 324,-159 324,-131C324,-103 302,-80 275,-80z" + id="glyph3207" /> +<glyph + unicode="Ớ" + horiz-adv-x="689" + d="M344,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60M593,740C602,722 609,700 609,676C609,647 597,632 564,632C532,632 484,658 453,668C427,677 389,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,449 619,530 574,582C646,575 679,616 679,671C679,705 671,731 661,753M393,827l-93,-117l72,0l127,117z" + id="glyph3209" /> +<glyph + unicode="ớ" + horiz-adv-x="553" + d="M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M470,554C479,537 486,517 486,495C486,469 472,455 446,455C415,455 385,474 361,481C334,490 308,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,311 493,366 461,406C522,403 554,439 554,493C554,521 547,545 537,566M319,693l-88,-143l63,0l122,143z" + id="glyph3211" /> +<glyph + unicode="Ờ" + horiz-adv-x="689" + d="M344,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60M593,740C602,722 609,700 609,676C609,647 597,632 564,632C532,632 484,658 453,668C427,677 389,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,449 619,530 574,582C646,575 679,616 679,671C679,705 671,731 661,753M206,827l127,-116l72,0l-93,116z" + id="glyph3213" /> +<glyph + unicode="ờ" + horiz-adv-x="553" + d="M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M470,554C479,537 486,517 486,495C486,469 472,455 446,455C415,455 385,474 361,481C334,490 308,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,311 493,366 461,406C522,403 554,439 554,493C554,521 547,545 537,566M146,693l122,-143l62,0l-87,143z" + id="glyph3215" /> +<glyph + unicode="Ở" + horiz-adv-x="689" + d="M344,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60M593,740C602,722 609,700 609,676C609,647 597,632 564,632C532,632 484,658 453,668C427,677 389,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,449 619,530 574,582C646,575 679,616 679,671C679,705 671,731 661,753M368,709C394,755 439,766 439,817C439,858 402,889 356,889C305,889 275,858 257,822l34,-20C302,819 319,837 340,837C360,837 373,825 373,806C373,773 337,765 321,722z" + id="glyph3217" /> +<glyph + unicode="ở" + horiz-adv-x="553" + d="M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M470,554C479,537 486,517 486,495C486,469 472,455 446,455C415,455 385,474 361,481C334,490 308,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,311 493,366 461,406C522,403 554,439 554,493C554,521 547,545 537,566M288,530C311,576 363,591 363,647C363,691 326,722 279,722C229,722 199,691 181,655l34,-20C227,653 242,670 264,670C282,670 297,657 297,636C297,599 253,586 241,542z" + id="glyph3219" /> +<glyph + unicode="Ỡ" + horiz-adv-x="689" + d="M344,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60M593,740C602,722 609,700 609,676C609,647 597,632 564,632C532,632 484,658 453,668C427,677 389,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,449 619,530 574,582C646,575 679,616 679,671C679,705 671,731 661,753M274,715C276,742 284,759 300,759C314,759 328,750 347,740C369,729 387,721 408,721C454,721 476,755 475,818l-46,0C426,786 417,779 402,779C386,779 370,788 354,797C333,808 317,817 295,817C251,817 225,776 227,715z" + id="glyph3221" /> +<glyph + unicode="ỡ" + horiz-adv-x="553" + d="M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M470,554C479,537 486,517 486,495C486,469 472,455 446,455C415,455 385,474 361,481C334,490 308,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,311 493,366 461,406C522,403 554,439 554,493C554,521 547,545 537,566M199,567C201,595 209,612 225,612C237,612 248,606 268,594C289,583 308,574 329,574C376,574 399,608 398,675l-47,0C349,641 339,632 323,632C311,632 297,640 281,649C258,662 241,671 219,671C176,671 149,632 151,567z" + id="glyph3223" /> +<glyph + unicode="Ợ" + horiz-adv-x="689" + d="M344,60C206,60 128,191 129,333C128,479 200,614 345,614C490,614 560,474 560,340C560,187 482,60 344,60M593,740C602,722 609,700 609,676C609,647 597,632 564,632C532,632 484,658 453,668C427,677 389,685 349,685C169,685 36,545 36,331C36,127 161,-11 339,-11C511,-11 652,112 652,344C652,449 619,530 574,582C646,575 679,616 679,671C679,705 671,731 661,753M345,-181C373,-181 394,-156 394,-130C394,-104 373,-80 346,-80C317,-80 295,-104 295,-130C295,-156 317,-181 344,-181z" + id="glyph3225" /> +<glyph + unicode="ợ" + horiz-adv-x="553" + d="M276,429C380,429 421,325 421,243C421,134 358,55 274,55C188,55 127,135 127,241C127,333 172,429 276,429M470,554C479,537 486,517 486,495C486,469 472,455 446,455C415,455 385,474 361,481C334,490 308,495 278,495C144,495 38,400 38,238C38,85 139,-11 270,-11C387,-11 511,67 511,246C511,311 493,366 461,406C522,403 554,439 554,493C554,521 547,545 537,566M276,-80C247,-80 224,-103 224,-131C224,-159 246,-183 274,-183C304,-183 325,-159 325,-131C325,-103 303,-80 276,-80z" + id="glyph3227" /> +<glyph + unicode="Ụ" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M324,-181C352,-181 373,-156 373,-130C373,-104 352,-80 325,-80C296,-80 274,-104 274,-130C274,-156 296,-181 323,-181z" + id="glyph3229" /> +<glyph + unicode="ụ" + horiz-adv-x="551" + d="M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132M276,-80C247,-80 224,-103 224,-131C224,-159 246,-183 274,-183C304,-183 325,-159 325,-131C325,-103 303,-80 276,-80z" + id="glyph3231" /> +<glyph + unicode="Ủ" + horiz-adv-x="647" + d="M75,674l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,393l-88,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399M348,709C374,755 419,766 419,817C419,858 382,889 336,889C285,889 255,858 237,822l34,-20C282,819 299,837 320,837C340,837 353,825 353,806C353,773 317,765 301,722z" + id="glyph3233" /> +<glyph + unicode="ủ" + horiz-adv-x="551" + d="M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132M290,530C313,576 365,591 365,647C365,691 328,722 281,722C231,722 201,691 183,655l34,-20C229,653 244,670 266,670C284,670 299,657 299,636C299,599 255,586 243,542z" + id="glyph3235" /> +<glyph + unicode="Ứ" + horiz-adv-x="677" + d="M593,770C603,752 609,731 609,712C609,686 597,674 563,674l-79,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,340l15,1C647,624 677,658 677,707C677,735 670,760 660,782M367,827l-93,-117l72,0l127,117z" + id="glyph3237" /> +<glyph + unicode="ứ" + horiz-adv-x="551" + d="M489,581C499,563 506,542 506,523C506,498 494,484 459,484l-69,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132l0,303l11,1C538,438 574,470 574,520C574,547 567,572 556,592M331,693l-88,-143l63,0l122,143z" + id="glyph3239" /> +<glyph + unicode="Ừ" + horiz-adv-x="677" + d="M593,770C603,752 609,731 609,712C609,686 597,674 563,674l-79,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,340l15,1C647,624 677,658 677,707C677,735 670,760 660,782M183,827l127,-116l72,0l-93,116z" + id="glyph3241" /> +<glyph + unicode="ừ" + horiz-adv-x="551" + d="M489,581C499,563 506,542 506,523C506,498 494,484 459,484l-69,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132l0,303l11,1C538,438 574,470 574,520C574,547 567,572 556,592M145,693l122,-143l62,0l-87,143z" + id="glyph3243" /> +<glyph + unicode="Ử" + horiz-adv-x="677" + d="M593,770C603,752 609,731 609,712C609,686 597,674 563,674l-79,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,340l15,1C647,624 677,658 677,707C677,735 670,760 660,782M363,709C389,755 434,766 434,817C434,858 397,889 351,889C300,889 270,858 252,822l34,-20C297,819 314,837 335,837C355,837 368,825 368,806C368,773 332,765 316,722z" + id="glyph3245" /> +<glyph + unicode="ử" + horiz-adv-x="551" + d="M489,581C499,563 506,542 506,523C506,498 494,484 459,484l-69,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132l0,303l11,1C538,438 574,470 574,520C574,547 567,572 556,592M298,530C321,576 373,591 373,647C373,691 336,722 289,722C239,722 209,691 191,655l34,-20C237,653 252,670 274,670C292,670 307,657 307,636C307,599 263,586 251,542z" + id="glyph3247" /> +<glyph + unicode="Ữ" + horiz-adv-x="677" + d="M593,770C603,752 609,731 609,712C609,686 597,674 563,674l-79,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,340l15,1C647,624 677,658 677,707C677,735 670,760 660,782M261,715C263,742 271,759 287,759C301,759 315,750 334,740C356,729 374,721 395,721C441,721 463,755 462,818l-46,0C413,786 404,779 389,779C373,779 357,788 341,797C320,808 304,817 282,817C238,817 212,776 214,715z" + id="glyph3249" /> +<glyph + unicode="ữ" + horiz-adv-x="551" + d="M489,581C499,563 506,542 506,523C506,498 494,484 459,484l-69,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132l0,303l11,1C538,438 574,470 574,520C574,547 567,572 556,592M201,567C203,595 211,612 227,612C239,612 250,606 270,594C291,583 310,574 331,574C378,574 401,608 400,675l-47,0C351,641 341,632 325,632C313,632 299,640 283,649C260,662 243,671 221,671C178,671 151,632 153,567z" + id="glyph3251" /> +<glyph + unicode="Ự" + horiz-adv-x="677" + d="M593,770C603,752 609,731 609,712C609,686 597,674 563,674l-79,0l0,-399C484,126 420,60 320,60C230,60 163,124 163,275l0,399l-88,0l0,-397C75,68 179,-11 317,-11C463,-11 572,74 572,281l0,340l15,1C647,624 677,658 677,707C677,735 670,760 660,782M339,-80C310,-80 287,-103 287,-131C287,-159 309,-183 337,-183C367,-183 388,-159 388,-131C388,-103 366,-80 339,-80z" + id="glyph3253" /> +<glyph + unicode="ự" + horiz-adv-x="551" + d="M489,581C499,563 506,542 506,523C506,498 494,484 459,484l-69,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132l0,303l11,1C538,438 574,470 574,520C574,547 567,572 556,592M273,-80C244,-80 221,-103 221,-131C221,-159 243,-183 271,-183C301,-183 322,-159 322,-131C322,-103 300,-80 273,-80z" + id="glyph3255" /> +<glyph + unicode="Ỵ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M271,-181C299,-181 320,-156 320,-130C320,-104 299,-80 272,-80C243,-80 221,-104 221,-130C221,-156 243,-181 270,-181z" + id="glyph3257" /> +<glyph + unicode="ỵ" + horiz-adv-x="471" + d="M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286M327,-80C298,-80 275,-103 275,-131C275,-159 297,-183 325,-183C355,-183 376,-159 376,-131C376,-103 354,-80 327,-80z" + id="glyph3259" /> +<glyph + unicode="Ỷ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M295,709C321,755 366,766 366,817C366,858 329,889 283,889C232,889 202,858 184,822l34,-20C229,819 246,837 267,837C287,837 300,825 300,806C300,773 264,765 248,722z" + id="glyph3261" /> +<glyph + unicode="ỷ" + horiz-adv-x="471" + d="M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286M259,530C282,576 334,591 334,647C334,691 297,722 250,722C200,722 170,691 152,655l34,-20C198,653 213,670 235,670C253,670 268,657 268,636C268,599 224,586 212,542z" + id="glyph3263" /> +<glyph + unicode="Ỹ" + horiz-adv-x="541" + d="M314,0l0,287l226,387l-99,0l-97,-186C318,437 295,393 276,349l-2,0C253,396 233,437 207,488l-95,186l-99,0l213,-388l0,-286M194,715C196,742 204,759 220,759C234,759 248,750 267,740C289,729 307,721 328,721C374,721 396,755 395,818l-46,0C346,786 337,779 322,779C306,779 290,788 274,797C253,808 237,817 215,817C171,817 145,776 147,715z" + id="glyph3265" /> +<glyph + unicode="ỹ" + horiz-adv-x="471" + d="M166,567C168,595 176,612 192,612C204,612 215,606 235,594C256,583 275,574 296,574C343,574 366,608 365,675l-47,0C316,641 306,632 290,632C278,632 264,640 248,649C225,662 208,671 186,671C143,671 116,632 118,567M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3267" /> +<glyph + unicode="∕" + horiz-adv-x="121" + d="M-101,-11l380,672l-57,0l-379,-672z" + id="glyph3269" /> +<glyph + unicode="∙" + horiz-adv-x="207" + d="M103,200C139,200 163,227 163,263C162,300 139,326 104,326C69,326 44,299 44,263C43,227 68,200 102,200z" + id="glyph3271" /> +<glyph + unicode="ų" + horiz-adv-x="551" + d="M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-76C386,-24 356,-71 356,-120C356,-176 394,-206 449,-206C475,-206 509,-199 530,-183l-14,45C505,-143 490,-147 471,-147C442,-147 420,-128 420,-95C420,-59 446,-18 462,3l20,-3C479,37 478,82 478,132z" + id="glyph3273" /> +<glyph + unicode="υ" + horiz-adv-x="524" + d="M360,484C371,459 392,377 392,287C392,148 348,62 264,62C211,62 154,99 154,196l0,197C154,446 149,473 140,484l-84,0C65,457 67,405 67,321l0,-137C67,52 151,-11 257,-11C432,-11 478,153 478,295C478,375 459,452 442,484z" + id="glyph3275" /> +<glyph + unicode="ϋ" + horiz-adv-x="524" + d="M165,557C194,557 213,580 213,607C213,636 193,658 166,658C137,658 116,635 116,607C116,580 136,557 164,557M349,557C378,557 398,580 398,607C398,636 377,658 349,658C321,658 300,635 300,607C300,580 319,557 348,557M360,484C371,459 392,377 392,287C392,148 348,62 264,62C211,62 154,99 154,196l0,197C154,446 149,473 140,484l-84,0C65,457 67,405 67,321l0,-137C67,52 151,-11 257,-11C432,-11 478,153 478,295C478,375 459,452 442,484z" + id="glyph3277" /> +<glyph + unicode="ΰ" + horiz-adv-x="524" + d="M360,484C371,459 392,377 392,287C392,148 348,62 264,62C211,62 154,99 154,196l0,197C154,446 149,473 140,484l-84,0C65,457 67,405 67,321l0,-137C67,52 151,-11 257,-11C432,-11 478,153 478,295C478,375 459,452 442,484M242,696l-21,-157l47,0l49,157M141,560C167,560 185,582 185,607C185,633 166,653 141,653C115,653 94,632 94,607C94,582 113,560 140,560M382,560C410,560 428,582 428,607C428,633 409,653 383,653C357,653 337,632 337,607C337,582 356,560 381,560z" + id="glyph3279" /> +<glyph + unicode="ύ" + horiz-adv-x="524" + d="M272,687l-53,-143l59,0l83,143M360,484C371,459 392,377 392,287C392,148 348,62 264,62C211,62 154,99 154,196l0,197C154,446 149,473 140,484l-84,0C65,457 67,405 67,321l0,-137C67,52 151,-11 257,-11C432,-11 478,153 478,295C478,375 459,452 442,484z" + id="glyph3281" /> +<glyph + unicode="ů" + horiz-adv-x="551" + d="M276,537C333,537 376,576 376,629C376,682 337,723 277,723C214,723 175,681 175,628C175,577 216,537 276,537M275,574C246,574 226,602 226,628C226,659 244,685 275,685C307,685 327,660 327,630C327,600 307,574 275,574M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3283" /> +<glyph + unicode="ũ" + horiz-adv-x="551" + d="M200,567C202,595 210,612 226,612C238,612 249,606 269,594C290,583 309,574 330,574C377,574 400,608 399,675l-47,0C350,641 340,632 324,632C312,632 298,640 282,649C259,662 242,671 220,671C177,671 150,632 152,567M478,484l-88,0l0,-297C390,171 387,155 382,142C366,103 325,62 266,62C186,62 158,124 158,216l0,268l-88,0l0,-283C70,31 161,-11 237,-11C323,-11 374,40 397,79l2,0l5,-79l78,0C479,38 478,82 478,132z" + id="glyph3285" /> +<glyph + unicode="v" + horiz-adv-x="481" + d="M13,484l184,-484l84,0l190,484l-92,0l-94,-272C269,168 255,128 244,88l-3,0C231,128 218,168 202,212l-95,272z" + id="glyph3287" /> +<glyph + unicode="w" + horiz-adv-x="736" + d="M18,484l146,-484l80,0l78,230C339,282 354,332 366,390l2,0C380,333 394,285 411,231l74,-231l80,0l156,484l-87,0l-69,-243C549,184 536,133 528,84l-3,0C514,133 500,184 482,242l-75,242l-74,0l-79,-246C238,185 222,133 211,84l-3,0C199,134 186,184 172,238l-64,246z" + id="glyph3289" /> +<glyph + unicode="ẃ" + horiz-adv-x="736" + d="M411,693l-88,-143l63,0l122,143M18,484l146,-484l80,0l78,230C339,282 354,332 366,390l2,0C380,333 394,285 411,231l74,-231l80,0l156,484l-87,0l-69,-243C549,184 536,133 528,84l-3,0C514,133 500,184 482,242l-75,242l-74,0l-79,-246C238,185 222,133 211,84l-3,0C199,134 186,184 172,238l-64,246z" + id="glyph3291" /> +<glyph + unicode="ŵ" + horiz-adv-x="736" + d="M337,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M18,484l146,-484l80,0l78,230C339,282 354,332 366,390l2,0C380,333 394,285 411,231l74,-231l80,0l156,484l-87,0l-69,-243C549,184 536,133 528,84l-3,0C514,133 500,184 482,242l-75,242l-74,0l-79,-246C238,185 222,133 211,84l-3,0C199,134 186,184 172,238l-64,246z" + id="glyph3293" /> +<glyph + unicode="ẅ" + horiz-adv-x="736" + d="M276,570C305,570 325,594 325,621C325,650 304,672 276,672C248,672 225,649 225,621C225,594 246,570 275,570M461,570C490,570 510,594 510,621C510,650 489,672 461,672C433,672 411,649 411,621C411,594 431,570 460,570M18,484l146,-484l80,0l78,230C339,282 354,332 366,390l2,0C380,333 394,285 411,231l74,-231l80,0l156,484l-87,0l-69,-243C549,184 536,133 528,84l-3,0C514,133 500,184 482,242l-75,242l-74,0l-79,-246C238,185 222,133 211,84l-3,0C199,134 186,184 172,238l-64,246z" + id="glyph3295" /> +<glyph + unicode="ẁ" + horiz-adv-x="736" + d="M237,693l122,-143l62,0l-87,143M18,484l146,-484l80,0l78,230C339,282 354,332 366,390l2,0C380,333 394,285 411,231l74,-231l80,0l156,484l-87,0l-69,-243C549,184 536,133 528,84l-3,0C514,133 500,184 482,242l-75,242l-74,0l-79,-246C238,185 222,133 211,84l-3,0C199,134 186,184 172,238l-64,246z" + id="glyph3297" /> +<glyph + unicode="x" + horiz-adv-x="463" + d="M16,484l164,-237l-172,-247l97,0l70,109C193,138 210,163 226,193l2,0C245,164 261,137 280,109l71,-109l100,0l-170,250l165,234l-95,0l-68,-103C267,355 251,330 235,301l-3,0C216,328 201,353 183,380l-69,104z" + id="glyph3299" /> +<glyph + unicode="ξ" + horiz-adv-x="433" + d="M361,435C344,435 327,435 313,435C234,438 172,477 172,547C172,603 214,656 298,656C340,656 377,644 397,633l18,61C388,710 346,721 293,721C153,721 84,636 84,553C84,487 121,435 195,412l0,-5C108,383 38,309 38,212C38,43 208,3 341,-16C340,-41 322,-103 309,-126l58,-17C395,-108 426,-20 426,32C426,38 423,43 417,43C377,49 331,55 284,68C185,94 130,139 130,220C130,303 195,371 334,371l27,0z" + id="glyph3301" /> +<glyph + unicode="y" + horiz-adv-x="471" + d="M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3303" /> +<glyph + unicode="ý" + horiz-adv-x="471" + d="M285,693l-88,-143l63,0l122,143M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3305" /> +<glyph + unicode="ŷ" + horiz-adv-x="471" + d="M207,693l-94,-143l65,0l58,95l2,0l58,-95l68,0l-96,143M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3307" /> +<glyph + unicode="ÿ" + horiz-adv-x="471" + d="M155,570C184,570 204,594 204,621C204,650 183,672 155,672C127,672 104,649 104,621C104,594 125,570 154,570M340,570C369,570 389,594 389,621C389,650 368,672 340,672C312,672 290,649 290,621C290,594 310,570 339,570M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3309" /> +<glyph + unicode="¥" + horiz-adv-x="513" + d="M292,0l0,175l156,0l0,50l-156,0l0,72l156,0l0,50l-130,0l179,303l-92,0l-108,-204C280,412 267,381 257,353l-3,0C242,383 230,410 214,444l-104,206l-94,0l168,-303l-130,0l0,-50l155,0l0,-72l-155,0l0,-50l155,0l0,-175z" + id="glyph3311" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M304,0l0,115l154,0l0,49l-154,0l0,60l154,0l0,49l-127,0l160,242l-91,0l-90,-151C294,335 279,308 268,283l-3,0C255,307 242,331 226,362l-85,153l-94,0l150,-242l-128,0l0,-49l153,0l0,-60l-153,0l0,-49l153,0l0,-115z" + id="glyph3313" /> +<glyph + unicode="ỳ" + horiz-adv-x="471" + d="M108,693l122,-143l62,0l-87,143M9,484l179,-446C192,27 194,20 194,15C194,10 191,3 187,-6C167,-51 137,-85 113,-104C87,-126 58,-140 36,-147l22,-74C80,-217 123,-202 166,-164C226,-112 269,-27 332,139l132,345l-93,0l-96,-284C263,165 253,128 244,99l-2,0C234,128 222,166 211,198l-106,286z" + id="glyph3315" /> +<glyph + unicode="z" + horiz-adv-x="428" + d="M18,0l393,0l0,70l-283,0l0,2C150,97 170,121 190,148l216,281l1,55l-369,0l0,-71l262,0l0,-2C278,385 258,362 237,336l-219,-285z" + id="glyph3317" /> +<glyph + unicode="ź" + horiz-adv-x="428" + d="M260,693l-88,-143l63,0l122,143M18,0l393,0l0,70l-283,0l0,2C150,97 170,121 190,148l216,281l1,55l-369,0l0,-71l262,0l0,-2C278,385 258,362 237,336l-219,-285z" + id="glyph3319" /> +<glyph + unicode="ž" + horiz-adv-x="428" + d="M259,550l94,143l-66,0l-59,-95l-2,0l-59,95l-68,0l96,-143M18,0l393,0l0,70l-283,0l0,2C150,97 170,121 190,148l216,281l1,55l-369,0l0,-71l262,0l0,-2C278,385 258,362 237,336l-219,-285z" + id="glyph3321" /> +<glyph + unicode="ż" + horiz-adv-x="428" + d="M217,570C246,570 266,594 266,621C266,650 245,673 217,673C188,673 166,650 166,621C166,594 187,570 216,570M18,0l393,0l0,70l-283,0l0,2C150,97 170,121 190,148l216,281l1,55l-369,0l0,-71l262,0l0,-2C278,385 258,362 237,336l-219,-285z" + id="glyph3323" /> +<glyph + unicode="0" + horiz-adv-x="513" + d="M262,661C130,661 36,541 36,323C38,108 124,-11 251,-11C395,-11 477,111 477,332C477,539 399,661 262,661M257,593C348,593 389,488 389,328C389,162 346,57 256,57C176,57 124,153 124,322C124,499 180,593 257,593z" + id="glyph3325" /> +<glyph + unicode="" + horiz-adv-x="336" + d="M166,-5C264,-5 313,76 313,198C313,319 262,395 170,395C80,395 23,318 23,194C23,70 78,-5 165,-5M167,46C122,46 93,100 93,197C93,290 123,344 168,344C218,344 243,290 243,197C243,102 217,46 168,46z" + id="glyph3327" /> +<glyph + unicode="" + horiz-adv-x="513" + d="M262,661C130,661 36,541 36,323C38,108 124,-11 251,-11C395,-11 477,111 477,332C477,539 399,661 262,661M257,593C348,593 389,488 389,328C389,162 346,57 256,57C176,57 124,153 124,322C124,499 180,593 257,593z" + id="glyph3329" /> +<glyph + unicode="₀" + horiz-adv-x="336" + d="M166,-152C264,-152 313,-71 313,51C313,172 262,248 170,248C80,248 23,171 23,47C23,-77 78,-152 165,-152M167,-101C122,-101 93,-47 93,50C93,143 123,197 168,197C218,197 243,143 243,50C243,-45 217,-101 168,-101z" + id="glyph3331" /> +<glyph + unicode="" + horiz-adv-x="336" + d="M166,261C264,261 313,342 313,464C313,585 262,661 170,661C80,661 23,584 23,460C23,336 78,261 165,261M167,312C122,312 93,366 93,463C93,556 123,610 168,610C218,610 243,556 243,463C243,368 217,312 168,312z" + id="glyph3333" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M265,-11C390,-11 490,84 490,243C490,404 389,496 265,496C141,496 40,400 40,243C40,81 141,-11 264,-11M265,56C177,56 125,135 125,243C125,350 177,429 265,429C353,429 405,350 405,243C405,135 354,56 266,56z" + id="glyph3335" /> +<glyph + unicode="" + horiz-adv-x="513" + d="M146,168C129,211 121,265 121,328C122,488 175,593 257,593C297,593 327,574 347,542M371,481C385,438 392,386 392,328C392,162 346,57 255,57C220,57 191,74 169,106M474,649l-45,29l-40,-66C356,645 313,661 262,661C130,661 35,542 36,323C36,219 57,137 93,81l-47,-74l44,-31l37,63C162,5 205,-11 252,-11C395,-11 477,111 477,332C477,432 459,513 424,570z" + id="glyph3337" /> +<glyph + unicode="" + horiz-adv-x="513" + d="M146,168C129,211 121,265 121,328C122,488 175,593 257,593C297,593 327,574 347,542M371,481C385,438 392,386 392,328C392,162 346,57 255,57C220,57 191,74 169,106M474,649l-45,29l-40,-66C356,645 313,661 262,661C130,661 35,542 36,323C36,219 57,137 93,81l-47,-74l44,-31l37,63C162,5 205,-11 252,-11C395,-11 477,111 477,332C477,432 459,513 424,570z" + id="glyph3339" /> +<glyph + unicode="⁰" + horiz-adv-x="336" + d="M166,438C264,438 313,519 313,641C313,762 262,838 170,838C80,838 23,761 23,637C23,513 78,438 165,438M167,489C122,489 93,543 93,640C93,733 123,787 168,787C218,787 243,733 243,640C243,545 217,489 168,489z" + id="glyph3341" /> +<glyph + unicode="" + horiz-adv-x="530" + d="M265,-11C390,-11 490,84 490,243C490,404 389,496 265,496C141,496 40,400 40,243C40,81 141,-11 264,-11M265,56C177,56 125,135 125,243C125,350 177,429 265,429C353,429 405,350 405,243C405,135 354,56 266,56z" + id="glyph3343" /> +<glyph + unicode="ζ" + horiz-adv-x="409" + d="M341,-143C368,-108 401,-26 401,32C401,38 397,43 392,44C345,49 298,56 252,73C179,99 128,142 128,249C128,395 247,565 407,646l-13,62l-167,0C182,708 137,708 105,712l-9,-59C126,644 167,643 212,643l71,0l0,-4C129,534 39,371 39,233C39,105 110,40 198,11C238,-2 281,-11 316,-16C314,-42 297,-103 283,-126z" + id="glyph3345" /> +</font> + + <path + style="fill:url(#linearGradient14446)" + id="path4675" + d="m 26.350772,27.118589 c 0,0.520748 -0.641563,0.942995 -1.432789,0.942995 l -18.3886986,0 c -0.7922734,0 -1.4327903,-0.422247 -1.4327903,-0.942995 l 0,-12.10325 c 0,-0.520748 0.6405169,-0.942306 1.4327903,-0.942306 l 18.3886986,0 c 0.791226,0 1.432789,0.421558 1.432789,0.942306 l 0,12.10325 z" /><path + d="m 10.493475,10.931152 c 0.01256,-2.8760461 2.348563,-5.2047228 5.228796,-5.2047228 2.880232,0 5.215189,2.3286767 5.228795,5.2047228 h 2.234482 C 23.172992,6.8211689 19.83644,3.4929928 15.722271,3.4929928 c -4.11417,0 -7.4496728,3.3292227 -7.4622321,7.4381592 h 2.2334361 z" + id="path4688" + style="fill:url(#linearGradient14596)" /><rect + x="8.2603464" + y="10.889266" + width="2.2282033" + height="3.9781125" + id="rect4699" + style="fill:url(#linearGradient14602)" /><rect + x="20.95764" + y="10.889266" + width="2.2282033" + height="3.9781125" + id="rect4710" + style="fill:url(#linearGradient14600)" /> +<i:pgf> + + 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 +e+5/ZM8sZYzx7QhQDp3d88j999x95wdu8m1y/Y233vnh+6bZvgNHF44dO7494dixhaMH9rHp+z58 +5603Xu8a5Obb775vz96jpxbPvry2vr6xHWF9fe3ls4unju7dc9/dt99sm+S6G2/dcc/03qcXN77w +5he/9OWvbE/48pe++OYXNhaf3jt9z45bb3SE3HbX/ezo4pW3vvq1r7/9jXe2J3zj7a9/7atvXVk8 +yu6/6zZLyPU33fGRR/ad2njr99755re/893vbU/47ne+/c13fu+tjVP7HvnIHTcZJrn+5s49ew4s +fuGr73zr+z9494c/2p7ww3d/8P1vvfPVLywe2HNP5+brDa/esuPe2aNn3/zaN7//Fz/+6Xvv/Ww7 +wnvv/fTHf/H9b37tzbNHZ+/dcQty63U33LLjY3Th5S9+/ds/+PFf/+L993+5HeH993/x1z/+wbe/ +/sWXF+jHdtxygyXkgx9nx9a+9PZ33v3pL/7v3/5qe8Lf/t9f/PTd77z9pbVj7OMfTAlZ//I3vvvD +997/2//36+0J/+9v33/vh9/9xpfXS4QcX//KO9/70Xvv/+rXf7c94de/ev+9H33vna+sH08JuY8f +30BCfvbLbSTklz9DQjaO8/smhEwImRAyIWRCyFYSUlTxo7I1aPSOj9z6/s1f/fBP/qDsBtCnL/wu ++CM/+fk2+iM//8mf//F//devRMcIXMWPkafO/6vf/9b//suf/fxvtslD+5uf/+z//Nkf/ee3zs1n +3lW8/pY7f6t3ZOVf/Kc//NN3//Inf7VNPutf/eT//Pn/+u//4Z+eOTRzz503ey/+N3fPffaNf/f2 +H/3pn/35tnnxf/5n/+t//Jd/89vPPb4zDCduuv3DU3tPXHjz37/9h//zj/9kewY23/2TP/6j//5f +vvrPzh2TD959mx/pfeBD92WHnv/8m//2P/7+f/uD7RnqfeMP/ut//g//5p+tPTs3c+8H/ZDzhlvu +/OjDav75V17/5//yd7+8PYPfL3/pX7/1T3/73LMH2YMfvuNmO0Ny3fU33fahe6flwU8+v/zShe2Z +DlhfX3vl3Jnnjs2xqd/84AfctAQ2yR133ftwlj95+BNPb9v8yML8ocflzIO/+Ru33eynjK67/sZb +7vjQRz/+0HQv26YpI5wwymZ2Pnjvhz8Y6bCU3Hbnb/zjj/4WzqFtxySam0K7+4N3fA== + + + 4OYb0tm862+46ZYP3H7Hnds4q7hjx51mUvGGwlTrdUDKjTdt80QrTrPeUJhmjTPP2zn1XDfxnJIz +4qn40YepqUPrK/ise+f0UfuuPHy7V+WPda64/wg+SJ91j3cy94B8hi/SSyF6UmrdzXPZy2mWd9dS +mOgRlemuFLwHwsoAwmwqKahP4yDLHcGzHiGaRNiFjmB5j8s8jzDBlE/lsCcQGXB5mM7yXq40N7g8 +jGQ9QTXrBuyaEJsq0BAgy51Aa4Bd6IQaBViodcAeIZ6G5Zr2utDZfwYad+ex1de6Ry4urZxfXb/c +Pfna0uXll7p82rQzVdjmttXt//efg26YoXkvE7nszhBKe0IAspmsl2EgULgqEX16rWN/BeBMTjXp +Mc3FgMxQxR5VjHf3L2OJhIN6h6q1K3E/lNjN+ifOterlAiAmcbsCCnmGrVJgJKjRWShy//7OKUPm +6ZWO7O6c7p5+FgDa/TU7N0/Ip1Zfv9zdC500e/ji6uqp9ZWN7h7IAl9La6vz6yurr8M37c6evLxx +8Q3//WinGWvdX1OLV5KIgja1WCddBehQ8jW1WCdhAN2kjGHOqpSl0OHkDGtWlTSEbk7W6toP8YG8 +TS1uQuKwV4aXOch1VVJnSh1a7jDXEJLXvpBSrquRvqnFs6ZgkMCpxVOOZJAX6LcgE0kPsigBCTc6 +6EKR71xDLKQY6qEFDOuWMebWN9a70AfKEYG0RTFNVQLSnCoF952oBUCYlTTD1OKjEOvre8oUcPHy +wfPLl89vrC9dfMOUAqCLS/bPnXPz3bkrlze61kKf/53VacR/8fz6OYsb6F/ZOLO6ODevF4+fubR6 +8bdXVxafWn1j0Sa6NB1J8HXVnJjyr4DKAisPHN9TwjE/GHnjExw+f2F1r/1z/+q58+vOQQDCIc2R +zvNBWgQ1mqT7Qpcw1eUKK/lS7e/m26Zx/+RdjnKXm8Qc/ss6U1OmOPRP9napUEJ3Ts/fNmqfaRL+ +wYZRb4ebxPHaSjjqOk3i+PLNqOmexPHmlVHTOonjzSejpnESRx/7hVHTNonjE5vCqOmaxPGJdWHU +NE3i+MVyGDU9kzh+sRxGTc8kjl8sh1HTM4njF8th1PRM4vjFNIyalkkc3+jDqOmYxPGNPoyajkkc +3+jDqOmYxPGNPoyajkkc3+jDqOmYxPGNPoyajkkc3+jDqOmYxPGNPoyajkkc3+jDqOmYxPGNPoya +jkkc3+jDqOmYxPGNPoyajkkc3+jDqOmYxPGNPoyajkkc3+jDqOmYxPGNPoyajkkc3+jDqOmYxPGN +Poyajkkc3+jDqOmYxPGNPoyajkkc35iGUdMyieMXy2HU9Ezi+MVyGDU9kzh+sRxGTc8kjl8sh1HT +M4njF+vCqGmaxPGJTWHUdE3i+MR+YdS0TeLoY5swahoncbz5w4dR0zqJ480faRg13ZM4vrzRL4y6 +TpM4PrwwCZMwpmFbX7preOvuOvP43za+/Vf/+h8QceON2/waon0P8frCe4jXXX/jzbfedkdnxzY+ +EPnBHTs65oXI9KHKG2669fYdd33knns/tj0vZoY3M+/acfutN4W3O6+7/qYP3Hn3Pfc/smd2m94Q +ta+Izu555P577r7zAzf5Nrn+xlvv/PB902zfgaMLx7btWdVjC0cP7GPT9334zlvd+67XXX/z7Xff +t2fv0VOLZ1/enndmzUuzL59dPHV075777r795uvdG8C37rhneu/TixtfePOLX9qel3fx7d0vvvmF +jcWn907fs8O/AXzjbXfdz44uXnnrq1/7+tvb8xbxO+984+2vf+2rb11ZPMruv8u9inz9TXd85JF9 +pzbe+r13vvnt72zP68zf+953v/Ptb77ze29tnNr3SHgn+ubOPXsOLH7hq+986/s/eHfbHqx+9wff +/9Y7X/3C4oE993TMy9n4lvi9s0fPvvm1b37/L3780/e26Qnv937647/4/je/9ubZo7P+LfEbbtnx +Mbrw8he//u0f/Pivf/H+Nj1q/v4v/vrHP/j217/4cnxd3b43v/alt7/z7k9/sY3PvP/ip+9+5+3y +e/OjePj+/fd++N1vfHm9RMjx9a+8870fvff+r379d9sTfv2r99/70ffe+cr68ZSQ+/jxDSTkZ7/c +RkJ++TMkZOM4v29CyISQCSETQiaEXDtCxsbWjJH1HYE/8vOfgD/yuxeeppGQUXhof/Pzn/3l//7W +7/+r80+RjzlXcTQ+61/95C/f/dM//E//YuVI77fuNISMyov/8z/70z96+9+98dm53b9pvfjRjGu+ ++yd//D//8O1//+aFE3unPny7HdeMZKT3jT/4b7//H//tm59//lB234c+cOPoxr5f/vLv/st//vor +z8+rhz965y12hmQUswHr62sXXlp+/pMH5fS9H7rNT5CMZn7k6U8cfjLPHr73rjtu8VNGI5gxwgmj +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 +Y3ep63j8PfzYXeq6HX+Px+0r8Xs8Zpfn93jcLs/v8bhdnt/jcbs8v8fjdvl+j8fs8vwej9vl+T0e +t8vzezxul+f3eNwuz+/xuF2e3+Nxuzy/x+N2eX6Px+3y/B6P2+X5PR63y/N7PG6X5/d43C7P7/G4 +XZ7f43G7PL/H43Z5flc/l7o+x9/jcftK/W76XOo6HX+Px+0r9Xs8bpfnd6/Ppa7X8fd43L7Svhf6 +XOr6HX+Px+0r5XvQz6Wu5/H38GPWPpe6vsffp/a51HX/avwe5edSt+Wr4fvl/Fzqtl6O3+PP8ecr +/PO0I//sXdAVV1xx5RF/iOSGIp92xZVXXXX1Ndce+eeaq6+6ikqct+vKq6657vpnPPPGG2860s+N +Ny6ecf1111yli6OGXfP0Z9z0rOfc/PxbXvCCF7zwiD5E6pbn3/ycr7vxhuuuvnIU97Qrr7n+pmc/ +/8UvPTFZ55w/og+Rsre97CUvuvlZz3z61b11KOxrnvviE/GVr75refc9R/e5++7lnSdf4b7xhc9+ +5nVXXVFLu+Kqp9/03Fvd7Xff++YH3r577tz5I/qcO7f7tvtPvfHOl7/shc+64ZraOGraM579YnfH +tz/wyPe854fe+74fObLP+977g9/33edPvf7l33jzjdddVUu76rqbnn/i9m/fffePvv+nPvChxz58 +ZJ/HPvgzP/Fj73nHqTvdi77u+l7a9c96cbz7gXf/2M/+3C9+5KMf+7Uj+3zsV3/5Fz78r99z/o2v +eMlznnG1DNwVVz/jOS995b2P/OjP/qdf/81PfPozR/f59Cc//hu/9OEf++5TJ1928+KaWto1z7z5 +xKvf/D3v/7lf/8Tv/N7nHv/8UX0ef/yzv/up3/iFn/i++++87fk3XsvT5GlXXHvj86e7HnjPT/3i +b/7O5//wj5744lF9nnjiC//3dz/+yz/zg29b2ltuvJYZ/GlXXnvjLXb59h/6wEc+8Xt/+Mdf+tOj ++3zpj7/w2U/+6gffu3u3e8FNvbSbXuDu3n3vhz766c/90Zf+7M+P7vNnX3ri8U9/7LH3nVst7Z5z +73vsY595/Ik//fO/OLrPn//pE49/5tc+/CPn7lkr7Uc+/Guf+fwXj7i0L34epZ2/x79QlfZCf8/5 +49KOS/v/qbQvLwdcPO7+/EbuFsl1EWTJuuSCVD77Q//2I5/87BeOWE7+0ec+9dEPzQ== + + + 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 +D54h2uffCYSytOS33vrqe16z+H9Xe92f + +</i:pgf> +</svg>
\ No newline at end of file diff --git a/Swift/resources/icons/polygon.png b/Swift/resources/icons/polygon.png Binary files differnew file mode 100644 index 0000000..da2af14 --- /dev/null +++ b/Swift/resources/icons/polygon.png diff --git a/Swift/resources/icons/rect.png b/Swift/resources/icons/rect.png Binary files differnew file mode 100644 index 0000000..6bdbea2 --- /dev/null +++ b/Swift/resources/icons/rect.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/text.png b/Swift/resources/icons/text.png Binary files differnew file mode 100644 index 0000000..ddd76a0 --- /dev/null +++ b/Swift/resources/icons/text.png 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/logo/logo-chat-100.png b/Swift/resources/logo/logo-chat-100.png Binary files differindex 14b9351..7f6f17d 100644 --- a/Swift/resources/logo/logo-chat-100.png +++ b/Swift/resources/logo/logo-chat-100.png diff --git a/Swift/resources/logo/logo-chat-16.png b/Swift/resources/logo/logo-chat-16.png Binary files differindex 870b94a..3560f3b 100644 --- a/Swift/resources/logo/logo-chat-16.png +++ b/Swift/resources/logo/logo-chat-16.png diff --git a/Swift/resources/swift.desktop b/Swift/resources/swift.desktop index ba0e341..bcd9407 100644 --- a/Swift/resources/swift.desktop +++ b/Swift/resources/swift.desktop @@ -9,5 +9,5 @@ GenericName=Jabber/XMPP Messenger Comment=Communicate over the Jabber/XMPP network Icon=swift -Exec=swift +Exec=swift-im Terminal=false Categories=Network;InstantMessaging;Qt; @@ -28,4 +28,7 @@ GenericName[gl]=Cliente de mensaxería Jabber/XMPP Comment[gl]=Comunícate empregando a rede Jabber/XMPP +GenericName[he]=מסנג׳ר Jabber/XMPP +Comment[he]=תקשורת ברשת העבודה Jabber/XMPP + GenericName[hu]=Jabber/XMPP Azonnali üzenetküldő Comment[hu]=Beszélgetés Jabber/XMPP protokollon keresztül diff --git a/Swift/resources/themes/Default/Incoming/Content.html b/Swift/resources/themes/Default/Incoming/Content.html index 2946716..dc5f8b2 100755 --- a/Swift/resources/themes/Default/Incoming/Content.html +++ b/Swift/resources/themes/Default/Incoming/Content.html @@ -3,5 +3,5 @@ <tr> <td valign="top"> - <img src="%userIconPath%" class="avatar" title="%sender%" /> + <img src="%userIconPath%" class="avatar" title="%sender%" ondragstart="return false" /> <div class="myBubble"> <div class="indicator"></div> @@ -12,7 +12,7 @@ </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% - <div class="timeStamp"><span class="name">%sender% @</span> %time%</div> + <div class="timeStamp"><span class="name">%wrapped_sender% @</span> %time%</div> <span id="insert"></span> </td> diff --git a/Swift/resources/themes/Default/Incoming/Context.html b/Swift/resources/themes/Default/Incoming/Context.html index b1aca27..1f0c528 100755 --- a/Swift/resources/themes/Default/Incoming/Context.html +++ b/Swift/resources/themes/Default/Incoming/Context.html @@ -3,5 +3,5 @@ <tr> <td valign="top"> - <img src="%userIconPath%" class="avatar"/> + <img src="%userIconPath%" class="avatar" ondragstart="return false" /> <div class="myBubble"> <div class="indicator"></div> @@ -12,5 +12,5 @@ </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/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,5 +1,5 @@ <div> <div class="followUp"></div> - <div> + <div style="direction: %direction%"> %message% 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,4 +1,4 @@ <div class="followUp"></div> - <div> + <div style="direction: %direction%"> %message% diff --git a/Swift/resources/themes/Default/Incoming/buddy_icon.png b/Swift/resources/themes/Default/Incoming/buddy_icon.png Binary files differindex 1d9f5f3..0757aaa 100644 --- a/Swift/resources/themes/Default/Incoming/buddy_icon.png +++ b/Swift/resources/themes/Default/Incoming/buddy_icon.png diff --git a/Swift/resources/themes/Default/Outgoing/Content.html b/Swift/resources/themes/Default/Outgoing/Content.html index beb57f0..b0475d2 100755 --- a/Swift/resources/themes/Default/Outgoing/Content.html +++ b/Swift/resources/themes/Default/Outgoing/Content.html @@ -3,5 +3,5 @@ <tr> <td valign="top"> - <img src="%userIconPath%" class="avatar" title="%sender%" /> + <img src="%userIconPath%" class="avatar" title="%sender%" ondragstart="return false" /> <div class="myBubble"> <div class="indicator"></div> @@ -12,7 +12,7 @@ </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% - <div class="timeStamp"><span class="name">%sender% @</span> %time%</div> + <div class="timeStamp"><span class="name">%wrapped_sender% @</span> %time%</div> <span id="insert"></span> </td> diff --git a/Swift/resources/themes/Default/Outgoing/Context.html b/Swift/resources/themes/Default/Outgoing/Context.html index 7822cac..679d786 100755 --- a/Swift/resources/themes/Default/Outgoing/Context.html +++ b/Swift/resources/themes/Default/Outgoing/Context.html @@ -3,5 +3,5 @@ <tr> <td valign="top"> - <img src="%userIconPath%" class="avatar"/> + <img src="%userIconPath%" class="avatar" ondragstart="return false" /> <div class="myBubble"> <div class="indicator"></div> @@ -12,5 +12,5 @@ </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% 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,5 +1,5 @@ <div> <div class="followUp"></div> - <div> + <div style="direction: %direction%"> %message% <div class="timeStamp">%time%</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,4 +1,4 @@ <div class="followUp"></div> -<div> +<div style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> diff --git a/Swift/resources/themes/Default/Outgoing/buddy_icon.png b/Swift/resources/themes/Default/Outgoing/buddy_icon.png Binary files differindex 1d9f5f3..0757aaa 100644 --- a/Swift/resources/themes/Default/Outgoing/buddy_icon.png +++ b/Swift/resources/themes/Default/Outgoing/buddy_icon.png 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 @@ -10,5 +10,5 @@ </tr> <tr> - <td class="message"> + <td class="message" style="direction: %direction%"> %message% <div class="timeStamp">%time%</div> diff --git a/Swift/resources/themes/Default/Template.html b/Swift/resources/themes/Default/Template.html index e94701a..9d5c3a0 100755 --- a/Swift/resources/themes/Default/Template.html +++ b/Swift/resources/themes/Default/Template.html @@ -277,5 +277,5 @@ if( intervall_scroll ) clearInterval( intervall_scroll ); intervall_scroll = setInterval( function() { - var target_scroll = (document.body.scrollHeight-window.innerHeight); + var target_scroll = (document.body.scrollHeight-window.innerHeight) - 1; var scrolldiff = target_scroll - document.body.scrollTop; if ( document.body.scrollTop != target_scroll ) { diff --git a/Swift/resources/themes/Default/main.css b/Swift/resources/themes/Default/main.css index 4982ba6..afca281 100755 --- a/Swift/resources/themes/Default/main.css +++ b/Swift/resources/themes/Default/main.css @@ -13,5 +13,4 @@ overflow: auto; color: white; - font-family: Lucida Grande; text-align: center; font-size: 10px; @@ -52,5 +51,4 @@ left:60px; margin:5px 0 0 0; - font: bold 16px Myriad Pro, Myriad, Lucida Grande, Trebuchet MS, Arial; overflow:hide; } @@ -60,5 +58,4 @@ left:60px; color:#6d6d6d; - font: bold 10px Myriad Pro, Myriad, Lucida Grande, Trebuchet MS, Arial; } @@ -70,9 +67,9 @@ body { .status_container { - font: 10px Myriad, Lucida Grande, Arial; } body { + font-family: "DejaVu Sans", "Myriad Pro", Myriad, "Lucida Grande", "Trebuchet MS", Arial, "Droid Sans", sans-serif; } @@ -130,5 +127,4 @@ body { .tableBubble .timeStamp { - margin:2px; margin-left:7px; text-align:right; @@ -292,2 +288,6 @@ body { color:#9ecf35; } + +html { + height: 101%; +} |