diff options
Diffstat (limited to 'Swift/QtUI')
362 files changed, 24587 insertions, 16149 deletions
diff --git a/Swift/QtUI/ApplicationTest/main.cpp b/Swift/QtUI/ApplicationTest/main.cpp index a5f3820..782bbf1 100644 --- a/Swift/QtUI/ApplicationTest/main.cpp +++ b/Swift/QtUI/ApplicationTest/main.cpp @@ -1,45 +1,48 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ +#include <string> + #include <QApplication> -#include <QWidget> -#include <QVBoxLayout> #include <QLineEdit> -#include "../QtSwiftUtil.h" -#include <string> -#include "SwifTools/Application/Platform/PlatformApplication.h" +#include <QVBoxLayout> +#include <QWidget> + +#include <SwifTools/Application/Platform/PlatformApplication.h> + +#include <Swift/QtUI/QtSwiftUtil.h> using namespace Swift; class MyWidget : public QWidget { - Q_OBJECT - - public: - MyWidget() : application_("MyApplication") { - QVBoxLayout *layout = new QVBoxLayout(this); - input_ = new QLineEdit(this); - layout->addWidget(input_); - connect(input_, SIGNAL(returnPressed()), SLOT(handleReturnPressed())); - } - - private slots: - void handleReturnPressed() { - application_.getApplicationMessageDisplay()->setMessage(Q2PSTRING(input_->text())); - } - - private: - PlatformApplication application_; - QLineEdit* input_; + Q_OBJECT + + public: + MyWidget() : application_("MyApplication") { + QVBoxLayout *layout = new QVBoxLayout(this); + input_ = new QLineEdit(this); + layout->addWidget(input_); + connect(input_, SIGNAL(returnPressed()), SLOT(handleReturnPressed())); + } + + private slots: + void handleReturnPressed() { + application_.getApplicationMessageDisplay()->setMessage(Q2PSTRING(input_->text())); + } + + private: + PlatformApplication application_; + QLineEdit* input_; }; int main(int argc, char* argv[]) { - QApplication app(argc, argv); - MyWidget widget; - widget.show(); - return app.exec(); + QApplication app(argc, argv); + MyWidget widget; + widget.show(); + return app.exec(); } #include "main.moc" diff --git a/Swift/QtUI/CAPICertificateSelector.cpp b/Swift/QtUI/CAPICertificateSelector.cpp index 953051b..7e4bc0b 100644 --- a/Swift/QtUI/CAPICertificateSelector.cpp +++ b/Swift/QtUI/CAPICertificateSelector.cpp @@ -18,99 +18,100 @@ #include <boost/algorithm/string.hpp> #include <Swift/Controllers/Intl.h> #include <Swift/QtUI/QtSwiftUtil.h> + #include <Swiften/Base/Log.h> +#include <Swiften/TLS/Schannel/SchannelUtil.h> namespace Swift { /////Hmm, maybe we should not exlude the "location" column -#define exclude_columns CRYPTUI_SELECT_LOCATION_COLUMN | CRYPTUI_SELECT_INTENDEDUSE_COLUMN +#define exclude_columns CRYPTUI_SELECT_LOCATION_COLUMN | CRYPTUI_SELECT_INTENDEDUSE_COLUMN #define SHA1_HASH_LENGTH 20 static std::string getCertUri(PCCERT_CONTEXT cert, const char * cert_store_name) { - DWORD cbHash = SHA1_HASH_LENGTH; - BYTE aHash[SHA1_HASH_LENGTH]; - std::string result("certstore:"); + DWORD cbHash = SHA1_HASH_LENGTH; + BYTE aHash[SHA1_HASH_LENGTH]; + std::string result("certstore:"); - result += cert_store_name; - result += ":sha1:"; + result += cert_store_name; + result += ":sha1:"; - if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, aHash, &cbHash) == FALSE ) { - return ""; - } + if (CertGetCertificateContextProperty(cert, CERT_HASH_PROP_ID, aHash, &cbHash) == FALSE ) { + return ""; + } - ByteArray byteArray = createByteArray((char *)(&aHash[0]), cbHash); - result += Hexify::hexify(byteArray); + ByteArray byteArray = createByteArray((char *)(&aHash[0]), cbHash); + result += Hexify::hexify(byteArray); - return result; + return result; } std::string selectCAPICertificate() { - const char* certStoreName = "MY"; - - DWORD storeFlags = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER; - - HCERTSTORE hstore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, 0, storeFlags, certStoreName); - if (!hstore) { - return ""; - } - - HWND hwnd = GetForegroundWindow(); - if (!hwnd) { - hwnd = GetActiveWindow(); - } - - std::string certificateDialogTitle = QT_TRANSLATE_NOOP("", "TLS Client Certificate Selection"); - std::string certificateDialogPrompt = QT_TRANSLATE_NOOP("", "Select a certificate to use for authentication"); - - int titleLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogTitle.c_str(), -1, NULL, 0); - int promptLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogPrompt.c_str(), -1, NULL, 0); - - wchar_t* titleChars = new wchar_t[titleLength]; - wchar_t* promptChars = new wchar_t[promptLength]; - - //titleChars[titleLength] = '\0'; - //promptChars[promptLength] = '\0'; - - titleLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogTitle.c_str(), -1, titleChars, titleLength); - promptLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogPrompt.c_str(), -1, promptChars, promptLength); - - if (titleLength == 0 || promptLength == 0) { - int error = GetLastError(); - switch (error) { - case ERROR_INSUFFICIENT_BUFFER: SWIFT_LOG(error) << "Insufficient buffer for rendering cert dialog" << std::endl;break; - case ERROR_INVALID_FLAGS: SWIFT_LOG(error) << "Invalid flags for rendering cert dialog" << std::endl;break; - case ERROR_INVALID_PARAMETER: SWIFT_LOG(error) << "Invalid parameter for rendering cert dialog" << std::endl;break; - case ERROR_NO_UNICODE_TRANSLATION: SWIFT_LOG(error) << "Invalid unicode for rendering cert dialog" << std::endl;break; - default: SWIFT_LOG(error) << "Unexpected multibyte conversion errorcode" << std::endl; - - } - } - - - - /* Call Windows dialog to select a suitable certificate */ - PCCERT_CONTEXT cert = CryptUIDlgSelectCertificateFromStore(hstore, hwnd, titleChars, promptChars, exclude_columns, 0, NULL); - - delete[] titleChars; - delete[] promptChars; - - if (hstore) { - CertCloseStore(hstore, 0); - } - - std::string result; - - if (cert) { - result = getCertUri(cert, certStoreName); - CertFreeCertificateContext(cert); - } - - return result; + const char* certStoreName = "MY"; + + DWORD storeFlags = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER; + + HCERTSTORE hstore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, 0, storeFlags, certStoreName); + if (!hstore) { + return ""; + } + + HWND hwnd = GetForegroundWindow(); + if (!hwnd) { + hwnd = GetActiveWindow(); + } + + std::string certificateDialogTitle = QT_TRANSLATE_NOOP("", "TLS Client Certificate Selection"); + std::string certificateDialogPrompt = QT_TRANSLATE_NOOP("", "Select a certificate to use for authentication"); + + int titleLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogTitle.c_str(), -1, NULL, 0); + int promptLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogPrompt.c_str(), -1, NULL, 0); + + wchar_t* titleChars = new wchar_t[titleLength]; + wchar_t* promptChars = new wchar_t[promptLength]; + + //titleChars[titleLength] = '\0'; + //promptChars[promptLength] = '\0'; + + titleLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogTitle.c_str(), -1, titleChars, titleLength); + promptLength = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, certificateDialogPrompt.c_str(), -1, promptChars, promptLength); + + if (titleLength == 0 || promptLength == 0) { + int error = GetLastError(); + switch (error) { + case ERROR_INSUFFICIENT_BUFFER: SWIFT_LOG(error) << "Insufficient buffer for rendering cert dialog"; break; + case ERROR_INVALID_FLAGS: SWIFT_LOG(error) << "Invalid flags for rendering cert dialog"; break; + case ERROR_INVALID_PARAMETER: SWIFT_LOG(error) << "Invalid parameter for rendering cert dialog"; break; + case ERROR_NO_UNICODE_TRANSLATION: SWIFT_LOG(error) << "Invalid unicode for rendering cert dialog"; break; + default: SWIFT_LOG(error) << "Unexpected multibyte conversion errorcode"; + + } + } + + std::string result; + /* Call Windows dialog to select a suitable certificate */ + { + ScopedCertContext cert(CryptUIDlgSelectCertificateFromStore(hstore, hwnd, titleChars, promptChars, exclude_columns, 0, NULL)); + if (cert) { + result = getCertUri(cert, certStoreName); + } + } + + delete[] titleChars; + delete[] promptChars; + + if (hstore) { + if (CertCloseStore(hstore, 0) == FALSE) { + SWIFT_LOG(debug) << "Failed to close the certificate store handle."; + } + } + + return result; } bool isCAPIURI(std::string uri) { - return (boost::iequals(uri.substr(0, 10), "certstore:")); + return (boost::iequals(uri.substr(0, 10), "certstore:")); } } diff --git a/Swift/QtUI/CAPICertificateSelector.h b/Swift/QtUI/CAPICertificateSelector.h index 714f1c5..8273c78 100644 --- a/Swift/QtUI/CAPICertificateSelector.h +++ b/Swift/QtUI/CAPICertificateSelector.h @@ -9,6 +9,6 @@ #include <string> namespace Swift { - std::string selectCAPICertificate(); - bool isCAPIURI(std::string uri); + std::string selectCAPICertificate(); + bool isCAPIURI(std::string uri); } diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp index 5b03ac5..f818e50 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.cpp +++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp @@ -1,141 +1,148 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <QPen> +#include <Swift/QtUI/ChatList/ChatListDelegate.h> + +#include <QColor> #include <QPainter> +#include <QPen> -#include "Swift/QtUI/ChatList/ChatListDelegate.h" -#include "Swift/QtUI/Roster/GroupItemDelegate.h" -#include "Swift/QtUI/ChatList/ChatListItem.h" -#include "Swift/QtUI/ChatList/ChatListMUCItem.h" -#include "Swift/QtUI/ChatList/ChatListRecentItem.h" -#include "Swift/QtUI/ChatList/ChatListWhiteboardItem.h" -#include "Swift/QtUI/ChatList/ChatListGroupItem.h" +#include <Swift/QtUI/ChatList/ChatListGroupItem.h> +#include <Swift/QtUI/ChatList/ChatListItem.h> +#include <Swift/QtUI/ChatList/ChatListMUCItem.h> +#include <Swift/QtUI/ChatList/ChatListRecentItem.h> +#include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h> +#include <Swift/QtUI/Roster/GroupItemDelegate.h> namespace Swift { +namespace { + const QColor secondLineColor = QColor(160,160,160); +} + ChatListDelegate::ChatListDelegate(bool compact) : compact_(compact) { - groupDelegate_ = new GroupItemDelegate(); + groupDelegate_ = new GroupItemDelegate(); } ChatListDelegate::~ChatListDelegate() { - delete groupDelegate_; + delete groupDelegate_; } void ChatListDelegate::setCompact(bool compact) { - compact_ = compact; + compact_ = compact; + emit sizeHintChanged(QModelIndex()); } QSize ChatListDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { - ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer()); - if (item && dynamic_cast<ChatListMUCItem*>(item)) { - return mucSizeHint(option, index); - } - else if (item && dynamic_cast<ChatListRecentItem*>(item)) { - return common_.contactSizeHint(option, index, compact_); - } - else if (item && dynamic_cast<ChatListGroupItem*>(item)) { - return groupDelegate_->sizeHint(option, index); - } - else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) { - return common_.contactSizeHint(option, index, compact_); - } - return QStyledItemDelegate::sizeHint(option, index); + ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer()); + if (item && dynamic_cast<ChatListMUCItem*>(item)) { + return mucSizeHint(option, index); + } + else if (item && dynamic_cast<ChatListRecentItem*>(item)) { + return common_.contactSizeHint(option, index, compact_); + } + else if (item && dynamic_cast<ChatListGroupItem*>(item)) { + return groupDelegate_->sizeHint(option, index); + } + else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) { + return common_.contactSizeHint(option, index, compact_); + } + return QStyledItemDelegate::sizeHint(option, index); } QSize ChatListDelegate::mucSizeHint(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); + QFontMetrics nameMetrics(common_.nameFont); + QFontMetrics statusMetrics(common_.detailFont); + int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); + return QSize(150, sizeByText); } QSize ChatListDelegate::recentSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - return mucSizeHint(option, index); + return mucSizeHint(option, index); } void ChatListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer()); - if (item && dynamic_cast<ChatListMUCItem*>(item)) { - paintMUC(painter, option, dynamic_cast<ChatListMUCItem*>(item)); - } - else if (item && dynamic_cast<ChatListRecentItem*>(item)) { - paintRecent(painter, option, dynamic_cast<ChatListRecentItem*>(item)); - } - else if (item && dynamic_cast<ChatListGroupItem*>(item)) { - ChatListGroupItem* group = dynamic_cast<ChatListGroupItem*>(item); - groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); - } - else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) { - paintWhiteboard(painter, option, dynamic_cast<ChatListWhiteboardItem*>(item)); - } - else { - QStyledItemDelegate::paint(painter, option, index); - } + ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer()); + if (item && dynamic_cast<ChatListMUCItem*>(item)) { + paintMUC(painter, option, dynamic_cast<ChatListMUCItem*>(item)); + } + else if (item && dynamic_cast<ChatListRecentItem*>(item)) { + paintRecent(painter, option, dynamic_cast<ChatListRecentItem*>(item)); + } + else if (item && dynamic_cast<ChatListGroupItem*>(item)) { + ChatListGroupItem* group = dynamic_cast<ChatListGroupItem*>(item); + groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); + } + else if (item && dynamic_cast<ChatListWhiteboardItem*>(item)) { + paintWhiteboard(painter, option, dynamic_cast<ChatListWhiteboardItem*>(item)); + } + else { + QStyledItemDelegate::paint(painter, option, index); + } } void ChatListDelegate::paintMUC(QPainter* painter, const QStyleOptionViewItem& option, ChatListMUCItem* item) const { - painter->save(); - QRect fullRegion(option.rect); - if ( option.state & QStyle::State_Selected ) { - painter->fillRect(fullRegion, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } else { - QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); - painter->setPen(QPen(nameColor)); - } - - QFontMetrics nameMetrics(common_.nameFont); - painter->setFont(common_.nameFont); - int extraFontWidth = nameMetrics.width("H"); - int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; - QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); - - int nameHeight = nameMetrics.height() + common_.verticalMargin; - QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); - - DelegateCommons::drawElidedText(painter, nameRegion, item->data(Qt::DisplayRole).toString()); - - painter->setFont(common_.detailFont); - painter->setPen(QPen(QColor(160,160,160))); - - QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); - DelegateCommons::drawElidedText(painter, detailRegion, item->data(ChatListMUCItem::DetailTextRole).toString()); - - painter->restore(); + painter->save(); + QRect fullRegion(option.rect); + if ( option.state & QStyle::State_Selected ) { + painter->fillRect(fullRegion, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } else { + QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); + painter->setPen(QPen(nameColor)); + } + + QFontMetrics nameMetrics(common_.nameFont); + painter->setFont(common_.nameFont); + int extraFontWidth = nameMetrics.width("H"); + int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; + QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); + + int nameHeight = nameMetrics.height() + common_.verticalMargin; + QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); + + DelegateCommons::drawElidedText(painter, nameRegion, item->data(Qt::DisplayRole).toString()); + + painter->setFont(common_.detailFont); + painter->setPen(QPen(secondLineColor)); + + QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); + DelegateCommons::drawElidedText(painter, detailRegion, item->data(ChatListMUCItem::DetailTextRole).toString()); + + painter->restore(); } void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const { - QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); - QString avatarPath; - if (item->data(ChatListRecentItem::AvatarRole).isValid() && !item->data(ChatListRecentItem::AvatarRole).value<QString>().isNull()) { - avatarPath = item->data(ChatListRecentItem::AvatarRole).value<QString>(); - } - QIcon presenceIcon = item->data(ChatListRecentItem::PresenceIconRole).isValid() && !item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull() - ? item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>() - : QIcon(":/icons/offline.png"); - QString name = item->data(Qt::DisplayRole).toString(); - //qDebug() << "Avatar for " << name << " = " << avatarPath; - QString statusText = item->data(ChatListRecentItem::DetailTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, item->getChat().unreadCount, compact_); + QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); + QString avatarPath; + if (item->data(ChatListRecentItem::AvatarRole).isValid() && !item->data(ChatListRecentItem::AvatarRole).value<QString>().isNull()) { + avatarPath = item->data(ChatListRecentItem::AvatarRole).value<QString>(); + } + QIcon presenceIcon = item->data(ChatListRecentItem::PresenceIconRole).isValid() && !item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull() + ? item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>() + : QIcon(":/icons/offline.png"); + QString name = item->data(Qt::DisplayRole).toString(); + //qDebug() << "Avatar for " << name << " = " << avatarPath; + QString statusText = item->data(ChatListRecentItem::DetailTextRole).toString(); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, 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_); + 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 9460c28..44ca947 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.h +++ b/Swift/QtUI/ChatList/ChatListDelegate.h @@ -1,38 +1,38 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QStyledItemDelegate> -#include "Swift/QtUI/Roster/GroupItemDelegate.h" +#include <Swift/QtUI/Roster/GroupItemDelegate.h> namespace Swift { - class ChatListMUCItem; - class ChatListRecentItem; - class ChatListWhiteboardItem; - class ChatListDelegate : public QStyledItemDelegate { - public: - ChatListDelegate(bool compact); - ~ChatListDelegate(); - 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: - 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; + class ChatListMUCItem; + class ChatListRecentItem; + class ChatListWhiteboardItem; + class ChatListDelegate : public QStyledItemDelegate { + public: + ChatListDelegate(bool compact); + ~ChatListDelegate(); + 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: + 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; - bool compact_; - DelegateCommons common_; - GroupItemDelegate* groupDelegate_; - }; + bool compact_; + DelegateCommons common_; + GroupItemDelegate* groupDelegate_; + }; } diff --git a/Swift/QtUI/ChatList/ChatListGroupItem.h b/Swift/QtUI/ChatList/ChatListGroupItem.h index 17defea..a9bb9b1 100644 --- a/Swift/QtUI/ChatList/ChatListGroupItem.h +++ b/Swift/QtUI/ChatList/ChatListGroupItem.h @@ -1,35 +1,42 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QList> -#include "Swift/QtUI/ChatList/ChatListItem.h" +#include <Swift/QtUI/ChatList/ChatListItem.h> 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();} - private: - static bool pointerItemLessThan(const ChatListItem* first, const ChatListItem* second) { - QString myName = first->data(Qt::DisplayRole).toString().toLower(); - QString theirName = second->data(Qt::DisplayRole).toString().toLower(); - return myName < theirName; - } + class ChatListGroupItem : public ChatListItem { + public: + 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() { + for (auto item : items_) { + delete item; + } + items_.clear(); + } - QString name_; - QList<ChatListItem*> items_; - bool sorted_; - }; + private: + static bool pointerItemLessThan(const ChatListItem* first, const ChatListItem* second) { + QString myName = first->data(Qt::DisplayRole).toString().toLower(); + QString theirName = second->data(Qt::DisplayRole).toString().toLower(); + return myName < theirName; + } + + QString name_; + QList<ChatListItem*> items_; + bool sorted_; + }; } diff --git a/Swift/QtUI/ChatList/ChatListItem.h b/Swift/QtUI/ChatList/ChatListItem.h index 28c0f9c..c6fd762 100644 --- a/Swift/QtUI/ChatList/ChatListItem.h +++ b/Swift/QtUI/ChatList/ChatListItem.h @@ -1,25 +1,26 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QVariant> + #include <qdebug.h> namespace Swift { - class ChatListGroupItem; - class ChatListItem { - public: - ChatListItem(ChatListGroupItem* parent) {parent_ = parent;} - virtual ~ChatListItem() {} + class ChatListGroupItem; + class ChatListItem { + public: + ChatListItem(ChatListGroupItem* parent) {parent_ = parent;} + virtual ~ChatListItem() {} - ChatListGroupItem* parent() {return parent_;} - virtual QVariant data(int role) const = 0; + ChatListGroupItem* parent() {return parent_;} + virtual QVariant data(int role) const = 0; - private: - ChatListGroupItem* parent_; - }; + private: + ChatListGroupItem* parent_; + }; } diff --git a/Swift/QtUI/ChatList/ChatListMUCItem.cpp b/Swift/QtUI/ChatList/ChatListMUCItem.cpp index 68f9581..e701ddc 100644 --- a/Swift/QtUI/ChatList/ChatListMUCItem.cpp +++ b/Swift/QtUI/ChatList/ChatListMUCItem.cpp @@ -1,12 +1,14 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/ChatList/ChatListMUCItem.h" +#include <Swift/QtUI/ChatList/ChatListMUCItem.h> -#include "Swift/QtUI/QtSwiftUtil.h" +#include <QColor> + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { ChatListMUCItem::ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* parent) : ChatListItem(parent), bookmark_(bookmark) { @@ -14,21 +16,21 @@ ChatListMUCItem::ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* } const MUCBookmark& ChatListMUCItem::getBookmark() const { - return bookmark_; + return bookmark_; } QVariant ChatListMUCItem::data(int role) const { - switch (role) { - case Qt::DisplayRole: return P2QSTRING(bookmark_.getName()); - case DetailTextRole: return P2QSTRING(bookmark_.getRoom().toString()); - /*case Qt::TextColorRole: return textColor_; - case Qt::BackgroundColorRole: return backgroundColor_; - case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant(); - case StatusTextRole: return statusText_; - case AvatarRole: return avatar_; - case PresenceIconRole: return getPresenceIcon();*/ - default: return QVariant(); - } + switch (role) { + case Qt::DisplayRole: return P2QSTRING(bookmark_.getName()); + case DetailTextRole: return P2QSTRING(bookmark_.getRoom().toString()); + case Qt::TextColorRole: return QColor(89,89,89); + /*case Qt::BackgroundColorRole: return backgroundColor_; + case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant(); + case StatusTextRole: return statusText_; + case AvatarRole: return avatar_; + case PresenceIconRole: return getPresenceIcon();*/ + default: return QVariant(); + } } } diff --git a/Swift/QtUI/ChatList/ChatListMUCItem.h b/Swift/QtUI/ChatList/ChatListMUCItem.h index 046d1d4..c77c284 100644 --- a/Swift/QtUI/ChatList/ChatListMUCItem.h +++ b/Swift/QtUI/ChatList/ChatListMUCItem.h @@ -1,33 +1,33 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QList> +#include <memory> -#include <boost/shared_ptr.hpp> +#include <QList> -#include "Swiften/MUC/MUCBookmark.h" +#include <Swiften/MUC/MUCBookmark.h> -#include "Swift/QtUI/ChatList/ChatListItem.h" +#include <Swift/QtUI/ChatList/ChatListItem.h> namespace Swift { - class ChatListMUCItem : public ChatListItem { - public: - enum MUCItemRoles { - DetailTextRole = Qt::UserRole/*, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2, - StatusShowTypeRole = Qt::UserRole + 3*/ - }; - ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* parent); - const MUCBookmark& getBookmark() const; - QVariant data(int role) const; - private: - MUCBookmark bookmark_; - QList<ChatListItem*> items_; - }; + class ChatListMUCItem : public ChatListItem { + public: + enum MUCItemRoles { + DetailTextRole = Qt::UserRole/*, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2, + StatusShowTypeRole = Qt::UserRole + 3*/ + }; + ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* parent); + const MUCBookmark& getBookmark() const; + QVariant data(int role) const; + private: + MUCBookmark bookmark_; + QList<ChatListItem*> items_; + }; } diff --git a/Swift/QtUI/ChatList/ChatListModel.cpp b/Swift/QtUI/ChatList/ChatListModel.cpp index 7913c61..416b786 100644 --- a/Swift/QtUI/ChatList/ChatListModel.cpp +++ b/Swift/QtUI/ChatList/ChatListModel.cpp @@ -1,125 +1,187 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #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() : whiteboards_(NULL) { - root_ = new ChatListGroupItem("", NULL, false); - mucBookmarks_ = new ChatListGroupItem(tr("Bookmarked Rooms"), root_); - recents_ = new ChatListGroupItem(tr("Recent Chats"), root_, false); +ChatListModel::ChatListModel() : whiteboards_(nullptr) { + root_ = new ChatListGroupItem("", nullptr, 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_); + whiteboards_ = new ChatListGroupItem(tr("Opened Whiteboards"), root_, false); + root_->addItem(whiteboards_); #endif - root_->addItem(recents_); - root_->addItem(mucBookmarks_); + 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(); - mucBookmarks_->clear(); - emit layoutChanged(); + beginRemoveRows(mucBookmarksIndex_, 0, mucBookmarks_->rowCount()); + mucBookmarks_->clear(); + endRemoveRows(); } void ChatListModel::addMUCBookmark(const Swift::MUCBookmark& bookmark) { - emit layoutAboutToBeChanged(); - 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)); + beginInsertRows(mucBookmarksIndex_, 0, mucBookmarks_->rowCount()); + mucBookmarks_->addItem(new ChatListMUCItem(bookmark, mucBookmarks_)); + endInsertRows(); } void ChatListModel::removeMUCBookmark(const Swift::MUCBookmark& bookmark) { - for (int i = 0; i < mucBookmarks_->rowCount(); i++) { - ChatListMUCItem* item = dynamic_cast<ChatListMUCItem*>(mucBookmarks_->item(i)); - if (item->getBookmark() == bookmark) { - emit layoutAboutToBeChanged(); - mucBookmarks_->remove(i); - emit layoutChanged(); - break; - } - } + for (int i = 0; i < mucBookmarks_->rowCount(); i++) { + ChatListMUCItem* item = dynamic_cast<ChatListMUCItem*>(mucBookmarks_->item(i)); + if (item->getBookmark() == bookmark) { + beginRemoveRows(mucBookmarksIndex_, i, i+1); + mucBookmarks_->remove(i); + endRemoveRows(); + break; + } + } } void ChatListModel::addWhiteboardSession(const ChatListWindow::Chat& chat) { - emit layoutAboutToBeChanged(); - whiteboards_->addItem(new ChatListWhiteboardItem(chat, whiteboards_)); - emit layoutChanged(); + 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) { - emit layoutAboutToBeChanged(); - whiteboards_->remove(i); - emit layoutChanged(); - break; - } - } + 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; + } + } } void ChatListModel::setRecents(const std::list<ChatListWindow::Chat>& recents) { - emit layoutAboutToBeChanged(); - recents_->clear(); - foreach (const ChatListWindow::Chat chat, recents) { - recents_->addItem(new ChatListRecentItem(chat, recents_)); + beginRemoveRows(recentsIndex_, 0, recents_->rowCount()); + recents_->clear(); + endRemoveRows(); + beginInsertRows(recentsIndex_, 0, recents.size()); + for (const auto& chat : recents) { + recents_->addItem(new ChatListRecentItem(chat, recents_)); //whiteboards_->addItem(new ChatListRecentItem(chat, whiteboards_)); - } - emit layoutChanged(); + } + endInsertRows(); +} + +QMimeData* ChatListModel::mimeData(const QModelIndexList& indexes) const { + QMimeData* data = QAbstractItemModel::mimeData(indexes); + ChatListRecentItem *item = dynamic_cast<ChatListRecentItem*>(getItemForIndex(indexes.first())); + if (item == nullptr) { + 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 { + for (const auto& jid : chat.impromptuJIDs) { + dataStream << P2QSTRING(jid.second.toString()); + } + } + + data->setData(mimeType, itemData); + return data; +} + +const ChatListMUCItem* ChatListModel::getChatListMUCItem(const JID& roomJID) const { + const ChatListMUCItem* mucItem = nullptr; + for (int i = 0; i < mucBookmarks_->rowCount(); i++) { + ChatListMUCItem* item = dynamic_cast<ChatListMUCItem*>(mucBookmarks_->item(i)); + if (item->getBookmark().getRoom() == roomJID) { + mucItem = item; + break; + } + } + return mucItem; } int ChatListModel::columnCount(const QModelIndex& /*parent*/) const { - return 1; + return 1; } ChatListItem* ChatListModel::getItemForIndex(const QModelIndex& index) const { - return index.isValid() ? static_cast<ChatListItem*>(index.internalPointer()) : NULL; + return index.isValid() ? static_cast<ChatListItem*>(index.internalPointer()) : nullptr; } QVariant ChatListModel::data(const QModelIndex& index, int role) const { - ChatListItem* item = getItemForIndex(index); - return item ? item->data(role) : QVariant(); + ChatListItem* item = getItemForIndex(index); + return item ? item->data(role) : QVariant(); } QModelIndex ChatListModel::index(int row, int column, const QModelIndex & parent) const { - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } - ChatListGroupItem *parentItem = parent.isValid() ? static_cast<ChatListGroupItem*>(parent.internalPointer()) : root_; + ChatListGroupItem *parentItem = parent.isValid() ? static_cast<ChatListGroupItem*>(parent.internalPointer()) : root_; - return row < parentItem->rowCount() ? createIndex(row, column, parentItem->item(row)) : QModelIndex(); + return row < parentItem->rowCount() ? createIndex(row, column, parentItem->item(row)) : QModelIndex(); } QModelIndex ChatListModel::parent(const QModelIndex& index) const { - if (!index.isValid()) { - return QModelIndex(); - } - ChatListGroupItem* parent = static_cast<ChatListGroupItem*>(index.internalPointer())->parent(); - return (parent == root_) ? QModelIndex() : createIndex(parent->parent()->row(parent), 0, parent); + if (!index.isValid()) { + return QModelIndex(); + } + ChatListGroupItem* parent = static_cast<ChatListGroupItem*>(index.internalPointer())->parent(); + return (parent == root_) ? QModelIndex() : createIndex(parent->parent()->row(parent), 0, parent); } int ChatListModel::rowCount(const QModelIndex& parentIndex) const { - ChatListGroupItem* parent = NULL; - if (parentIndex.isValid()) { - parent = dynamic_cast<ChatListGroupItem*>(static_cast<ChatListItem*>(parentIndex.internalPointer())); - } else { - parent = root_; - } - int count = (parent ? parent->rowCount() : 0); - return count; + ChatListGroupItem* parent = nullptr; + if (parentIndex.isValid()) { + parent = dynamic_cast<ChatListGroupItem*>(static_cast<ChatListItem*>(parentIndex.internalPointer())); + } else { + parent = root_; + } + int count = (parent ? parent->rowCount() : 0); + return count; } } diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h index 04e369a..363b2e6 100644 --- a/Swift/QtUI/ChatList/ChatListModel.h +++ b/Swift/QtUI/ChatList/ChatListModel.h @@ -1,41 +1,53 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QAbstractItemModel> -#include <QList> +#include <QPersistentModelIndex> #include <Swiften/MUC/MUCBookmark.h> + #include <Swift/Controllers/UIInterfaces/ChatListWindow.h> #include <Swift/QtUI/ChatList/ChatListGroupItem.h> namespace Swift { - class ChatListModel : public QAbstractItemModel { - Q_OBJECT - public: - ChatListModel(); - 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; - QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex& index) const; - int rowCount(const QModelIndex& parent = QModelIndex()) const; - ChatListItem* getItemForIndex(const QModelIndex& index) const; - void clearBookmarks(); - void setRecents(const std::list<ChatListWindow::Chat>& recents); - private: - ChatListGroupItem* mucBookmarks_; - ChatListGroupItem* recents_; - ChatListGroupItem* whiteboards_; - ChatListGroupItem* root_; - }; + class ChatListMUCItem; + + class ChatListModel : public QAbstractItemModel { + Q_OBJECT + 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; + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + ChatListItem* getItemForIndex(const QModelIndex& index) const; + void clearBookmarks(); + void setRecents(const std::list<ChatListWindow::Chat>& recents); + QMimeData* mimeData(const QModelIndexList& indexes) const; + + const ChatListMUCItem* getChatListMUCItem(const JID& roomJID) 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 e9ecec8..9e55e1b 100644 --- a/Swift/QtUI/ChatList/ChatListRecentItem.cpp +++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp @@ -1,48 +1,41 @@ /* - * Copyright (c) 2011-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/ChatList/ChatListRecentItem.h> -#include <Swift/QtUI/QtSwiftUtil.h> #include <Swiften/Base/Path.h> +#include <Swift/QtUI/QtResourceHelper.h> +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { ChatListRecentItem::ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) { } const ChatListWindow::Chat& ChatListRecentItem::getChat() const { - return chat_; + return chat_; } QVariant ChatListRecentItem::data(int role) const { - switch (role) { - case Qt::DisplayRole: return chat_.impromptuJIDs.empty() ? P2QSTRING(chat_.chatName) : P2QSTRING(chat_.getImpromptuTitle()); - case DetailTextRole: return P2QSTRING(chat_.activity); - /*case Qt::TextColorRole: return textColor_; - case Qt::BackgroundColorRole: return backgroundColor_; - case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant(); - case StatusTextRole: return statusText_;*/ - case AvatarRole: return QVariant(P2QSTRING(pathToString(chat_.avatarPath))); - case PresenceIconRole: return getPresenceIcon(); - default: return QVariant(); - } + switch (role) { + case Qt::DisplayRole: return P2QSTRING(chat_.getTitle()); + case DetailTextRole: return P2QSTRING(chat_.activity); + case Qt::TextColorRole: return QColor(89,89,89); + /*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 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 3f27a68..3c9635b 100644 --- a/Swift/QtUI/ChatList/ChatListRecentItem.h +++ b/Swift/QtUI/ChatList/ChatListRecentItem.h @@ -1,36 +1,37 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QList> -#include <QIcon> +#include <memory> -#include <boost/shared_ptr.hpp> +#include <QIcon> +#include <QList> #include <Swiften/MUC/MUCBookmark.h> + #include <Swift/Controllers/UIInterfaces/ChatListWindow.h> #include <Swift/QtUI/ChatList/ChatListItem.h> namespace Swift { - class ChatListRecentItem : public ChatListItem { - public: - enum RecentItemRoles { - DetailTextRole = Qt::UserRole, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2/*, - StatusShowTypeRole = Qt::UserRole + 3, - IdleRole = Qt::UserRole + 4*/ - }; - ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent); - const ChatListWindow::Chat& getChat() const; - QVariant data(int role) const; - private: - QIcon getPresenceIcon() const; - ChatListWindow::Chat chat_; - }; + class ChatListRecentItem : public ChatListItem { + public: + enum RecentItemRoles { + DetailTextRole = Qt::UserRole, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2/*, + StatusShowTypeRole = Qt::UserRole + 3, + IdleRole = Qt::UserRole + 4*/ + }; + ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent); + const ChatListWindow::Chat& getChat() const; + QVariant data(int role) const; + private: + QIcon getPresenceIcon() const; + ChatListWindow::Chat chat_; + }; } diff --git a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp index 6791aa5..8a4447e 100644 --- a/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp +++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.cpp @@ -5,50 +5,46 @@ */ /* - * Copyright (c) 2013 Remko Tronçon - * Licensed under the GNU General Public License. + * Copyright (c) 2013-2016 Isode Limited. + * All rights reserved. * See the COPYING file for more information. */ #include <Swift/QtUI/ChatList/ChatListWhiteboardItem.h> -#include <Swift/QtUI/QtSwiftUtil.h> +#include <QColor> + #include <Swiften/Base/Path.h> +#include <Swift/QtUI/QtResourceHelper.h> +#include <Swift/QtUI/QtSwiftUtil.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 { - 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"); - } + 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 QColor(89,89,89); + /*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 index 2dc6255..6dbc5f6 100644 --- a/Swift/QtUI/ChatList/ChatListWhiteboardItem.h +++ b/Swift/QtUI/ChatList/ChatListWhiteboardItem.h @@ -1,35 +1,36 @@ /* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QList> -#include <QIcon> +#include <memory> -#include <boost/shared_ptr.hpp> +#include <QIcon> +#include <QList> #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_; - }; + 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 4d1f19b..2fd05c4 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.cpp +++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp @@ -1,15 +1,25 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/ChatList/QtChatListWindow.h" +#include <Swift/QtUI/ChatList/QtChatListWindow.h> #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> @@ -17,166 +27,180 @@ #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/UIEvents/ShowWhiteboardUIEvent.h> -#include <Swift/Controllers/Settings/SettingsProvider.h> - namespace Swift { -QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) { - eventStream_ = uiEventStream; - settings_ = settings; - bookmarksEnabled_ = false; - model_ = new ChatListModel(); - setModel(model_); - delegate_ = new ChatListDelegate(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); - setItemDelegate(delegate_); - setHeaderHidden(true); +QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent), isOnline_(false) { + eventStream_ = uiEventStream; + settings_ = settings; + bookmarksEnabled_ = false; + model_ = new ChatListModel(); + setModel(model_); + delegate_ = new ChatListDelegate(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + setItemDelegate(delegate_); + setHeaderHidden(true); #ifdef SWIFT_PLATFORM_MACOSX - setAlternatingRowColors(true); + setAlternatingRowColors(true); #endif - expandAll(); - setAnimated(true); - setIndentation(0); - setRootIsDecorated(true); - setupContextMenus(); - connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); - connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleClicked(const QModelIndex&))); + expandAll(); + setAnimated(true); + setIndentation(0); + setDragEnabled(true); + setRootIsDecorated(true); + setupContextMenus(); + connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleClicked(const QModelIndex&))); - settings_->onSettingChanged.connect(boost::bind(&QtChatListWindow::handleSettingChanged, this, _1)); + settings_->onSettingChanged.connect(boost::bind(&QtChatListWindow::handleSettingChanged, this, _1)); } QtChatListWindow::~QtChatListWindow() { - settings_->onSettingChanged.disconnect(boost::bind(&QtChatListWindow::handleSettingChanged, this, _1)); - delete model_; - delete delegate_; - delete mucMenu_; - delete emptyMenu_; + settings_->onSettingChanged.disconnect(boost::bind(&QtChatListWindow::handleSettingChanged, this, _1)); + delete model_; + delete delegate_; + delete mucMenu_; + delete emptyMenu_; } void QtChatListWindow::handleSettingChanged(const std::string& setting) { - if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) { - delegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); - repaint(); - } + if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) { + delegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + repaint(); + } +} + +void QtChatListWindow::handleClearRecentsRequested() { + onClearRecentsRequested(); } void QtChatListWindow::setBookmarksEnabled(bool enabled) { - bookmarksEnabled_ = enabled; + bookmarksEnabled_ = enabled; } void QtChatListWindow::handleClicked(const QModelIndex& index) { - ChatListGroupItem* item = dynamic_cast<ChatListGroupItem*>(static_cast<ChatListItem*>(index.internalPointer())); - if (item) { - setExpanded(index, !isExpanded(index)); - } + ChatListGroupItem* item = dynamic_cast<ChatListGroupItem*>(static_cast<ChatListItem*>(index.internalPointer())); + if (item) { + setExpanded(index, !isExpanded(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())); - emptyMenu_ = new QMenu(); - emptyMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); - + mucMenu_ = new QMenu(); + onlineOnlyActions_ << mucMenu_->addAction(tr("Edit Bookmark"), this, SLOT(handleEditBookmark())); + onlineOnlyActions_ << mucMenu_->addAction(tr("Remove Bookmark"), this, SLOT(handleRemoveBookmark())); + emptyMenu_ = new QMenu(); } void QtChatListWindow::handleItemActivated(const QModelIndex& index) { - ChatListItem* item = model_->getItemForIndex(index); - if (ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(item)) { - if (bookmarksEnabled_) { - onMUCBookmarkActivated(mucItem->getBookmark()); - } - } - else if (ChatListRecentItem* recentItem = dynamic_cast<ChatListRecentItem*>(item)) { - if (!recentItem->getChat().isMUC || bookmarksEnabled_) { - onRecentActivated(recentItem->getChat()); - } - } - else if (ChatListWhiteboardItem* whiteboardItem = dynamic_cast<ChatListWhiteboardItem*>(item)) { - if (!whiteboardItem->getChat().isMUC || bookmarksEnabled_) { - eventStream_->send(boost::make_shared<ShowWhiteboardUIEvent>(whiteboardItem->getChat().jid)); - } - } + ChatListItem* item = model_->getItemForIndex(index); + if (ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(item)) { + onMUCBookmarkActivated(mucItem->getBookmark()); + } + else if (ChatListRecentItem* recentItem = dynamic_cast<ChatListRecentItem*>(item)) { + onRecentActivated(recentItem->getChat()); + } + else if (ChatListWhiteboardItem* whiteboardItem = dynamic_cast<ChatListWhiteboardItem*>(item)) { + if (!whiteboardItem->getChat().isMUC || bookmarksEnabled_) { + eventStream_->send(std::make_shared<ShowWhiteboardUIEvent>(whiteboardItem->getChat().jid)); + } + } } void QtChatListWindow::clearBookmarks() { - model_->clearBookmarks(); + model_->clearBookmarks(); } void QtChatListWindow::addMUCBookmark(const MUCBookmark& bookmark) { - model_->addMUCBookmark(bookmark); + model_->addMUCBookmark(bookmark); } void QtChatListWindow::removeMUCBookmark(const MUCBookmark& bookmark) { - model_->removeMUCBookmark(bookmark); + model_->removeMUCBookmark(bookmark); } void QtChatListWindow::addWhiteboardSession(const ChatListWindow::Chat& chat) { - model_->addWhiteboardSession(chat); + model_->addWhiteboardSession(chat); } void QtChatListWindow::removeWhiteboardSession(const JID& jid) { - model_->removeWhiteboardSession(jid); + model_->removeWhiteboardSession(jid); } void QtChatListWindow::setRecents(const std::list<ChatListWindow::Chat>& recents) { - model_->setRecents(recents); + model_->setRecents(recents); } -void QtChatListWindow::setUnreadCount(int unread) { - emit onCountUpdated(unread); +void QtChatListWindow::setUnreadCount(size_t unread) { + emit onCountUpdated(unread); } -void QtChatListWindow::handleRemoveBookmark() { - ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(contextMenuItem_); - if (!mucItem) return; - eventStream_->send(boost::shared_ptr<UIEvent>(new RemoveMUCBookmarkUIEvent(mucItem->getBookmark()))); +void QtChatListWindow::setOnline(bool isOnline) { + isOnline_ = isOnline; } -void QtChatListWindow::handleAddBookmark() { - (new QtAddBookmarkWindow(eventStream_))->show(); +void QtChatListWindow::handleRemoveBookmark() { + const ChatListMUCItem* mucItem = dynamic_cast<const ChatListMUCItem*>(contextMenuItem_); + if (!mucItem) return; + eventStream_->send(std::make_shared<RemoveMUCBookmarkUIEvent>(mucItem->getBookmark())); } - void QtChatListWindow::handleEditBookmark() { - ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(contextMenuItem_); - if (!mucItem) return; - QtEditBookmarkWindow* window = new QtEditBookmarkWindow(eventStream_, mucItem->getBookmark()); - window->show(); + const ChatListMUCItem* mucItem = dynamic_cast<const ChatListMUCItem*>(contextMenuItem_); + if (!mucItem) return; + QtEditBookmarkWindow* window = new QtEditBookmarkWindow(eventStream_, mucItem->getBookmark()); + window->show(); } +void QtChatListWindow::dragEnterEvent(QDragEnterEvent *event) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + event->acceptProposedAction(); + } +} void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) { - QModelIndex index = indexAt(event->pos()); - ChatListItem* baseItem = index.isValid() ? static_cast<ChatListItem*>(index.internalPointer()) : NULL; - contextMenuItem_ = baseItem; - if (!baseItem) { - emptyMenu_->exec(QCursor::pos()); - return; - } - ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(baseItem); - if (mucItem) { - if (!bookmarksEnabled_) { - return; - } - mucMenu_->exec(QCursor::pos()); - } - else { - QMenu menu; - QAction* clearRecents = menu.addAction(tr("Clear recents")); - menu.addAction(clearRecents); - QAction* result = menu.exec(event->globalPos()); - if (result == clearRecents) { - onClearRecentsRequested(); - } - } + QModelIndex index = indexAt(event->pos()); + ChatListItem* baseItem = index.isValid() ? static_cast<ChatListItem*>(index.internalPointer()) : nullptr; + contextMenuItem_ = baseItem; + + for (auto action : onlineOnlyActions_) { + action->setEnabled(isOnline_); + } + + if (!baseItem) { + emptyMenu_->exec(QCursor::pos()); + return; + } + + ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(baseItem); + if (mucItem) { + if (!bookmarksEnabled_) { + return; + } + mucMenu_->exec(QCursor::pos()); + return; + } + + ChatListRecentItem* recentItem = dynamic_cast<ChatListRecentItem*>(baseItem); + if (recentItem) { + const ChatListWindow::Chat& chat = recentItem->getChat(); + if (chat.isMUC) { + QMenu mucRecentsMenu; + QAction* bookmarkAction = nullptr; + const ChatListMUCItem* mucItem = model_->getChatListMUCItem(chat.jid); + if (mucItem) { + contextMenuItem_ = mucItem; + bookmarkAction = mucRecentsMenu.addAction(tr("Edit Bookmark"), this, SLOT(handleEditBookmark())); + bookmarkAction->setEnabled(isOnline_); + } + mucRecentsMenu.addAction(tr("Clear recents"), this, SLOT(handleClearRecentsRequested())); + 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 ef4ce0f..3322001 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.h +++ b/Swift/QtUI/ChatList/QtChatListWindow.h @@ -1,57 +1,62 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QTreeView> -#include "Swift/Controllers/UIInterfaces/ChatListWindow.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/QtUI/ChatList/ChatListModel.h" -#include "Swift/QtUI/ChatList/ChatListDelegate.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> + +#include <Swift/QtUI/ChatList/ChatListDelegate.h> +#include <Swift/QtUI/ChatList/ChatListModel.h> namespace Swift { - class SettingsProvider; - class QtChatListWindow : public QTreeView, public ChatListWindow { - Q_OBJECT - public: - QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent = NULL); - virtual ~QtChatListWindow(); - 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(); - - signals: - void onCountUpdated(int count); - private slots: - void handleItemActivated(const QModelIndex&); - void handleAddBookmark(); - void handleEditBookmark(); - void handleRemoveBookmark(); - void handleClicked(const QModelIndex& index); - void handleSettingChanged(const std::string& setting); - - protected: - void contextMenuEvent(QContextMenuEvent* event); - - private: - void setupContextMenus(); - bool bookmarksEnabled_; - UIEventStream* eventStream_; - ChatListModel* model_; - ChatListDelegate* delegate_; - QMenu* mucMenu_; - QMenu* emptyMenu_; - ChatListItem* contextMenuItem_; - SettingsProvider* settings_; - }; + class SettingsProvider; + class QtChatListWindow : public QTreeView, public ChatListWindow { + Q_OBJECT + public: + QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent = nullptr); + virtual ~QtChatListWindow(); + 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(size_t unread); + void clearBookmarks(); + virtual void setOnline(bool isOnline); + + signals: + void onCountUpdated(size_t count); + private slots: + void handleItemActivated(const QModelIndex&); + void handleEditBookmark(); + void handleRemoveBookmark(); + void handleClicked(const QModelIndex& index); + void handleSettingChanged(const std::string& setting); + void handleClearRecentsRequested(); + + protected: + void dragEnterEvent(QDragEnterEvent* event); + void contextMenuEvent(QContextMenuEvent* event); + + private: + void setupContextMenus(); + bool bookmarksEnabled_; + UIEventStream* eventStream_; + ChatListModel* model_; + ChatListDelegate* delegate_; + QMenu* mucMenu_; + QMenu* emptyMenu_; + const ChatListItem* contextMenuItem_; + SettingsProvider* settings_; + QList<QAction*> onlineOnlyActions_; + bool isOnline_; + }; } diff --git a/Swift/QtUI/ChatSnippet.cpp b/Swift/QtUI/ChatSnippet.cpp index 3436531..87dfac2 100644 --- a/Swift/QtUI/ChatSnippet.cpp +++ b/Swift/QtUI/ChatSnippet.cpp @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/ChatSnippet.h> @@ -19,79 +19,79 @@ ChatSnippet::~ChatSnippet() { } QString ChatSnippet::timeToEscapedString(const QDateTime& time) { - QDate now(QDate::currentDate()); - QString date = ""; - if (time.date().daysTo(now) > 0) { - date = "ddd "; - } - if (time.date().month() != now.month()) { - date = date + "MMMM "; - } - if (time.date().daysTo(now) > 6) { - date = date + "d "; - } - if (time.date().year() != now.year()) { - date = date + "yy "; - } - date += "h:mm"; - return escape(time.toString(date)); + QDate now(QDate::currentDate()); + QString date = ""; + if (time.date().daysTo(now) > 0) { + date = "ddd "; + } + if (time.date().month() != now.month()) { + date = date + "MMMM "; + } + if (time.date().daysTo(now) > 6) { + date = date + "d "; + } + if (time.date().year() != now.year()) { + date = date + "yy "; + } + date += "h:mm"; + return escape(time.toString(date)); } QString ChatSnippet::wrapResizable(const QString& text) { - return "<span class='swift_resizable'>" + text + "</span>"; + return "<span class='swift_resizable'>" + text + "</span>"; } QString ChatSnippet::directionToCSS(Direction direction) { - return direction == RTL ? QString("rtl") : QString("ltr"); + 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); + std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + std::string text = ""; + for (auto&& part : message.getParts()) { + if ((textPart = std::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)); + 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; + /* + 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 f60d486..d8bc209 100644 --- a/Swift/QtUI/ChatSnippet.h +++ b/Swift/QtUI/ChatSnippet.h @@ -1,70 +1,70 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/shared_ptr.hpp> +#include <memory> -#include <QString> #include <QDateTime> +#include <QString> -#include <Swiften/Base/foreach.h> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> -#include <Swift/QtUI/QtChatTheme.h> +#include <Swift/QtUI/QtChatTheme.h> namespace Swift { - class ChatSnippet { - public: - enum Direction { - RTL, - LTR - }; - - ChatSnippet(bool appendToPrevious); - virtual ~ChatSnippet(); - - virtual const QString& getContent() const = 0; - virtual QString getContinuationElementID() const { return ""; } - - boost::shared_ptr<ChatSnippet> getContinuationFallbackSnippet() const {return continuationFallback_;} - - bool getAppendToPrevious() const { - return appendToPrevious_; - } - - static QString escape(const QString& original) { - QString result(original); - result.replace("%message%", "%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; - } - - static QString timeToEscapedString(const QDateTime& time); - - static Direction getDirection(const std::string& message); - static Direction getDirection(const ChatWindow::ChatMessage& message); - static Direction getDirection(const QString& message); - - protected: - static QString directionToCSS(Direction direction); - - QString wrapResizable(const QString& text); - void setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet> continuationFallback) { - continuationFallback_ = continuationFallback; - } - private: - bool appendToPrevious_; - boost::shared_ptr<ChatSnippet> continuationFallback_; - }; + class ChatSnippet { + public: + enum Direction { + RTL, + LTR + }; + + ChatSnippet(bool appendToPrevious); + virtual ~ChatSnippet(); + + virtual const QString& getContent() const = 0; + virtual QString getContinuationElementID() const { return ""; } + + std::shared_ptr<ChatSnippet> getContinuationFallbackSnippet() const {return continuationFallback_;} + + bool getAppendToPrevious() const { + return appendToPrevious_; + } + + static QString escape(const QString& original) { + QString result(original); + 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("%id%", "%id%"); + result.replace("\t", " "); + result.replace(" ", " "); + return result; + } + + 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(std::shared_ptr<ChatSnippet> continuationFallback) { + continuationFallback_ = continuationFallback; + } + private: + bool appendToPrevious_; + std::shared_ptr<ChatSnippet> continuationFallback_; + }; } diff --git a/Swift/QtUI/ChatView/main.cpp b/Swift/QtUI/ChatView/main.cpp index 0f53432..aa3255e 100644 --- a/Swift/QtUI/ChatView/main.cpp +++ b/Swift/QtUI/ChatView/main.cpp @@ -1,178 +1,180 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <QtDebug> -#include <QApplication> #include <iostream> -#include <QWidget> -#include <QFile> + +#include <QApplication> #include <QDateTime> +#include <QFile> #include <QLineEdit> +#include <QNetworkAccessManager> +#include <QNetworkReply> +#include <QNetworkRequest> #include <QVBoxLayout> -#include <QWebView> #include <QWebFrame> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QNetworkAccessManager> -#include "../QtChatView.h" -#include "../MessageSnippet.h" -#include "../SystemMessageSnippet.h" +#include <QWebView> +#include <QWidget> +#include <QtDebug> + +#include <Swift/QtUI/MessageSnippet.h> +#include <Swift/QtUI/QtChatView.h> +#include <Swift/QtUI/SystemMessageSnippet.h> using namespace Swift; /* class MyNetworkReply : public QNetworkReply { - public: - MyNetworkReply() { - } - - qint64 readData(char*, qint64) { - return 0; - } - - virtual void abort() { - } + public: + MyNetworkReply() { + } + + qint64 readData(char*, qint64) { + return 0; + } + + virtual void abort() { + } }; class MyNetworkAccessManager : public QNetworkAccessManager { - public: - MyNetworkAccessManager() { - } - - QNetworkReply * createRequest (Operation op, const QNetworkRequest& request, QIODevice* outgoingData = 0) { - assert(op == QNetworkAccessManager::GetOperation); - qDebug() << "Requesting: " << request.url(); - return QNetworkAccessManager::createRequest(op, request, outgoingData); - //return new MyNetworkReply(); - } + public: + MyNetworkAccessManager() { + } + + QNetworkReply * createRequest (Operation op, const QNetworkRequest& request, QIODevice* outgoingData = 0) { + assert(op == QNetworkAccessManager::GetOperation); + qDebug() << "Requesting: " << request.url(); + return QNetworkAccessManager::createRequest(op, request, outgoingData); + //return new MyNetworkReply(); + } }; - QVBoxLayout* mainLayout = new QVBoxLayout(this); - webView_ = new QWebView(this); - - QFile file(":/themes/Stockholm/Contents/Resources/Incoming/Content.html"); - file.open(QIODevice::ReadOnly); - QString content = QString::fromUtf8(file.readAll()); - - webPage_ = new QWebPage(this); - webPage_->setNetworkAccessManager(new MyNetworkAccessManager()); - webView_->setPage(webPage_); - QString pagehtml = - "<head>" - //"<base href=\"file:///Users/remko/src/swift/resources/themes/Stockholm/Contents/Resources/\"/>" - "<base href=\"file:///Users/remko/src/swift/resources/themes/Stockholm/Contents/Resources/\"/>" - "<link rel=\"stylesheet\" type=\"text/css\" href=\"main.css\"/>" - "<link rel=\"stylesheet\" type=\"text/css\" href=\"Variants/Alt Blue - Blue.css\"/>" - "</head><body>" + content + "</body>"; - qDebug() << pagehtml; - webPage_->mainFrame()->setHtml(pagehtml); + QVBoxLayout* mainLayout = new QVBoxLayout(this); + webView_ = new QWebView(this); + + QFile file(":/themes/Stockholm/Contents/Resources/Incoming/Content.html"); + file.open(QIODevice::ReadOnly); + QString content = QString::fromUtf8(file.readAll()); + + webPage_ = new QWebPage(this); + webPage_->setNetworkAccessManager(new MyNetworkAccessManager()); + webView_->setPage(webPage_); + QString pagehtml = + "<head>" + //"<base href=\"file:///Users/remko/src/swift/resources/themes/Stockholm/Contents/Resources/\"/>" + "<base href=\"file:///Users/remko/src/swift/resources/themes/Stockholm/Contents/Resources/\"/>" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"main.css\"/>" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"Variants/Alt Blue - Blue.css\"/>" + "</head><body>" + content + "</body>"; + qDebug() << pagehtml; + webPage_->mainFrame()->setHtml(pagehtml); */ /* class ChatView : public QWidget { - public: - ChatView(QWidget* parent) : QWidget(parent) { - setFocusPolicy(Qt::NoFocus); - - QVBoxLayout* mainLayout = new QVBoxLayout(this); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0,0,0,0); - - webView_ = new QWebView(this); - webView_->setFocusPolicy(Qt::NoFocus); - mainLayout->addWidget(webView_); - - webPage_ = new QWebPage(this); - webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); - webView_->setPage(webPage_); - connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); - - QString pageHTML = "<head></head><body><div id=\"chat\"></div></body>"; - webPage_->mainFrame()->setHtml(pageHTML); - } - - void appendHTML(const QString& html) { - webPage_->mainFrame()->evaluateJavaScript( - "newNode = document.createElement(\"div\");" - "newNode.innerHTML = \"" + html + "\";" - "chatElement = document.getElementById(\"chat\");" - "chatElement.appendChild(newNode);"); - webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)); - } - - private: - QWebView* webView_; - QWebPage* webPage_; + public: + ChatView(QWidget* parent) : QWidget(parent) { + setFocusPolicy(Qt::NoFocus); + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + + webView_ = new QWebView(this); + webView_->setFocusPolicy(Qt::NoFocus); + mainLayout->addWidget(webView_); + + webPage_ = new QWebPage(this); + webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); + webView_->setPage(webPage_); + connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); + + QString pageHTML = "<head></head><body><div id=\"chat\"></div></body>"; + webPage_->mainFrame()->setHtml(pageHTML); + } + + void appendHTML(const QString& html) { + webPage_->mainFrame()->evaluateJavaScript( + "newNode = document.createElement(\"div\");" + "newNode.innerHTML = \"" + html + "\";" + "chatElement = document.getElementById(\"chat\");" + "chatElement.appendChild(newNode);"); + webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)); + } + + private: + QWebView* webView_; + QWebPage* webPage_; }; */ class MyWidget : public QWidget { - Q_OBJECT - - public: - MyWidget() : previousWasIncoming_(false), previousWasOutgoing_(false), previousWasSystem_(false) { - QVBoxLayout* mainLayout = new QVBoxLayout(this); - chatView_ = new QtChatView(this); - mainLayout->addWidget(chatView_); - input1_ = new QLineEdit(this); - connect(input1_, SIGNAL(returnPressed()), SLOT(addIncoming())); - mainLayout->addWidget(input1_); - input2_ = new QLineEdit(this); - connect(input2_, SIGNAL(returnPressed()), SLOT(addOutgoing())); - mainLayout->addWidget(input2_); - input3_ = new QLineEdit(this); - connect(input3_, SIGNAL(returnPressed()), SLOT(addSystem())); - mainLayout->addWidget(input3_); - - resize(300,200); - } - - public slots: - void addIncoming() { - chatView_->addMessage(MessageSnippet(input1_->text(), "Me", QDateTime::currentDateTime(), "qrc:/icons/avatar.png", true, previousWasIncoming_)); - previousWasIncoming_ = true; - previousWasOutgoing_ = false; - previousWasSystem_ = false; - input1_->clear(); - } - - void addOutgoing() { - chatView_->addMessage(MessageSnippet(input2_->text(), "You", QDateTime::currentDateTime(), "qrc:/icons/avatar.png", false, previousWasOutgoing_)); - previousWasIncoming_ = false; - previousWasOutgoing_ = true; - previousWasSystem_ = false; - input2_->clear(); - } - - void addSystem() { - chatView_->addMessage(SystemMessageSnippet(input3_->text(), QDateTime::currentDateTime(), previousWasSystem_)); - previousWasIncoming_ = false; - previousWasOutgoing_ = false; - previousWasSystem_ = true; - input3_->clear(); - } - - private: - bool previousWasIncoming_; - bool previousWasOutgoing_; - bool previousWasSystem_; - QtChatView* chatView_; - QLineEdit* input1_; - QLineEdit* input2_; - QLineEdit* input3_; + Q_OBJECT + + public: + MyWidget() : previousWasIncoming_(false), previousWasOutgoing_(false), previousWasSystem_(false) { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + chatView_ = new QtChatView(this); + mainLayout->addWidget(chatView_); + input1_ = new QLineEdit(this); + connect(input1_, SIGNAL(returnPressed()), SLOT(addIncoming())); + mainLayout->addWidget(input1_); + input2_ = new QLineEdit(this); + connect(input2_, SIGNAL(returnPressed()), SLOT(addOutgoing())); + mainLayout->addWidget(input2_); + input3_ = new QLineEdit(this); + connect(input3_, SIGNAL(returnPressed()), SLOT(addSystem())); + mainLayout->addWidget(input3_); + + resize(300,200); + } + + public slots: + void addIncoming() { + chatView_->addMessage(MessageSnippet(input1_->text(), "Me", QDateTime::currentDateTime(), "qrc:/icons/avatar.png", true, previousWasIncoming_)); + previousWasIncoming_ = true; + previousWasOutgoing_ = false; + previousWasSystem_ = false; + input1_->clear(); + } + + void addOutgoing() { + chatView_->addMessage(MessageSnippet(input2_->text(), "You", QDateTime::currentDateTime(), "qrc:/icons/avatar.png", false, previousWasOutgoing_)); + previousWasIncoming_ = false; + previousWasOutgoing_ = true; + previousWasSystem_ = false; + input2_->clear(); + } + + void addSystem() { + chatView_->addMessage(SystemMessageSnippet(input3_->text(), QDateTime::currentDateTime(), previousWasSystem_)); + previousWasIncoming_ = false; + previousWasOutgoing_ = false; + previousWasSystem_ = true; + input3_->clear(); + } + + private: + bool previousWasIncoming_; + bool previousWasOutgoing_; + bool previousWasSystem_; + QtChatView* chatView_; + QLineEdit* input1_; + QLineEdit* input2_; + QLineEdit* input3_; }; - + int main(int argc, char* argv[]) { - QApplication app(argc, argv); - MyWidget w; - w.show(); - return app.exec(); + QApplication app(argc, argv); + MyWidget w; + w.show(); + return app.exec(); } #include "main.moc" diff --git a/Swift/QtUI/ChattablesModel.cpp b/Swift/QtUI/ChattablesModel.cpp new file mode 100644 index 0000000..67d0579 --- /dev/null +++ b/Swift/QtUI/ChattablesModel.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/ChattablesModel.h> + +#include <QDebug> + +#include <Swift/Controllers/Chat/Chattables.h> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +ChattablesModel::ChattablesModel(Chattables& chattables, QObject* parent) : QAbstractListModel(parent), chattables_(chattables) { + using scoped_connection = boost::signals2::scoped_connection; + connectionList_.emplace_back(std::make_unique<scoped_connection>( + chattables_.onBeginAdd.connect([this](int index) {beginInsertRows(QModelIndex(), index, index);}) + )); + connectionList_.emplace_back(std::make_unique<scoped_connection>( + chattables_.onAdded.connect([this]() {endInsertRows();}) + )); + connectionList_.emplace_back(std::make_unique<scoped_connection>( + chattables_.onChanged.connect( + [this](const JID& jid, int index) { + auto modelIndex = createIndex(index, 0, const_cast<JID*>(&jid)); + dataChanged(modelIndex, modelIndex, {}); + } + ) + )); +} + +int ChattablesModel::rowCount(const QModelIndex& /*parent*/) const { + return chattables_.get().size(); +} + +QVariant ChattablesModel::data(const QModelIndex& index, int role) const { + //FIXME: Check validity + auto state = chattables_.getState(chattables_.get()[index.row()]); + if (role == Qt::DisplayRole) { + return P2QSTRING((state.name.empty() ? state.jid.toString() : state.name)); + } + if (role == UnreadCountRole) { + return QString::number(state.unreadCount); + } + if (role == TypeRole) { + switch (state.type) { + case Chattables::State::Type::Room: return "ROOM"; + case Chattables::State::Type::Person: return "PERSON"; + } + } + if (role == StatusRole) { + return QVariant(static_cast<int>(state.status)); + } + if (role == JIDRole) { + return P2QSTRING(state.jid.toString()); + } + return QVariant(); +} + +} diff --git a/Swift/QtUI/ChattablesModel.h b/Swift/QtUI/ChattablesModel.h new file mode 100644 index 0000000..6617d97 --- /dev/null +++ b/Swift/QtUI/ChattablesModel.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <memory> +#include <vector> + +#include <boost/signals2/connection.hpp> + +#include <QAbstractListModel> + +namespace Swift { +class Chattables; +class ChattablesModel : public QAbstractListModel { + public: + enum ChattablesRoles { + UnreadCountRole = Qt::UserRole, + TypeRole = Qt::UserRole + 1, + StatusRole = Qt::UserRole + 2, + JIDRole = Qt::UserRole + 3 + }; + ChattablesModel(Chattables& chattables, QObject* parent); + int rowCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + private: + Chattables& chattables_; + std::vector<std::unique_ptr<boost::signals2::scoped_connection>> connectionList_; +}; + +} diff --git a/Swift/QtUI/CocoaApplicationActivateHelper.h b/Swift/QtUI/CocoaApplicationActivateHelper.h index 8bc2af5..83fa886 100644 --- a/Swift/QtUI/CocoaApplicationActivateHelper.h +++ b/Swift/QtUI/CocoaApplicationActivateHelper.h @@ -1,28 +1,30 @@ /* - * Copyright (c) 2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <memory> + #include <QObject> namespace Swift { - /** - * This class is here as a workaround for a bug in Qt. - * See Case #501. - */ - class CocoaApplicationActivateHelper : public QObject { - public: - CocoaApplicationActivateHelper(); - ~CocoaApplicationActivateHelper(); + /** + * This class is here as a workaround for a bug in Qt. + * See Case #501. + */ + class CocoaApplicationActivateHelper : public QObject { + public: + CocoaApplicationActivateHelper(); + virtual ~CocoaApplicationActivateHelper(); - private: - bool eventFilter(QObject* o, QEvent* e); + private: + bool eventFilter(QObject* o, QEvent* e); - private: - struct Private; - Private* p; - }; + private: + struct Private; + const std::unique_ptr<Private> p; + }; } diff --git a/Swift/QtUI/CocoaApplicationActivateHelper.mm b/Swift/QtUI/CocoaApplicationActivateHelper.mm index 2904b20..2bd985a 100644 --- a/Swift/QtUI/CocoaApplicationActivateHelper.mm +++ b/Swift/QtUI/CocoaApplicationActivateHelper.mm @@ -1,15 +1,19 @@ /* - * Copyright (c) 2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <Cocoa/Cocoa.h> -#include "CocoaApplicationActivateHelper.h" +#include <Swift/QtUI/CocoaApplicationActivateHelper.h> + +#include <memory> #include <boost/function.hpp> + #include <QApplication> +#include <Cocoa/Cocoa.h> + @interface CocoaApplicationActivateHelperDelegate : NSObject { } - (void) handleActivate: (NSAppleEventDescriptor*) event withReply: (NSAppleEventDescriptor*) reply; @@ -17,37 +21,35 @@ @implementation CocoaApplicationActivateHelperDelegate - (void) handleActivate: (NSAppleEventDescriptor*) event withReply: (NSAppleEventDescriptor*) reply { - (void) event; (void) reply; - QApplication::postEvent(qApp, new QEvent(QEvent::ApplicationActivate)); + (void) event; (void) reply; + QApplication::postEvent(qApp, new QEvent(QEvent::ApplicationActivate)); } @end namespace Swift { struct CocoaApplicationActivateHelper::Private { - CocoaApplicationActivateHelperDelegate* delegate; - bool initialized; + CocoaApplicationActivateHelperDelegate* delegate; + bool initialized; }; -CocoaApplicationActivateHelper::CocoaApplicationActivateHelper() { - p = new Private(); - p->delegate = [[CocoaApplicationActivateHelperDelegate alloc] init]; - p->initialized = false; - qApp->installEventFilter(this); +CocoaApplicationActivateHelper::CocoaApplicationActivateHelper() : p(new Private()) { + p->delegate = [[CocoaApplicationActivateHelperDelegate alloc] init]; + p->initialized = false; + qApp->installEventFilter(this); } CocoaApplicationActivateHelper::~CocoaApplicationActivateHelper() { - [[NSAppleEventManager sharedAppleEventManager] removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEReopenApplication]; - [p->delegate release]; - delete p; + [[NSAppleEventManager sharedAppleEventManager] removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEReopenApplication]; + [p->delegate release]; } bool CocoaApplicationActivateHelper::eventFilter(QObject* object, QEvent* event) { - if (object == qApp && event->type() == QEvent::ApplicationActivate && !p->initialized) { - [[NSAppleEventManager sharedAppleEventManager] setEventHandler:p->delegate andSelector:@selector(handleActivate:withReply:) forEventClass:kCoreEventClass andEventID:kAEReopenApplication]; - p->initialized = true; - } - return QObject::eventFilter(object, event); + if (object == qApp && event->type() == QEvent::ApplicationActivate && !p->initialized) { + [[NSAppleEventManager sharedAppleEventManager] setEventHandler:p->delegate andSelector:@selector(handleActivate:withReply:) forEventClass:kCoreEventClass andEventID:kAEReopenApplication]; + p->initialized = true; + } + return QObject::eventFilter(object, event); } diff --git a/Swift/QtUI/CocoaUIHelpers.h b/Swift/QtUI/CocoaUIHelpers.h index 25da0e3..8d96bd9 100644 --- a/Swift/QtUI/CocoaUIHelpers.h +++ b/Swift/QtUI/CocoaUIHelpers.h @@ -4,16 +4,24 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once -#include <Swiften/TLS/Certificate.h> #include <QWidget> +#include <Swiften/TLS/Certificate.h> + namespace Swift { class CocoaUIHelpers { public: - static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); + static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); + static void sendCocoaApplicationWillTerminateNotification(); }; } diff --git a/Swift/QtUI/CocoaUIHelpers.mm b/Swift/QtUI/CocoaUIHelpers.mm index 3cb62f3..1f4ffc1 100644 --- a/Swift/QtUI/CocoaUIHelpers.mm +++ b/Swift/QtUI/CocoaUIHelpers.mm @@ -4,9 +4,16 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #include "CocoaUIHelpers.h" -#include <boost/shared_ptr.hpp> +#include <memory> + #include <boost/type_traits.hpp> #include <Cocoa/Cocoa.h> @@ -14,30 +21,31 @@ #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]; + NSWindow* parentWindow = [((NSView*)parent->winId()) window]; + NSMutableArray* certificates = [[NSMutableArray alloc] init]; + for (auto&& cert : chain) { + // convert chain to SecCertificateRef + ByteArray certAsDER = cert->toDER(); + std::shared_ptr<boost::remove_pointer<CFDataRef>::type> certData(CFDataCreate(nullptr, certAsDER.data(), certAsDER.size()), CFRelease); + std::shared_ptr<OpaqueSecCertificateRef> macCert(SecCertificateCreateWithData(nullptr, 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:nullptr contextInfo:nullptr certificates:certificates showGroup:YES]; + [certificates release]; +} + +void CocoaUIHelpers::sendCocoaApplicationWillTerminateNotification() { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NSApplicationWillTerminateNotification" object:nil]; } } diff --git a/Swift/QtUI/EventViewer/EventDelegate.cpp b/Swift/QtUI/EventViewer/EventDelegate.cpp index c0904b3..1c3ed7f 100644 --- a/Swift/QtUI/EventViewer/EventDelegate.cpp +++ b/Swift/QtUI/EventViewer/EventDelegate.cpp @@ -1,73 +1,85 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "EventDelegate.h" +#include <Swift/QtUI/EventViewer/EventDelegate.h> #include <QDebug> -#include "Swift/Controllers/XMPPEvents/MessageEvent.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" -#include "Swift/Controllers/XMPPEvents/MUCInviteEvent.h" +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h> +#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> +#include <Swift/Controllers/XMPPEvents/MessageEvent.h> +#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> namespace Swift { -EventDelegate::EventDelegate() : QStyledItemDelegate(), messageDelegate_(QtEvent::SenderRole, Qt::DisplayRole, false), subscriptionDelegate_(QtEvent::SenderRole, Qt::DisplayRole, true), errorDelegate_(QtEvent::SenderRole, Qt::DisplayRole, true), mucInviteDelegate_(QtEvent::SenderRole, Qt::DisplayRole, false) { +EventDelegate::EventDelegate() : QStyledItemDelegate(), + messageDelegate_(QtEvent::SenderRole, Qt::DisplayRole, false), + subscriptionDelegate_(QtEvent::SenderRole, Qt::DisplayRole, true), + errorDelegate_(QtEvent::SenderRole, Qt::DisplayRole, true), + mucInviteDelegate_(QtEvent::SenderRole, Qt::DisplayRole, false), + incomingFileTransferDelegate_(QtEvent::SenderRole, Qt::DisplayRole, false) { } QSize EventDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { - QtEvent* item = static_cast<QtEvent*>(index.internalPointer()); - if (!item) { - return QStyledItemDelegate::sizeHint(option, index); - } - switch (getEventType(item->getEvent())) { - case MessageEventType: return messageDelegate_.sizeHint(option, item); - case SubscriptionEventType: return subscriptionDelegate_.sizeHint(option, item); - case ErrorEventType: return errorDelegate_.sizeHint(option, item); - case MUCInviteEventType: return mucInviteDelegate_.sizeHint(option, item); - } - assert(false); - return QSize(); + QtEvent* item = static_cast<QtEvent*>(index.internalPointer()); + if (!item) { + return QStyledItemDelegate::sizeHint(option, index); + } + switch (getEventType(item->getEvent())) { + case MessageEventType: return messageDelegate_.sizeHint(option, item); + case SubscriptionEventType: return subscriptionDelegate_.sizeHint(option, item); + case ErrorEventType: return errorDelegate_.sizeHint(option, item); + case MUCInviteEventType: return mucInviteDelegate_.sizeHint(option, item); + case IncomingFileTransferEventType: return incomingFileTransferDelegate_.sizeHint(option, item); + } + assert(false); + return QSize(); } void EventDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - QtEvent* item = static_cast<QtEvent*>(index.internalPointer()); - if (!item) { - QStyledItemDelegate::paint(painter, option, index); - return; - } - switch (getEventType(item->getEvent())) { - case MessageEventType: messageDelegate_.paint(painter, option, item);break; - case SubscriptionEventType: subscriptionDelegate_.paint(painter, option, item);break; - case ErrorEventType: errorDelegate_.paint(painter, option, item);break; - case MUCInviteEventType: mucInviteDelegate_.paint(painter, option, item);break; - } + QtEvent* item = static_cast<QtEvent*>(index.internalPointer()); + if (!item) { + QStyledItemDelegate::paint(painter, option, index); + return; + } + switch (getEventType(item->getEvent())) { + case MessageEventType: messageDelegate_.paint(painter, option, item);break; + case SubscriptionEventType: subscriptionDelegate_.paint(painter, option, item);break; + case ErrorEventType: errorDelegate_.paint(painter, option, item);break; + case MUCInviteEventType: mucInviteDelegate_.paint(painter, option, item);break; + case IncomingFileTransferEventType: incomingFileTransferDelegate_.paint(painter, option, item);break; + } } -EventType EventDelegate::getEventType(boost::shared_ptr<StanzaEvent> event) const { - boost::shared_ptr<MessageEvent> messageEvent = boost::dynamic_pointer_cast<MessageEvent>(event); - if (messageEvent) { - return MessageEventType; - } - boost::shared_ptr<SubscriptionRequestEvent> subscriptionEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(event); - if (subscriptionEvent) { - return SubscriptionEventType; - } - boost::shared_ptr<ErrorEvent> errorEvent = boost::dynamic_pointer_cast<ErrorEvent>(event); - if (errorEvent) { - return ErrorEventType; - } - boost::shared_ptr<MUCInviteEvent> mucInviteEvent = boost::dynamic_pointer_cast<MUCInviteEvent>(event); - if (mucInviteEvent) { - return MUCInviteEventType; - } - //I don't know what this is. - assert(false); - return MessageEventType; +EventType EventDelegate::getEventType(std::shared_ptr<StanzaEvent> event) const { + std::shared_ptr<MessageEvent> messageEvent = std::dynamic_pointer_cast<MessageEvent>(event); + if (messageEvent) { + return MessageEventType; + } + std::shared_ptr<SubscriptionRequestEvent> subscriptionEvent = std::dynamic_pointer_cast<SubscriptionRequestEvent>(event); + if (subscriptionEvent) { + return SubscriptionEventType; + } + std::shared_ptr<ErrorEvent> errorEvent = std::dynamic_pointer_cast<ErrorEvent>(event); + if (errorEvent) { + return ErrorEventType; + } + std::shared_ptr<MUCInviteEvent> mucInviteEvent = std::dynamic_pointer_cast<MUCInviteEvent>(event); + if (mucInviteEvent) { + return MUCInviteEventType; + } + std::shared_ptr<IncomingFileTransferEvent> incomingFileTransferEvent = std::dynamic_pointer_cast<IncomingFileTransferEvent>(event); + if (incomingFileTransferEvent) { + return IncomingFileTransferEventType; + } + //I don't know what this is. + assert(false); + return MessageEventType; } } diff --git a/Swift/QtUI/EventViewer/EventDelegate.h b/Swift/QtUI/EventViewer/EventDelegate.h index 1ebaff2..79ee8ed 100644 --- a/Swift/QtUI/EventViewer/EventDelegate.h +++ b/Swift/QtUI/EventViewer/EventDelegate.h @@ -1,31 +1,32 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QStyledItemDelegate> -#include "Swift/QtUI/Roster/DelegateCommons.h" -#include "Swift/QtUI/EventViewer/TwoLineDelegate.h" +#include <Swift/QtUI/EventViewer/TwoLineDelegate.h> +#include <Swift/QtUI/Roster/DelegateCommons.h> namespace Swift { - enum EventType {MessageEventType, SubscriptionEventType, ErrorEventType, MUCInviteEventType}; - class EventDelegate : public QStyledItemDelegate { - Q_OBJECT - public: - EventDelegate(); - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - private: - EventType getEventType(boost::shared_ptr<StanzaEvent> event) const; - DelegateCommons common_; - TwoLineDelegate messageDelegate_; - TwoLineDelegate subscriptionDelegate_; - TwoLineDelegate errorDelegate_; - TwoLineDelegate mucInviteDelegate_; - }; + enum EventType {MessageEventType, SubscriptionEventType, ErrorEventType, MUCInviteEventType, IncomingFileTransferEventType}; + class EventDelegate : public QStyledItemDelegate { + Q_OBJECT + public: + EventDelegate(); + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + private: + EventType getEventType(std::shared_ptr<StanzaEvent> event) const; + DelegateCommons common_; + TwoLineDelegate messageDelegate_; + TwoLineDelegate subscriptionDelegate_; + TwoLineDelegate errorDelegate_; + TwoLineDelegate mucInviteDelegate_; + TwoLineDelegate incomingFileTransferDelegate_; + }; } diff --git a/Swift/QtUI/EventViewer/EventModel.cpp b/Swift/QtUI/EventViewer/EventModel.cpp index a19027a..5b97b3e 100644 --- a/Swift/QtUI/EventViewer/EventModel.cpp +++ b/Swift/QtUI/EventViewer/EventModel.cpp @@ -1,92 +1,110 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "EventModel.h" +#include <Swift/QtUI/EventViewer/EventModel.h> -#include <QtDebug> +#include <Swiften/Base/Log.h> namespace Swift { + +namespace { + const int inactiveEventsLimit = 50; +} + EventModel::EventModel() { - + } EventModel::~EventModel() { - foreach (QtEvent* event, activeEvents_) { - delete event; - } - foreach (QtEvent* event, inactiveEvents_) { - delete event; - } + for (auto event : activeEvents_) { + delete event; + } + for (auto event : inactiveEvents_) { + delete event; + } } QtEvent* EventModel::getItem(int row) const { - return row < activeEvents_.size() ? activeEvents_[row] : inactiveEvents_[row - activeEvents_.size()]; + QtEvent* event = nullptr; + if (row < activeEvents_.size()) { + event = activeEvents_[row]; + } + else { + int inactiveRow = row - activeEvents_.size(); + if (inactiveRow < inactiveEvents_.size()) { + event = inactiveEvents_[inactiveRow]; + } + else { + SWIFT_LOG(error) << "Misbehaving EventModel requests row index outside of range"; + } + } + return event; } int EventModel::getNewEventCount() { - return activeEvents_.size(); + return activeEvents_.size(); } QVariant EventModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) { - return QVariant(); - } - QtEvent* item = getItem(index.row()); - QVariant result = item ? item->data(role) : QVariant(); - return result; + if (!index.isValid()) { + return QVariant(); + } + QtEvent* item = getItem(index.row()); + QVariant result = item ? item->data(role) : QVariant(); + return result; } /* * We only reimplement this to get the pointers inside the indices. */ QModelIndex EventModel::index(int row, int column, const QModelIndex & parent) const { - if (!hasIndex(row, column, parent) || parent.isValid()) { - return QModelIndex(); - } + if (!hasIndex(row, column, parent) || parent.isValid()) { + return QModelIndex(); + } - return row < rowCount() ? createIndex(row, column, getItem(row)) : QModelIndex(); + return row < rowCount() ? createIndex(row, column, getItem(row)) : QModelIndex(); } int EventModel::rowCount(const QModelIndex& parent) const { - /* Invalid parent = root, valid parent = child, and we're a list not a tree.*/ - int count = parent.isValid() ? 0 : activeEvents_.size() + inactiveEvents_.size(); - return count; + /* Invalid parent = root, valid parent = child, and we're a list not a tree.*/ + int count = parent.isValid() ? 0 : activeEvents_.size() + inactiveEvents_.size(); + return count; } -void EventModel::addEvent(boost::shared_ptr<StanzaEvent> event, bool active) { - if (active) { - activeEvents_.push_front(new QtEvent(event, active)); - emit dataChanged(createIndex(0, 0), createIndex(1, 0)); - } else { - inactiveEvents_.push_front(new QtEvent(event, active)); - emit dataChanged(createIndex(activeEvents_.size() -1, 0), createIndex(activeEvents_.size(), 0)); - if (inactiveEvents_.size() > 50) { - removeEvent(inactiveEvents_[20]->getEvent()); - } - } - emit layoutChanged(); +void EventModel::addEvent(std::shared_ptr<StanzaEvent> event, bool active) { + beginResetModel(); + if (active) { + activeEvents_.push_front(new QtEvent(event, active)); + } else { + inactiveEvents_.push_front(new QtEvent(event, active)); + if (inactiveEvents_.size() > inactiveEventsLimit) { + removeEvent(inactiveEvents_[inactiveEventsLimit]->getEvent()); + } + } + endResetModel(); } -void EventModel::removeEvent(boost::shared_ptr<StanzaEvent> event) { - for (int i = inactiveEvents_.size() - 1; i >= 0; i--) { - if (event == inactiveEvents_[i]->getEvent()) { - inactiveEvents_.removeAt(i); - emit dataChanged(createIndex(activeEvents_.size() + i - 1, 0), createIndex(activeEvents_.size() + i - 1, 0)); - return; - } - } - - for (int i = 0; i < activeEvents_.size(); i++) { - if (event == activeEvents_[i]->getEvent()) { - activeEvents_.removeAt(i); - emit dataChanged(createIndex(i - 1, 0), createIndex(i - 1, 0)); - return; - } - } +void EventModel::removeEvent(std::shared_ptr<StanzaEvent> event) { + beginResetModel(); + for (int i = inactiveEvents_.size() - 1; i >= 0; i--) { + if (event == inactiveEvents_[i]->getEvent()) { + inactiveEvents_.removeAt(i); + endResetModel(); + return; + } + } + for (int i = 0; i < activeEvents_.size(); i++) { + if (event == activeEvents_[i]->getEvent()) { + activeEvents_.removeAt(i); + endResetModel(); + return; + } + } + endResetModel(); } } diff --git a/Swift/QtUI/EventViewer/EventModel.h b/Swift/QtUI/EventViewer/EventModel.h index acbbb68..de72c1b 100644 --- a/Swift/QtUI/EventViewer/EventModel.h +++ b/Swift/QtUI/EventViewer/EventModel.h @@ -1,37 +1,37 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/shared_ptr.hpp> +#include <memory> #include <QAbstractListModel> #include <QList> -#include "Swift/Controllers/XMPPEvents/StanzaEvent.h" +#include <Swift/Controllers/XMPPEvents/StanzaEvent.h> -#include "Swift/QtUI/EventViewer/QtEvent.h" +#include <Swift/QtUI/EventViewer/QtEvent.h> namespace Swift { class EventModel : public QAbstractListModel { - Q_OBJECT - public: - EventModel(); - ~EventModel(); - void addEvent(boost::shared_ptr<StanzaEvent> event, bool active); - void removeEvent(boost::shared_ptr<StanzaEvent> event); - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - int rowCount(const QModelIndex& parent = QModelIndex()) const; - QtEvent* getItem(int row) const; - int getNewEventCount(); - protected: - QModelIndex index ( int row, int column = 0, const QModelIndex & parent = QModelIndex() ) const; - private: - QList<QtEvent*> activeEvents_; - QList<QtEvent*> inactiveEvents_; + Q_OBJECT + public: + EventModel(); + virtual ~EventModel(); + void addEvent(std::shared_ptr<StanzaEvent> event, bool active); + void removeEvent(std::shared_ptr<StanzaEvent> event); + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + QtEvent* getItem(int row) const; + int getNewEventCount(); + protected: + QModelIndex index(int row, int column = 0, const QModelIndex & parent = QModelIndex()) const; + private: + QList<QtEvent*> activeEvents_; + QList<QtEvent*> inactiveEvents_; }; } diff --git a/Swift/QtUI/EventViewer/EventView.cpp b/Swift/QtUI/EventViewer/EventView.cpp index 559c100..4d56548 100644 --- a/Swift/QtUI/EventViewer/EventView.cpp +++ b/Swift/QtUI/EventViewer/EventView.cpp @@ -1,14 +1,14 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/EventViewer/EventView.h" +#include <Swift/QtUI/EventViewer/EventView.h> namespace Swift { EventView::EventView(QWidget* parent) : QListView(parent) { - + } } diff --git a/Swift/QtUI/EventViewer/EventView.h b/Swift/QtUI/EventViewer/EventView.h index e26c628..83898bd 100644 --- a/Swift/QtUI/EventViewer/EventView.h +++ b/Swift/QtUI/EventViewer/EventView.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,9 +9,9 @@ #include <QListView> namespace Swift { - class EventView : public QListView { - Q_OBJECT - public: - EventView(QWidget* parent); - }; + class EventView : public QListView { + Q_OBJECT + public: + EventView(QWidget* parent); + }; } diff --git a/Swift/QtUI/EventViewer/QtEvent.cpp b/Swift/QtUI/EventViewer/QtEvent.cpp index c3ff944..5bd0fad 100644 --- a/Swift/QtUI/EventViewer/QtEvent.cpp +++ b/Swift/QtUI/EventViewer/QtEvent.cpp @@ -1,88 +1,98 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/EventViewer/QtEvent.h" +#include <Swift/QtUI/EventViewer/QtEvent.h> -#include <QDateTime> #include <QColor> +#include <QDateTime> -#include "Swift/Controllers/XMPPEvents/MessageEvent.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" -#include "Swift/Controllers/XMPPEvents/MUCInviteEvent.h" +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h> +#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> +#include <Swift/Controllers/XMPPEvents/MessageEvent.h> +#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> -#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtEvent::QtEvent(boost::shared_ptr<StanzaEvent> event, bool active) : event_(event) { - active_ = active; +QtEvent::QtEvent(std::shared_ptr<StanzaEvent> event, bool active) : event_(event) { + active_ = active; } QVariant QtEvent::data(int role) { - switch (role) { - case Qt::ToolTipRole: return QVariant(text()).toString() + "\n" + B2QDATE(event_->getTime()).toString(); - case Qt::DisplayRole: return QVariant(text()); - case Qt::TextColorRole: return QColor(active_ ? Qt::black : Qt::darkGray); - case Qt::BackgroundColorRole: return QColor(active_ ? Qt::white : Qt::lightGray); - case SenderRole: return QVariant(sender()); - /*case StatusTextRole: return statusText_; - case AvatarRole: return avatar_; - case PresenceIconRole: return getPresenceIcon();*/ - default: return QVariant(); - } + switch (role) { + case Qt::ToolTipRole: return QVariant(text()).toString() + "\n" + B2QDATE(event_->getTime()).toString(); + case Qt::DisplayRole: return QVariant(text()); + case Qt::TextColorRole: return QColor(active_ ? Qt::black : Qt::darkGray); + case Qt::BackgroundColorRole: return QColor(active_ ? Qt::white : Qt::lightGray); + case SenderRole: return QVariant(sender()); + /*case StatusTextRole: return statusText_; + case AvatarRole: return avatar_; + case PresenceIconRole: return getPresenceIcon();*/ + default: return QVariant(); + } } QString QtEvent::sender() { - boost::shared_ptr<MessageEvent> messageEvent = boost::dynamic_pointer_cast<MessageEvent>(event_); - if (messageEvent) { - return P2QSTRING(messageEvent->getStanza()->getFrom().toString()); - } - boost::shared_ptr<SubscriptionRequestEvent> subscriptionRequestEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(event_); - if (subscriptionRequestEvent) { - return P2QSTRING(subscriptionRequestEvent->getJID().toBare().toString()); - } - boost::shared_ptr<ErrorEvent> errorEvent = boost::dynamic_pointer_cast<ErrorEvent>(event_); - if (errorEvent) { - return P2QSTRING(errorEvent->getJID().toBare().toString()); - } - boost::shared_ptr<MUCInviteEvent> mucInviteEvent = boost::dynamic_pointer_cast<MUCInviteEvent>(event_); - if (mucInviteEvent) { - return P2QSTRING(mucInviteEvent->getInviter().toString()); - } - return ""; + std::shared_ptr<MessageEvent> messageEvent = std::dynamic_pointer_cast<MessageEvent>(event_); + if (messageEvent) { + return P2QSTRING(messageEvent->getStanza()->getFrom().toString()); + } + std::shared_ptr<SubscriptionRequestEvent> subscriptionRequestEvent = std::dynamic_pointer_cast<SubscriptionRequestEvent>(event_); + if (subscriptionRequestEvent) { + return P2QSTRING(subscriptionRequestEvent->getJID().toBare().toString()); + } + std::shared_ptr<ErrorEvent> errorEvent = std::dynamic_pointer_cast<ErrorEvent>(event_); + if (errorEvent) { + return P2QSTRING(errorEvent->getJID().toBare().toString()); + } + std::shared_ptr<MUCInviteEvent> mucInviteEvent = std::dynamic_pointer_cast<MUCInviteEvent>(event_); + if (mucInviteEvent) { + return P2QSTRING(mucInviteEvent->getInviter().toString()); + } + std::shared_ptr<IncomingFileTransferEvent> incomingFTEvent = std::dynamic_pointer_cast<IncomingFileTransferEvent>(event_); + if (incomingFTEvent) { + return P2QSTRING(incomingFTEvent->getSender().toString()); + } + return ""; } QString QtEvent::text() { - boost::shared_ptr<MessageEvent> messageEvent = boost::dynamic_pointer_cast<MessageEvent>(event_); - if (messageEvent) { - return P2QSTRING(messageEvent->getStanza()->getBody()); - } - boost::shared_ptr<SubscriptionRequestEvent> subscriptionRequestEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(event_); - if (subscriptionRequestEvent) { - std::string reason = subscriptionRequestEvent->getReason(); - QString message; - if (reason.empty()) { - message = QString(QObject::tr("%1 would like to add you to their contact list.")).arg(subscriptionRequestEvent->getJID().toBare().toString().c_str()); - } - else { - message = QString(QObject::tr("%1 would like to add you to their contact list, saying '%2'")).arg(subscriptionRequestEvent->getJID().toBare().toString().c_str()).arg(reason.c_str()); - } - return message; - } - boost::shared_ptr<ErrorEvent> errorEvent = boost::dynamic_pointer_cast<ErrorEvent>(event_); - if (errorEvent) { - return P2QSTRING(errorEvent->getText()); - } - boost::shared_ptr<MUCInviteEvent> mucInviteEvent = boost::dynamic_pointer_cast<MUCInviteEvent>(event_); - if (mucInviteEvent) { - QString message = QString(QObject::tr("%1 has invited you to enter the %2 room.")).arg(P2QSTRING(mucInviteEvent->getInviter().toBare().toString())).arg(P2QSTRING(mucInviteEvent->getRoomJID().toString())); - return message; - } - return ""; + std::shared_ptr<MessageEvent> messageEvent = std::dynamic_pointer_cast<MessageEvent>(event_); + if (messageEvent) { + return P2QSTRING(messageEvent->getStanza()->getBody().get_value_or("")); + } + std::shared_ptr<SubscriptionRequestEvent> subscriptionRequestEvent = std::dynamic_pointer_cast<SubscriptionRequestEvent>(event_); + if (subscriptionRequestEvent) { + std::string reason = subscriptionRequestEvent->getReason(); + QString message; + if (reason.empty()) { + message = QString(QObject::tr("%1 would like to add you to their contact list.")).arg(subscriptionRequestEvent->getJID().toBare().toString().c_str()); + } + else { + message = QString(QObject::tr("%1 would like to add you to their contact list, saying '%2'")).arg(subscriptionRequestEvent->getJID().toBare().toString().c_str()).arg(reason.c_str()); + } + return message; + } + std::shared_ptr<ErrorEvent> errorEvent = std::dynamic_pointer_cast<ErrorEvent>(event_); + if (errorEvent) { + return P2QSTRING(errorEvent->getText()); + } + std::shared_ptr<MUCInviteEvent> mucInviteEvent = std::dynamic_pointer_cast<MUCInviteEvent>(event_); + if (mucInviteEvent) { + QString message = QString(QObject::tr("%1 has invited you to enter the %2 room.")).arg(P2QSTRING(mucInviteEvent->getInviter().toBare().toString())).arg(P2QSTRING(mucInviteEvent->getRoomJID().toString())); + return message; + } + std::shared_ptr<IncomingFileTransferEvent> incomingFTEvent = std::dynamic_pointer_cast<IncomingFileTransferEvent>(event_); + if (incomingFTEvent) { + QString message = QString(QObject::tr("%1 would like to send a file to you.")).arg(P2QSTRING(incomingFTEvent->getSender().toBare().toString())); + return message; + } + return ""; } } diff --git a/Swift/QtUI/EventViewer/QtEvent.h b/Swift/QtUI/EventViewer/QtEvent.h index 11efd60..cb78b00 100644 --- a/Swift/QtUI/EventViewer/QtEvent.h +++ b/Swift/QtUI/EventViewer/QtEvent.h @@ -1,32 +1,32 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/shared_ptr.hpp> +#include <memory> #include <QVariant> -#include "Swift/Controllers/XMPPEvents/StanzaEvent.h" +#include <Swift/Controllers/XMPPEvents/StanzaEvent.h> namespace Swift { - class QtEvent { - public: - QtEvent(boost::shared_ptr<StanzaEvent> event, bool active); - QVariant data(int role); - boost::shared_ptr<StanzaEvent> getEvent() { return event_; } - enum EventRoles { - SenderRole = Qt::UserRole + class QtEvent { + public: + QtEvent(std::shared_ptr<StanzaEvent> event, bool active); + QVariant data(int role); + std::shared_ptr<StanzaEvent> getEvent() { return event_; } + enum EventRoles { + SenderRole = Qt::UserRole - }; + }; - private: - QString text(); - QString sender(); - boost::shared_ptr<StanzaEvent> event_; - bool active_; - }; + private: + QString text(); + QString sender(); + std::shared_ptr<StanzaEvent> event_; + bool active_; + }; } diff --git a/Swift/QtUI/EventViewer/QtEventWindow.cpp b/Swift/QtUI/EventViewer/QtEventWindow.cpp index 35473b6..c8d1f0c 100644 --- a/Swift/QtUI/EventViewer/QtEventWindow.cpp +++ b/Swift/QtUI/EventViewer/QtEventWindow.cpp @@ -1,117 +1,123 @@ - /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/EventViewer/QtEventWindow.h" +#include <Swift/QtUI/EventViewer/QtEventWindow.h> -#include <QtDebug> #include <QBoxLayout> -#include <QPushButton> #include <QMessageBox> +#include <QPushButton> +#include <QTreeView> -#include "Swift/Controllers/XMPPEvents/MessageEvent.h" -#include "Swift/Controllers/XMPPEvents/ErrorEvent.h" -#include "Swift/QtUI/QtSubscriptionRequestWindow.h" -#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" -#include "Swift/Controllers/XMPPEvents/MUCInviteEvent.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" +#include <Swiften/Base/Platform.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/XMPPEvents/ErrorEvent.h> +#include <Swift/Controllers/XMPPEvents/IncomingFileTransferEvent.h> +#include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h> +#include <Swift/Controllers/XMPPEvents/MessageEvent.h> +#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> -#include "Swiften/Base/Platform.h" +#include <Swift/QtUI/QtSubscriptionRequestWindow.h> namespace Swift { QtEventWindow::QtEventWindow(UIEventStream* eventStream) : EventWindow(false) { - QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); - layout->setContentsMargins(0,0,0,0); - layout->setSpacing(0); - - view_ = new QTreeView(this); - layout->addWidget(view_); - eventStream_ = eventStream; - model_ = new EventModel(); - view_->setModel(model_); - delegate_ = new EventDelegate(); - view_->setItemDelegate(delegate_); - view_->setHeaderHidden(true); + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(0); + + view_ = new QTreeView(this); + layout->addWidget(view_); + eventStream_ = eventStream; + model_ = new EventModel(); + view_->setModel(model_); + delegate_ = new EventDelegate(); + view_->setItemDelegate(delegate_); + view_->setHeaderHidden(true); #ifdef SWIFT_PLATFORM_MACOSX - view_->setAlternatingRowColors(true); + view_->setAlternatingRowColors(true); #endif - view_->setAnimated(true); - view_->setIndentation(0); - view_->setRootIsDecorated(true); - - readButton_ = new QPushButton(tr("Display Notice"), this); - layout->addWidget(readButton_); - readButton_->setEnabled(false); - connect(readButton_, SIGNAL(clicked()), this, SLOT(handleReadClicked())); - connect(view_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleItemClicked(const QModelIndex&))); - connect(view_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); - + view_->setAnimated(true); + view_->setIndentation(0); + view_->setRootIsDecorated(true); + + readButton_ = new QPushButton(tr("Display Notice"), this); + layout->addWidget(readButton_); + readButton_->setEnabled(false); + connect(readButton_, SIGNAL(clicked()), this, SLOT(handleReadClicked())); + connect(view_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleItemClicked(const QModelIndex&))); + connect(view_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); } QtEventWindow::~QtEventWindow() { - delete model_; - delete delegate_; - /* Not view_ because this is the parent */ + delete model_; + delete delegate_; + /* Not view_ because this is the parent */ } void QtEventWindow::handleItemClicked(const QModelIndex&) { - readButton_->setEnabled(true); + readButton_->setEnabled(true); } void QtEventWindow::handleReadClicked() { - QModelIndex index = view_->currentIndex(); - if (!index.isValid()) { - return; - } - handleItemActivated(index); + QModelIndex index = view_->currentIndex(); + if (!index.isValid()) { + return; + } + handleItemActivated(index); } void QtEventWindow::handleItemActivated(const QModelIndex& item) { - QtEvent* event = model_->getItem(item.row()); - boost::shared_ptr<MessageEvent> messageEvent = boost::dynamic_pointer_cast<MessageEvent>(event->getEvent()); - boost::shared_ptr<SubscriptionRequestEvent> subscriptionEvent = boost::dynamic_pointer_cast<SubscriptionRequestEvent>(event->getEvent()); - boost::shared_ptr<MUCInviteEvent> mucInviteEvent = boost::dynamic_pointer_cast<MUCInviteEvent>(event->getEvent()); - boost::shared_ptr<ErrorEvent> errorEvent = boost::dynamic_pointer_cast<ErrorEvent>(event->getEvent()); - - if (messageEvent) { - if (messageEvent->getStanza()->getType() == Message::Groupchat) { - eventStream_->send(boost::shared_ptr<UIEvent>(new JoinMUCUIEvent(messageEvent->getStanza()->getFrom().toBare(), messageEvent->getStanza()->getTo().getResource()))); - } else { - eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(messageEvent->getStanza()->getFrom()))); - } - } else if (subscriptionEvent) { - QtSubscriptionRequestWindow* window = QtSubscriptionRequestWindow::getWindow(subscriptionEvent, this); - window->show(); - } else if (mucInviteEvent) { - eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(mucInviteEvent->getInviter()))); - mucInviteEvent->conclude(); - } else { - if (errorEvent) { - errorEvent->conclude(); - } - QMessageBox msgBox; - msgBox.setText(model_->data(item, Qt::DisplayRole).toString()); - msgBox.exec(); - } + QtEvent* event = model_->getItem(item.row()); + std::shared_ptr<MessageEvent> messageEvent = std::dynamic_pointer_cast<MessageEvent>(event->getEvent()); + std::shared_ptr<SubscriptionRequestEvent> subscriptionEvent = std::dynamic_pointer_cast<SubscriptionRequestEvent>(event->getEvent()); + std::shared_ptr<MUCInviteEvent> mucInviteEvent = std::dynamic_pointer_cast<MUCInviteEvent>(event->getEvent()); + std::shared_ptr<IncomingFileTransferEvent> incomingFTEvent = std::dynamic_pointer_cast<IncomingFileTransferEvent>(event->getEvent()); + std::shared_ptr<ErrorEvent> errorEvent = std::dynamic_pointer_cast<ErrorEvent>(event->getEvent()); + + if (messageEvent) { + if (messageEvent->getStanza()->getType() == Message::Groupchat) { + eventStream_->send(std::make_shared<JoinMUCUIEvent>(messageEvent->getStanza()->getFrom().toBare(), messageEvent->getStanza()->getTo().getResource())); + } else { + eventStream_->send(std::make_shared<RequestChatUIEvent>(messageEvent->getStanza()->getFrom())); + } + } else if (subscriptionEvent) { + QtSubscriptionRequestWindow* window = QtSubscriptionRequestWindow::getWindow(subscriptionEvent, this); + window->show(); + } else if (mucInviteEvent) { + eventStream_->send(std::make_shared<RequestChatUIEvent>(mucInviteEvent->getInviter())); + mucInviteEvent->conclude(); + } else if (incomingFTEvent) { + eventStream_->send(std::make_shared<RequestChatUIEvent>(incomingFTEvent->getSender())); + incomingFTEvent->conclude(); + } else { + if (errorEvent) { + errorEvent->conclude(); + } + QMessageBox msgBox; + msgBox.setText(event->data(Qt::DisplayRole).toString()); + msgBox.exec(); + } } -void QtEventWindow::addEvent(boost::shared_ptr<StanzaEvent> event, bool active) { - view_->clearSelection(); - model_->addEvent(event, active); - emit onNewEventCountUpdated(model_->getNewEventCount()); +void QtEventWindow::addEvent(std::shared_ptr<StanzaEvent> event, bool active) { + view_->clearSelection(); + model_->addEvent(event, active); + emit onNewEventCountUpdated(model_->getNewEventCount()); + readButton_->setEnabled(model_->rowCount() > 0); } -void QtEventWindow::removeEvent(boost::shared_ptr<StanzaEvent> event) { - view_->clearSelection(); - model_->removeEvent(event); - emit onNewEventCountUpdated(model_->getNewEventCount()); +void QtEventWindow::removeEvent(std::shared_ptr<StanzaEvent> event) { + view_->clearSelection(); + model_->removeEvent(event); + emit onNewEventCountUpdated(model_->getNewEventCount()); + readButton_->setEnabled(model_->rowCount() > 0); } } diff --git a/Swift/QtUI/EventViewer/QtEventWindow.h b/Swift/QtUI/EventViewer/QtEventWindow.h index a20e5ab..7ae33ec 100644 --- a/Swift/QtUI/EventViewer/QtEventWindow.h +++ b/Swift/QtUI/EventViewer/QtEventWindow.h @@ -1,44 +1,48 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/shared_ptr.hpp> +#include <memory> -#include <QTreeView> +#include <Swift/Controllers/UIInterfaces/EventWindow.h> -#include "Swift/Controllers/UIInterfaces/EventWindow.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/QtUI/EventViewer/EventView.h" -#include "Swift/QtUI/EventViewer/EventModel.h" -#include "Swift/QtUI/EventViewer/EventDelegate.h" +#include <Swift/QtUI/EventViewer/EventDelegate.h> +#include <Swift/QtUI/EventViewer/EventModel.h> +#include <Swift/QtUI/EventViewer/EventView.h> class QPushButton; +class QTreeView; namespace Swift { - class QtEventWindow : public QWidget, public EventWindow { - Q_OBJECT - public: - QtEventWindow(UIEventStream* eventStream); - ~QtEventWindow(); - void addEvent(boost::shared_ptr<StanzaEvent> event, bool active); - void removeEvent(boost::shared_ptr<StanzaEvent> event); - signals: - void onNewEventCountUpdated(int count); - private slots: - void handleItemActivated(const QModelIndex& item); - void handleItemClicked(const QModelIndex& item); - void handleReadClicked(); - private: - EventModel* model_; - EventDelegate* delegate_; - UIEventStream* eventStream_; - QTreeView* view_; - QPushButton* readButton_; - }; - + class UIEventStream; + + class QtEventWindow : public QWidget, public EventWindow { + Q_OBJECT + public: + QtEventWindow(UIEventStream* eventStream); + ~QtEventWindow(); + void addEvent(std::shared_ptr<StanzaEvent> event, bool active); + void removeEvent(std::shared_ptr<StanzaEvent> event); + + signals: + void onNewEventCountUpdated(int count); + + private slots: + void handleItemActivated(const QModelIndex& item); + void handleItemClicked(const QModelIndex& item); + void handleReadClicked(); + + private: + EventModel* model_; + EventDelegate* delegate_; + UIEventStream* eventStream_; + QTreeView* view_; + QPushButton* readButton_; + }; + } diff --git a/Swift/QtUI/EventViewer/TwoLineDelegate.cpp b/Swift/QtUI/EventViewer/TwoLineDelegate.cpp index 08491b9..5ba24ec 100644 --- a/Swift/QtUI/EventViewer/TwoLineDelegate.cpp +++ b/Swift/QtUI/EventViewer/TwoLineDelegate.cpp @@ -1,20 +1,25 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "TwoLineDelegate.h" +#include <Swift/QtUI/EventViewer/TwoLineDelegate.h> -#include <QPen> +#include <QColor> #include <QPainter> -#include <QDebug> +#include <QPen> namespace Swift { + +namespace { + const QColor secondLineColor = QColor(160,160,160); +} + TwoLineDelegate::TwoLineDelegate(int firstRole, int secondRole, bool wrap) { - firstRole_ = firstRole; - secondRole_ = secondRole; - wrap_ = wrap; + firstRole_ = firstRole; + secondRole_ = secondRole; + wrap_ = wrap; } TwoLineDelegate::~TwoLineDelegate() { @@ -22,41 +27,41 @@ TwoLineDelegate::~TwoLineDelegate() { } QSize TwoLineDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, QtEvent* /*event*/ ) const { - QFontMetrics nameMetrics(common_.nameFont); - QFontMetrics statusMetrics(common_.detailFont); - int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); - return QSize(150, sizeByText); + QFontMetrics nameMetrics(common_.nameFont); + QFontMetrics statusMetrics(common_.detailFont); + int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); + return QSize(150, sizeByText); } void TwoLineDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, QtEvent* event) const { - painter->save(); - QRect fullRegion(option.rect); - if ( option.state & QStyle::State_Selected ) { - painter->fillRect(fullRegion, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } else { - QColor nameColor = event->data(Qt::TextColorRole).value<QColor>(); - painter->setPen(QPen(nameColor)); - } - - QFontMetrics nameMetrics(common_.nameFont); - painter->setFont(common_.nameFont); - int extraFontWidth = nameMetrics.width("H"); - int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; - QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); - - int nameHeight = nameMetrics.height() + common_.verticalMargin; - QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); - - DelegateCommons::drawElidedText(painter, nameRegion, event->data(firstRole_).toString()); - - painter->setFont(common_.detailFont); - painter->setPen(QPen(QColor(160,160,160))); - - QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); - DelegateCommons::drawElidedText(painter, detailRegion, event->data(secondRole_).toString()); - - painter->restore(); + painter->save(); + QRect fullRegion(option.rect); + if ( option.state & QStyle::State_Selected ) { + painter->fillRect(fullRegion, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } else { + QColor nameColor = event->data(Qt::TextColorRole).value<QColor>(); + painter->setPen(QPen(nameColor)); + } + + QFontMetrics nameMetrics(common_.nameFont); + painter->setFont(common_.nameFont); + int extraFontWidth = nameMetrics.width("H"); + int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; + QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); + + int nameHeight = nameMetrics.height() + common_.verticalMargin; + QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); + + DelegateCommons::drawElidedText(painter, nameRegion, event->data(firstRole_).toString()); + + painter->setFont(common_.detailFont); + painter->setPen(QPen(secondLineColor)); + + QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); + DelegateCommons::drawElidedText(painter, detailRegion, event->data(secondRole_).toString()); + + painter->restore(); } } diff --git a/Swift/QtUI/EventViewer/TwoLineDelegate.h b/Swift/QtUI/EventViewer/TwoLineDelegate.h index 15a4476..089f97a 100644 --- a/Swift/QtUI/EventViewer/TwoLineDelegate.h +++ b/Swift/QtUI/EventViewer/TwoLineDelegate.h @@ -1,29 +1,29 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QStyledItemDelegate> -#include "Swift/QtUI/Roster/DelegateCommons.h" -#include "QtEvent.h" +#include <Swift/QtUI/EventViewer/QtEvent.h> +#include <Swift/QtUI/Roster/DelegateCommons.h> namespace Swift { - class TwoLineDelegate { - public: - TwoLineDelegate(int firstRole, int secondRole, bool wrap); - ~TwoLineDelegate(); - QSize sizeHint(const QStyleOptionViewItem& option, QtEvent* event) const; - void paint(QPainter* painter, const QStyleOptionViewItem& option, QtEvent* event) const; - private: - DelegateCommons common_; - int firstRole_; - int secondRole_; - bool wrap_; - }; + class TwoLineDelegate { + public: + TwoLineDelegate(int firstRole, int secondRole, bool wrap); + ~TwoLineDelegate(); + QSize sizeHint(const QStyleOptionViewItem& option, QtEvent* event) const; + void paint(QPainter* painter, const QStyleOptionViewItem& option, QtEvent* event) const; + private: + DelegateCommons common_; + int firstRole_; + int secondRole_; + bool wrap_; + }; } diff --git a/Swift/QtUI/EventViewer/main.cpp b/Swift/QtUI/EventViewer/main.cpp index 07a437c..492599e 100644 --- a/Swift/QtUI/EventViewer/main.cpp +++ b/Swift/QtUI/EventViewer/main.cpp @@ -1,31 +1,33 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <QtGui> -#include "EventView.h" -#include "EventModel.h" -#include "QtEventWindow.h" -#include "Swiften/Events/MessageEvent.h" -#include "Swiften/Events/ErrorEvent.h" -#include "Swiften/JID/JID.h" +#include <EventModel.h> +#include <EventView.h> + +#include <Swiften/Events/ErrorEvent.h> +#include <Swiften/Events/MessageEvent.h> +#include <Swiften/JID/JID.h> + +#include <Swift/QtUI/EventViewer/QtEventWindow.h> int main(int argc, char *argv[]) { - QApplication app(argc, argv); - Swift::UIEventStream eventStream; - Swift::QtEventWindow* viewer = new Swift::QtEventWindow(&eventStream); - viewer->show(); - boost::shared_ptr<Swift::Message> message1(new Swift::Message()); - message1->setBody("Oooh, shiny"); - boost::shared_ptr<Swift::MessageEvent> event1(new Swift::MessageEvent(message1)); - viewer->addEvent(boost::dynamic_pointer_cast<Swift::StanzaEvent>(event1), true); - for (int i = 0; i < 100; i++) { - viewer->addEvent(boost::dynamic_pointer_cast<Swift::StanzaEvent>(event1), false); - } - viewer->addEvent(boost::dynamic_pointer_cast<Swift::StanzaEvent>(boost::make_shared<Swift::ErrorEvent>(Swift::JID("me@example.com"), "Something bad did happen to you.")), true); - return app.exec(); + QApplication app(argc, argv); + Swift::UIEventStream eventStream; + Swift::QtEventWindow* viewer = new Swift::QtEventWindow(&eventStream); + viewer->show(); + std::shared_ptr<Swift::Message> message1(new Swift::Message()); + message1->setBody("Oooh, shiny"); + std::shared_ptr<Swift::MessageEvent> event1(new Swift::MessageEvent(message1)); + viewer->addEvent(std::dynamic_pointer_cast<Swift::StanzaEvent>(event1), true); + for (int i = 0; i < 100; i++) { + viewer->addEvent(std::dynamic_pointer_cast<Swift::StanzaEvent>(event1), false); + } + viewer->addEvent(std::dynamic_pointer_cast<Swift::StanzaEvent>(std::make_shared<Swift::ErrorEvent>(Swift::JID("me@example.com"), "Something bad did happen to you.")), true); + return app.exec(); } diff --git a/Swift/QtUI/FlowLayout.cpp b/Swift/QtUI/FlowLayout.cpp new file mode 100644 index 0000000..8a12841 --- /dev/null +++ b/Swift/QtUI/FlowLayout.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets> +#include "FlowLayout.h" +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) + : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} + +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} + +int FlowLayout::horizontalSpacing() const +{ + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FlowLayout::verticalSpacing() const +{ + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +int FlowLayout::count() const +{ + return itemList.size(); +} + +QLayoutItem *FlowLayout::itemAt(int index) const +{ + return itemList.value(index); +} + +QLayoutItem *FlowLayout::takeAt(int index) +{ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return nullptr; +} + +Qt::Orientations FlowLayout::expandingDirections() const +{ + return nullptr; +} + +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + QLayoutItem *item; + foreach (item, itemList) + size = size.expandedTo(item->minimumSize()); + + size += QSize(2*margin(), 2*margin()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const +{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + QLayoutItem *item; + foreach (item, itemList) { + QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const +{ + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast<QWidget *>(parent); + return pw->style()->pixelMetric(pm, nullptr, pw); + } else { + return static_cast<QLayout *>(parent)->spacing(); + } +} diff --git a/Swift/QtUI/FlowLayout.h b/Swift/QtUI/FlowLayout.h new file mode 100644 index 0000000..29d527f --- /dev/null +++ b/Swift/QtUI/FlowLayout.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include <QLayout> +#include <QRect> +#include <QStyle> +class FlowLayout : public QLayout +{ +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout() override; + + void addItem(QLayoutItem *item) override; + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int) const override; + int count() const override; + QLayoutItem *itemAt(int index) const override; + QSize minimumSize() const override; + void setGeometry(const QRect &rect) override; + QSize sizeHint() const override; + QLayoutItem *takeAt(int index) override; + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList<QLayoutItem *> itemList; + int m_hSpace; + int m_vSpace; +}; + +#endif // FLOWLAYOUT_H diff --git a/Swift/QtUI/FreeDesktopNotifier.cpp b/Swift/QtUI/FreeDesktopNotifier.cpp index 1f1ccda..16f6a11 100644 --- a/Swift/QtUI/FreeDesktopNotifier.cpp +++ b/Swift/QtUI/FreeDesktopNotifier.cpp @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2013 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma GCC diagnostic ignored "-Wredundant-decls" @@ -23,37 +23,37 @@ FreeDesktopNotifier::FreeDesktopNotifier(const std::string& name) : applicationN } void FreeDesktopNotifier::showMessage(Type type, const std::string& subject, const std::string& description, const boost::filesystem::path& picture, boost::function<void()>) { - QDBusConnection bus = QDBusConnection::sessionBus(); - if (!bus.isConnected()) { - return; - } - std::vector<Notifier::Type> defaultTypes = getDefaultTypes(); - if (std::find(defaultTypes.begin(), defaultTypes.end(), type) == defaultTypes.end()) { - return; - } - - QString body = description.c_str(); - body = body.replace("&", "&"); - body = body.replace("<", "<"); - body = body.replace(">", ">"); - - int timeout = (type == IncomingMessage || type == SystemMessage) ? DEFAULT_MESSAGE_NOTIFICATION_TIMEOUT_SECONDS : DEFAULT_STATUS_NOTIFICATION_TIMEOUT_SECONDS; - - QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "", "Notify"); - - QStringList actions; - QMap<QString, QVariant> hints; - hints["x-canonical-append"] = QString("allowed"); - msg << applicationName.c_str(); - msg << quint32(0); // ID of previous notification to replace - msg << P2QSTRING(pathToString(imageScaler.getScaledImage(picture, 48))); // Icon to display - msg << subject.c_str(); // Summary / Header of the message to display - msg << body; // Body of the message to display - msg << actions; // Actions from which the user may choose - msg << hints; // Hints to the server displaying the message - msg << qint32(timeout*1000); // Timeout in milliseconds - - bus.asyncCall(msg); + QDBusConnection bus = QDBusConnection::sessionBus(); + if (!bus.isConnected()) { + return; + } + std::vector<Notifier::Type> defaultTypes = getDefaultTypes(); + if (std::find(defaultTypes.begin(), defaultTypes.end(), type) == defaultTypes.end()) { + return; + } + + QString body = description.c_str(); + body = body.replace("&", "&"); + body = body.replace("<", "<"); + body = body.replace(">", ">"); + + int timeout = (type == IncomingMessage || type == SystemMessage) ? DEFAULT_MESSAGE_NOTIFICATION_TIMEOUT_SECONDS : DEFAULT_STATUS_NOTIFICATION_TIMEOUT_SECONDS; + + QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "", "Notify"); + + QStringList actions; + QMap<QString, QVariant> hints; + hints["x-canonical-append"] = QString("allowed"); + msg << applicationName.c_str(); + msg << quint32(0); // ID of previous notification to replace + msg << P2QSTRING(pathToString(imageScaler.getScaledImage(picture, 48))); // Icon to display + msg << subject.c_str(); // Summary / Header of the message to display + msg << body; // Body of the message to display + msg << actions; // Actions from which the user may choose + msg << hints; // Hints to the server displaying the message + msg << qint32(timeout*1000); // Timeout in milliseconds + + bus.asyncCall(msg); } } diff --git a/Swift/QtUI/FreeDesktopNotifier.h b/Swift/QtUI/FreeDesktopNotifier.h index 6da7621..dab7e73 100644 --- a/Swift/QtUI/FreeDesktopNotifier.h +++ b/Swift/QtUI/FreeDesktopNotifier.h @@ -1,24 +1,25 @@ /* - * Copyright (c) 2010 -2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "SwifTools/Notifier/Notifier.h" -#include "QtCachedImageScaler.h" +#include <SwifTools/Notifier/Notifier.h> + +#include <Swift/QtUI/QtCachedImageScaler.h> namespace Swift { - class FreeDesktopNotifier : public Notifier { - public: - FreeDesktopNotifier(const std::string& name); + class FreeDesktopNotifier : public Notifier { + public: + FreeDesktopNotifier(const std::string& name); - virtual void showMessage(Type type, const std::string& subject, const std::string& description, const boost::filesystem::path& picture, boost::function<void()> callback); - virtual void purgeCallbacks() {} + 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() {} - private: - std::string applicationName; - QtCachedImageScaler imageScaler; - }; + private: + std::string applicationName; + QtCachedImageScaler imageScaler; + }; } diff --git a/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp b/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp index be92efe..0163e03 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp +++ b/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp @@ -1,17 +1,18 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <QPen> +#include <Swift/QtUI/MUCSearch/MUCSearchDelegate.h> + #include <QPainter> +#include <QPen> -#include "Swift/QtUI/MUCSearch/MUCSearchDelegate.h" -#include "Swift/QtUI/Roster/GroupItemDelegate.h" -#include "Swift/QtUI/MUCSearch/MUCSearchItem.h" -#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h" -#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h" +#include <Swift/QtUI/MUCSearch/MUCSearchItem.h> +#include <Swift/QtUI/MUCSearch/MUCSearchRoomItem.h> +#include <Swift/QtUI/MUCSearch/MUCSearchServiceItem.h> +#include <Swift/QtUI/Roster/GroupItemDelegate.h> namespace Swift { @@ -24,63 +25,63 @@ MUCSearchDelegate::~MUCSearchDelegate() { } // QSize MUCSearchDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { -// // MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); -// // if (item && dynamic_cast<MUCSearchMUCItem*>(item)) { -// // return mucSizeHint(option, index); -// // } else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) { -// // return groupDelegate_->sizeHint(option, index); -// // } -// return QStyledItemDelegate::sizeHint(option, index); +// // MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); +// // if (item && dynamic_cast<MUCSearchMUCItem*>(item)) { +// // return mucSizeHint(option, index); +// // } else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) { +// // return groupDelegate_->sizeHint(option, index); +// // } +// return QStyledItemDelegate::sizeHint(option, index); // } // QSize MUCSearchDelegate::mucSizeHint(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); +// QFontMetrics nameMetrics(common_.nameFont); +// QFontMetrics statusMetrics(common_.detailFont); +// int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); +// return QSize(150, sizeByText); // } // void MUCSearchDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { -// MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); -// if (item && dynamic_cast<MUCSearchMUCItem*>(item)) { -// paintMUC(painter, option, dynamic_cast<MUCSearchMUCItem*>(item)); -// } else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) { -// MUCSearchGroupItem* group = dynamic_cast<MUCSearchGroupItem*>(item); -// groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); -// } else { -// QStyledItemDelegate::paint(painter, option, index); -// } +// MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); +// if (item && dynamic_cast<MUCSearchMUCItem*>(item)) { +// paintMUC(painter, option, dynamic_cast<MUCSearchMUCItem*>(item)); +// } else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) { +// MUCSearchGroupItem* group = dynamic_cast<MUCSearchGroupItem*>(item); +// groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); +// } else { +// QStyledItemDelegate::paint(painter, option, index); +// } // } // void MUCSearchDelegate::paintMUC(QPainter* painter, const QStyleOptionViewItem& option, MUCSearchMUCItem* item) const { -// painter->save(); -// QRect fullRegion(option.rect); -// if ( option.state & QStyle::State_Selected ) { -// painter->fillRect(fullRegion, option.palette.highlight()); -// painter->setPen(option.palette.highlightedText().color()); -// } else { -// QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); -// painter->setPen(QPen(nameColor)); -// } - -// QFontMetrics nameMetrics(common_.nameFont); -// painter->setFont(common_.nameFont); -// int extraFontWidth = nameMetrics.width("H"); -// int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; -// QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); - -// int nameHeight = nameMetrics.height() + common_.verticalMargin; -// QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); - -// painter->drawText(nameRegion, Qt::AlignTop, item->data(Qt::DisplayRole).toString()); - -// painter->setFont(common_.detailFont); -// painter->setPen(QPen(QColor(160,160,160))); - -// QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); -// painter->drawText(detailRegion, Qt::AlignTop, item->data(DetailTextRole).toString()); - -// painter->restore(); +// painter->save(); +// QRect fullRegion(option.rect); +// if ( option.state & QStyle::State_Selected ) { +// painter->fillRect(fullRegion, option.palette.highlight()); +// painter->setPen(option.palette.highlightedText().color()); +// } else { +// QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); +// painter->setPen(QPen(nameColor)); +// } + +// QFontMetrics nameMetrics(common_.nameFont); +// painter->setFont(common_.nameFont); +// int extraFontWidth = nameMetrics.width("H"); +// int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; +// QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); + +// int nameHeight = nameMetrics.height() + common_.verticalMargin; +// QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); + +// painter->drawText(nameRegion, Qt::AlignTop, item->data(Qt::DisplayRole).toString()); + +// painter->setFont(common_.detailFont); +// painter->setPen(QPen(QColor(160,160,160))); + +// QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); +// painter->drawText(detailRegion, Qt::AlignTop, item->data(DetailTextRole).toString()); + +// painter->restore(); // } } diff --git a/Swift/QtUI/MUCSearch/MUCSearchDelegate.h b/Swift/QtUI/MUCSearch/MUCSearchDelegate.h index 2662bb9..5bf9646 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchDelegate.h +++ b/Swift/QtUI/MUCSearch/MUCSearchDelegate.h @@ -1,28 +1,28 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QStyledItemDelegate> -#include "Swift/QtUI/Roster/DelegateCommons.h" +#include <Swift/QtUI/Roster/DelegateCommons.h> namespace Swift { - class MUCSearchDelegate : public QStyledItemDelegate { - public: - MUCSearchDelegate(); - ~MUCSearchDelegate(); - /* QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; */ - /* void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; */ - private: -// void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, MUCSearchMUCItem* item) const; -// QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const; + class MUCSearchDelegate : public QStyledItemDelegate { + public: + MUCSearchDelegate(); + ~MUCSearchDelegate(); + /* QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; */ + /* void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; */ + private: +// void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, MUCSearchMUCItem* item) const; +// QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const; - DelegateCommons common_; - }; + DelegateCommons common_; + }; } diff --git a/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.cpp b/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.cpp index f392859..3a3b841 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.cpp +++ b/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.cpp @@ -1,38 +1,44 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h> -#include <Swift/QtUI/MUCSearch/MUCSearchServiceItem.h> -#include <QFont> +#include <memory> + #include <QColor> +#include <QFont> + +#include <Swift/QtUI/MUCSearch/MUCSearchServiceItem.h> namespace Swift { -MUCSearchEmptyItem::MUCSearchEmptyItem(MUCSearchServiceItem* parent) : parent(parent) { - parent->addRoom(this); +MUCSearchEmptyItem::MUCSearchEmptyItem() { +} + +void MUCSearchEmptyItem::setParent(std::weak_ptr<MUCSearchServiceItem> parent) { + parent_ = parent; } -MUCSearchServiceItem* MUCSearchEmptyItem::getParent() { - return parent; +std::shared_ptr<MUCSearchServiceItem> MUCSearchEmptyItem::getParent() { + return parent_.lock(); } QVariant MUCSearchEmptyItem::data(int role) { - switch (role) { - case Qt::DisplayRole: - return QVariant(QObject::tr("No rooms found")); - case Qt::FontRole: { - QFont font; - font.setItalic(true); - return font; - } - case Qt::ForegroundRole: - return QColor(Qt::gray); - default: - return QVariant(); - } + switch (role) { + case Qt::DisplayRole: + return QVariant(QObject::tr("No rooms found")); + case Qt::FontRole: { + QFont font; + font.setItalic(true); + return font; + } + case Qt::ForegroundRole: + return QColor(Qt::gray); + default: + return QVariant(); + } } } diff --git a/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h b/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h index d752ae3..8a5bb06 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h +++ b/Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h @@ -1,25 +1,28 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <memory> + #include <Swift/QtUI/MUCSearch/MUCSearchItem.h> namespace Swift { - class MUCSearchServiceItem; + class MUCSearchServiceItem; - class MUCSearchEmptyItem : public MUCSearchItem { - public: - MUCSearchEmptyItem(MUCSearchServiceItem* parent); + class MUCSearchEmptyItem : public MUCSearchItem { + public: + MUCSearchEmptyItem(); - MUCSearchServiceItem* getParent(); + void setParent(std::weak_ptr<MUCSearchServiceItem> parent); + std::shared_ptr<MUCSearchServiceItem> getParent(); - QVariant data(int role); + QVariant data(int role); - private: - MUCSearchServiceItem* parent; - }; + private: + std::weak_ptr<MUCSearchServiceItem> parent_; + }; } diff --git a/Swift/QtUI/MUCSearch/MUCSearchItem.h b/Swift/QtUI/MUCSearch/MUCSearchItem.h index 7bac25d..08daa21 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchItem.h +++ b/Swift/QtUI/MUCSearch/MUCSearchItem.h @@ -1,17 +1,23 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <memory> + #include <QVariant> namespace Swift { - class MUCSearchItem { - public: - virtual ~MUCSearchItem() {} - virtual QVariant data(int role) = 0; - }; + +class MUCSearchServiceItem; + +class MUCSearchItem { + public: + virtual ~MUCSearchItem() {} + virtual void setParent(std::weak_ptr<MUCSearchServiceItem>) { } + virtual QVariant data(int role) = 0; + }; } diff --git a/Swift/QtUI/MUCSearch/MUCSearchModel.cpp b/Swift/QtUI/MUCSearch/MUCSearchModel.cpp index c657190..cc36f5f 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchModel.cpp +++ b/Swift/QtUI/MUCSearch/MUCSearchModel.cpp @@ -1,11 +1,14 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/MUCSearch/MUCSearchModel.h" -#include "Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h" +#include <Swift/QtUI/MUCSearch/MUCSearchModel.h> + +#include <memory> + +#include <Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h> namespace Swift { @@ -13,78 +16,91 @@ MUCSearchModel::MUCSearchModel() { } void MUCSearchModel::clear() { - emit layoutAboutToBeChanged(); - services_.clear(); - emit layoutChanged(); + // We need to reset the model, so that model indices containing raw pointers + // to MUCSearchServiceItems are invalaidated before we delete the + // MUCSearchServiceItems. + emit beginResetModel(); + services_.clear(); + emit endResetModel(); } -void MUCSearchModel::addService(MUCSearchServiceItem* service) { - emit layoutAboutToBeChanged(); - services_.push_back(service); - emit layoutChanged(); +void MUCSearchModel::addService(std::shared_ptr<MUCSearchServiceItem> service) { + emit layoutAboutToBeChanged(); + if (sortOrder_) { + service->setSorting(*sortOrder_); + } + services_.push_back(service); + emit layoutChanged(); } int MUCSearchModel::columnCount(const QModelIndex& /*parent*/) const { - return 1; + return 1; } QVariant MUCSearchModel::data(const QModelIndex& index, int role) const { - return index.isValid() ? static_cast<MUCSearchItem*>(index.internalPointer())->data(role) : QVariant(); + return index.isValid() ? static_cast<MUCSearchItem*>(index.internalPointer())->data(role) : QVariant(); } QModelIndex MUCSearchModel::index(int row, int column, const QModelIndex & parent) const { - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } - - if (parent.isValid()) { - MUCSearchServiceItem* parentItem = static_cast<MUCSearchServiceItem*>(parent.internalPointer()); - return row < parentItem->rowCount() ? createIndex(row, column, parentItem->getItem(row)) : QModelIndex(); - } else { - return row < services_.size() ? createIndex(row, column, services_[row]) : QModelIndex(); - } - + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + if (parent.isValid()) { + MUCSearchServiceItem* parentItem = static_cast<MUCSearchServiceItem*>(parent.internalPointer()); + return row < parentItem->rowCount() ? createIndex(row, column, parentItem->getItem(row)) : QModelIndex(); + } else { + return row < services_.size() ? createIndex(row, column, services_[row].get()) : QModelIndex(); + } } QModelIndex MUCSearchModel::parent(const QModelIndex& index) const { - if (!index.isValid()) { - return QModelIndex(); - } - MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); - if (!item) { - return QModelIndex(); - } - else if (dynamic_cast<MUCSearchServiceItem*>(item)) { - return QModelIndex(); - } - - MUCSearchServiceItem* parent = NULL; - if (MUCSearchRoomItem* roomItem = dynamic_cast<MUCSearchRoomItem*>(item)) { - parent = roomItem->getParent(); - } - else if (MUCSearchEmptyItem* emptyItem = dynamic_cast<MUCSearchEmptyItem*>(item)) { - parent = emptyItem->getParent(); - } - if (parent) { - int row = services_.indexOf(parent); - return createIndex(row, 1, parent); - } - else { - return QModelIndex(); - } + if (!index.isValid()) { + return QModelIndex(); + } + MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer()); + if (!item) { + return QModelIndex(); + } + else if (dynamic_cast<MUCSearchServiceItem*>(item)) { + return QModelIndex(); + } + + std::shared_ptr<MUCSearchServiceItem> parent; + if (MUCSearchRoomItem* roomItem = dynamic_cast<MUCSearchRoomItem*>(item)) { + parent = roomItem->getParent(); + } + else if (MUCSearchEmptyItem* emptyItem = dynamic_cast<MUCSearchEmptyItem*>(item)) { + parent = emptyItem->getParent(); + } + if (parent) { + int row = services_.indexOf(parent); + return createIndex(row, 1, parent.get()); + } + else { + return QModelIndex(); + } } int MUCSearchModel::rowCount(const QModelIndex& parentIndex) const { - if (!parentIndex.isValid()) { - return services_.size(); - } - if (dynamic_cast<MUCSearchServiceItem*>(static_cast<MUCSearchItem*>(parentIndex.internalPointer()))) { - return services_[parentIndex.row()]->rowCount(); - } - else { - return 0; - } + if (!parentIndex.isValid()) { + return services_.size(); + } + if (dynamic_cast<MUCSearchServiceItem*>(static_cast<MUCSearchItem*>(parentIndex.internalPointer()))) { + return services_[parentIndex.row()]->rowCount(); + } + else { + return 0; + } +} + +void MUCSearchModel::sort(int column, Qt::SortOrder order) { + sortOrder_ = order; + if (column == 0) { + for (auto&& serviceItem : services_) { + serviceItem->setSorting(*sortOrder_); + } + } } } diff --git a/Swift/QtUI/MUCSearch/MUCSearchModel.h b/Swift/QtUI/MUCSearch/MUCSearchModel.h index 0c02c72..f36d147 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchModel.h +++ b/Swift/QtUI/MUCSearch/MUCSearchModel.h @@ -1,35 +1,37 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/shared_ptr.hpp> +#include <memory> + +#include <boost/optional.hpp> #include <QAbstractItemModel> -#include <QList> +#include <QVector> -#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h" +#include <Swift/QtUI/MUCSearch/MUCSearchServiceItem.h> namespace Swift { - class MUCSearchModel : public QAbstractItemModel { - Q_OBJECT - public: - MUCSearchModel(); - void clear(); - void addService(MUCSearchServiceItem* service); - 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; -// ChatListItem* getItemForIndex(const QModelIndex& index) const; - private: -// ChatListGroupItem* mucBookmarks_; -// ChatListGroupItem* root_; - QList<MUCSearchServiceItem*> services_; - }; + class MUCSearchModel : public QAbstractItemModel { + Q_OBJECT + public: + MUCSearchModel(); + void clear(); + void addService(std::shared_ptr<MUCSearchServiceItem> service); + 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; + virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + + private: + QVector<std::shared_ptr<MUCSearchServiceItem>> services_; + boost::optional<Qt::SortOrder> sortOrder_; + }; } diff --git a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp index a53a577..9c3ef2c 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp +++ b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp @@ -1,26 +1,34 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h" +#include <Swift/QtUI/MUCSearch/MUCSearchRoomItem.h> -#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h" +#include <memory> + +#include <Swift/QtUI/MUCSearch/MUCSearchServiceItem.h> namespace Swift { -MUCSearchRoomItem::MUCSearchRoomItem(const QString& node, MUCSearchServiceItem* parent) : parent_(parent), node_(node) { - parent_->addRoom(this); + +MUCSearchRoomItem::MUCSearchRoomItem(const QString& node) : node_(node) { + } -MUCSearchServiceItem* MUCSearchRoomItem::getParent() { - return parent_; +void MUCSearchRoomItem::setParent(std::weak_ptr<MUCSearchServiceItem> parent) { + parent_ = parent; } + +std::shared_ptr<MUCSearchServiceItem> MUCSearchRoomItem::getParent() { + return parent_.lock(); +} + QVariant MUCSearchRoomItem::data(int role) { - switch (role) { - case Qt::DisplayRole: return QVariant(node_); - default: return QVariant(); - } + switch (role) { + case Qt::DisplayRole: return QVariant(node_); + default: return QVariant(); + } } } diff --git a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h index a9eb74d..5ecb7d7 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h +++ b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h @@ -1,23 +1,27 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/QtUI/MUCSearch/MUCSearchItem.h" +#include <memory> + +#include <Swift/QtUI/MUCSearch/MUCSearchItem.h> namespace Swift { - class MUCSearchServiceItem; - class MUCSearchRoomItem : public MUCSearchItem { - public: - MUCSearchRoomItem(const QString& node, MUCSearchServiceItem* parent); - MUCSearchServiceItem* getParent(); - QVariant data(int role); - QString getNode() const {return node_;} - private: - MUCSearchServiceItem* parent_; - QString node_; - }; + class MUCSearchServiceItem; + class MUCSearchRoomItem : public MUCSearchItem { + public: + MUCSearchRoomItem(const QString& node); + void setParent(std::weak_ptr<MUCSearchServiceItem> parent); + std::shared_ptr<MUCSearchServiceItem> getParent(); + QVariant data(int role); + QString getNode() const {return node_;} + + private: + std::weak_ptr<MUCSearchServiceItem> parent_; + QString node_; + }; } diff --git a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.cpp b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.cpp new file mode 100644 index 0000000..57d5aac --- /dev/null +++ b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/MUCSearch/MUCSearchServiceItem.h> + +#include <memory> + +#include <QString> +#include <QVector> +#include <QtAlgorithms> + +namespace Swift { + +MUCSearchServiceItem::MUCSearchServiceItem(const QString& jidString) : jidString_(jidString) { + +} + +void MUCSearchServiceItem::addRoom(std::shared_ptr<MUCSearchItem> room) { + room->setParent(shared_from_this()); + rooms_.push_back(room); + if (sortOrder_) { + sort(); + } +} + +void MUCSearchServiceItem::addRooms(const std::vector<std::shared_ptr<MUCSearchItem> >& rooms) { + for (auto&& room: rooms) { + room->setParent(shared_from_this()); + rooms_.push_back(room); + } + if (sortOrder_) { + sort(); + } +} + +int MUCSearchServiceItem::rowCount() { + return rooms_.count(); +} + +MUCSearchItem* MUCSearchServiceItem::getItem(int i) { + return rooms_[i].get(); +} + +QVariant MUCSearchServiceItem::data(int role) { + switch (role) { + case Qt::DisplayRole: + return QVariant(jidString_); + default: + return QVariant(); + } +} + +QString MUCSearchServiceItem::getHost() const { + return jidString_; +} + +void MUCSearchServiceItem::setSorting(Qt::SortOrder sortOrder) { + sortOrder_ = sortOrder; + sort(); +} + +void MUCSearchServiceItem::sort() { + if (*sortOrder_ == Qt::AscendingOrder) { + qStableSort(rooms_.begin(), rooms_.end(), [](const std::shared_ptr<MUCSearchItem>& item1, const std::shared_ptr<MUCSearchItem>& item2) -> bool { + return QString::localeAwareCompare(item1->data(Qt::DisplayRole).toString(), item2->data(Qt::DisplayRole).toString()) < 0; + }); + } + else { + qStableSort(rooms_.begin(), rooms_.end(), [](const std::shared_ptr<MUCSearchItem>& item1, const std::shared_ptr<MUCSearchItem>& item2) -> bool { + return QString::localeAwareCompare(item1->data(Qt::DisplayRole).toString(), item2->data(Qt::DisplayRole).toString()) > 0; + }); + } +} + +} diff --git a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h index 2a28922..9f5e000 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h +++ b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h @@ -1,32 +1,39 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QList> +#include <memory> + +#include <boost/optional.hpp> + #include <QString> +#include <QVector> -#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h" +#include <Swift/QtUI/MUCSearch/MUCSearchRoomItem.h> namespace Swift { - class MUCSearchServiceItem : public MUCSearchItem { - public: - MUCSearchServiceItem(const QString& jidString) : jidString_(jidString) {} - void addRoom(MUCSearchItem* room) {rooms_.push_back(room);} - int rowCount() {return rooms_.count();} - MUCSearchItem* getItem(int i) {return rooms_[i];} - QVariant data(int role) { - switch (role) { - case Qt::DisplayRole: return QVariant(jidString_); - default: return QVariant(); - } - } - QString getHost() const {return jidString_;} - private: - QList<MUCSearchItem*> rooms_; - QString jidString_; - }; + class MUCSearchServiceItem : public MUCSearchItem, public std::enable_shared_from_this<MUCSearchServiceItem> { + public: + MUCSearchServiceItem(const QString& jidString); + + void addRoom(std::shared_ptr<MUCSearchItem> room); + void addRooms(const std::vector<std::shared_ptr<MUCSearchItem>>& rooms); + int rowCount(); + MUCSearchItem* getItem(int i); + QVariant data(int role); + QString getHost() const; + void setSorting(Qt::SortOrder sortOrder); + + private: + void sort(); + + private: + QVector<std::shared_ptr<MUCSearchItem>> rooms_; + QString jidString_; + boost::optional<Qt::SortOrder> sortOrder_; + }; } diff --git a/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.cpp b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.cpp new file mode 100644 index 0000000..b8cf15a --- /dev/null +++ b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h> + +namespace Swift { + +QtLeafSortFilterProxyModel::QtLeafSortFilterProxyModel(QObject* parent) : QSortFilterProxyModel(parent) { + +} + +QtLeafSortFilterProxyModel::~QtLeafSortFilterProxyModel() { + +} + +bool QtLeafSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { + if (!sourceModel()->hasChildren(sourceModel()->index(source_row, 0, source_parent))) { + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); + } + else { + return true; + } +} + +} diff --git a/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h new file mode 100644 index 0000000..b4be622 --- /dev/null +++ b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QSortFilterProxyModel> + +namespace Swift { + +/** + * @brief The QtLeafSortFilterProxyModel class is similar to the QSortFilterProxyModel + * class. While the basic QSortFilterProxyModel class filters all hierarchical items, + * the QtLeafSortFilterProxyModel class will only filter on items without children. + */ +class QtLeafSortFilterProxyModel : public QSortFilterProxyModel { + Q_OBJECT + +public: + QtLeafSortFilterProxyModel(QObject* parent = nullptr); + virtual ~QtLeafSortFilterProxyModel(); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; +}; + +} diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp index 7bd16e3..f69da41 100644 --- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp +++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp @@ -1,58 +1,68 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/MUCSearch/QtMUCSearchWindow.h" +#include <Swift/QtUI/MUCSearch/QtMUCSearchWindow.h> + +#include <memory> +#include <vector> -#include <qdebug.h> #include <QMovie> +#include <QPushButton> #include <QScrollBar> +#include <QSortFilterProxyModel> #include <QTimer> -#include <QPushButton> -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h" -#include "Swift/QtUI/MUCSearch/MUCSearchModel.h" -#include "Swift/QtUI/MUCSearch/MUCSearchDelegate.h" -#include "Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h" -#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> + +#include <Swift/QtUI/MUCSearch/MUCSearchDelegate.h> +#include <Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h> +#include <Swift/QtUI/MUCSearch/MUCSearchModel.h> +#include <Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtMUCSearchWindow::QtMUCSearchWindow() { - ui_.setupUi(this); + ui_.setupUi(this); #ifndef Q_OS_MAC - setWindowIcon(QIcon(":/logo-icon-16.png")); + setWindowIcon(QIcon(":/logo-icon-16.png")); #endif - setModal(true); - ui_.filter_->hide(); - model_ = new MUCSearchModel(); - delegate_ = new MUCSearchDelegate(); - ui_.results_->setModel(model_); - ui_.results_->setItemDelegate(delegate_); - ui_.results_->setHeaderHidden(true); - ui_.results_->setRootIsDecorated(true); - ui_.results_->setAnimated(true); - ui_.results_->setAlternatingRowColors(true); - connect(ui_.searchButton, SIGNAL(clicked()), this, SLOT(handleSearch())); - connect(ui_.service_, SIGNAL(activated(const QString&)), this, SLOT(handleSearch(const QString&))); - connect(ui_.results_->selectionModel(), SIGNAL(selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT(handleSelectionChanged (const QItemSelection&, const QItemSelection&))); - connect(ui_.results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&))); - connect(ui_.results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&))); - // Not using a button box, because i can't seem to be able to make the ok button non-default (on mac) - connect(ui_.okButton, SIGNAL(clicked()), this, SLOT(accept())); - ui_.okButton->setEnabled(false); - connect(ui_.cancelButton, SIGNAL(clicked()), this, SLOT(reject())); - - throbber_ = new QLabel(tr("Searching"), ui_.results_); - throbber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), throbber_)); - throbber_->setToolTip(tr("Searching")); - - hasHadScrollBars_ = false; - updateThrobberPosition(); - setSearchInProgress(false); + setModal(true); + model_ = new MUCSearchModel(); + sortFilterProxyModel_ = new QtLeafSortFilterProxyModel(this); + sortFilterProxyModel_->setSourceModel(model_); + sortFilterProxyModel_->setDynamicSortFilter(true); + delegate_ = new MUCSearchDelegate(); + ui_.results_->setModel(sortFilterProxyModel_); + ui_.results_->setItemDelegate(delegate_); + ui_.results_->setHeaderHidden(true); + ui_.results_->setRootIsDecorated(true); + ui_.results_->setAnimated(true); + ui_.results_->setAlternatingRowColors(true); + ui_.results_->setSortingEnabled(true); + ui_.results_->sortByColumn(0, Qt::AscendingOrder); + connect(ui_.searchButton_, SIGNAL(clicked()), this, SLOT(handleSearch())); + connect(ui_.service_, SIGNAL(activated(const QString&)), this, SLOT(handleSearch(const QString&))); + connect(ui_.results_->selectionModel(), SIGNAL(selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT(handleSelectionChanged (const QItemSelection&, const QItemSelection&))); + connect(ui_.results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&))); + connect(ui_.results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&))); + // Not using a button box, because i can't seem to be able to make the ok button non-default (on mac) + connect(ui_.okButton, SIGNAL(clicked()), this, SLOT(accept())); + ui_.okButton->setEnabled(false); + connect(ui_.cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + connect(ui_.filter_, SIGNAL(textChanged(const QString&)), this, SLOT(handleFilterStringChanged(const QString&))); + + throbber_ = new QLabel(tr("Searching"), ui_.results_); + throbber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), throbber_)); + throbber_->setToolTip(tr("Searching")); + + hasHadScrollBars_ = false; + updateThrobberPosition(); + setSearchInProgress(false); } QtMUCSearchWindow::~QtMUCSearchWindow() { @@ -60,136 +70,145 @@ QtMUCSearchWindow::~QtMUCSearchWindow() { } void QtMUCSearchWindow::resizeEvent(QResizeEvent* /*event*/) { - updateThrobberPosition(); + updateThrobberPosition(); } void QtMUCSearchWindow::updateThrobberPosition() { - bool isShown = throbber_->isVisible(); - int resultWidth = ui_.results_->width(); - int resultHeight = ui_.results_->height(); - //throbberWidth = throbber_->movie()->scaledSize().width(); - //throbberHeight = throbber_->movie()->scaledSize().height(); - int throbberWidth = 16; /* This is nasty, but the above doesn't work! */ - int throbberHeight = 16; - /* It's difficult (or I spent a while trying) to work out whether the scrollbars are currently shown and their appropriate size, - * because if you listen for the expanded/collapsed signals, you seem to get them before the scrollbars are updated. - * This seems an acceptable workaround. - */ - hasHadScrollBars_ |= ui_.results_->verticalScrollBar()->isVisible(); - int hMargin = hasHadScrollBars_ ? ui_.results_->verticalScrollBar()->width() + 2 : 2; - int vMargin = 2; /* We don't get horizontal scrollbars */ - throbber_->setGeometry(QRect(resultWidth - throbberWidth - hMargin, resultHeight - throbberHeight - vMargin, throbberWidth, throbberHeight)); /* include margins */ - throbber_->setVisible(isShown); + bool isShown = throbber_->isVisible(); + int resultWidth = ui_.results_->width(); + int resultHeight = ui_.results_->height(); + //throbberWidth = throbber_->movie()->scaledSize().width(); + //throbberHeight = throbber_->movie()->scaledSize().height(); + int throbberWidth = 16; /* This is nasty, but the above doesn't work! */ + int throbberHeight = 16; + /* It's difficult (or I spent a while trying) to work out whether the scrollbars are currently shown and their appropriate size, + * because if you listen for the expanded/collapsed signals, you seem to get them before the scrollbars are updated. + * This seems an acceptable workaround. + */ + hasHadScrollBars_ |= ui_.results_->verticalScrollBar()->isVisible(); + int hMargin = hasHadScrollBars_ ? ui_.results_->verticalScrollBar()->width() + 2 : 2; + int vMargin = 2; /* We don't get horizontal scrollbars */ + throbber_->setGeometry(QRect(resultWidth - throbberWidth - hMargin, resultHeight - throbberHeight - vMargin, throbberWidth, throbberHeight)); /* include margins */ + throbber_->setVisible(isShown); } void QtMUCSearchWindow::addSavedServices(const std::list<JID>& services) { - ui_.service_->clear(); - foreach (const JID& jid, services) { - ui_.service_->addItem(P2QSTRING(jid.toString())); - } - if (!services.empty()) { - ui_.service_->setEditText(P2QSTRING(services.begin()->toString())); - } - else { - ui_.service_->clearEditText(); - } + ui_.service_->clear(); + for (const auto& jid : services) { + ui_.service_->addItem(P2QSTRING(jid.toString())); + } + if (!services.empty()) { + ui_.service_->setEditText(P2QSTRING(services.begin()->toString())); + } + else { + ui_.service_->clearEditText(); + } } void QtMUCSearchWindow::handleActivated(const QModelIndex& index) { - if (!index.isValid()) { - return; - } - if (dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(index.internalPointer()))) { - accept(); - } + if (!index.isValid()) { + return; + } + if (dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(sortFilterProxyModel_->mapToSource(index).internalPointer()))) { + accept(); + } } void QtMUCSearchWindow::handleSearch() { - handleSearch(ui_.service_->currentText()); + handleSearch(ui_.service_->currentText()); } void QtMUCSearchWindow::handleSearch(const QString& service) { - if (!service.isEmpty()) { - onSearchService(JID(Q2PSTRING(service))); - } + if (!service.isEmpty()) { + onSearchService(JID(Q2PSTRING(service))); + } +} + +void QtMUCSearchWindow::handleFilterStringChanged(const QString& filterString) { + sortFilterProxyModel_->setFilterRegExp(filterString); } void QtMUCSearchWindow::show() { - QWidget::show(); - QWidget::activateWindow(); + ui_.filter_->clear(); + QWidget::show(); + QWidget::activateWindow(); } void QtMUCSearchWindow::clearList() { - model_->clear(); + model_->clear(); } void QtMUCSearchWindow::addService(const MUCService& service) { - updateThrobberPosition(); - MUCSearchServiceItem* serviceItem = new MUCSearchServiceItem(P2QSTRING(service.getJID().toString())); - if (service.getRooms().size() > 0) { - foreach (MUCService::MUCRoom room, service.getRooms()) { - new MUCSearchRoomItem(P2QSTRING(room.getNode()), serviceItem); - } - } - else { - new MUCSearchEmptyItem(serviceItem); - } - model_->addService(serviceItem); - ui_.results_->expandAll(); + updateThrobberPosition(); + auto serviceItem = std::make_shared<MUCSearchServiceItem>(P2QSTRING(service.getJID().toString())); + if (service.getRooms().size() > 0) { + std::vector<std::shared_ptr<MUCSearchItem>> rooms; + for (auto&& room : service.getRooms()) { + if (!room.getNode().empty()) { + rooms.push_back(std::make_shared<MUCSearchRoomItem>(P2QSTRING(room.getNode()))); + } + } + serviceItem->addRooms(rooms); + } + else { + serviceItem->addRoom(std::make_shared<MUCSearchEmptyItem>()); + } + model_->addService(serviceItem); + ui_.results_->expandAll(); } void QtMUCSearchWindow::setSearchInProgress(bool searching) { - if (searching) { - throbber_->movie()->start(); - } else { - throbber_->movie()->stop(); - } - throbber_->setVisible(searching); + if (searching) { + throbber_->movie()->start(); + } else { + throbber_->movie()->stop(); + } + throbber_->setVisible(searching); } void QtMUCSearchWindow::accept() { - MUCSearchRoomItem* room = getSelectedRoom(); - if (room) { - onFinished(JID(Q2PSTRING(room->getNode()), Q2PSTRING(room->getParent()->getHost()))); - } - else { - onFinished(boost::optional<JID>()); - } - QDialog::accept(); + MUCSearchRoomItem* room = getSelectedRoom(); + if (room) { + onFinished(JID(Q2PSTRING(room->getNode()), Q2PSTRING(room->getParent()->getHost()))); + } + else { + onFinished(boost::optional<JID>()); + } + QDialog::accept(); } void QtMUCSearchWindow::reject() { - onFinished(boost::optional<JID>()); - QDialog::reject(); + onFinished(boost::optional<JID>()); + QDialog::reject(); } void QtMUCSearchWindow::handleSelectionChanged(const QItemSelection&, const QItemSelection&) { - ui_.okButton->setEnabled(getSelectedRoom()); + ui_.okButton->setEnabled(getSelectedRoom()); } MUCSearchRoomItem* QtMUCSearchWindow::getSelectedRoom() const { - // Not using selectedIndexes(), because this seems to cause a crash in Qt (4.7.0) in the - // QModelIndexList destructor. - // This is a workaround posted in http://www.qtcentre.org/threads/16933 (although this case - // was resolved by linking against the debug libs, ours isn't, and we're not alone) - QItemSelection ranges = ui_.results_->selectionModel()->selection(); - QModelIndexList lstIndex; - for (int i = 0; i < ranges.count(); ++i) { - QModelIndex parent = ranges.at(i).parent(); - int right = ranges.at(i).model()->columnCount(parent) - 1; - if (ranges.at(i).left() == 0 && ranges.at(i).right() == right) { - for (int r = ranges.at(i).top(); r <= ranges.at(i).bottom(); ++r) { - lstIndex.append(ranges.at(i).model()->index(r, 0, parent)); - } - } - } - if (lstIndex.isEmpty()) { - return NULL; - } - else { - return dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(lstIndex.first().internalPointer())); - } + // Not using selectedIndexes(), because this seems to cause a crash in Qt (4.7.0) in the + // QModelIndexList destructor. + // This is a workaround posted in http://www.qtcentre.org/threads/16933 (although this case + // was resolved by linking against the debug libs, ours isn't, and we're not alone) + QItemSelection ranges = ui_.results_->selectionModel()->selection(); + QModelIndexList lstIndex; + for (int i = 0; i < ranges.count(); ++i) { + QModelIndex parent = ranges.at(i).parent(); + int right = ranges.at(i).model()->columnCount(parent) - 1; + if (ranges.at(i).left() == 0 && ranges.at(i).right() == right) { + for (int r = ranges.at(i).top(); r <= ranges.at(i).bottom(); ++r) { + lstIndex.append(ranges.at(i).model()->index(r, 0, parent)); + } + } + } + if (lstIndex.isEmpty()) { + return nullptr; + } + else { + return dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(sortFilterProxyModel_->mapToSource(lstIndex.first()).internalPointer())); + } } } diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h index 05ab9a5..6f38533 100644 --- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h +++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h @@ -1,51 +1,55 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/QtUI/MUCSearch/ui_QtMUCSearchWindow.h" +#include <Swift/Controllers/UIInterfaces/MUCSearchWindow.h> -#include "Swift/Controllers/UIInterfaces/MUCSearchWindow.h" +#include <Swift/QtUI/MUCSearch/ui_QtMUCSearchWindow.h> + +class QSortFilterProxyModel; namespace Swift { - class MUCSearchModel; - class MUCSearchDelegate; - class MUCSearchRoomItem; - - class QtMUCSearchWindow : public QDialog, public MUCSearchWindow { - Q_OBJECT - public: - QtMUCSearchWindow(); - virtual ~QtMUCSearchWindow(); - - virtual void clearList(); - virtual void addService(const MUCService& service); - virtual void addSavedServices(const std::list<JID>& services); - virtual void setSearchInProgress(bool searching); - - virtual void show(); - virtual void accept(); - virtual void reject(); - - protected: - virtual void resizeEvent(QResizeEvent* event); - - private slots: - void handleSearch(); - void handleSearch(const QString&); - void handleActivated(const QModelIndex& index); - void updateThrobberPosition(); - void handleSelectionChanged (const QItemSelection&, const QItemSelection&); - MUCSearchRoomItem* getSelectedRoom() const; - - private: - Ui::QtMUCSearchWindow ui_; - MUCSearchModel* model_; - MUCSearchDelegate* delegate_; - QLabel* throbber_; - bool hasHadScrollBars_; - }; + class MUCSearchModel; + class MUCSearchDelegate; + class MUCSearchRoomItem; + + class QtMUCSearchWindow : public QDialog, public MUCSearchWindow { + Q_OBJECT + public: + QtMUCSearchWindow(); + virtual ~QtMUCSearchWindow(); + + virtual void clearList(); + virtual void addService(const MUCService& service); + virtual void addSavedServices(const std::list<JID>& services); + virtual void setSearchInProgress(bool searching); + + virtual void show(); + virtual void accept(); + virtual void reject(); + + protected: + virtual void resizeEvent(QResizeEvent* event); + + private slots: + void handleSearch(); + void handleSearch(const QString&); + void handleFilterStringChanged(const QString&); + void handleActivated(const QModelIndex& index); + void updateThrobberPosition(); + void handleSelectionChanged (const QItemSelection&, const QItemSelection&); + MUCSearchRoomItem* getSelectedRoom() const; + + private: + Ui::QtMUCSearchWindow ui_; + MUCSearchModel* model_; + MUCSearchDelegate* delegate_; + QLabel* throbber_; + bool hasHadScrollBars_; + QSortFilterProxyModel* sortFilterProxyModel_ = nullptr; + }; } diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui index 49460ab..52714c4 100644 --- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui +++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui @@ -6,21 +6,14 @@ <rect> <x>0</x> <y>0</y> - <width>523</width> - <height>368</height> + <width>566</width> + <height>264</height> </rect> </property> <property name="windowTitle"> <string>Search Room</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Service:</string> - </property> - </widget> - </item> <item row="0" column="1"> <widget class="QComboBox" name="service_"> <property name="sizePolicy"> @@ -34,26 +27,14 @@ </property> </widget> </item> - <item row="0" column="3"> - <widget class="QLineEdit" name="filter_"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>1</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> + <item row="0" column="0"> + <widget class="QLabel" name="serviceLabel_"> <property name="text"> - <string/> - </property> - <property name="frame"> - <bool>true</bool> + <string>Service:</string> </property> </widget> </item> - <item row="1" column="0" colspan="4"> - <widget class="QTreeView" name="results_"/> - </item> - <item row="2" column="0" colspan="4"> + <item row="2" column="0" colspan="5"> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <spacer name="horizontalSpacer"> @@ -90,13 +71,42 @@ </item> </layout> </item> + <item row="0" column="4"> + <widget class="QLineEdit" name="filter_"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="frame"> + <bool>true</bool> + </property> + <property name="placeholderText"> + <string/> + </property> + </widget> + </item> <item row="0" column="2"> - <widget class="QToolButton" name="searchButton"> + <widget class="QToolButton" name="searchButton_"> <property name="text"> <string>List rooms</string> </property> </widget> </item> + <item row="1" column="0" colspan="5"> + <widget class="QTreeView" name="results_"/> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="filterLabel_"> + <property name="text"> + <string>Search for</string> + </property> + </widget> + </item> </layout> </widget> <resources/> diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp index 28c44c4..4682365 100644 --- a/Swift/QtUI/MessageSnippet.cpp +++ b/Swift/QtUI/MessageSnippet.cpp @@ -1,45 +1,43 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "MessageSnippet.h" +#include <Swift/QtUI/MessageSnippet.h> -#include <QtDebug> #include <QDateTime> 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, Direction direction) : ChatSnippet(appendToPrevious) { - if (appendToPrevious) { - setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet>(new MessageSnippet(message, sender, time, iconURI, isIncoming, false, theme, id, direction))); - } - if (isIncoming) { - if (appendToPrevious) { - content_ = theme->getIncomingNextContent(); - } - else { - content_ = theme->getIncomingContent(); - } - } - else { - if (appendToPrevious) { - content_ = theme->getOutgoingNextContent(); - } - else { - content_ = theme->getOutgoingContent(); - } - } - - content_.replace("%direction%", directionToCSS(direction)); - content_.replace("%message%", wrapResizable("<span class='swift_message'>" + escape(message) + "</span><span class='swift_ack'></span><span class='swift_receipt'></span>")); - content_.replace("%wrapped_sender%", wrapResizable(escape(sender))); - content_.replace("%sender%", escape(sender)); - content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); - content_.replace("%userIconPath%", escape(iconURI)); - content_ = QString("<div id='%1'>%2</div>").arg(id).arg(content_); - content_ = "<span class='date" + time.date().toString(Qt::ISODate) + "'>" + content_ + "</span>"; + if (appendToPrevious) { + setContinuationFallbackSnippet(std::make_shared<MessageSnippet>(message, sender, time, iconURI, isIncoming, false, theme, id, direction)); + } + if (isIncoming) { + if (appendToPrevious) { + content_ = theme->getIncomingNextContent(); + } + else { + content_ = theme->getIncomingContent(); + } + } + else { + if (appendToPrevious) { + content_ = theme->getOutgoingNextContent(); + } + else { + content_ = theme->getOutgoingContent(); + } + } + + content_.replace("%direction%", directionToCSS(direction)); + content_.replace("%message%", wrapResizable("<span class='swift_message'>" + escape(message) + "</span>")); + content_.replace("%wrapped_sender%", wrapResizable(escape(sender))); + content_.replace("%sender%", escape(sender)); + content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); + content_.replace("%userIconPath%", escape(iconURI)); + content_.replace("%id%", id); } MessageSnippet::~MessageSnippet() { diff --git a/Swift/QtUI/MessageSnippet.h b/Swift/QtUI/MessageSnippet.h index 8186d19..302785f 100644 --- a/Swift/QtUI/MessageSnippet.h +++ b/Swift/QtUI/MessageSnippet.h @@ -1,31 +1,31 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QString> -#include "ChatSnippet.h" +#include <Swift/QtUI/ChatSnippet.h> class QDateTime; namespace Swift { - class MessageSnippet : public ChatSnippet { - public: - MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme, const QString& id, Direction direction); - virtual ~MessageSnippet(); - const QString& getContent() const { - return content_; - } + 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, Direction direction); + virtual ~MessageSnippet(); + const QString& getContent() const { + return content_; + } - QString getContinuationElementID() const { - return "insert"; - } + QString getContinuationElementID() const { + return "insert"; + } - private: - QString content_; - }; + private: + QString content_; + }; } diff --git a/Swift/QtUI/NotifierTest/NotifierTest.cpp b/Swift/QtUI/NotifierTest/NotifierTest.cpp index e165993..8d2e467 100644 --- a/Swift/QtUI/NotifierTest/NotifierTest.cpp +++ b/Swift/QtUI/NotifierTest/NotifierTest.cpp @@ -1,26 +1,28 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <iostream> +#include <string> + #include <boost/bind.hpp> -#include <string> -#include "Swiften/Base/ByteArray.h" -#include "Swiften/Notifier/GrowlNotifier.h" #include <QApplication> +#include <Swiften/Base/ByteArray.h> +#include <Swiften/Notifier/GrowlNotifier.h> + using namespace Swift; void notificationClicked(const std::string& message) { - std::cout << "Notification clicked: " << message << std::endl; + std::cout << "Notification clicked: " << message << std::endl; } int main(int argc, char* argv[]) { - QApplication app(argc, argv); - GrowlNotifier notifier("Swift-NotifierTest"); - notifier.showMessage(Notifier::ContactAvailable, "Contact is available", "The contact has become available", ByteArray(), boost::bind(¬ificationClicked, "Message 1")); - return app.exec(); + QApplication app(argc, argv); + GrowlNotifier notifier("Swift-NotifierTest"); + notifier.showMessage(Notifier::ContactAvailable, "Contact is available", "The contact has become available", ByteArray(), boost::bind(¬ificationClicked, "Message 1")); + return app.exec(); } diff --git a/Swift/QtUI/QtAboutWidget.cpp b/Swift/QtUI/QtAboutWidget.cpp index c00acf7..0a4e0ba 100644 --- a/Swift/QtUI/QtAboutWidget.cpp +++ b/Swift/QtUI/QtAboutWidget.cpp @@ -1,76 +1,224 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/QtAboutWidget.h" +#include <Swift/QtUI/QtAboutWidget.h> #include <QCoreApplication> +#include <QDesktopServices> +#include <QFile> #include <QIcon> #include <QLabel> -#include <QVBoxLayout> -#include <QtGlobal> +#include <QProgressBar> #include <QPushButton> #include <QTextEdit> -#include <QFile> #include <QTextStream> +#include <QVBoxLayout> +#include <QtGlobal> + +#include <Swiften/Base/Log.h> +#include <Swiften/Base/Platform.h> + +#include <SwifTools/AutoUpdater/AutoUpdater.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtUpdateFeedSelectionDialog.h> +#include <Swift/QtUI/SwiftUpdateFeeds.h> namespace Swift { -QtAboutWidget::QtAboutWidget() : QDialog() { +QtAboutWidget::QtAboutWidget(SettingsProvider* settingsProvider, AutoUpdater* autoUpdater) : QDialog(), settingsProvider_(settingsProvider), autoUpdater_(autoUpdater) { #ifndef Q_OS_MAC - setWindowTitle(QString(tr("About %1")).arg("Swift")); + setWindowTitle(QString(tr("About %1")).arg("Swift")); #endif - setWindowIcon(QIcon(":/logo-icon-16.png")); - - resize(180, 240); - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setAlignment(Qt::AlignHCenter); - setLayout(mainLayout); - - QLabel* iconLabel = new QLabel(this); - iconLabel->setPixmap(QIcon(":/logo-shaded-text.256.png").pixmap(90, 90)); - iconLabel->setAlignment(Qt::AlignHCenter); - mainLayout->addWidget(iconLabel); - - QLabel* appNameLabel = new QLabel("<center><font size='+1'><b>" + QCoreApplication::applicationName() + "</b></font></center>", this); - mainLayout->addWidget(appNameLabel); - - QLabel* versionLabel = new QLabel(QString("<center><font size='-1'>") + tr("Version %1").arg(QCoreApplication::applicationVersion()) + "</font></center>", this); - mainLayout->addWidget(versionLabel); - - QString buildString = QString("<center><font size='-1'>") + QString(tr("Built with Qt %1")).arg(QT_VERSION_STR); - buildString += QString("<br/>") + QString(tr("Running with Qt %1")).arg(qVersion()); - buildString += "</font></center>"; - QLabel* buildLabel = new QLabel(buildString, this); - mainLayout->addWidget(buildLabel); - - if (QCoreApplication::translate("TRANSLATION_INFO", "TRANSLATION_AUTHOR") != "TRANSLATION_AUTHOR") { - mainLayout->addWidget(new QLabel(QString("<center><font size='-1'>") + QString(tr("Using the English translation by\n%1")).arg(QCoreApplication::translate("TRANSLATION_INFO", "TRANSLATION_AUTHOR")).replace("\n", "<br/>") + "</font></center>", this)); - } - QCoreApplication::translate("TRANSLATION_INFO", "TRANSLATION_LICENSE", "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'"); - - QPushButton* licenseButton = new QPushButton(tr("View License"), this); - mainLayout->addWidget(licenseButton); - connect(licenseButton, SIGNAL(clicked()), this, SLOT(handleLicenseClicked())); - - setFixedSize(minimumSizeHint()); + setWindowIcon(QIcon(":/logo-icon-16.png")); + + resize(180, 240); + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setAlignment(Qt::AlignHCenter); + setLayout(mainLayout); + + QLabel* iconLabel = new QLabel(this); + iconLabel->setPixmap(QIcon(":/logo-icon.svg").pixmap(90, 90)); + iconLabel->setAlignment(Qt::AlignHCenter); + mainLayout->addWidget(iconLabel); + + QLabel* appNameLabel = new QLabel("<center><font size='+1'><b>" + QCoreApplication::applicationName() + "</b></font></center>", this); + mainLayout->addWidget(appNameLabel); + + auto websiteLabel = new QLabel("<center><font size='-1'><a href='https://swift.im/'>https://swift.im/</a></font></center>", this); + websiteLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); + connect(websiteLabel, &QLabel::linkActivated, [](){ + QDesktopServices::openUrl(QUrl("https://swift.im/")); + }); + mainLayout->addWidget(websiteLabel); + + QLabel* versionLabel = new QLabel((QString("<center><font size='-1'>") + tr("Version %1") + "</font></center><center><font size='-1'><br/>" + QString(tr("Built with Qt %2")) + QString("<br/>") + QString(tr("Running with Qt %3")) + "</font></center>").arg(QCoreApplication::applicationVersion()).arg(QT_VERSION_STR).arg(qVersion())); + versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + mainLayout->addWidget(versionLabel); + + if (autoUpdater_) { + settingsChangedConnection_ = settingsProvider_->onSettingChanged.connect([&](const std::string& path) { + if (path == QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL.getKey() || path == QtUISettingConstants::ENABLE_SOFTWARE_UPDATES.getKey()) { + updateUpdateInfo(); + } + }); + + autoUpdaterChangeConnection_ = autoUpdater_->onUpdateStateChanged.connect([&](AutoUpdater::State /*updatedState*/) { + updateUpdateInfo(); + }); + } + + updateChannelInfoLabel_ = new QLabel("", this); + updateChannelInfoLabel_->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse); + connect(updateChannelInfoLabel_, SIGNAL(linkActivated(const QString &)), this, SLOT(handleChangeUpdateChannelClicked())); + mainLayout->addWidget(updateChannelInfoLabel_); + + updateStateInfoLabel_ = new QLabel("", this); + mainLayout->addWidget(updateStateInfoLabel_); + + updateProgressBar_ = new QProgressBar(this); + updateProgressBar_->setMinimum(0); + updateProgressBar_->setMaximum(0); + mainLayout->addWidget(updateProgressBar_); + + updateUpdateInfo(); + + if (QCoreApplication::translate("TRANSLATION_INFO", "TRANSLATION_AUTHOR") != "TRANSLATION_AUTHOR") { + mainLayout->addWidget(new QLabel(QString("<center><font size='-1'>") + QString(tr("Using the English translation by\n%1")).arg(QCoreApplication::translate("TRANSLATION_INFO", "TRANSLATION_AUTHOR")).replace("\n", "<br/>") + "</font></center>", this)); + } + QCoreApplication::translate("TRANSLATION_INFO", "TRANSLATION_LICENSE", "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'"); +#if defined(SWIFTEN_PLATFORM_WINDOWS) || defined(SWIFTEN_PLATFORM_MACOSX) + QPushButton* licenseButton = new QPushButton(tr("View License"), this); + mainLayout->addWidget(licenseButton); + connect(licenseButton, SIGNAL(clicked()), this, SLOT(handleLicenseClicked())); + + QPushButton* changelogButton = new QPushButton(tr("View Changes"), this); + mainLayout->addWidget(changelogButton); + connect(changelogButton, SIGNAL(clicked()), this, SLOT(handleChangelogClicked())); +#else + // Some Linux desktops have dialog window decorations without close window buttons. + // This code adds a dedicated button to close the about window dialog. + QHBoxLayout* buttonLayout = new QHBoxLayout(); + mainLayout->addLayout(buttonLayout); + + QPushButton* licenseButton = new QPushButton(tr("View License"), this); + buttonLayout->addWidget(licenseButton); + connect(licenseButton, SIGNAL(clicked()), this, SLOT(handleLicenseClicked())); + + QPushButton* changelogButton = new QPushButton(tr("View Changes"), this); + buttonLayout->addWidget(changelogButton); + connect(changelogButton, SIGNAL(clicked()), this, SLOT(handleChangelogClicked())); + + buttonLayout->addItem(new QSpacerItem(20,20)); + + QPushButton* closeButton = new QPushButton(tr("Close"), this); + buttonLayout->addWidget(closeButton); + connect(closeButton, SIGNAL(clicked()), this, SLOT(accept())); +#endif + setFixedSize(minimumSizeHint()); } void QtAboutWidget::handleLicenseClicked() { - QTextEdit* text = new QTextEdit(); - text->setAttribute(Qt::WA_DeleteOnClose); - text->setReadOnly(true); - QFile file(":/COPYING"); - file.open(QIODevice::ReadOnly); - QTextStream in(&file); - in.setCodec("UTF-8"); - text->setPlainText(in.readAll()); - file.close(); - text->resize(500, 600); - text->show(); - text->activateWindow(); + openPlainTextWindow(":/COPYING"); +} + +void QtAboutWidget::handleChangelogClicked() { + openPlainTextWindow(":/ChangeLog.md"); +} + +void QtAboutWidget::handleChangeUpdateChannelClicked() { + auto feedSelectionDialog = new QtUpdateFeedSelectionDialog(settingsProvider_); + feedSelectionDialog->show(); +} + +void QtAboutWidget::openPlainTextWindow(const QString& path) { + QTextEdit* text = new QTextEdit(); + text->setAttribute(Qt::WA_DeleteOnClose); + text->setReadOnly(true); + QFile file(path); + if (file.open(QIODevice::ReadOnly)) { + QTextStream in(&file); + in.setCodec("UTF-8"); + text->setPlainText(in.readAll()); + file.close(); + text->resize(500, 600); + text->show(); + text->activateWindow(); + } + else { + SWIFT_LOG(error) << "Failed to open " << Q2PSTRING(path) << "."; + } +} + +void QtAboutWidget::updateUpdateInfo() { + updateChannelInfoLabel_->hide(); + updateStateInfoLabel_->hide(); + updateProgressBar_->hide(); + // Currently auto updating is only supported on macOS. +#ifdef SWIFTEN_PLATFORM_MACOSX + if (autoUpdater_ && settingsProvider_->getSetting(QtUISettingConstants::ENABLE_SOFTWARE_UPDATES)) { + if (!settingsProvider_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL).empty()) { + QString updateFeedDescription; + auto addUpdateFeedDialogLink = false; + if (settingsProvider_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL) == UpdateFeeds::StableChannel) { + updateFeedDescription = tr("You are receiving updates from the Stable update channel."); + addUpdateFeedDialogLink = true; + } + else if (settingsProvider_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL) == UpdateFeeds::DevelopmentChannel) { + updateFeedDescription = tr("You are receiving updates from the Development update channel."); + addUpdateFeedDialogLink = true; + } + else if (settingsProvider_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL) == UpdateFeeds::TestingChannel) { + updateFeedDescription = tr("You are receiving updates from the Testing update channel."); + addUpdateFeedDialogLink = true; + } + auto updateFeedDialogLink = QString( addUpdateFeedDialogLink ? "<a href=\"#\">%1</a>" : "" ).arg(tr("Change the update channel.")); + updateChannelInfoLabel_->setText(QString("<center><font size='-1'>%1<br/>%2</font></center>").arg(updateFeedDescription, updateFeedDialogLink)); + updateChannelInfoLabel_->show(); + + auto currentState = autoUpdater_->getCurrentState(); + auto currentStateStringPattern = QString("<center><font size='-1'>%1</font></center>"); + switch (currentState) { + case AutoUpdater::State::NotCheckedForUpdatesYet: + // Simply not showing any current state info. + break; + case AutoUpdater::State::CheckingForUpdate: + updateStateInfoLabel_->setText(currentStateStringPattern.arg(tr("Checking for updates…"))); + updateStateInfoLabel_->show(); + updateProgressBar_->show(); + break; + case AutoUpdater::State::ErrorCheckingForUpdate: + updateStateInfoLabel_->setText(currentStateStringPattern.arg(tr("Error checking for updates!"))); + updateStateInfoLabel_->show(); + break; + case AutoUpdater::State::NoUpdateAvailable: + updateStateInfoLabel_->setText(currentStateStringPattern.arg(tr("Swift is up to date."))); + updateStateInfoLabel_->show(); + break; + case AutoUpdater::State::DownloadingUpdate: + updateStateInfoLabel_->setText(currentStateStringPattern.arg(tr("Downloading update…"))); + updateStateInfoLabel_->show(); + updateProgressBar_->show(); + break; + case AutoUpdater::State::RestartToInstallUpdate: + updateStateInfoLabel_->setText(currentStateStringPattern.arg(tr("Update will be installed when you next restart Swift."))); + updateStateInfoLabel_->show(); + break; + } + } + } +#endif + setFixedSize(minimumSizeHint()); +} + +void QtAboutWidget::showEvent(QShowEvent*) { + if (autoUpdater_) { + autoUpdater_->checkForUpdates(); + } } } diff --git a/Swift/QtUI/QtAboutWidget.h b/Swift/QtUI/QtAboutWidget.h index 1800676..b07c6b0 100644 --- a/Swift/QtUI/QtAboutWidget.h +++ b/Swift/QtUI/QtAboutWidget.h @@ -1,21 +1,47 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <boost/signals2/connection.hpp> + #include <QDialog> +class QLabel; +class QProgressBar; + namespace Swift { - class QtAboutWidget : public QDialog { - Q_OBJECT + class AutoUpdater; + class SettingsProvider; + + class QtAboutWidget : public QDialog { + Q_OBJECT + + public: + QtAboutWidget(SettingsProvider* settings, AutoUpdater* autoUpdater); + + private slots: + void handleLicenseClicked(); + void handleChangelogClicked(); + void handleChangeUpdateChannelClicked(); + + private: + void openPlainTextWindow(const QString& path); + void updateUpdateInfo(); - public: - QtAboutWidget(); + protected: + void showEvent(QShowEvent*); - private slots: - void handleLicenseClicked(); - }; + private: + SettingsProvider* settingsProvider_; + AutoUpdater* autoUpdater_; + QLabel* updateChannelInfoLabel_ = nullptr; + QLabel* updateStateInfoLabel_ = nullptr; + QProgressBar* updateProgressBar_ = nullptr; + boost::signals2::scoped_connection settingsChangedConnection_; + boost::signals2::scoped_connection autoUpdaterChangeConnection_; + }; } diff --git a/Swift/QtUI/QtAdHocCommandWindow.cpp b/Swift/QtUI/QtAdHocCommandWindow.cpp index 5d87031..65dac91 100644 --- a/Swift/QtUI/QtAdHocCommandWindow.cpp +++ b/Swift/QtUI/QtAdHocCommandWindow.cpp @@ -1,143 +1,174 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtAdHocCommandWindow.h> #include <boost/bind.hpp> + #include <QBoxLayout> -#include <Swift/QtUI/QtFormWidget.h> + +#include <Swiften/Base/format.h> #include <Swiften/Elements/Command.h> + +#include <Swift/QtUI/QtFormWidget.h> #include <Swift/QtUI/QtSwiftUtil.h> const int FormLayoutIndex = 1; namespace Swift { -QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) : command_(command) { - formWidget_ = NULL; - - setAttribute(Qt::WA_DeleteOnClose); - command->onNextStageReceived.connect(boost::bind(&QtAdHocCommandWindow::handleNextStageReceived, this, _1)); - command->onError.connect(boost::bind(&QtAdHocCommandWindow::handleError, this, _1)); - command->start(); - - layout_ = new QBoxLayout(QBoxLayout::TopToBottom, this); - layout_->setContentsMargins(0,0,0,0); - layout_->setSpacing(2); - label_ = new QLabel(this); - label_->setTextFormat(Qt::PlainText); - layout_->addWidget(label_); - QWidget* buttonsWidget = new QWidget(this); - layout_->addWidget(buttonsWidget); - - QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget); - cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget); - buttonsLayout->addWidget(cancelButton_); - connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked())); - backButton_ = new QPushButton(tr("Back"), buttonsWidget); - buttonsLayout->addWidget(backButton_); - connect(backButton_, SIGNAL(clicked()), this, SLOT(handlePrevClicked())); - nextButton_ = new QPushButton(tr("Next"), buttonsWidget); - buttonsLayout->addWidget(nextButton_); - connect(nextButton_, SIGNAL(clicked()), this, SLOT(handleNextClicked())); - completeButton_ = new QPushButton(tr("Complete"), buttonsWidget); - buttonsLayout->addWidget(completeButton_); - connect(completeButton_, SIGNAL(clicked()), this, SLOT(handleCompleteClicked())); - nextButton_->setEnabled(false); - backButton_->setEnabled(false); - completeButton_->setEnabled(false); - actions_[Command::Next] = nextButton_; - actions_[Command::Prev] = backButton_; - actions_[Command::Complete] = completeButton_; - actions_[Command::Cancel] = cancelButton_; +QtAdHocCommandWindow::QtAdHocCommandWindow(std::shared_ptr<OutgoingAdHocCommandSession> command) : command_(command) { + formWidget_ = nullptr; + + setAttribute(Qt::WA_DeleteOnClose); + command->onNextStageReceived.connect(boost::bind(&QtAdHocCommandWindow::handleNextStageReceived, this, _1)); + command->onError.connect(boost::bind(&QtAdHocCommandWindow::handleError, this, _1)); + command->start(); + + layout_ = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout_->setContentsMargins(0,0,0,0); + layout_->setSpacing(2); + label_ = new QLabel(this); + 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_); + + dialogButtons_ = new QDialogButtonBox(this); + layout_->addWidget(dialogButtons_); + + dialogButtons_->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + okButton_ = dialogButtons_->button(QDialogButtonBox::Ok); + connect(okButton_, SIGNAL(clicked()), this, SLOT(close())); + cancelButton_ = dialogButtons_->button(QDialogButtonBox::Cancel); + connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked())); + // Buttons appear next to the Ok button, right of Cancel with YesRole + completeButton_ = dialogButtons_->addButton(tr("Complete"), QDialogButtonBox::YesRole); + connect(completeButton_, SIGNAL(clicked()), this, SLOT(handleCompleteClicked())); + nextButton_ = dialogButtons_->addButton(tr("Next"), QDialogButtonBox::YesRole); + connect(nextButton_, SIGNAL(clicked()), this, SLOT(handleNextClicked())); + backButton_ = dialogButtons_->addButton(tr("Back"), QDialogButtonBox::YesRole); + connect(backButton_, SIGNAL(clicked()), this, SLOT(handlePrevClicked())); + + okButton_->setEnabled(false); + okButton_->hide(); + + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + + actions_[Command::Next] = nextButton_; + actions_[Command::Prev] = backButton_; + actions_[Command::Complete] = completeButton_; + actions_[Command::Cancel] = cancelButton_; } 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(); } void QtAdHocCommandWindow::handleCancelClicked() { - command_->cancel(); - close(); + command_->cancel(); + close(); } void QtAdHocCommandWindow::handlePrevClicked() { - command_->goBack(); + command_->goBack(); } void QtAdHocCommandWindow::handleNextClicked() { - command_->goNext(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); + command_->goNext(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); } void QtAdHocCommandWindow::handleCompleteClicked() { - command_->complete(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); + command_->complete(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); } void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) { - QString notes; - foreach (Command::Note note, command->getNotes()) { - if (!notes.isEmpty()) { - notes += "\n"; - } - QString qNote(P2QSTRING(note.note)); - switch (note.type) { - case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break; - case Command::Note::Warn: notes += tr("Warning: %1").arg(qNote); break; - case Command::Note::Info: notes += qNote; break; - } - } - label_->setText(notes); - if (command->getForm()) { - setForm(command->getForm()); - } else { - setNoForm(notes.isEmpty()); - } - setAvailableActions(command); + QString notes; + for (const auto& note : command->getNotes()) { + if (!notes.isEmpty()) { + notes += "\n"; + } + QString qNote(P2QSTRING(note.note)); + switch (note.type) { + case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break; + case Command::Note::Warn: notes += tr("Warning: %1").arg(qNote); break; + case Command::Note::Info: notes += qNote; break; + } + } + label_->setText(notes); + if (command->getForm()) { + setForm(command->getForm()); + } else { + setNoForm(notes.isEmpty()); + } + setAvailableActions(command); } void QtAdHocCommandWindow::handleError(ErrorPayload::ref /*error*/) { - nextButton_->setEnabled(false); - backButton_->setEnabled(false); - completeButton_->setEnabled(false); - label_->setText(tr("Error executing command")); + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + label_->setText(tr("Error executing command")); } void QtAdHocCommandWindow::setForm(Form::ref form) { - form_ = form; - delete formWidget_; - formWidget_ = new QtFormWidget(form, this); - layout_->insertWidget(FormLayoutIndex, formWidget_); - show(); + form_ = form; + delete formWidget_; + formWidget_ = new QtFormWidget(form, this); + layout_->insertWidget(FormLayoutIndex, formWidget_); + show(); } void QtAdHocCommandWindow::setNoForm(bool andHide) { - form_.reset(); - delete formWidget_; - formWidget_ = NULL; - resize(minimumSize()); - setVisible(!andHide); + form_.reset(); + delete formWidget_; + formWidget_ = nullptr; + resize(minimumSize()); + setVisible(!andHide); } typedef std::pair<Command::Action, QPushButton*> ActionButton; void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) { - foreach (ActionButton pair, actions_) { - OutgoingAdHocCommandSession::ActionState state = command_->getActionState(pair.first); - if (state & OutgoingAdHocCommandSession::Present) { - pair.second->show(); - } - else { - pair.second->hide(); - } - if (state & OutgoingAdHocCommandSession::Enabled) { - pair.second->setEnabled(true); - } - else { - pair.second->setEnabled(false); - } - } + okButton_->show(); + okButton_->setEnabled(true); + for (auto&& pair : actions_) { + OutgoingAdHocCommandSession::ActionState state = command_->getActionState(pair.first); + if (state & OutgoingAdHocCommandSession::Present) { + okButton_->hide(); + okButton_->setEnabled(false); + pair.second->show(); + } + else { + pair.second->hide(); + } + if (state & OutgoingAdHocCommandSession::Enabled) { + pair.second->setEnabled(true); + } + else { + pair.second->setEnabled(false); + } + } } } diff --git a/Swift/QtUI/QtAdHocCommandWindow.h b/Swift/QtUI/QtAdHocCommandWindow.h index d42a77d..1135ef9 100644 --- a/Swift/QtUI/QtAdHocCommandWindow.h +++ b/Swift/QtUI/QtAdHocCommandWindow.h @@ -1,48 +1,58 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWidget> -#include <QPushButton> +#include <QDialogButtonBox> #include <QLabel> +#include <QPushButton> +#include <QWidget> -#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h> #include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h> + class QBoxLayout; namespace Swift { - class QtFormWidget; - class QtAdHocCommandWindow : public QWidget, public AdHocCommandWindow { - Q_OBJECT - public: - QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); - virtual ~QtAdHocCommandWindow(); - private: - void handleNextStageReceived(Command::ref command); - void handleError(ErrorPayload::ref error); - void setForm(Form::ref); - void setNoForm(bool andHide); - void setAvailableActions(Command::ref commandResult); - private slots: - void handleCancelClicked(); - void handlePrevClicked(); - void handleNextClicked(); - void handleCompleteClicked(); - private: - boost::shared_ptr<OutgoingAdHocCommandSession> command_; - QtFormWidget* formWidget_; - Form::ref form_; - QLabel* label_; - QPushButton* backButton_; - QPushButton* nextButton_; - QPushButton* completeButton_; - QPushButton* cancelButton_; - std::map<Command::Action, QPushButton*> actions_; - QBoxLayout* layout_; - }; + class QtFormWidget; + class QtAdHocCommandWindow : public QWidget, public AdHocCommandWindow { + Q_OBJECT + public: + QtAdHocCommandWindow(std::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(bool andHide); + void setAvailableActions(Command::ref commandResult); + + private slots: + void handleCancelClicked(); + void handlePrevClicked(); + void handleNextClicked(); + void handleCompleteClicked(); + + private: + std::shared_ptr<OutgoingAdHocCommandSession> command_; + QtFormWidget* formWidget_; + Form::ref form_; + QLabel* label_; + QLabel* errorLabel_; + QPushButton* backButton_; + QPushButton* nextButton_; + QPushButton* completeButton_; + QPushButton* cancelButton_; + QPushButton* okButton_; + std::map<Command::Action, QPushButton*> actions_; + QDialogButtonBox* dialogButtons_; + QBoxLayout* layout_; + }; } diff --git a/Swift/QtUI/QtAdHocCommandWithJIDWindow.cpp b/Swift/QtUI/QtAdHocCommandWithJIDWindow.cpp new file mode 100644 index 0000000..1b114d9 --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWithJIDWindow.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtAdHocCommandWithJIDWindow.h> + +#include <boost/bind.hpp> + +#include <QBoxLayout> +#include <QDialogButtonBox> +#include <QLabel> +#include <QPushButton> + +#include <Swiften/Elements/Command.h> + +#include <Swift/Controllers/UIEvents/RequestAdHocWithJIDUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <Swift/QtUI/QtFormWidget.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +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()); + std::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..0e83555 --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWithJIDWindow.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2010-2012 Isode Limited. + * All rights reserved. + * See the COPYING file 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..8c5d662 100644 --- a/Swift/QtUI/QtAddBookmarkWindow.cpp +++ b/Swift/QtUI/QtAddBookmarkWindow.cpp @@ -1,27 +1,30 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtAddBookmarkWindow.h" - -#include <qdebug.h> +#include <Swift/QtUI/QtAddBookmarkWindow.h> namespace Swift { QtAddBookmarkWindow::QtAddBookmarkWindow(UIEventStream* eventStream) : eventStream_(eventStream) { + setWindowTitle(tr("Add Bookmark Details")); +} +QtAddBookmarkWindow::QtAddBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark) : eventStream_(eventStream) { + createFormFromBookmark(bookmark); + setWindowTitle(tr("Add Bookmark Details")); } bool QtAddBookmarkWindow::commit() { - boost::optional<MUCBookmark> bookmark = createBookmarkFromForm(); - if (bookmark) { - eventStream_->send(boost::shared_ptr<UIEvent>(new AddMUCBookmarkUIEvent(*bookmark))); - return true; - } - else { - return false; - } + boost::optional<MUCBookmark> bookmark = createBookmarkFromForm(); + if (bookmark) { + eventStream_->send(std::make_shared<AddMUCBookmarkUIEvent>(*bookmark)); + return true; + } + else { + return false; + } } } diff --git a/Swift/QtUI/QtAddBookmarkWindow.h b/Swift/QtUI/QtAddBookmarkWindow.h index f026cc3..607f647 100644 --- a/Swift/QtUI/QtAddBookmarkWindow.h +++ b/Swift/QtUI/QtAddBookmarkWindow.h @@ -1,23 +1,24 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "QtBookmarkDetailWindow.h" +#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h" +#include <Swift/QtUI/QtBookmarkDetailWindow.h> namespace Swift { - class QtAddBookmarkWindow : public QtBookmarkDetailWindow { - Q_OBJECT - public: - QtAddBookmarkWindow(UIEventStream* eventStream); - bool commit(); - private: - UIEventStream* eventStream_; - }; + class QtAddBookmarkWindow : public QtBookmarkDetailWindow { + Q_OBJECT + public: + QtAddBookmarkWindow(UIEventStream* eventStream); + QtAddBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark); + bool commit(); + private: + UIEventStream* eventStream_; + }; } diff --git a/Swift/QtUI/QtAffiliationEditor.cpp b/Swift/QtUI/QtAffiliationEditor.cpp index 0896b92..92b6aff 100644 --- a/Swift/QtUI/QtAffiliationEditor.cpp +++ b/Swift/QtUI/QtAffiliationEditor.cpp @@ -1,79 +1,78 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtAffiliationEditor.h" +#include <Swift/QtUI/QtAffiliationEditor.h> -#include <QListWidgetItem> #include <QInputDialog> +#include <QListWidgetItem> -#include "QtSwiftUtil.h" - +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtAffiliationEditor::QtAffiliationEditor(QWidget* parent) : QDialog(parent){ - ui_.setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); - connect(ui_.affiliation, SIGNAL(currentIndexChanged(int)), this, SLOT(handleCurrentIndexChanged(int))); - connect(ui_.addJID, SIGNAL(clicked()), this, SLOT(handleAddClicked())); - connect(ui_.removeJID, SIGNAL(clicked()), this, SLOT(handleRemoveClicked())); + ui_.setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + connect(ui_.affiliation, SIGNAL(currentIndexChanged(int)), this, SLOT(handleCurrentIndexChanged(int))); + connect(ui_.addJID, SIGNAL(clicked()), this, SLOT(handleAddClicked())); + connect(ui_.removeJID, SIGNAL(clicked()), this, SLOT(handleRemoveClicked())); } QtAffiliationEditor::~QtAffiliationEditor() { - + } void QtAffiliationEditor::setAffiliations(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) { - affiliations_[affiliation] = jids; - if (affiliationFromIndex(ui_.affiliation->currentIndex()) == affiliation) { - handleCurrentIndexChanged(ui_.affiliation->currentIndex()); - } + affiliations_[affiliation] = jids; + if (affiliationFromIndex(ui_.affiliation->currentIndex()) == affiliation) { + handleCurrentIndexChanged(ui_.affiliation->currentIndex()); + } } const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& QtAffiliationEditor::getChanges() const { - return changes_; + return changes_; } void QtAffiliationEditor::handleCurrentIndexChanged(int index) { - ui_.list->clear(); - foreach (const JID& jid, affiliations_[affiliationFromIndex(index)]) { - ui_.list->addItem(P2QSTRING(jid.toString())); - } + ui_.list->clear(); + for (const auto& jid : affiliations_[affiliationFromIndex(index)]) { + ui_.list->addItem(P2QSTRING(jid.toString())); + } } void QtAffiliationEditor::handleAddClicked() { - bool ok = false; - JID jid = JID(Q2PSTRING(QInputDialog::getText(this, tr("Add User"), tr("Added User's Address:"), QLineEdit::Normal, "", &ok))); - if (ok && jid.isValid()) { - //FIXME: validation - MUCOccupant::Affiliation affiliation = affiliationFromIndex(ui_.affiliation->currentIndex()); - changes_.push_back(ChangePair(affiliation, jid)); - ui_.list->addItem(P2QSTRING(jid.toString())); - affiliations_[affiliation].push_back(jid); - } + bool ok = false; + JID jid = JID(Q2PSTRING(QInputDialog::getText(this, tr("Add User"), tr("Added User's Address:"), QLineEdit::Normal, "", &ok))); + if (ok && jid.isValid()) { + //FIXME: validation + MUCOccupant::Affiliation affiliation = affiliationFromIndex(ui_.affiliation->currentIndex()); + changes_.push_back(ChangePair(affiliation, jid)); + ui_.list->addItem(P2QSTRING(jid.toString())); + affiliations_[affiliation].push_back(jid); + } } void QtAffiliationEditor::handleRemoveClicked() { - QListWidgetItem* item = ui_.list->currentItem(); - if (item) { - JID jid(Q2PSTRING(item->text())); - changes_.push_back(ChangePair(MUCOccupant::NoAffiliation, jid)); - std::vector<JID>& jids = affiliations_[affiliationFromIndex(ui_.affiliation->currentIndex())]; - jids.erase(std::remove(jids.begin(), jids.end(), jid), jids.end()); - handleCurrentIndexChanged(ui_.affiliation->currentIndex()); - } + QListWidgetItem* item = ui_.list->currentItem(); + if (item) { + JID jid(Q2PSTRING(item->text())); + changes_.push_back(ChangePair(MUCOccupant::NoAffiliation, jid)); + std::vector<JID>& jids = affiliations_[affiliationFromIndex(ui_.affiliation->currentIndex())]; + jids.erase(std::remove(jids.begin(), jids.end(), jid), jids.end()); + handleCurrentIndexChanged(ui_.affiliation->currentIndex()); + } } MUCOccupant::Affiliation QtAffiliationEditor::affiliationFromIndex(int affiliation) { - switch (affiliation) { - case 0: return MUCOccupant::Owner; - case 1: return MUCOccupant::Admin; - case 2: return MUCOccupant::Member; - case 3: return MUCOccupant::Outcast; - default: return MUCOccupant::Outcast; - } + switch (affiliation) { + case 0: return MUCOccupant::Owner; + case 1: return MUCOccupant::Admin; + case 2: return MUCOccupant::Member; + case 3: return MUCOccupant::Outcast; + default: return MUCOccupant::Outcast; + } } } diff --git a/Swift/QtUI/QtAffiliationEditor.h b/Swift/QtUI/QtAffiliationEditor.h index 96536eb..58e2497 100644 --- a/Swift/QtUI/QtAffiliationEditor.h +++ b/Swift/QtUI/QtAffiliationEditor.h @@ -1,37 +1,38 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <vector> #include <map> +#include <vector> #include <QDialog> -#include <Swift/QtUI/ui_QtAffiliationEditor.h> -#include <Swiften/JID/JID.h> #include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/JID/JID.h> + +#include <Swift/QtUI/ui_QtAffiliationEditor.h> namespace Swift { - class QtAffiliationEditor : public QDialog { - Q_OBJECT - public: - QtAffiliationEditor(QWidget* parent = NULL); - ~QtAffiliationEditor(); - void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>& jids); - const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& getChanges() const; - private slots: - void handleCurrentIndexChanged(int); - void handleAddClicked(); - void handleRemoveClicked(); - private: - typedef std::pair<MUCOccupant::Affiliation, JID> ChangePair; - MUCOccupant::Affiliation affiliationFromIndex(int affiliation); - Ui::QtAffiliationEditor ui_; - std::map<MUCOccupant::Affiliation, std::vector<JID> > affiliations_; - std::vector<ChangePair> changes_; - }; + class QtAffiliationEditor : public QDialog { + Q_OBJECT + public: + QtAffiliationEditor(QWidget* parent = nullptr); + ~QtAffiliationEditor(); + void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>& jids); + const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& getChanges() const; + private slots: + void handleCurrentIndexChanged(int); + void handleAddClicked(); + void handleRemoveClicked(); + private: + typedef std::pair<MUCOccupant::Affiliation, JID> ChangePair; + MUCOccupant::Affiliation affiliationFromIndex(int affiliation); + Ui::QtAffiliationEditor ui_; + std::map<MUCOccupant::Affiliation, std::vector<JID> > affiliations_; + std::vector<ChangePair> changes_; + }; } diff --git a/Swift/QtUI/QtAvatarWidget.cpp b/Swift/QtUI/QtAvatarWidget.cpp index 015c2da..7f6e275 100644 --- a/Swift/QtUI/QtAvatarWidget.cpp +++ b/Swift/QtUI/QtAvatarWidget.cpp @@ -1,108 +1,111 @@ /* - * Copyright (c) 2011-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtAvatarWidget.h" -#include <QLabel> -#include <QVBoxLayout> -#include <QPixmap> -#include <QMenu> + +#include <Swift/QtUI/QtAvatarWidget.h> + #include <QAction> -#include <QMouseEvent> +#include <QBuffer> #include <QFileDialog> #include <QImageReader> -#include <QBuffer> +#include <QLabel> +#include <QMenu> #include <QMessageBox> +#include <QMouseEvent> #include <QPainter> +#include <QPixmap> +#include <QVBoxLayout> -#include <QtSwiftUtil.h> #include <Swiften/Base/Path.h> +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { -QtAvatarWidget::QtAvatarWidget(QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins(0,0,0,0); - - QSizePolicy sp(QSizePolicy::Fixed, QSizePolicy::Fixed); - sp.setHorizontalStretch(0); - sp.setVerticalStretch(0); - setSizePolicy(sp); - setMinimumSize(QSize(96, 96)); - setMaximumSize(QSize(96, 96)); - - label = new QLabel(this); - label->setWordWrap(true); - label->setSizePolicy(sp); - label->setMinimumSize(QSize(96, 96)); - label->setMaximumSize(QSize(96, 96)); - label->setAlignment(Qt::AlignCenter); - layout->addWidget(label); +QtAvatarWidget::QtAvatarWidget(QWidget* parent) : QWidget(parent), editable(false) { + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0,0,0,0); + + QSizePolicy sp(QSizePolicy::Fixed, QSizePolicy::Fixed); + sp.setHorizontalStretch(0); + sp.setVerticalStretch(0); + setSizePolicy(sp); + setMinimumSize(QSize(96, 96)); + setMaximumSize(QSize(96, 96)); + + label = new QLabel(this); + label->setWordWrap(true); + label->setSizePolicy(sp); + label->setMinimumSize(QSize(96, 96)); + label->setMaximumSize(QSize(96, 96)); + label->setAlignment(Qt::AlignCenter); + layout->addWidget(label); } void QtAvatarWidget::setAvatar(const ByteArray& data, const std::string& type) { - this->data = data; - this->type = type; - - QImage image; - if (!data.empty()) { - image.loadFromData(reinterpret_cast<const uchar*>(vecptr(data)), data.size()); - } - - if (image.isNull()) { - image = QImage(":/icons/no-avatar.png"); - QPainter painter(&image); - painter.setPen(Qt::gray); - QFont font = painter.font(); - font.setPointSize(14); - painter.setFont(font); - painter.drawText(0, 0, image.height(), image.width(), Qt::AlignHCenter | Qt::AlignVCenter, tr("No picture")); - } - - if (image.height() > label->height() || image.width() > label->width()) { - image = image.scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - label->setPixmap(QPixmap::fromImage(image)); + this->data = data; + this->type = type; + + QImage image; + if (!data.empty()) { + image.loadFromData(reinterpret_cast<const uchar*>(vecptr(data)), data.size()); + } + + if (image.isNull()) { + image = QImage(":/icons/no-avatar.png"); + QPainter painter(&image); + painter.setPen(Qt::gray); + QFont font = painter.font(); + font.setPointSize(14); + painter.setFont(font); + painter.drawText(0, 0, image.height(), image.width(), Qt::AlignHCenter | Qt::AlignVCenter, tr("No picture")); + } + + if (image.height() > label->height() || image.width() > label->width()) { + image = image.scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + label->setPixmap(QPixmap::fromImage(image)); } void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { - if (!editable) { - return; - } - QMenu menu; - - QAction* selectPicture = new QAction(tr("Select picture ..."), this); - menu.addAction(selectPicture); - - QAction* clearPicture = new QAction(tr("Clear picture"), this); - menu.addAction(clearPicture); - - QAction* result = menu.exec(event->globalPos()); - if (result == selectPicture) { - QString fileName = QFileDialog::getOpenFileName(this, tr("Select picture"), "", tr("Image Files (*.png *.jpg *.jpeg *.gif)")); - if (!fileName.isEmpty()) { - ByteArray data; - readByteArrayFromFile(data, stringToPath(Q2PSTRING(fileName))); - - QBuffer buffer; - buffer.setData(reinterpret_cast<const char*>(vecptr(data)), data.size()); - buffer.open(QIODevice::ReadOnly); - QString type = QImageReader::imageFormat(&buffer).toLower(); - if (!type.isEmpty()) { - type = "image/" + type; - setAvatar(data, Q2PSTRING(type)); - } - else { - QMessageBox::critical(this, tr("Error"), tr("The selected picture is in an unrecognized format")); - } - } - } - else if (result == clearPicture) { - setAvatar(ByteArray(), ""); - } + if (!editable) { + return; + } + QMenu menu; + + QAction* selectPicture = new QAction(tr("Select picture ..."), this); + menu.addAction(selectPicture); + + QAction* clearPicture = new QAction(tr("Clear picture"), this); + menu.addAction(clearPicture); + + QAction* result = menu.exec(event->globalPos()); + if (result == selectPicture) { + QString fileName = QFileDialog::getOpenFileName(this, tr("Select picture"), "", tr("Image Files (*.png *.jpg *.jpeg *.gif)")); + if (!fileName.isEmpty()) { + ByteArray data; + readByteArrayFromFile(data, stringToPath(Q2PSTRING(fileName))); + + QBuffer buffer; + buffer.setData(reinterpret_cast<const char*>(vecptr(data)), data.size()); + buffer.open(QIODevice::ReadOnly); + QString type = QImageReader::imageFormat(&buffer).toLower(); + if (!type.isEmpty()) { + type = "image/" + type; + setAvatar(data, Q2PSTRING(type)); + } + else { + QMessageBox::critical(this, tr("Error"), tr("The selected picture is in an unrecognized format")); + } + } + } + else if (result == clearPicture) { + setAvatar(ByteArray(), ""); + } } diff --git a/Swift/QtUI/QtAvatarWidget.h b/Swift/QtUI/QtAvatarWidget.h index f4ac4cf..612c79b 100644 --- a/Swift/QtUI/QtAvatarWidget.h +++ b/Swift/QtUI/QtAvatarWidget.h @@ -1,48 +1,49 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWidget> #include <QImage> +#include <QWidget> + #include <Swiften/Base/ByteArray.h> class QLabel; namespace Swift { - class QtAvatarWidget : public QWidget { - Q_OBJECT - Q_PROPERTY(bool editable READ isEditable WRITE setEditable) - public: - QtAvatarWidget(QWidget* parent); - - void setAvatar(const ByteArray& data, const std::string& type); - - const ByteArray& getAvatarData() const { - return data; - } - - const std::string& getAvatarType() const { - return type; - } - - void setEditable(bool b) { - editable = b; - } - - bool isEditable() const { - return editable; - } - - void mousePressEvent(QMouseEvent* event); - - private: - bool editable; - ByteArray data; - std::string type; - QLabel* label; - }; + class QtAvatarWidget : public QWidget { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + public: + QtAvatarWidget(QWidget* parent); + + void setAvatar(const ByteArray& data, const std::string& type); + + const ByteArray& getAvatarData() const { + return data; + } + + const std::string& getAvatarType() const { + return type; + } + + void setEditable(bool b) { + editable = b; + } + + bool isEditable() const { + return editable; + } + + void mousePressEvent(QMouseEvent* event); + + private: + bool editable; + ByteArray data; + std::string type; + QLabel* label; + }; } diff --git a/Swift/QtUI/QtBlockListEditorWindow.cpp b/Swift/QtUI/QtBlockListEditorWindow.cpp index 150b634..a6eca0e 100644 --- a/Swift/QtUI/QtBlockListEditorWindow.cpp +++ b/Swift/QtUI/QtBlockListEditorWindow.cpp @@ -4,159 +4,249 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include <QtBlockListEditorWindow.h> -#include <ui_QtBlockListEditorWindow.h> +/* + * Copyright (c) 2014-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtBlockListEditorWindow.h> #include <boost/bind.hpp> +#include <QClipboard> #include <QLineEdit> #include <QMovie> #include <QShortcut> #include <QStyledItemDelegate> #include <QValidator> -#include <Swift/QtUI/QtUtilities.h> #include <Swiften/Client/ClientBlockListManager.h> -#include <Swiften/Base/foreach.h> -#include <Swift/QtUI/QtSwiftUtil.h> #include <Swiften/JID/JID.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/ui_QtBlockListEditorWindow.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; - } + 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); - } + public: + QtJIDValidatedItemDelegate(QObject* parent) : QItemDelegate(parent) {} + virtual ~QtJIDValidatedItemDelegate() {} + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const { + QLineEdit *editor = new QLineEdit(parent); + editor->setValidator(new QtJIDValidator(editor)); + return editor; + } + + void setEditorData(QWidget *editor, const QModelIndex &index) const { + QString value = index.model()->data(index, Qt::EditRole).toString(); + + QLineEdit *lineEdit = static_cast<QLineEdit*>(editor); + lineEdit->setText(value); + } + + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { + QLineEdit *lineEdit = static_cast<QLineEdit*>(editor); + QString currentValue = lineEdit->text(); + int pos = 0; + if (lineEdit->validator()->validate(currentValue, pos) == QValidator::Acceptable) { + model->setData(index, lineEdit->text(), Qt::EditRole); + } else { + model->setData(index, QString(), Qt::EditRole); + } + } + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const { + editor->setGeometry(option.rect); + } }; -QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow) { - ui->setupUi(this); - new QShortcut(QKeySequence::Close, this, SLOT(close())); - ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); +QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow), removeItemDelegate(nullptr), editItemDelegate(nullptr) { + ui->setupUi(this); - itemDelegate = new QtRemovableItemDelegate(style()); + freshBlockListTemplate = tr("Double-click to add contact"); - connect(ui->savePushButton, SIGNAL(clicked()), SLOT(applyChanges())); + new QShortcut(QKeySequence::Close, this, SLOT(close())); + ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); - ui->blockListTreeWidget->setColumnCount(2); - ui->blockListTreeWidget->header()->setStretchLastSection(false); - int closeIconWidth = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0); - ui->blockListTreeWidget->header()->resizeSection(1, closeIconWidth); + 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); + ui->blockListTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); #else - ui->blockListTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch); + ui->blockListTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch); #endif - ui->blockListTreeWidget->setHeaderHidden(true); - ui->blockListTreeWidget->setRootIsDecorated(false); - ui->blockListTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); - ui->blockListTreeWidget->setItemDelegateForColumn(0, new QtJIDValidatedItemDelegate(this)); - ui->blockListTreeWidget->setItemDelegateForColumn(1, itemDelegate); - connect(ui->blockListTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int))); + 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); + + // Allow pasting a newline seperated list of JIDs into the dialog. + auto pasteShortcut = new QShortcut(QKeySequence::Paste, this); + connect(pasteShortcut, &QShortcut::activated, [&](){ + auto currentBlocklist = getCurrentBlockList(); + + auto clipboardText = QGuiApplication::clipboard()->text(); + auto stringList = clipboardText.split("\n"); + for (const auto& string : stringList) { + auto jid = JID(Q2PSTRING(string.trimmed())); + if (jid.isValid()) { + if (std::find(currentBlocklist.begin(), currentBlocklist.end(), jid) == currentBlocklist.end()) { + currentBlocklist.push_back(jid); + } + } + } + setCurrentBlockList(currentBlocklist); + }); - QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); - item->setFlags(item->flags() | Qt::ItemIsEditable); - ui->blockListTreeWidget->addTopLevelItem(item); } QtBlockListEditorWindow::~QtBlockListEditorWindow() { } void QtBlockListEditorWindow::show() { - QWidget::show(); - QWidget::activateWindow(); + QWidget::showNormal(); + QWidget::activateWindow(); + QWidget::raise(); +} + +void QtBlockListEditorWindow::hide() { + QWidget::hide(); } -void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *, int) { - bool hasEmptyRow = false; - QList<QTreeWidgetItem*> rows = ui->blockListTreeWidget->findItems("", Qt::MatchFixedString); - foreach(QTreeWidgetItem* row, rows) { - if (row->text(0).isEmpty()) { - hasEmptyRow = true; - } - } - - if (!hasEmptyRow) { - QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); - item->setFlags(item->flags() | Qt::ItemIsEditable); - ui->blockListTreeWidget->addTopLevelItem(item); - } +void QtBlockListEditorWindow::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()); + onSetNewBlockList(getCurrentBlockList()); } -void Swift::QtBlockListEditorWindow::setCurrentBlockList(const std::vector<JID> &blockedJIDs) { - ui->blockListTreeWidget->clear(); +void QtBlockListEditorWindow::setCurrentBlockList(const std::vector<JID> &blockedJIDs) { + ui->blockListTreeWidget->clear(); + + QStringList blockedStrings; + for (const auto& jid : blockedJIDs) { + blockedStrings << P2QSTRING(jid.toString()); + } + blockedStrings.sort(); + + for (const auto& jid : blockedStrings) { + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(jid) << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + ui->blockListTreeWidget->addTopLevelItem(item); + } + handleItemChanged(nullptr,0); +} - 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 Swift::QtBlockListEditorWindow::setBusy(bool isBusy) { - if (isBusy) { - ui->throbberLabel->movie()->start(); - ui->throbberLabel->show(); - } else { - ui->throbberLabel->movie()->stop(); - ui->throbberLabel->hide(); - } +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); - if (!row->text(0).isEmpty()) { - futureBlockedJIDs.push_back(JID(Q2PSTRING(row->text(0)))); - } - } - return futureBlockedJIDs; + 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 index 4b124a3..dc900ab 100644 --- a/Swift/QtUI/QtBlockListEditorWindow.h +++ b/Swift/QtUI/QtBlockListEditorWindow.h @@ -4,39 +4,53 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once +#include <QTreeWidgetItem> +#include <QWidget> + #include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h> -#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> -#include <QWidget> -#include <QTreeWidgetItem> +#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> namespace Ui { - class QtBlockListEditorWindow; + class QtBlockListEditorWindow; } namespace Swift { -class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { - Q_OBJECT - - public: - QtBlockListEditorWindow(); - virtual ~QtBlockListEditorWindow(); +class QtJIDValidatedItemDelegate; - virtual void show(); - virtual void setCurrentBlockList(const std::vector<JID>& blockedJIDs); - virtual void setBusy(bool isBusy); - virtual std::vector<JID> getCurrentBlockList() const; - - private slots: - void handleItemChanged(QTreeWidgetItem*, int); - void applyChanges(); - - private: - Ui::QtBlockListEditorWindow* ui; - QtRemovableItemDelegate* itemDelegate; +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 index f71bbae..7a5b685 100644 --- a/Swift/QtUI/QtBlockListEditorWindow.ui +++ b/Swift/QtUI/QtBlockListEditorWindow.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>348</width> + <width>333</width> <height>262</height> </rect> </property> @@ -17,9 +17,34 @@ <property name="spacing"> <number>5</number> </property> - <property name="margin"> + <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 following list shows all contacts that you have currently blocked. You can add contacts to the list at the bottom 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"> @@ -38,19 +63,6 @@ <enum>QLayout::SetDefaultConstraint</enum> </property> <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> <widget class="QLabel" name="errorLabel"> <property name="text"> <string/> @@ -71,6 +83,19 @@ </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> diff --git a/Swift/QtUI/QtBookmarkDetailWindow.cpp b/Swift/QtUI/QtBookmarkDetailWindow.cpp index ae84b4b..efa0e25 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.cpp +++ b/Swift/QtUI/QtBookmarkDetailWindow.cpp @@ -1,52 +1,73 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtBookmarkDetailWindow.h" -#include "QtSwiftUtil.h" +#include <Swift/QtUI/QtBookmarkDetailWindow.h> + #include <QMessageBox> +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { QtBookmarkDetailWindow::QtBookmarkDetailWindow(QWidget* parent) : QDialog(parent) { - setupUi(this); - setAttribute(Qt::WA_DeleteOnClose, true); - //connect(buttons_, SIGNAL(accepted()), SLOT(accept())); - //connect(buttons_, SIGNAL(rejected()), SLOT(reject())); + setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + //connect(buttons_, SIGNAL(accepted()), SLOT(accept())); + //connect(buttons_, SIGNAL(rejected()), SLOT(reject())); + setFixedHeight(sizeHint().height()); } void QtBookmarkDetailWindow::accept() { - if (commit()) { - QDialog::accept(); - } + if (commit()) { + QDialog::accept(); + } } boost::optional<MUCBookmark> QtBookmarkDetailWindow::createBookmarkFromForm() { - //check room - //check bookmarkName - JID room(Q2PSTRING(room_->text())); - if (!room.isValid() || room.getNode().empty() || !room.getResource().empty()) { - QMessageBox::warning(this, tr("Bookmark not valid"), tr("You must specify a valid room address (e.g. someroom@rooms.example.com).")); - return boost::optional<MUCBookmark>(); - } - std::string name(Q2PSTRING(name_->text())); - if (name.empty()) { - name = room.toString(); - } - - MUCBookmark bookmark(room, name); - std::string nick(Q2PSTRING(nick_->text())); - std::string password(Q2PSTRING(password_->text())); - bookmark.setAutojoin(autojoin_->isChecked()); - if (!nick.empty()) { - bookmark.setNick(nick); - } - if (!password.empty()) { - bookmark.setPassword(password); - } - return bookmark; + //check room + //check bookmarkName + JID room(Q2PSTRING(room_->text())); + if (!room.isValid() || room.getNode().empty() || !room.getResource().empty()) { + QMessageBox::warning(this, tr("Bookmark not valid"), tr("You must specify a valid room address (e.g. someroom@rooms.example.com).")); + return boost::optional<MUCBookmark>(); + } + std::string name(Q2PSTRING(name_->text())); + if (name.empty()) { + name = room.toString(); + } + + MUCBookmark bookmark(room, name); + std::string nick(Q2PSTRING(nick_->text())); + std::string password(Q2PSTRING(password_->text())); + bookmark.setAutojoin(true); + if (!nick.empty()) { + bookmark.setNick(nick); + } + if (!password.empty()) { + bookmark.setPassword(password); + } + return bookmark; +} + +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()))); + } } } diff --git a/Swift/QtUI/QtBookmarkDetailWindow.h b/Swift/QtUI/QtBookmarkDetailWindow.h index fd2b7b4..82e757d 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.h +++ b/Swift/QtUI/QtBookmarkDetailWindow.h @@ -1,29 +1,32 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "ui_QtBookmarkDetailWindow.h" - #include <boost/optional.hpp> #include <QDialog> -#include "Swiften/MUC/MUCBookmark.h" +#include <Swiften/MUC/MUCBookmark.h> + +#include <Swift/QtUI/ui_QtBookmarkDetailWindow.h> namespace Swift { - class QtBookmarkDetailWindow : public QDialog, protected Ui::QtBookmarkDetailWindow { - Q_OBJECT - public: - QtBookmarkDetailWindow(QWidget* parent = NULL); - virtual bool commit() = 0; - boost::optional<MUCBookmark> createBookmarkFromForm(); - - public slots: - void accept(); - }; + class QtBookmarkDetailWindow : public QDialog, protected Ui::QtBookmarkDetailWindow { + Q_OBJECT + public: + QtBookmarkDetailWindow(QWidget* parent = nullptr); + virtual bool commit() = 0; + 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..affb7e4 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.ui +++ b/Swift/QtUI/QtBookmarkDetailWindow.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>396</width> - <height>282</height> + <width>382</width> + <height>207</height> </rect> </property> <property name="sizePolicy"> @@ -22,95 +22,81 @@ <property name="sizeGripEnabled"> <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> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QFormLayout" name="formLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Bookmark Name:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="name_"/> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Room Address:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="room_"/> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Your Nickname:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="nick_"/> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Room password:</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLineEdit" name="password_"/> - </item> - <item row="4" column="0"> - <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 row="4" column="1"> - <widget class="QCheckBox" name="autojoin_"> - <property name="text"> - <string>Enter automatically</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QDialogButtonBox" name="buttons_"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - </item> - </layout> - </widget> + <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"> + <property name="text"> + <string>Bookmark Name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="name_"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Room Address:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="room_"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Your Nickname:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="nick_"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Room password:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="password_"/> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttons_"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> </widget> <resources/> <connections> diff --git a/Swift/QtUI/QtCachedImageScaler.cpp b/Swift/QtUI/QtCachedImageScaler.cpp index 45375e7..445d113 100644 --- a/Swift/QtUI/QtCachedImageScaler.cpp +++ b/Swift/QtUI/QtCachedImageScaler.cpp @@ -1,14 +1,17 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtCachedImageScaler.h" +#include <Swift/QtUI/QtCachedImageScaler.h> -#include <QImage> #include <boost/lexical_cast.hpp> + +#include <QImage> + #include <Swiften/Base/Path.h> + #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -17,25 +20,25 @@ QtCachedImageScaler::QtCachedImageScaler() { } boost::filesystem::path QtCachedImageScaler::getScaledImage(const boost::filesystem::path& imagePath, int 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(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(P2QSTRING(pathToString(scaledImagePath)), "PNG"); - } - else { - image.save(P2QSTRING(pathToString(scaledImagePath)), "PNG"); - } - } - else { - return imagePath; - } - } - return scaledImagePath; + boost::filesystem::path scaledImagePath(imagePath); + std::string suffix = "." + std::to_string(size); + scaledImagePath = stringToPath(pathToString(scaledImagePath) + suffix); + if (!boost::filesystem::exists(scaledImagePath)) { + 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(P2QSTRING(pathToString(scaledImagePath)), "PNG"); + } + else { + image.save(P2QSTRING(pathToString(scaledImagePath)), "PNG"); + } + } + else { + return imagePath; + } + } + return scaledImagePath; } diff --git a/Swift/QtUI/QtCachedImageScaler.h b/Swift/QtUI/QtCachedImageScaler.h index f85357f..15d868e 100644 --- a/Swift/QtUI/QtCachedImageScaler.h +++ b/Swift/QtUI/QtCachedImageScaler.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,11 +9,11 @@ #include <boost/filesystem.hpp> namespace Swift { - class QtCachedImageScaler { - public: - QtCachedImageScaler(); + class QtCachedImageScaler { + public: + QtCachedImageScaler(); - boost::filesystem::path getScaledImage(const boost::filesystem::path& image, int size); - }; + boost::filesystem::path getScaledImage(const boost::filesystem::path& image, int size); + }; } diff --git a/Swift/QtUI/QtCertificateViewerDialog.cpp b/Swift/QtUI/QtCertificateViewerDialog.cpp index 15a52ba..a36ccdb 100644 --- a/Swift/QtUI/QtCertificateViewerDialog.cpp +++ b/Swift/QtUI/QtCertificateViewerDialog.cpp @@ -4,131 +4,143 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtCertificateViewerDialog.h" -#include "ui_QtCertificateViewerDialog.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swiften/Base/foreach.h> +#include <Swift/QtUI/QtCertificateViewerDialog.h> -#include <QTreeWidgetItem> -#include <QLabel> #include <QDateTime> +#include <QLabel> +#include <QString> +#include <QStringList> +#include <QTreeWidgetItem> + +#include <Swift/QtUI/ui_QtCertificateViewerDialog.h> 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*))); + ui->setupUi(this); + connect(ui->certChainTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*))); - setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose); } QtCertificateViewerDialog::~QtCertificateViewerDialog() { - delete ui; + 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); - } + // clean widgets + ui->certChainTreeWidget->clear(); + + if (chain.empty()) return; + + // convert Swift certificate chain to qt certificate chain (root goes first) + currentChain.clear(); + for (auto&& 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("↳ ") + (QStringList(currentChain.at(n).subjectInfo(QSslCertificate::CommonName)).join(", ")))); + 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(); + 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())); + 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); + 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); + ui->certGridLayout->addWidget(new QLabel(TITLE), rowCount, 0, 1, 1, Qt::AlignRight); \ + valueLabel = new QLabel(); \ + valueLabel->setText(QStringList(VALUE).join(", ")); \ + 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); + 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 QT_VERSION < 0x050000 + QMultiMap<QSsl::AlternateNameEntryType, QString> altNames = cert.alternateSubjectNames(); +#else + QMultiMap<QSsl::AlternativeNameEntryType, QString> altNames = cert.subjectAlternativeNames(); +#endif + if (!altNames.empty()) { + ADD_SECTION(tr("Alternate Subject Names")); + for (const auto& type : altNames.uniqueKeys()) { + for (auto&& 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 index 9475a83..7b72f1d 100644 --- a/Swift/QtUI/QtCertificateViewerDialog.h +++ b/Swift/QtUI/QtCertificateViewerDialog.h @@ -20,25 +20,25 @@ class QtCertificateViewerDialog; namespace Swift { class QtCertificateViewerDialog : public QDialog { - Q_OBJECT - - public: - explicit QtCertificateViewerDialog(QWidget* parent = 0); - ~QtCertificateViewerDialog(); + Q_OBJECT - void setCertificateChain(const std::vector<Certificate::ref>& chain); + public: + explicit QtCertificateViewerDialog(QWidget* parent = 0); + ~QtCertificateViewerDialog(); - static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); + void setCertificateChain(const std::vector<Certificate::ref>& chain); - private slots: - void currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); + static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); - private: - void setCertificateDetails(const QSslCertificate& cert); + private slots: + void currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*); - private: - Ui::QtCertificateViewerDialog *ui; - QList<QSslCertificate> currentChain; + private: + void setCertificateDetails(const QSslCertificate& cert); + + private: + Ui::QtCertificateViewerDialog *ui; + QList<QSslCertificate> currentChain; }; } diff --git a/Swift/QtUI/QtChatOverview.cpp b/Swift/QtUI/QtChatOverview.cpp new file mode 100644 index 0000000..76943e9 --- /dev/null +++ b/Swift/QtUI/QtChatOverview.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtChatOverview.h> + +#include <QHBoxLayout> +#include <QLabel> +#include <QPalette> +#include <QVBoxLayout> + +#include <Swift/Controllers/Chat/Chattables.h> + +#include <Swift/QtUI/ChattablesModel.h> +#include <Swift/QtUI/QtChatOverviewBundle.h> + +namespace Swift { + +QtChatOverview::QtChatOverview(Chattables& chattables, QWidget* parent) : QWidget(parent), chattables_(chattables) { + QPalette newPalette = palette(); + newPalette.setColor(QPalette::Background, {38, 81, 112}); + setAutoFillBackground(true); + newPalette.setColor(QPalette::Foreground, {255, 255, 255}); + setPalette(newPalette); + + auto mainLayout = new QVBoxLayout(); + setLayout(mainLayout); + + auto headerLayout = new QHBoxLayout(); + mainLayout->addLayout(headerLayout); + auto allLabel = new QLabel(tr("All"), this); + allLabel->setStyleSheet("color: white;"); + auto peopleLabel = new QLabel(tr("People"), this); + peopleLabel->setStyleSheet("color: white;"); + auto roomsLabel = new QLabel(tr("Rooms"), this); + roomsLabel->setStyleSheet("color: white;"); + headerLayout->addWidget(allLabel); + headerLayout->addWidget(peopleLabel); + headerLayout->addWidget(roomsLabel); + + rootModel_ = new ChattablesModel(chattables_, this); + + auto unreadBundle = new QtChatOverviewBundle(rootModel_, "UNREAD", true, this); + connect(unreadBundle, SIGNAL(clicked(JID)), this, SLOT(handleItemClicked(JID))); + mainLayout->addWidget(unreadBundle); + + auto peopleBundle = new QtChatOverviewBundle(rootModel_, "PEOPLE", false, this); + connect(peopleBundle, SIGNAL(clicked(JID)), this, SLOT(handleItemClicked(JID))); + mainLayout->addWidget(peopleBundle); + + auto roomsBundle = new QtChatOverviewBundle(rootModel_, "ROOMS", false, this); + connect(roomsBundle, SIGNAL(clicked(JID)), this, SLOT(handleItemClicked(JID))); + mainLayout->addWidget(roomsBundle); + + mainLayout->addStretch(); +} + +QtChatOverview::~QtChatOverview() {} + +void QtChatOverview::handleItemClicked(JID jid) { + chattables_.onActivated(jid); +} + +} // namespace Swift diff --git a/Swift/QtUI/QtChatOverview.h b/Swift/QtUI/QtChatOverview.h new file mode 100644 index 0000000..8cd7762 --- /dev/null +++ b/Swift/QtUI/QtChatOverview.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QWidget> + +#include <Swiften/JID/JID.h> + +namespace Swift { + class Chattables; + class ChattablesModel; + class QtChatOverview : public QWidget { + Q_OBJECT + public: + QtChatOverview(Chattables&, QWidget* parent); + ~QtChatOverview() override; + + private slots: + void handleItemClicked(JID jid); + private: + Chattables& chattables_; + ChattablesModel* rootModel_; + }; +} diff --git a/Swift/QtUI/QtChatOverviewBundle.cpp b/Swift/QtUI/QtChatOverviewBundle.cpp new file mode 100644 index 0000000..121ae2e --- /dev/null +++ b/Swift/QtUI/QtChatOverviewBundle.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtChatOverviewBundle.h> + +#include <QHBoxLayout> +#include <QLabel> +#include <QPalette> +#include <QSortFilterProxyModel> +#include <QVBoxLayout> + +#include <Swiften/Elements/StatusShow.h> + +#include <Swift/QtUI/ChattablesModel.h> +#include <Swift/QtUI/QtChatOverviewDelegate.h> +#include <Swift/QtUI/QtClickableLabel.h> +#include <Swift/QtUI/QtExpandedListView.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +BundleFilter::BundleFilter(QObject* parent) : QSortFilterProxyModel(parent) { + sort(0, Qt::AscendingOrder); + setDynamicSortFilter(true); + setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); +} + +void BundleFilter::addFilter(Filter filter) { + filters_.emplace(filter); + invalidateFilter(); +} + +bool BundleFilter::hasFilter(Filter filter) { + return filters_.count(filter) > 0; +} + +void BundleFilter::removeFilter(Filter filter) { + filters_.erase(filter); + invalidateFilter(); +} + +bool BundleFilter::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { + auto row = sourceModel()->index(sourceRow, 0, sourceParent); + if (filters_.count(Filter::Unread)) { + if (row.data(ChattablesModel::UnreadCountRole).toInt() == 0) { + return false; + } + } + if (filters_.count(Filter::People)) { + if (row.data(ChattablesModel::TypeRole).toString() != "PERSON") { + return false; + } + } + if (filters_.count(Filter::Rooms)) { + if (row.data(ChattablesModel::TypeRole).toString() != "ROOM") { + return false; + } + } + if (filters_.count(Filter::Online)) { + if (static_cast<StatusShow::Type>(row.data(ChattablesModel::StatusRole).toInt()) == StatusShow::None) { + return false; + } + } + return true; +} + +QtChatOverviewBundle::QtChatOverviewBundle(ChattablesModel* rootModel, QString name, bool hideWhenEmpty, QWidget* parent) : QWidget(parent), rootModel_(rootModel), hideWhenEmpty_(hideWhenEmpty) { + proxyModel_ = new BundleFilter(this); + if (name == "UNREAD") { // FIXME: Obviously needs a better approach + proxyModel_->addFilter(BundleFilter::Filter::Unread); + } + if (name == "PEOPLE") { + proxyModel_->addFilter(BundleFilter::Filter::People); + proxyModel_->addFilter(BundleFilter::Filter::Online); + } + if (name == "ROOMS") { + proxyModel_->addFilter(BundleFilter::Filter::Rooms); + proxyModel_->addFilter(BundleFilter::Filter::Online); + } + proxyModel_->setSourceModel(rootModel); + + + auto mainLayout = new QVBoxLayout(); + setLayout(mainLayout); + + auto headerLayout = new QHBoxLayout(); + mainLayout->addLayout(headerLayout); + auto nameLabel = new QLabel(name, this); + nameLabel->setStyleSheet("color: white;"); + headerLayout->addWidget(nameLabel); + headerLayout->addStretch(); + if (!hideWhenEmpty_) { + filterLabel_ = new QtClickableLabel(this); + filterLabel_->setText(tr("Online")); + filterLabel_->setStyleSheet("color: white;"); + headerLayout->addWidget(filterLabel_); + connect(filterLabel_, SIGNAL(clicked()), this, SLOT(handleFilterClicked())); + } + listView_ = new QtExpandedListView(this); + listView_->setModel(proxyModel_); + listView_->setFrameStyle(QFrame::NoFrame); + listView_->setItemDelegate(new QtChatOverviewDelegate(this)); + connect(listView_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleItemClicked(const QModelIndex&))); + mainLayout->addWidget(listView_); + + if (hideWhenEmpty_) { + connect(proxyModel_, &QAbstractItemModel::modelReset, this, [&](){ + updateVisibility(); + }); + connect(proxyModel_, &QAbstractItemModel::rowsInserted, this, [&](){ + updateVisibility(); + }); + connect(proxyModel_, &QAbstractItemModel::rowsRemoved, this, [&](){ + updateVisibility(); + }); + updateVisibility(); + } +} + +QtChatOverviewBundle::~QtChatOverviewBundle() {} + +void QtChatOverviewBundle::handleFilterClicked() { + if (proxyModel_->hasFilter(BundleFilter::Filter::Online)) { + proxyModel_->removeFilter(BundleFilter::Filter::Online); + filterLabel_->setText(tr("All")); + } + else { + proxyModel_->addFilter(BundleFilter::Filter::Online); + filterLabel_->setText(tr("Online")); + } +} + +void QtChatOverviewBundle::updateVisibility() { + auto shouldBeVisible = (proxyModel_->rowCount(listView_->rootIndex()) > 0); + setVisible(shouldBeVisible); +} + + +void QtChatOverviewBundle::handleItemClicked(const QModelIndex& index) { + clicked(JID(Q2PSTRING(index.data(ChattablesModel::JIDRole).toString()))); +} + +} // namespace Swift diff --git a/Swift/QtUI/QtChatOverviewBundle.h b/Swift/QtUI/QtChatOverviewBundle.h new file mode 100644 index 0000000..95fd5d2 --- /dev/null +++ b/Swift/QtUI/QtChatOverviewBundle.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <set> + +#include <QSortFilterProxyModel> +#include <QString> +#include <QWidget> + +#include <Swiften/JID/JID.h> + +class QListView; + +namespace Swift { + class ChattablesModel; + class QtClickableLabel; + class QtExpandedListView; + + class BundleFilter : public QSortFilterProxyModel { + Q_OBJECT + public: + enum class Filter {Unread, People, Rooms, Online}; + BundleFilter(QObject* parent); + void addFilter(Filter); + bool hasFilter(Filter); + void removeFilter(Filter); + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const; + private: + std::set<Filter> filters_; + }; + + class QtChatOverviewBundle : public QWidget { + Q_OBJECT + public: + QtChatOverviewBundle(ChattablesModel*, QString name, bool hideWhenEmpty, QWidget* parent); + ~QtChatOverviewBundle() override; + + signals: + void clicked(JID jid); + + private slots: + void handleFilterClicked(); + void handleItemClicked(const QModelIndex&); + + private: + void updateVisibility(); + + private: + ChattablesModel* rootModel_; + QtExpandedListView* listView_; + BundleFilter* proxyModel_; + bool hideWhenEmpty_; + QtClickableLabel* filterLabel_ = nullptr; + }; +} diff --git a/Swift/QtUI/QtChatOverviewDelegate.cpp b/Swift/QtUI/QtChatOverviewDelegate.cpp new file mode 100644 index 0000000..00821fe --- /dev/null +++ b/Swift/QtUI/QtChatOverviewDelegate.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtChatOverviewDelegate.h> + +#include <Swiften/Elements/StatusShow.h> + +#include <Swift/QtUI/ChattablesModel.h> +#include <Swift/QtUI/Roster/DelegateCommons.h> + +namespace Swift { + +QtChatOverviewDelegate::QtChatOverviewDelegate(QObject* parent) : QItemDelegate(parent), nameFont(QApplication::font()) { + +} + +QtChatOverviewDelegate::~QtChatOverviewDelegate() {} + +QSize QtChatOverviewDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { + int heightByAvatar = DelegateCommons::avatarSize + DelegateCommons::verticalMargin * 2; + QFontMetrics nameMetrics(nameFont); + int sizeByText = 2 * DelegateCommons::verticalMargin + nameMetrics.height(); + return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar); +} + +void QtChatOverviewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + painter->save(); + QRect fullRegion(option.rect); + const int statusCircleRadius = 3; + const int horizontalMargin = 4; + + QColor bgColor(38, 81, 112); + QPen fontPen("white"); // FIXME + + if (option.state & QStyle::State_Selected) { + //FIXME + } + painter->fillRect(fullRegion, bgColor); + painter->setPen(fontPen); + + QFontMetrics nameMetrics(nameFont); + painter->setFont(nameFont); + QRect nameRegion(fullRegion.adjusted((horizontalMargin + statusCircleRadius) * 2, DelegateCommons::verticalMargin, 0, 0)); + DelegateCommons::drawElidedText(painter, nameRegion, index.data(Qt::DisplayRole).toString()); + + const auto green = QColor(124, 243, 145); + const auto yellow = QColor(243, 243, 0); + const auto red = QColor(255, 45, 71); + const auto grey = QColor(159,159,159); + auto circleColour = grey; + auto status = static_cast<StatusShow::Type>(index.data(ChattablesModel::StatusRole).toInt()); + switch (status) { + case StatusShow::Online: circleColour = green; break; + case StatusShow::FFC: circleColour = green; break; + case StatusShow::Away: circleColour = yellow; break; + case StatusShow::XA: circleColour = yellow; break; + case StatusShow::DND: circleColour = red; break; + case StatusShow::None: circleColour = grey; break; + } + + painter->setRenderHint(QPainter::Antialiasing, true); + + int unreadCount = index.data(ChattablesModel::UnreadCountRole).toInt(); + if (unreadCount > 0) { + int unreadCountSize = 16; + QRect unreadRect(fullRegion.right() - unreadCountSize - horizontalMargin, fullRegion.top() + (fullRegion.height() - unreadCountSize) / 2, unreadCountSize, unreadCountSize); + QPen pen(QColor("white")); + pen.setWidth(1); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(pen); + painter->drawEllipse(unreadRect); + painter->setBackgroundMode(Qt::TransparentMode); + painter->setPen(QColor("white")); + DelegateCommons::drawElidedText(painter, unreadRect, QString("%1").arg(unreadCount), Qt::AlignCenter); + } + + painter->setPen(circleColour); + painter->setBrush(circleColour); + painter->drawEllipse(fullRegion.topLeft() + QPointF(horizontalMargin + 4, fullRegion.height() / 2), statusCircleRadius, statusCircleRadius); + + + painter->restore(); +} + +} // namespace Swift diff --git a/Swift/QtUI/QtChatOverviewDelegate.h b/Swift/QtUI/QtChatOverviewDelegate.h new file mode 100644 index 0000000..b00337d --- /dev/null +++ b/Swift/QtUI/QtChatOverviewDelegate.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QItemDelegate> +#include <QFont> + +namespace Swift { + class QtChatOverviewDelegate : public QItemDelegate { + Q_OBJECT + public: + QtChatOverviewDelegate(QObject* parent); + ~QtChatOverviewDelegate() override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + private: + QFont nameFont; + }; +} diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp index de1ee7c..edd0b87 100644 --- a/Swift/QtUI/QtChatTabs.cpp +++ b/Swift/QtUI/QtChatTabs.cpp @@ -1,294 +1,428 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtChatTabs.h" +#include <Swift/QtUI/QtChatTabs.h> #include <algorithm> #include <vector> -#include <Swift/Controllers/ChatMessageSummarizer.h> -#include <Swift/QtUI/QtSwiftUtil.h> - +#include <QAction> +#include <QApplication> #include <QCloseEvent> +#include <QCursor> #include <QDesktopWidget> -#include <QtGlobal> -#include <QTabWidget> #include <QLayout> +#include <QMenu> #include <QTabBar> -#include <QApplication> -#include <qdebug.h> +#include <QTabWidget> +#include <QWindow> +#include <QtGlobal> + +#include <Swiften/Base/Log.h> + +#include <Swift/Controllers/ChatMessageSummarizer.h> +#include <Swift/Controllers/SettingConstants.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTabWidget.h> +#include <Swift/QtUI/QtTabbable.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/Trellis/QtDynamicGridLayout.h> +#include <Swift/QtUI/Trellis/QtGridSelectionDialog.h> namespace Swift { -QtChatTabs::QtChatTabs(bool singleWindow) : QWidget(), singleWindow_(singleWindow) { +QtChatTabs::QtChatTabs(SettingsProvider* settingsProvider, bool trellisMode) : QWidget(), settingsProvider_(settingsProvider), trellisMode_(trellisMode), dynamicGrid_(nullptr), gridSelectionDialog_(nullptr) { #ifndef Q_OS_MAC - setWindowIcon(QIcon(":/logo-chat-16.png")); + setWindowIcon(QIcon(":/logo-chat-16.png")); #else - setAttribute(Qt::WA_ShowWithoutActivating); + setAttribute(Qt::WA_ShowWithoutActivating); #endif + dynamicGrid_ = new QtDynamicGridLayout(settingsProvider->getSetting(SettingConstants::FUTURE), this, trellisMode); + connect(dynamicGrid_, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int))); + connect(dynamicGrid_, SIGNAL(onCurrentIndexChanged(int)), this, SLOT(handleCurrentTabIndexChanged(int))); - tabs_ = new QtTabWidget(this); - tabs_->setUsesScrollButtons(true); - tabs_->setElideMode(Qt::ElideRight); -#if QT_VERSION >= 0x040500 - /*For Macs, change the tab rendering.*/ - tabs_->setDocumentMode(true); - /*Closable tabs are only in Qt4.5 and later*/ - tabs_->setTabsClosable(true); - connect(tabs_, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int))); -#else -#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled. -#endif - QVBoxLayout *layout = new QVBoxLayout; - layout->setSpacing(0); - layout->setContentsMargins(0, 3, 0, 0); - layout->addWidget(tabs_); - setLayout(layout); + QVBoxLayout *layout = new QVBoxLayout; + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(dynamicGrid_); + setLayout(layout); + + if (trellisMode) { + // restore size + std::string gridSizeString = settingsProvider->getSetting(QtUISettingConstants::TRELLIS_GRID_SIZE); + if (!gridSizeString.empty()) { + QByteArray gridSizeData = QByteArray::fromBase64(P2QSTRING(gridSizeString).toUtf8()); + QDataStream dataStreamGridSize(&gridSizeData, QIODevice::ReadWrite); + QSize gridSize(1,1); + dataStreamGridSize >> gridSize; + dynamicGrid_->setDimensions(gridSize); + } + + // restore positions + std::string tabPositionsString = settingsProvider->getSetting(QtUISettingConstants::TRELLIS_GRID_POSITIONS); + if (!tabPositionsString.empty()) { + QByteArray tabPositionsData = QByteArray::fromBase64(P2QSTRING(tabPositionsString).toUtf8()); + QDataStream inTabPositions(&tabPositionsData, QIODevice::ReadWrite); + QHash<QString, QPoint> tabPositions; + inTabPositions >> tabPositions; + dynamicGrid_->setTabPositions(tabPositions); + } + } + + gridSelectionDialog_ = new QtGridSelectionDialog(); + + // setup shortcuts + shortcuts_ << new QShortcut(QKeySequence(tr("CTRL+W", "Close chat tab.")), window(), SLOT(handleCloseTabShortcut())); + shortcuts_ << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageUp), window(), SLOT(handleRequestedPreviousTab())); + shortcuts_ << new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageDown), window(), SLOT(handleRequestedNextTab())); + shortcuts_ << new QShortcut(QKeySequence(Qt::ALT + Qt::Key_A), window(), SLOT(handleRequestedActiveTab())); +} + +QtChatTabs::~QtChatTabs() { + for (auto shortcut : shortcuts_) { + delete shortcut; + } + + if (trellisMode_) { + storeTabPositions(); + } + delete gridSelectionDialog_; } void QtChatTabs::closeEvent(QCloseEvent* event) { - //Hide first to prevent flickering as each tab is removed. - hide(); - for (int i = tabs_->count() - 1; i >= 0; i--) { - tabs_->widget(i)->close(); - } - event->accept(); + //Hide first to prevent flickering as each tab is removed. + hide(); + if (trellisMode_) { + storeTabPositions(); + } + + for (int i = dynamicGrid_->count() - 1; i >= 0; i--) { + dynamicGrid_->widget(i)->close(); + } + event->accept(); } QtTabbable* QtChatTabs::getCurrentTab() { - return qobject_cast<QtTabbable*>(tabs_->currentWidget()); + return qobject_cast<QtTabbable*>(dynamicGrid_->currentWidget()); +} + +void QtChatTabs::setViewMenu(QMenu* viewMenu) { + if (trellisMode_) { + viewMenu->addSeparator(); + QAction* action = new QAction(tr("Change &layout"), this); + connect(action, SIGNAL(triggered()), this, SLOT(handleOpenLayoutChangeDialog())); + viewMenu->addAction(action); + + action = new QAction(tr("Move Tab right"), this); + action->setShortcutContext(Qt::ApplicationShortcut); + action->setShortcut(QKeySequence(tr("Ctrl+Shift+PgDown"))); + connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabRight())); + viewMenu->addAction(action); + + action = new QAction(tr("Move Tab left"), this); + action->setShortcutContext(Qt::ApplicationShortcut); + action->setShortcut(QKeySequence(tr("Ctrl+Shift+PgUp"))); + connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabLeft())); + viewMenu->addAction(action); + + action = new QAction(tr("Move Tab to next group"), this); + action->setShortcutContext(Qt::ApplicationShortcut); + action->setShortcut(QKeySequence(tr("Ctrl+Alt+PgDown"))); + connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabToNextGroup())); + viewMenu->addAction(action); + + action = new QAction(tr("Move Tab to previous group"), this); + action->setShortcutContext(Qt::ApplicationShortcut); + action->setShortcut(QKeySequence(tr("Ctrl+Alt+PgUp"))); + connect(action, SIGNAL(triggered()), dynamicGrid_, SLOT(moveCurrentTabToPreviousGroup())); + viewMenu->addAction(action); + } } void QtChatTabs::addTab(QtTabbable* tab) { - QSizePolicy policy = sizePolicy(); - /* Chat windows like to grow - don't let them */ - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - tabs_->addTab(tab, tab->windowTitle()); - connect(tab, SIGNAL(titleUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection); - connect(tab, SIGNAL(countUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection); - connect(tab, SIGNAL(windowClosing()), this, SLOT(handleTabClosing()), Qt::UniqueConnection); - connect(tab, SIGNAL(windowOpening()), this, SLOT(handleWidgetShown()), Qt::UniqueConnection); - connect(tab, SIGNAL(wantsToActivate()), this, SLOT(handleWantsToActivate()), Qt::UniqueConnection); - connect(tab, SIGNAL(requestNextTab()), this, SLOT(handleRequestedNextTab()), Qt::UniqueConnection); - connect(tab, SIGNAL(requestActiveTab()), this, SLOT(handleRequestedActiveTab()), Qt::UniqueConnection); - connect(tab, SIGNAL(requestPreviousTab()), this, SLOT(handleRequestedPreviousTab()), Qt::UniqueConnection); - connect(tab, SIGNAL(requestFlash()), this, SLOT(flash()), Qt::UniqueConnection); - setSizePolicy(policy); + QSizePolicy policy = sizePolicy(); + /* Chat windows like to grow - don't let them */ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + dynamicGrid_->addTab(tab, tab->windowTitle()); + connect(tab, SIGNAL(titleUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection); + connect(tab, SIGNAL(countUpdated()), this, SLOT(handleTabTitleUpdated()), Qt::UniqueConnection); + connect(tab, SIGNAL(windowClosing()), this, SLOT(handleTabClosing()), Qt::UniqueConnection); + connect(tab, SIGNAL(windowOpening()), this, SLOT(handleWidgetShown()), Qt::UniqueConnection); + connect(tab, SIGNAL(wantsToActivate()), this, SLOT(handleWantsToActivate()), Qt::UniqueConnection); + connect(tab, SIGNAL(requestNextTab()), this, SLOT(handleRequestedNextTab()), Qt::UniqueConnection); + connect(tab, SIGNAL(requestActiveTab()), this, SLOT(handleRequestedActiveTab()), Qt::UniqueConnection); + connect(tab, SIGNAL(requestPreviousTab()), this, SLOT(handleRequestedPreviousTab()), Qt::UniqueConnection); + connect(tab, SIGNAL(requestFlash()), this, SLOT(flash()), Qt::UniqueConnection); + setSizePolicy(policy); } void QtChatTabs::handleWidgetShown() { - QtTabbable* widget = qobject_cast<QtTabbable*>(sender()); - if (!widget) { - return; - } - checkForFirstShow(); - if (tabs_->indexOf(widget) >= 0) { - handleTabTitleUpdated(widget); - return; - } - addTab(widget); - show(); + QtTabbable* widget = qobject_cast<QtTabbable*>(sender()); + if (!widget) { + return; + } + checkForFirstShow(); + if (dynamicGrid_->indexOf(widget) >= 0) { + handleTabTitleUpdated(widget); + return; + } + widget->blockSignals(true); + addTab(widget); + widget->blockSignals(false); + show(); +} + +void QtChatTabs::handleCurrentTabIndexChanged(int newIndex) { + handleTabTitleUpdated(dynamicGrid_->widget(newIndex)); } void QtChatTabs::handleWantsToActivate() { - QtTabbable* widget = qobject_cast<QtTabbable*>(sender()); - Q_ASSERT(widget); - //Un-minimize and bring to front. - setWindowState(windowState() & ~Qt::WindowMinimized); - setWindowState(windowState() | Qt::WindowActive); - show(); - widget->show(); - tabs_->setCurrentWidget(widget); - handleTabTitleUpdated(widget); - widget->setFocus(); - raise(); - activateWindow(); + QtTabbable* widget = qobject_cast<QtTabbable*>(sender()); + Q_ASSERT(widget); + //Un-minimize and bring to front. + setWindowState(windowState() & ~Qt::WindowMinimized); + setWindowState(windowState() | Qt::WindowActive); + show(); + widget->show(); + dynamicGrid_->setCurrentWidget(widget); + handleTabTitleUpdated(widget); + widget->setFocus(); + raise(); + activateWindow(); } void QtChatTabs::handleTabClosing() { - QWidget* widget = qobject_cast<QWidget*>(sender()); - 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()); - } - } + QWidget* widget = qobject_cast<QWidget*>(sender()); + int index; + if (widget && ((index = dynamicGrid_->indexOf(widget)) >= 0)) { + dynamicGrid_->removeTab(index); + if (dynamicGrid_->count() == 0) { + setWindowTitle(""); + onTitleChanged(""); + } + else { + handleTabTitleUpdated(dynamicGrid_->currentWidget()); + } + } } void QtChatTabs::handleRequestedPreviousTab() { - int newIndex = tabs_->currentIndex() - 1; - tabs_->setCurrentIndex(newIndex >= 0 ? newIndex : tabs_->count() - 1); + int newIndex = dynamicGrid_->currentIndex() - 1; + dynamicGrid_->setCurrentIndex(newIndex >= 0 ? newIndex : dynamicGrid_->count() - 1); } void QtChatTabs::handleRequestedNextTab() { - int newIndex = tabs_->currentIndex() + 1; - tabs_->setCurrentIndex(newIndex < tabs_->count() ? newIndex : 0); + int newIndex = dynamicGrid_->currentIndex() + 1; + dynamicGrid_->setCurrentIndex(newIndex < dynamicGrid_->count() ? newIndex : 0); } void QtChatTabs::handleRequestedActiveTab() { - QtTabbable::AlertType types[] = {QtTabbable::WaitingActivity, QtTabbable::ImpendingActivity}; - bool finished = false; - for (int j = 0; j < 2; j++) { - bool looped = false; - for (int i = tabs_->currentIndex() + 1; !finished && i != tabs_->currentIndex(); i++) { - if (i >= tabs_->count()) { - if (looped) { - break; - } - looped = true; - i = 0; - } - if (qobject_cast<QtTabbable*>(tabs_->widget(i))->getWidgetAlertState() == types[j]) { - tabs_->setCurrentIndex(i); - finished = true; - break; - } - } - } + QtTabbable::AlertType types[] = {QtTabbable::WaitingActivity, QtTabbable::ImpendingActivity}; + bool finished = false; + for (auto& type : types) { + bool looped = false; + for (int i = dynamicGrid_->currentIndex() + 1; !finished && i != dynamicGrid_->currentIndex(); i++) { + if (i >= dynamicGrid_->count()) { + if (looped) { + break; + } + looped = true; + i = 0; + } + if (qobject_cast<QtTabbable*>(dynamicGrid_->widget(i))->getWidgetAlertState() == type) { + dynamicGrid_->setCurrentIndex(i); + finished = true; + break; + } + } + } } +void QtChatTabs::handleCloseTabShortcut() { + QWidget* currentWidget = dynamicGrid_->currentWidget(); + if (currentWidget) { + currentWidget->close(); + } +} + void QtChatTabs::handleTabCloseRequested(int index) { - QWidget* widget = tabs_->widget(index); - widget->close(); + if (trellisMode_) { + storeTabPositions(); + } + + assert(index < dynamicGrid_->count()); + QWidget* widget = dynamicGrid_->widget(index); + assert(widget); + widget->close(); } void QtChatTabs::handleTabTitleUpdated() { - QWidget* widget = qobject_cast<QWidget*>(sender()); - handleTabTitleUpdated(widget); + QWidget* widget = qobject_cast<QWidget*>(sender()); + handleTabTitleUpdated(widget); } void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { - if (!widget) { - return; - } - QtTabbable* tabbable = qobject_cast<QtTabbable*>(widget); - int index = tabs_->indexOf(widget); - if (index < 0) { - return; - } - - 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].*)$"); - if (hasTrailingGarbage.exactMatch(tabText) && - hasTrailingGarbage.cap(1).simplified().length() >= 2 && - hasTrailingGarbage.cap(2).length() >= 7) { - // there may be some trailing garbage, and it's long - // enough to be worth removing, and we'd leave at - // least a couple of characters. - tabText = hasTrailingGarbage.cap(1).simplified(); - } - // QTabBar interprets &, so escape that - tabText.replace("&", "&&"); - // see which alt[a-z] keys other tabs use - bool accelsTaken[26]; - int i = 0; - while (i < 26) { - accelsTaken[i] = (i == 0); //A is used for 'switch to active tab' - i++; - } - int other = tabs_->tabBar()->count(); - while (other >= 0) { - other--; - if (other != index) { - QString t = tabs_->tabBar()->tabText(other).toLower(); - int r = t.indexOf('&'); - if (r >= 0 && t[r+1] >= 'a' && t[r+1] <= 'z') { - accelsTaken[t[r+1].unicode()-'a'] = true; - } - } - } - // then look to see which letters in tabText may be used - i = 0; - int accelPos = -1; - while (i < tabText.length() && accelPos < 0) { - if (tabText[i] >= 'A' && tabText[i] <= 'Z' && - !accelsTaken[tabText[i].unicode()-'A']) { - accelPos = i; - } - if (tabText[i] >= 'a' && tabText[i] <= 'z' && - !accelsTaken[tabText[i].unicode()-'a']) { - accelPos = i; - } - ++i; - } - if (accelPos >= 0) { - tabText = tabText.mid(0, accelPos) + "&" + tabText.mid(accelPos); - } - // this could be improved on some european keyboards, such as - // the German one (where alt-Sz-Ligature is available) and basically - // doesn't work on Arabic/Indic keyboards (where Latin letters - // aren't available), but I don't care to deal with those. - - tabs_->setTabText(index, tabbable->getCount() > 0 ? QString("(%1) %2").arg(tabbable->getCount()).arg(tabText) : tabText); - QColor tabTextColor; - switch (tabbable->getWidgetAlertState()) { - case QtTabbable::WaitingActivity : tabTextColor = QColor(217, 20, 43); break; - case QtTabbable::ImpendingActivity : tabTextColor = QColor(27, 171, 32); break; - case QtTabbable::NoActivity : tabTextColor = QColor(); break; - } - tabs_->tabBar()->setTabTextColor(index, tabTextColor); - - std::vector<std::pair<std::string, int> > unreads; - for (int i = 0; i < tabs_->count(); i++) { - QtTabbable* tab = qobject_cast<QtTabbable*>(tabs_->widget(i)); - if (tab) { - unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount())); - } - } - - std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(tabs_->currentWidget())->windowTitle())); - ChatMessageSummarizer summary; - QString title = summary.getSummary(current, unreads).c_str(); - setWindowTitle(title); - emit onTitleChanged(title); + if (!widget) { + return; + } + QtTabbable* tabbable = qobject_cast<QtTabbable*>(widget); + int index = dynamicGrid_->indexOf(widget); + if (index < 0) { + return; + } + + 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].*)$"); + if (hasTrailingGarbage.exactMatch(tabText) && + hasTrailingGarbage.cap(1).simplified().length() >= 2 && + hasTrailingGarbage.cap(2).length() >= 7) { + // there may be some trailing garbage, and it's long + // enough to be worth removing, and we'd leave at + // least a couple of characters. + tabText = hasTrailingGarbage.cap(1).simplified(); + } + // QTabBar interprets &, so escape that + tabText.replace("&", "&&"); + // see which alt[a-z] keys other tabs use + bool accelsTaken[26]; + int i = 0; + while (i < 26) { + accelsTaken[i] = (i == 0); //A is used for 'switch to active tab' + i++; + } + + int other = dynamicGrid_->count(); + while (other >= 0) { + other--; + if (other != index) { + int tabIndex = -1; + QtTabWidget* tabWidget = dynamicGrid_->indexToTabWidget(other, tabIndex); + QString t = tabWidget->tabBar()->tabText(tabIndex).toLower(); + int r = t.indexOf('&'); + if (r >= 0 && t[r+1] >= 'a' && t[r+1] <= 'z') { + accelsTaken[t[r+1].unicode()-'a'] = true; + } + } + } + // then look to see which letters in tabText may be used + i = 0; + int accelPos = -1; + while (i < tabText.length() && accelPos < 0) { + if (tabText[i] >= 'A' && tabText[i] <= 'Z' && + !accelsTaken[tabText[i].unicode()-'A']) { + accelPos = i; + } + if (tabText[i] >= 'a' && tabText[i] <= 'z' && + !accelsTaken[tabText[i].unicode()-'a']) { + accelPos = i; + } + ++i; + } + if (accelPos >= 0) { + tabText = tabText.mid(0, accelPos) + "&" + tabText.mid(accelPos); + } + // this could be improved on some european keyboards, such as + // the German one (where alt-Sz-Ligature is available) and basically + // doesn't work on Arabic/Indic keyboards (where Latin letters + // aren't available), but I don't care to deal with those. + + int tabIndex = -1; + QtTabWidget* tabWidget = dynamicGrid_->indexToTabWidget(index, tabIndex); + tabWidget->setTabText(tabIndex, tabbable->getCount() > 0 ? QString("(%1) %2").arg(tabbable->getCount()).arg(tabText) : tabText); + QColor tabTextColor; + switch (tabbable->getWidgetAlertState()) { + case QtTabbable::WaitingActivity : tabTextColor = QColor(217, 20, 43); break; + case QtTabbable::ImpendingActivity : tabTextColor = QColor(27, 171, 32); break; + case QtTabbable::NoActivity : tabTextColor = QColor(); break; + } + tabWidget->tabBar()->setTabTextColor(tabIndex, tabTextColor); + + std::vector<std::pair<std::string, int> > unreads; + for (int i = 0; i < dynamicGrid_->count(); i++) { + QtTabbable* tab = qobject_cast<QtTabbable*>(dynamicGrid_->widget(i)); + if (tab) { + unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount())); + } + } + + std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(dynamicGrid_->currentWidget())->windowTitle())); + ChatMessageSummarizer summary; + QString title = summary.getSummary(current, unreads).c_str(); + setWindowTitle(title); + emit onTitleChanged(title); } void QtChatTabs::flash() { #ifndef SWIFTEN_PLATFORM_MACOSX - QApplication::alert(this, 0); + QApplication::alert(this, 0); #endif } +void QtChatTabs::handleOpenLayoutChangeDialog() { + disconnect(gridSelectionDialog_, SIGNAL(currentGridSizeChanged(QSize)), dynamicGrid_, SLOT(setDimensions(QSize))); + gridSelectionDialog_->setCurrentGridSize(dynamicGrid_->getDimension()); + + int screen = QApplication::desktop()->screenNumber(QCursor::pos()); + QPoint center = QApplication::desktop()->screenGeometry(screen).center(); + gridSelectionDialog_->move(center); + + connect(gridSelectionDialog_, SIGNAL(currentGridSizeChanged(QSize)), dynamicGrid_, SLOT(setDimensions(QSize))); + gridSelectionDialog_->show(); + + QPoint pos(gridSelectionDialog_->getFrameSize().width() / 2, gridSelectionDialog_->getFrameSize().height() / 2); + QCursor::setPos(gridSelectionDialog_->windowHandle()->screen(), gridSelectionDialog_->mapToGlobal(QPoint(gridSelectionDialog_->width(), (gridSelectionDialog_->height() - gridSelectionDialog_->getDescriptionTextHeight())) - pos)); +} + +void QtChatTabs::storeTabPositions() { + // save size + QByteArray gridSizeData; + QDataStream dataStreamGridSize(&gridSizeData, QIODevice::ReadWrite); + dataStreamGridSize << dynamicGrid_->getDimension(); + settingsProvider_->storeSetting(QtUISettingConstants::TRELLIS_GRID_SIZE, Q2PSTRING(QString(gridSizeData.toBase64()))); + + // save positions + QByteArray tabPositionsData; + QDataStream dataStreamTabPositions(&tabPositionsData, QIODevice::ReadWrite); + dynamicGrid_->updateTabPositions(); + dataStreamTabPositions << dynamicGrid_->getTabPositions(); + settingsProvider_->storeSetting(QtUISettingConstants::TRELLIS_GRID_POSITIONS, Q2PSTRING(QString(tabPositionsData.toBase64()))); +} + void QtChatTabs::resizeEvent(QResizeEvent*) { - emit geometryChanged(); + emit geometryChanged(); } void QtChatTabs::moveEvent(QMoveEvent*) { - emit geometryChanged(); + emit geometryChanged(); } void QtChatTabs::checkForFirstShow() { - if (!isVisible()) { + if (!isVisible()) { #ifndef Q_OS_MAC - showMinimized(); + 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 */ - } + /* 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 - } + } +} + +QSize QtChatTabs::sizeHint() const { + return QSize(600, 600); } } diff --git a/Swift/QtUI/QtChatTabs.h b/Swift/QtUI/QtChatTabs.h index f9cd685..6a758ca 100644 --- a/Swift/QtUI/QtChatTabs.h +++ b/Swift/QtUI/QtChatTabs.h @@ -1,51 +1,77 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "QtTabbable.h" -#include "QtTabWidget.h" -#include <QWidget> #include <QRect> +#include <QShortcut> +#include <QWidget> + +#include <Swift/QtUI/QtChatTabsBase.h> class QTabWidget; +class QMenu; namespace Swift { - class QtChatTabs : public QWidget { - Q_OBJECT - public: - QtChatTabs(bool singleWindow); - void addTab(QtTabbable* tab); - void minimise(); - QtTabbable* getCurrentTab(); - signals: - void geometryChanged(); - void onTitleChanged(const QString& title); - - protected slots: - void closeEvent(QCloseEvent* event); - void resizeEvent(QResizeEvent* event); - void moveEvent(QMoveEvent* event); - - private slots: - void handleTabClosing(); - void handleTabTitleUpdated(); - void handleTabTitleUpdated(QWidget* widget); - void handleTabCloseRequested(int index); - void handleWidgetShown(); - void handleWantsToActivate(); - void handleRequestedPreviousTab(); - void handleRequestedNextTab(); - void handleRequestedActiveTab(); - void flash(); - - private: - void checkForFirstShow(); - QtTabWidget* tabs_; - bool singleWindow_; - }; + class SettingsProvider; + + class QtTabbable; + class QtTabWidget; + class QtDynamicGridLayout; + class QtGridSelectionDialog; + + class QtChatTabs : public QWidget, public QtChatTabsBase { + Q_OBJECT + public: + QtChatTabs(SettingsProvider* settingsProvider, bool trellisMode); + virtual ~QtChatTabs(); + + virtual void addTab(QtTabbable* tab); + void minimise(); + QtTabbable* getCurrentTab(); + void setViewMenu(QMenu* viewMenu); + QSize sizeHint() const; + + signals: + void geometryChanged(); + void onTitleChanged(const QString& title); + + protected slots: + void closeEvent(QCloseEvent* event); + void resizeEvent(QResizeEvent* event); + void moveEvent(QMoveEvent* event); + + private slots: + void handleCurrentTabIndexChanged(int newIndex); + void handleTabClosing(); + void handleTabTitleUpdated(); + void handleTabTitleUpdated(QWidget* widget); + void handleTabCloseRequested(int index); + void handleWidgetShown(); + void handleWantsToActivate(); + void handleRequestedPreviousTab(); + void handleRequestedNextTab(); + void handleRequestedActiveTab(); + void flash(); + + void handleOpenLayoutChangeDialog(); + + void handleCloseTabShortcut(); + + private: + void storeTabPositions(); + void checkForFirstShow(); + + private: + SettingsProvider* settingsProvider_; + bool trellisMode_; + QtDynamicGridLayout* dynamicGrid_; + QtGridSelectionDialog* gridSelectionDialog_; + + QList<QShortcut*> shortcuts_; + }; } diff --git a/Swift/QtUI/QtChatTabsBase.cpp b/Swift/QtUI/QtChatTabsBase.cpp new file mode 100644 index 0000000..140ff08 --- /dev/null +++ b/Swift/QtUI/QtChatTabsBase.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtChatTabsBase.h> + +namespace Swift { + +QtChatTabsBase::QtChatTabsBase() { + +} + +QtChatTabsBase::~QtChatTabsBase() { + +} + +} diff --git a/Swift/QtUI/QtChatTabsBase.h b/Swift/QtUI/QtChatTabsBase.h new file mode 100644 index 0000000..b49bb10 --- /dev/null +++ b/Swift/QtUI/QtChatTabsBase.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +namespace Swift { + +class QtTabbable; + +class QtChatTabsBase { + public: + QtChatTabsBase(); + virtual ~QtChatTabsBase(); + + virtual void addTab(QtTabbable* tab) = 0; +}; + +} diff --git a/Swift/QtUI/QtChatTheme.cpp b/Swift/QtUI/QtChatTheme.cpp index 1d7a970..e73f8ac 100644 --- a/Swift/QtUI/QtChatTheme.cpp +++ b/Swift/QtUI/QtChatTheme.cpp @@ -1,67 +1,71 @@ /* - * Copyright (c) 2010 Kevin Smith. - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtChatTheme.h" +#include <Swift/QtUI/QtChatTheme.h> #include <QFile> -#include <qdebug.h> namespace Swift { /** * Load Adium themes, as http://trac.adium.im/wiki/CreatingMessageStyles */ -QtChatTheme::QtChatTheme(const QString& themePath) : qrc_(themePath.isEmpty()), themePath_(qrc_ ? ":/themes/Default/" : themePath + "/Contents/Resources/") { - QString fileNames[EndMarker]; - fileNames[Header] = "Header.html"; - fileNames[Footer] = "Footer.html"; - fileNames[Content] = "Content.html"; - fileNames[Status] = "Status.html"; - fileNames[Topic] = "Topic.html"; - fileNames[FileTransferRequest] = "FileTransferRequest.html"; - fileNames[IncomingContent] = "Incoming/Content.html"; - fileNames[IncomingNextContent] = "Incoming/NextContent.html"; - fileNames[IncomingContext] = "Incoming/Context.html"; - fileNames[IncomingNextContext] = "Incoming/NextContext.html"; - fileNames[OutgoingContent] = "Outgoing/Content.html"; - fileNames[OutgoingNextContent] = "Outgoing/NextContent.html"; - fileNames[OutgoingContext] = "Outgoing/Context.html"; - fileNames[OutgoingNextContext] = "Outgoing/NextContext.html"; - fileNames[Template] = "Template.html"; - fileNames[MainCSS] = "main.css"; - fileNames[TemplateDefault] = ":/themes/Template.html"; - for (int i = 0; i < EndMarker; i++) { - QString source; - QFile sourceFile((i != TemplateDefault ? themePath_ : "") + fileNames[i]); - if (sourceFile.exists() && sourceFile.open(QIODevice::ReadOnly)) { - source = sourceFile.readAll(); - sourceFile.close(); - } else { - //qWarning() << "Couldn't load file " << sourceFile.fileName(); - } - fileContents_.append(source); - } +QtChatTheme::QtChatTheme(const QString& themePath) : qrc_(themePath[0] == ':'), themePath_(qrc_ ? themePath : themePath + "/Contents/Resources/") { + QString fileNames[EndMarker]; + fileNames[Header] = "Header.html"; + fileNames[Footer] = "Footer.html"; + fileNames[Content] = "Content.html"; + fileNames[Status] = "Status.html"; + fileNames[Topic] = "Topic.html"; + fileNames[FileTransferRequest] = "FileTransferRequest.html"; + fileNames[IncomingContent] = "Incoming/Content.html"; + fileNames[IncomingNextContent] = "Incoming/NextContent.html"; + fileNames[IncomingContext] = "Incoming/Context.html"; + fileNames[IncomingNextContext] = "Incoming/NextContext.html"; + fileNames[OutgoingContent] = "Outgoing/Content.html"; + fileNames[OutgoingNextContent] = "Outgoing/NextContent.html"; + fileNames[OutgoingContext] = "Outgoing/Context.html"; + fileNames[OutgoingNextContext] = "Outgoing/NextContext.html"; + fileNames[Template] = "Template.html"; + fileNames[MainCSS] = "main.css"; + fileNames[Unread] = "Unread.html"; + fileNames[TemplateDefault] = ":/themes/Template.html"; + for (int i = 0; i < EndMarker; i++) { + QString source; + QFile sourceFile((i != TemplateDefault ? themePath_ : "") + fileNames[i]); + if (sourceFile.exists() && sourceFile.open(QIODevice::ReadOnly)) { + source = sourceFile.readAll(); + sourceFile.close(); + } else { + //qWarning() << "Couldn't load file " << sourceFile.fileName(); + } + fileContents_.append(source); + } - /* Fallbacks */ - if (fileContents_[Template].isEmpty()) fileContents_[Template] = fileContents_[TemplateDefault]; - if (fileContents_[Status].isEmpty()) fileContents_[Status] = fileContents_[Content]; - if (fileContents_[IncomingContent].isEmpty()) fileContents_[IncomingContent] = fileContents_[Content]; - if (fileContents_[IncomingNextContent].isEmpty()) fileContents_[IncomingNextContent] = fileContents_[IncomingContent]; - if (fileContents_[FileTransferRequest].isEmpty()) fileContents_[FileTransferRequest] = fileContents_[Status]; - if (fileContents_[IncomingContext].isEmpty()) fileContents_[IncomingContext] = fileContents_[IncomingContent]; - if (fileContents_[IncomingNextContext].isEmpty()) fileContents_[IncomingNextContext] = fileContents_[IncomingNextContent]; - if (fileContents_[OutgoingContent].isEmpty()) fileContents_[OutgoingContent] = fileContents_[IncomingContent]; - if (fileContents_[OutgoingContext].isEmpty()) fileContents_[OutgoingContext] = fileContents_[OutgoingContent]; - if (fileContents_[OutgoingNextContent].isEmpty()) fileContents_[OutgoingNextContent] = fileContents_[OutgoingContent]; - if (fileContents_[OutgoingNextContext].isEmpty()) fileContents_[OutgoingNextContext] = fileContents_[OutgoingNextContent]; + /* Fallbacks */ + if (fileContents_[Template].isEmpty()) fileContents_[Template] = fileContents_[TemplateDefault]; + if (fileContents_[Status].isEmpty()) fileContents_[Status] = fileContents_[Content]; + if (fileContents_[IncomingContent].isEmpty()) fileContents_[IncomingContent] = fileContents_[Content]; + if (fileContents_[IncomingNextContent].isEmpty()) fileContents_[IncomingNextContent] = fileContents_[IncomingContent]; + if (fileContents_[FileTransferRequest].isEmpty()) fileContents_[FileTransferRequest] = fileContents_[Status]; + if (fileContents_[IncomingContext].isEmpty()) fileContents_[IncomingContext] = fileContents_[IncomingContent]; + if (fileContents_[IncomingNextContext].isEmpty()) fileContents_[IncomingNextContext] = fileContents_[IncomingNextContent]; + if (fileContents_[OutgoingContent].isEmpty()) fileContents_[OutgoingContent] = fileContents_[IncomingContent]; + if (fileContents_[OutgoingContext].isEmpty()) fileContents_[OutgoingContext] = fileContents_[OutgoingContent]; + if (fileContents_[OutgoingNextContent].isEmpty()) fileContents_[OutgoingNextContent] = fileContents_[OutgoingContent]; + if (fileContents_[OutgoingNextContext].isEmpty()) fileContents_[OutgoingNextContext] = fileContents_[OutgoingNextContent]; } QString QtChatTheme::getBase() const { - return qrc_ ? "qrc" + themePath_ : "file://" + themePath_; + return qrc_ ? "qrc" + themePath_ : "file://" + themePath_; +} + +QString QtChatTheme::getUnread() const { + return fileContents_[Unread].isEmpty() ? "<hr/>" : fileContents_[Unread]; } } diff --git a/Swift/QtUI/QtChatTheme.h b/Swift/QtUI/QtChatTheme.h index f72a48b..8db662f 100644 --- a/Swift/QtUI/QtChatTheme.h +++ b/Swift/QtUI/QtChatTheme.h @@ -1,40 +1,41 @@ /* - * Copyright (c) 2010 Kevin Smith. - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QString> #include <QList> +#include <QString> namespace Swift { - class QtChatTheme { - public: - QtChatTheme(const QString& themePath); - QString getHeader() const {return fileContents_[Header];} - QString getFooter() const {return fileContents_[Footer];} - QString getContent() const {return fileContents_[Content];} - QString getStatus() const {return fileContents_[Status];} - QString getTopic() const {return fileContents_[Topic];} - QString getFileTransferRequest() const {return fileContents_[FileTransferRequest];} - QString getIncomingContent() const {return fileContents_[IncomingContent];} - QString getIncomingNextContent() const {return fileContents_[IncomingNextContent];} - QString getIncomingContext() const {return fileContents_[IncomingContext];} - QString getIncomingNextContext() const {return fileContents_[IncomingNextContext];} - QString getOutgoingContent() const {return fileContents_[OutgoingContent];} - QString getOutgoingNextContent() const {return fileContents_[OutgoingNextContent];} - QString getOutgoingContext() const {return fileContents_[OutgoingContext];} - QString getOutgoingNextContext() const {return fileContents_[OutgoingNextContext];} - QString getTemplate() const {return fileContents_[Template];} - QString getMainCSS() const {return fileContents_[MainCSS];} - QString getBase() const; + class QtChatTheme { + public: + QtChatTheme(const QString& themePath); + QString getHeader() const {return fileContents_[Header];} + QString getFooter() const {return fileContents_[Footer];} + QString getContent() const {return fileContents_[Content];} + QString getStatus() const {return fileContents_[Status];} + QString getTopic() const {return fileContents_[Topic];} + QString getFileTransferRequest() const {return fileContents_[FileTransferRequest];} + QString getIncomingContent() const {return fileContents_[IncomingContent];} + QString getIncomingNextContent() const {return fileContents_[IncomingNextContent];} + QString getIncomingContext() const {return fileContents_[IncomingContext];} + QString getIncomingNextContext() const {return fileContents_[IncomingNextContext];} + QString getOutgoingContent() const {return fileContents_[OutgoingContent];} + QString getOutgoingNextContent() const {return fileContents_[OutgoingNextContent];} + QString getOutgoingContext() const {return fileContents_[OutgoingContext];} + QString getOutgoingNextContext() const {return fileContents_[OutgoingNextContext];} + QString getTemplate() const {return fileContents_[Template];} + QString getMainCSS() const {return fileContents_[MainCSS];} + QString getBase() const; + QString getUnread() const; - private: - enum files {Header = 0, Footer, Content, Status, Topic, FileTransferRequest, IncomingContent, IncomingNextContent, IncomingContext, IncomingNextContext, OutgoingContent, OutgoingNextContent, OutgoingContext, OutgoingNextContext, Template, MainCSS, TemplateDefault, EndMarker}; - bool qrc_; - QList<QString> fileContents_; - QString themePath_; - }; + private: + enum files {Header = 0, Footer, Content, Status, Topic, FileTransferRequest, IncomingContent, IncomingNextContent, IncomingContext, IncomingNextContext, OutgoingContent, OutgoingNextContent, OutgoingContext, OutgoingNextContext, Template, MainCSS, TemplateDefault, Unread, /*Must be last!*/EndMarker}; + bool qrc_; + QList<QString> fileContents_; + QString themePath_; + }; } diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index db4fe51..6d9a17d 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -1,12 +1,11 @@ /* - * Copyright (c) 2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2013-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtChatView.h> - namespace Swift { QtChatView::QtChatView(QWidget* parent) : QWidget(parent) { @@ -14,7 +13,7 @@ QtChatView::QtChatView(QWidget* parent) : QWidget(parent) { } QtChatView::~QtChatView() { - + } } diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index c8519b7..3128f72 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -1,13 +1,14 @@ /* - * Copyright (c) 2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2013-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <memory> #include <string> -#include <boost/shared_ptr.hpp> + #include <boost/date_time/posix_time/posix_time.hpp> #include <QWidget> @@ -15,48 +16,49 @@ #include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { - class HighlightAction; - class SecurityLabel; - - class QtChatView : public QWidget { - Q_OBJECT - public: - QtChatView(QWidget* parent); - virtual ~QtChatView(); - - /** Add message to window. - * @return id of added message (for acks). - */ - virtual std::string addMessage(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; - /** Adds action to window. - * @return id of added message (for acks); - */ - virtual std::string addAction(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; - - virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; - virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; - - virtual void addErrorMessage(const ChatWindow::ChatMessage& message) = 0; - virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; - virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0; - virtual void replaceLastMessage(const ChatWindow::ChatMessage& message) = 0; - virtual void setAckState(const std::string& id, ChatWindow::AckState state) = 0; - - virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; - virtual void setFileTransferProgress(std::string, const int percentageDone) = 0; - virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") = 0; - virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) = 0; - virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) = 0; - virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0; - virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0; - - virtual void showEmoticons(bool show) = 0; - virtual void addLastSeenLine() = 0; - - public slots: - virtual void resizeFont(int fontSizeSteps) = 0; - virtual void scrollToBottom() = 0; - virtual void handleKeyPressEvent(QKeyEvent* event) = 0; - - }; + class HighlightAction; + class SecurityLabel; + + class QtChatView : public QWidget { + Q_OBJECT + public: + QtChatView(QWidget* parent); + virtual ~QtChatView(); + + /** Add message to window. + * @return id of added message (for acks). + */ + virtual std::string addMessage(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 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, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0; + + virtual std::string addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; + virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0; + + virtual void addErrorMessage(const ChatWindow::ChatMessage& message) = 0; + virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + virtual void replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/) = 0; + virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + virtual void replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/) = 0; + virtual void setAckState(const std::string& id, ChatWindow::AckState state) = 0; + + virtual std::string addFileTransfer(const std::string& senderName, const std::string& avatarPath, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description) = 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; + + 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; + + }; } diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 224f343..82c65ce 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -1,781 +1,1074 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtChatWindow.h> +#include <map> +#include <memory> +#include <string> + #include <boost/cstdint.hpp> #include <boost/lexical_cast.hpp> -#include <boost/smart_ptr/make_shared.hpp> -#include <qdebug.h> #include <QApplication> #include <QBoxLayout> #include <QCloseEvent> #include <QComboBox> +#include <QCursor> +#include <QDebug> #include <QFileDialog> #include <QFileInfo> +#include <QFontMetrics> #include <QInputDialog> #include <QLabel> #include <QLineEdit> #include <QMenu> #include <QMessageBox> #include <QMimeData> +#include <QPoint> #include <QPushButton> +#include <QSize> #include <QSplitter> #include <QString> #include <QTextDocument> #include <QTextEdit> #include <QTime> +#include <QTimer> #include <QToolButton> #include <QUrl> #include <Swiften/Base/Log.h> +#include <Swiften/Base/Platform.h> #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 <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <SwifTools/EmojiMapper.h> #include <SwifTools/TabComplete.h> -#include <Swift/QtUI/Roster/QtOccupantListWidget.h> -#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/QtAddBookmarkWindow.h> +#include <Swift/QtUI/QtEditBookmarkWindow.h> +#include <Swift/QtUI/QtEmojisSelector.h> +#include <Swift/QtUI/QtPlainChatView.h> #include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtSettingsProvider.h> #include <Swift/QtUI/QtTextEdit.h> #include <Swift/QtUI/QtUISettingConstants.h> #include <Swift/QtUI/QtUtilities.h> #include <Swift/QtUI/QtWebKitChatView.h> +#include <Swift/QtUI/Roster/QtOccupantListWidget.h> namespace Swift { -QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) { - settings_ = settings; - unreadCount_ = 0; - inputEnabled_ = true; - completer_ = NULL; - affiliationEditor_ = NULL; - theme_ = theme; - isCorrection_ = false; - labelModel_ = NULL; - correctionEnabled_ = Maybe; - updateTitleWithUnreadCount(); - -#ifdef SWIFT_EXPERIMENTAL_FT - setAcceptDrops(true); +QtChatWindow::QtChatWindow(const QString& contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QtSettingsProvider* qtOnlySettings, const std::map<std::string, std::string>& emoticonsMap) : QtTabbable(), id_(Q2PSTRING(contact)), contact_(contact), nextAlertId_(0), eventStream_(eventStream), settings_(settings), qtOnlySettings_(qtOnlySettings), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false), roomBookmarkState_(RoomNotBookmarked), emoticonsMap_(emoticonsMap) { + unreadCount_ = 0; + isOnline_ = true; + completer_ = nullptr; + affiliationEditor_ = nullptr; + theme_ = theme; + isCorrection_ = false; + labelModel_ = nullptr; + correctionEnabled_ = Maybe; + fileTransferEnabled_ = Maybe; + updateTitleWithUnreadCount(); + assert(settings); + setAcceptDrops(true); + + alertStyleSheet_ = ".QWidget, QTextEdit { background: rgb(255, 255, 153); color: black }"; + + QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(2); + + alertLayout_ = new QVBoxLayout(); + layout->addLayout(alertLayout_); + + subjectLayout_ = new QBoxLayout(QBoxLayout::LeftToRight); + subject_ = new QLineEdit(this); + subjectLayout_->addWidget(subject_); + setSubject(""); + subject_->setReadOnly(true); + + QPushButton* actionButton_ = new QPushButton(this); + actionButton_->setIcon(QIcon(":/icons/actions.png")); + connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked())); + subject_->hide(); + + layout->addLayout(subjectLayout_); + + logRosterSplitter_ = new QSplitter(this); + logRosterSplitter_->setAutoFillBackground(true); + layout->addWidget(logRosterSplitter_); + 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, settings); // 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. + } + // When used with QSplitter and setChildrenCollapsible(false), the following prevents + // this widget to be hidden, i.e. resized to zero width. + messageLog_->setMinimumWidth(20); + logRosterSplitter_->addWidget(messageLog_); + + treeWidget_ = new QtOccupantListWidget(eventStream_, settings_, QtTreeWidget::MessageDisplayJID, this); + treeWidget_->hide(); + logRosterSplitter_->addWidget(treeWidget_); + logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + connect(logRosterSplitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int))); + + 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(); + + labelsWidget_ = new QComboBox(this); + labelsWidget_->setFocusPolicy(Qt::NoFocus); + labelsWidget_->hide(); + 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(settings_, this); + input_->setAcceptRichText(false); + inputBarLayout->addWidget(midBar_); + inputBarLayout->addWidget(input_); + correctingLabel_ = new QLabel(tr("Correcting"), this); + inputBarLayout->addWidget(correctingLabel_); + correctingLabel_->hide(); + + connect(input_, SIGNAL(receivedFocus()), this, SLOT(handleTextInputReceivedFocus())); + connect(input_, SIGNAL(lostFocus()), this, SLOT(handleTextInputLostFocus())); + connect(input_, SIGNAL(itemDropped(QDropEvent*)), this, SLOT(dropEvent(QDropEvent*))); + QPushButton* emojisButton_ = new QPushButton(this); + emojisButton_->setText("\xF0\x9F\x98\x83"); + +#if defined(SWIFTEN_PLATFORM_WINDOWS) || defined(SWIFTEN_PLATFORM_LINUX) + //Using a emoji glyph instead of an image makes the button sizes inequal in windows & linux, + //so we set fixed size for both buttons, the one that is hinted for actionButton_ + emojisButton_->setMaximumWidth(actionButton_->sizeHint().width()); + emojisButton_->setMaximumHeight(actionButton_->sizeHint().height()); + actionButton_->setMaximumWidth(actionButton_->sizeHint().width()); + actionButton_->setMaximumHeight(actionButton_->sizeHint().height()); #endif - - alertStyleSheet_ = "background: rgb(255, 255, 153); color: black"; - - QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); - layout->setContentsMargins(0,0,0,0); - layout->setSpacing(2); - - 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(); - - subjectLayout_ = new QBoxLayout(QBoxLayout::LeftToRight); - subject_ = new QLineEdit(this); - subjectLayout_->addWidget(subject_); - setSubject(""); - subject_->setReadOnly(true); - - QPushButton* actionButton_ = new QPushButton(this); - actionButton_->setIcon(QIcon(":/icons/actions.png")); - connect(actionButton_, SIGNAL(clicked()), this, SLOT(handleActionButtonClicked())); - subject_->hide(); - - layout->addLayout(subjectLayout_); - - logRosterSplitter_ = new QSplitter(this); - logRosterSplitter_->setAutoFillBackground(true); - layout->addWidget(logRosterSplitter_); - 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_->hide(); - logRosterSplitter_->addWidget(treeWidget_); - logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - connect(logRosterSplitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int))); - - 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(); - - labelsWidget_ = new QComboBox(this); - labelsWidget_->setFocusPolicy(Qt::NoFocus); - labelsWidget_->hide(); - 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(settings_, this); - input_->setAcceptRichText(false); - inputBarLayout->addWidget(midBar_); - inputBarLayout->addWidget(input_); - correctingLabel_ = new QLabel(tr("Correcting"), this); - inputBarLayout->addWidget(correctingLabel_); - correctingLabel_->hide(); - - // using an extra layout to work around Qt margin glitches on OS X - QHBoxLayout* actionLayout = new QHBoxLayout(); - actionLayout->addWidget(actionButton_); - - inputBarLayout->addLayout(actionLayout); - layout->addLayout(inputBarLayout); - - inputClearing_ = false; - contactIsTyping_ = false; - tabCompletion_ = false; - - connect(input_, SIGNAL(unhandledKeyPressEvent(QKeyEvent*)), this, SLOT(handleKeyPressEvent(QKeyEvent*))); - connect(input_, SIGNAL(returnPressed()), this, SLOT(returnPressed())); - connect(input_, SIGNAL(textChanged()), this, SLOT(handleInputChanged())); - connect(input_, SIGNAL(cursorPositionChanged()), this, SLOT(handleCursorPositionChanged())); - setFocusProxy(input_); - logRosterSplitter_->setFocusProxy(input_); - midBar_->setFocusProxy(input_); - messageLog_->setFocusProxy(input_); - connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(qAppFocusChanged(QWidget*, QWidget*))); - connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus())); - resize(400,300); - connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); - connect(messageLog_, SIGNAL(logCleared()), this, SLOT(handleLogCleared())); - - treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1)); - treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2)); - - settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1)); - messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); - + connect(emojisButton_, SIGNAL(clicked()), this, SLOT(handleEmojisButtonClicked())); + + // using an extra layout to work around Qt margin glitches on OS X + QHBoxLayout* actionLayout = new QHBoxLayout(); + actionLayout->addWidget(emojisButton_); + actionLayout->addWidget(actionButton_); + + inputBarLayout->addLayout(actionLayout); + layout->addLayout(inputBarLayout); + + inputClearing_ = false; + contactIsTyping_ = false; + tabCompletion_ = false; + + connect(input_, SIGNAL(unhandledKeyPressEvent(QKeyEvent*)), this, SLOT(handleKeyPressEvent(QKeyEvent*))); + connect(input_, SIGNAL(returnPressed()), this, SLOT(returnPressed())); + connect(input_, SIGNAL(textChanged()), this, SLOT(handleInputChanged())); + connect(input_, SIGNAL(cursorPositionChanged()), this, SLOT(handleCursorPositionChanged())); + setFocusProxy(input_); + logRosterSplitter_->setFocusProxy(input_); + midBar_->setFocusProxy(input_); + messageLog_->setFocusProxy(input_); + connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus())); + resize(400,300); + connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); + connect(messageLog_, SIGNAL(logCleared()), this, SLOT(handleLogCleared())); + + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1)); + treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2)); + + settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1)); + messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); + setMinimumSize(100, 100); + + dayChangeTimer = new QTimer(this); + dayChangeTimer->setSingleShot(true); + connect(dayChangeTimer, &QTimer::timeout, [this](){ + addSystemMessage(ChatMessage(Q2PSTRING(tr("The day is now %1").arg(QDateTime::currentDateTime().date().toString(Qt::SystemLocaleLongDate)))), ChatWindow::DefaultDirection); + onContinuationsBroken(); + resetDayChangeTimer(); + }); + + resetDayChangeTimer(); } QtChatWindow::~QtChatWindow() { - if (mucConfigurationWindow_) { - delete mucConfigurationWindow_.data(); - } + dayChangeTimer->stop(); + settings_->onSettingChanged.disconnect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1)); + if (mucConfigurationWindow_) { + delete mucConfigurationWindow_.data(); + } } void QtChatWindow::handleSettingChanged(const std::string& setting) { - if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { - bool showEmoticons = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); - messageLog_->showEmoticons(showEmoticons); - } + if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { + bool showEmoticons = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); + messageLog_->showEmoticons(showEmoticons); + } } void QtChatWindow::handleLogCleared() { - onLogCleared(); + onContinuationsBroken(); } void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { - onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); + onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); } void QtChatWindow::handleFontResized(int fontSizeSteps) { - messageLog_->resizeFont(fontSizeSteps); + messageLog_->resizeFont(fontSizeSteps); } void QtChatWindow::handleAlertButtonClicked() { - onAlertButtonClicked(); -} - -void QtChatWindow::setAlert(const std::string& alertText, const std::string& buttonText) { - alertLabel_->setText(alertText.c_str()); - if (buttonText.empty()) { - alertButton_->hide(); - } else { - alertButton_->setText(buttonText.c_str()); - alertButton_->show(); - } - alertWidget_->show(); -} - -void QtChatWindow::cancelAlert() { - alertWidget_->hide(); + 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; + } + } +} + +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); + } } void QtChatWindow::setTabComplete(TabComplete* completer) { - completer_ = completer; + completer_ = completer; } void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) { - event->ignore(); - QtTabbable::handleKeyPressEvent(event); - if (event->isAccepted()) { - return; - } - event->accept(); - - int key = event->key(); - if (key == Qt::Key_Tab) { - tabComplete(); - } else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) { - beginCorrection(); - } else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) { - cancelCorrection(); - } else if (key == Qt::Key_Down || key == Qt::Key_Up) { - /* Drop */ - } else { - messageLog_->handleKeyPressEvent(event); - } + event->ignore(); + if (event->isAccepted()) { + return; + } + event->accept(); + + int key = event->key(); + if (key == Qt::Key_Tab) { + tabComplete(); + } + else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) { + beginCorrection(); + } + else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) { + cancelCorrection(); + } + else if (key == Qt::Key_Down || key == Qt::Key_Up) { + event->ignore(); + } + else { + messageLog_->handleKeyPressEvent(event); + } } void QtChatWindow::beginCorrection() { - if (correctionEnabled_ == ChatWindow::Maybe) { - setAlert(Q2PSTRING(tr("This chat may not support message correction. If you send a correction anyway, it may appear as a duplicate message"))); - } else if (correctionEnabled_ == ChatWindow::No) { - setAlert(Q2PSTRING(tr("This chat does not support message correction. If you send a correction anyway, it will appear as a duplicate message"))); - } - QTextCursor cursor = input_->textCursor(); - cursor.select(QTextCursor::Document); - cursor.beginEditBlock(); - cursor.insertText(QString(lastSentMessage_)); - cursor.endEditBlock(); - isCorrection_ = true; - correctingLabel_->show(); - input_->setStyleSheet(alertStyleSheet_); - labelsWidget_->setEnabled(false); + boost::optional<AlertID> newCorrectingAlert; + if (correctionEnabled_ == Maybe) { + 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_ == 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); + cursor.beginEditBlock(); + cursor.insertText(QString(lastSentMessage_)); + cursor.endEditBlock(); + isCorrection_ = true; + correctingLabel_->show(); + input_->setCorrectionHighlight(true); + labelsWidget_->setEnabled(false); } void QtChatWindow::cancelCorrection() { - cancelAlert(); - QTextCursor cursor = input_->textCursor(); - cursor.select(QTextCursor::Document); - cursor.removeSelectedText(); - isCorrection_ = false; - correctingLabel_->hide(); - input_->setStyleSheet(qApp->styleSheet()); - labelsWidget_->setEnabled(true); + if (correctingAlert_) { + removeAlert(*correctingAlert_); + correctingAlert_.reset(); + } + QTextCursor cursor = input_->textCursor(); + cursor.select(QTextCursor::Document); + cursor.removeSelectedText(); + isCorrection_ = false; + correctingLabel_->hide(); + input_->setCorrectionHighlight(false); + labelsWidget_->setEnabled(true); } QByteArray QtChatWindow::getSplitterState() { - return logRosterSplitter_->saveState(); + return logRosterSplitter_->saveState(); } void QtChatWindow::handleChangeSplitterState(QByteArray state) { - logRosterSplitter_->restoreState(state); + logRosterSplitter_->restoreState(state); +#ifdef SWIFTEN_PLATFORM_MACOSX + logRosterSplitter_->setHandleWidth(0); +#endif + logRosterSplitter_->setChildrenCollapsible(false); } void QtChatWindow::handleSplitterMoved(int, int) { - emit splitterMoved(); + emit splitterMoved(); } void QtChatWindow::tabComplete() { - if (!completer_) { - return; - } - - QTextCursor cursor; - if (tabCompleteCursor_.hasSelection()) { - cursor = tabCompleteCursor_; - } else { - cursor = input_->textCursor(); - while(cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor) && cursor.document()->characterAt(cursor.position() - 1) != ' ') { } - } - QString root = cursor.selectedText(); - if (root.isEmpty()) { - return; - } - QString suggestion = P2QSTRING(completer_->completeWord(Q2PSTRING(root))); - if (root == suggestion) { - return; - } - tabCompletion_ = true; - cursor.beginEditBlock(); - cursor.removeSelectedText(); - int oldPosition = cursor.position(); - - cursor.insertText(suggestion); - tabCompleteCursor_ = cursor; - tabCompleteCursor_.setPosition(oldPosition, QTextCursor::KeepAnchor); - - cursor.endEditBlock(); - tabCompletion_ = false; + if (!completer_) { + return; + } + + QTextCursor cursor; + if (tabCompleteCursor_.hasSelection()) { + cursor = tabCompleteCursor_; + } + else { + cursor = input_->textCursor(); + while(cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor) && cursor.document()->characterAt(cursor.position() - 1) != ' ') { } + } + QString root = cursor.selectedText(); + if (root.isEmpty()) { + return; + } + QString suggestion = P2QSTRING(completer_->completeWord(Q2PSTRING(root))); + if (root == suggestion) { + return; + } + tabCompletion_ = true; + cursor.beginEditBlock(); + cursor.removeSelectedText(); + int oldPosition = cursor.position(); + + cursor.insertText(suggestion); + tabCompleteCursor_ = cursor; + tabCompleteCursor_.setPosition(oldPosition, QTextCursor::KeepAnchor); + + cursor.endEditBlock(); + tabCompletion_ = false; } void QtChatWindow::setRosterModel(Roster* roster) { - treeWidget_->setRosterModel(roster); + treeWidget_->setRosterModel(roster); } void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) { - delete labelModel_; - labelModel_ = new LabelModel(); - labelModel_->availableLabels_ = labels; - int i = 0; - int defaultIndex = 0; - labelsWidget_->setModel(labelModel_); - foreach (SecurityLabelsCatalog::Item label, labels) { - if (label.getIsDefault()) { - defaultIndex = i; - break; - } - i++; - } - labelsWidget_->setCurrentIndex(defaultIndex); + delete labelModel_; + labelModel_ = new LabelModel(); + labelModel_->availableLabels_ = labels; + int i = 0; + int defaultIndex = 0; + labelsWidget_->setModel(labelModel_); + for (const auto& label : labels) { + if (label.getIsDefault()) { + defaultIndex = i; + break; + } + i++; + } + labelsWidget_->setCurrentIndex(defaultIndex); } 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_); - } + if (static_cast<size_t>(index) >= labelModel_->availableLabels_.size()) { + SWIFT_LOG(debug) << "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() { - labelsWidget_->setEnabled(false); + labelsWidget_->setEnabled(false); } void QtChatWindow::setSecurityLabelsEnabled(bool enabled) { - if (enabled) { - labelsWidget_->setEnabled(true); - labelsWidget_->show(); - } else { - labelsWidget_->hide(); - } + if (enabled) { + labelsWidget_->setEnabled(true); + labelsWidget_->show(); + } + else { + labelsWidget_->hide(); + } } void QtChatWindow::setCorrectionEnabled(Tristate enabled) { - correctionEnabled_ = enabled; + correctionEnabled_ = enabled; } -SecurityLabelsCatalog::Item QtChatWindow::getSelectedSecurityLabel() { - assert(labelsWidget_->isEnabled()); - assert(labelsWidget_->currentIndex() >= 0 && static_cast<size_t>(labelsWidget_->currentIndex()) < labelModel_->availableLabels_.size()); - return labelModel_->availableLabels_[labelsWidget_->currentIndex()]; +void QtChatWindow::setFileTransferEnabled(Tristate enabled) { + fileTransferEnabled_ = enabled; } -void QtChatWindow::closeEvent(QCloseEvent* event) { - event->accept(); - emit windowClosing(); - onClosed(); +SecurityLabelsCatalog::Item QtChatWindow::getSelectedSecurityLabel() { + assert(labelsWidget_->isEnabled()); + assert(labelsWidget_->currentIndex() >= 0 && static_cast<size_t>(labelsWidget_->currentIndex()) < labelModel_->availableLabels_.size()); + return labelModel_->availableLabels_[labelsWidget_->currentIndex()]; } -void QtChatWindow::convertToMUC(bool impromptuMUC) { - impromptu_ = impromptuMUC; - isMUC_ = true; - treeWidget_->show(); - subject_->setVisible(!impromptu_); +void QtChatWindow::closeEvent(QCloseEvent* event) { + event->accept(); + emit windowClosing(); + onClosed(); } -void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { - if (isWidgetSelected()) { - lastLineTracker_.setHasFocus(true); - input_->setFocus(); - onAllMessagesRead(); - } - else { - lastLineTracker_.setHasFocus(false); - } +void QtChatWindow::convertToMUC(MUCType mucType) { + impromptu_ = (mucType == ImpromptuMUC); + isMUC_ = true; + treeWidget_->show(); + subject_->setVisible(!impromptu_); } -void QtChatWindow::setInputEnabled(bool enabled) { - inputEnabled_ = enabled; - if (!enabled) { - if (mucConfigurationWindow_) { - delete mucConfigurationWindow_.data(); - } - if (affiliationEditor_) { - delete affiliationEditor_.data(); - } - } +void QtChatWindow::setOnline(bool online) { + isOnline_ = online; + if (!online) { + if (mucConfigurationWindow_) { + delete mucConfigurationWindow_.data(); + } + if (affiliationEditor_) { + delete affiliationEditor_.data(); + } + } } void QtChatWindow::showEvent(QShowEvent* event) { - emit windowOpening(); - QWidget::showEvent(event); + emit windowOpening(); + QWidget::showEvent(event); } -void QtChatWindow::setUnreadMessageCount(int count) { - if (unreadCount_ != count) { - unreadCount_ = count; - updateTitleWithUnreadCount(); - emit countUpdated(); - } +void QtChatWindow::setUnreadMessageCount(size_t count) { + if (unreadCount_ != count) { + unreadCount_ = count; + updateTitleWithUnreadCount(); + emit countUpdated(); + } } void QtChatWindow::setContactChatState(ChatState::ChatStateType state) { - contactIsTyping_ = (state == ChatState::Composing); - emit titleUpdated(); + contactIsTyping_ = (state == ChatState::Composing); + emit titleUpdated(); } QtTabbable::AlertType QtChatWindow::getWidgetAlertState() { - if (contactIsTyping_) { - return ImpendingActivity; - } - if (unreadCount_ > 0) { - return WaitingActivity; - } - return NoActivity; + if (contactIsTyping_) { + return ImpendingActivity; + } + if (unreadCount_ > 0) { + return WaitingActivity; + } + return NoActivity; } void QtChatWindow::setName(const std::string& name) { - contact_ = P2QSTRING(name); - updateTitleWithUnreadCount(); + contact_ = P2QSTRING(name); + updateTitleWithUnreadCount(); } void QtChatWindow::updateTitleWithUnreadCount() { - if (isWindow()) { - setWindowTitle(unreadCount_ > 0 ? QString("(%1) %2").arg(unreadCount_).arg(contact_) : contact_); - } else { - setWindowTitle(contact_); - } - emit titleUpdated(); + if (isWindow()) { + setWindowTitle(unreadCount_ > 0 ? QString("(%1) %2").arg(unreadCount_).arg(contact_) : contact_); + } + else { + setWindowTitle(contact_); + } + emit titleUpdated(); } - - void QtChatWindow::flash() { - emit requestFlash(); + emit requestFlash(); } -int QtChatWindow::getCount() { - return unreadCount_; +size_t QtChatWindow::getCount() { + return unreadCount_; } +void QtChatWindow::replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, const ChatWindow::TimestampBehaviour timestampBehaviour) { + messageLog_->replaceSystemMessage(message, id, timestampBehaviour); +} void QtChatWindow::returnPressed() { - if (!inputEnabled_) { - return; - } - messageLog_->scrollToBottom(); - lastSentMessage_ = QString(input_->toPlainText()); - onSendMessageRequest(Q2PSTRING(input_->toPlainText()), isCorrection_); - inputClearing_ = true; - input_->clear(); - cancelCorrection(); - inputClearing_ = false; + if (!isOnline_ || (blockingState_ == IsBlocked)) { + return; + } + messageLog_->scrollToBottom(); + lastSentMessage_ = QString(input_->toPlainText()); + onSendMessageRequest(Q2PSTRING(input_->toPlainText()), isCorrection_); + inputClearing_ = true; + input_->clear(); + cancelCorrection(); + inputClearing_ = false; } void QtChatWindow::handleInputChanged() { - if (inputClearing_) { - return; - } - if (input_->toPlainText().isEmpty()) { - onUserCancelsTyping(); - } else { - onUserTyping(); - } + if (inputClearing_) { + return; + } + if (input_->toPlainText().isEmpty()) { + onUserCancelsTyping(); + } + else { + onUserTyping(); + } } void QtChatWindow::handleCursorPositionChanged() { - if (tabCompletion_) { - return; - } - tabCompleteCursor_.clearSelection(); + if (tabCompletion_) { + return; + } + tabCompleteCursor_.clearSelection(); } void QtChatWindow::show() { - if (parentWidget() == NULL) { - QWidget::show(); - } - emit windowOpening(); + if (parentWidget() == nullptr) { + QWidget::show(); + } + emit windowOpening(); +} + +bool QtChatWindow::isVisible() const { + return QWidget::isVisible(); } void QtChatWindow::activate() { - if (isWindow()) { - QWidget::show(); - } - emit wantsToActivate(); - input_->setFocus(); + if (isWindow()) { + QWidget::show(); + } + emit wantsToActivate(); + input_->setFocus(); } void QtChatWindow::resizeEvent(QResizeEvent*) { - emit geometryChanged(); + emit geometryChanged(); } void QtChatWindow::moveEvent(QMoveEvent*) { - emit geometryChanged(); + emit geometryChanged(); } void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) { - if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { - // TODO: check whether contact actually supports file transfer - if (!isMUC_) { - event->acceptProposedAction(); - } - } else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid")) { - if (isMUC_ || supportsImpromptuChat_) { - event->acceptProposedAction(); - } - } + if (isOnline_ && (blockingState_ != IsBlocked)) { + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + if (!isMUC_ && fileTransferEnabled_ == Yes) { + event->acceptProposedAction(); + } + } + else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list")) { + if (isMUC_ || supportsImpromptuChat_) { + // Prevent invitations or impromptu initializations for contacts that you are already chatting to. + std::vector<JID> droppedJIDs =jidListFromQByteArray(event->mimeData()->data("application/vnd.swift.contact-jid-list")); + std::set<JID> conversationJIDs; + if (isMUC_) { + conversationJIDs = treeWidget_->getRoster()->getJIDs(); + } + + for (std::vector<JID>::iterator i = droppedJIDs.begin(); i != droppedJIDs.end(); ) { + const JID& droppedJID = *i; + if (conversationJIDs.find(droppedJID) != conversationJIDs.end()) { + i = droppedJIDs.erase(i); + } + else { + ++i; + } + } + + if (droppedJIDs.empty()) { + event->ignore(); + } + else { + event->acceptProposedAction(); + } + } + } + } } void QtChatWindow::dropEvent(QDropEvent *event) { - if (event->mimeData()->hasUrls()) { - if (event->mimeData()->urls().size() == 1) { - onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile())); - } else { - std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time."))); - ChatMessage message; - message.append(boost::make_shared<ChatTextMessagePart>(messageText)); - addSystemMessage(message, DefaultDirection); - } - } else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid")) { - QByteArray dataBytes = event->mimeData()->data("application/vnd.swift.contact-jid"); - QDataStream dataStream(&dataBytes, QIODevice::ReadOnly); - QString jidString; - dataStream >> jidString; - onInviteToChat(std::vector<JID>(1, JID(Q2PSTRING(jidString)))); - } + if (fileTransferEnabled_ == Yes && event->mimeData()->hasUrls()) { + if (event->mimeData()->urls().size() == 1) { + onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile())); + } + else { + std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time."))); + ChatMessage message; + message.append(std::make_shared<ChatTextMessagePart>(messageText)); + addSystemMessage(message, DefaultDirection); + } + } + else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid-list")) { + std::vector<JID> invites = jidListFromQByteArray(event->mimeData()->data("application/vnd.swift.contact-jid-list")); + onInviteToChat(invites); + } +} + +std::vector<JID> QtChatWindow::jidListFromQByteArray(const QByteArray& dataBytes) { + QDataStream dataStream(dataBytes); + std::vector<JID> invites; + while (!dataStream.atEnd()) { + QString jidString; + dataStream >> jidString; + invites.push_back(Q2PSTRING(jidString)); + } + return invites; +} + +void QtChatWindow::resetDayChangeTimer() { + assert(dayChangeTimer); + // Add a second so the handled is definitly called on the next day, and not multiple times exactly at midnight. + dayChangeTimer->start(QtUtilities::secondsToNextMidnight(QDateTime::currentDateTime()) * 1000 + 1000); } - void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) { - treeWidget_->setAvailableOccupantActions(actions); + treeWidget_->setAvailableOccupantActions(actions); } void QtChatWindow::setSubject(const std::string& subject) { - //subject_->setVisible(!subject.empty()); - subject_->setText(P2QSTRING(subject)); - subject_->setToolTip(P2QSTRING(subject)); - subject_->setCursorPosition(0); + //subject_->setVisible(!subject.empty()); + subject_->setText(P2QSTRING(subject)); + subject_->setToolTip(P2QSTRING(subject)); + subject_->setCursorPosition(0); +} + +void QtChatWindow::handleEmojisButtonClicked() { + // Create QtEmojisSelector and QMenu + emojisGrid_ = new QtEmojisSelector(qtOnlySettings_->getQSettings(), emoticonsMap_); + auto emojisLayout = new QVBoxLayout(); + emojisLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_MenuHMargin),style()->pixelMetric(QStyle::PM_MenuVMargin), + style()->pixelMetric(QStyle::PM_MenuHMargin),style()->pixelMetric(QStyle::PM_MenuVMargin)); + emojisLayout->addWidget(emojisGrid_); + emojisMenu_ = std::make_unique<QMenu>(); + emojisMenu_->setLayout(emojisLayout); + emojisMenu_->adjustSize(); + + connect(emojisGrid_, SIGNAL(emojiClicked(QString)), this, SLOT(handleEmojiClicked(QString))); + + QSize menuSize = emojisMenu_->size(); + emojisMenu_->exec(QPoint(QCursor::pos().x() - menuSize.width(), QCursor::pos().y() - menuSize.height())); +} + +void QtChatWindow::handleEmojiClicked(QString emoji) { + if (isVisible()) { + input_->textCursor().insertText(emoji); + input_->setFocus(); + // We cannot delete the emojisGrid_ + // Grid may not close yet and we should not try to destroy it. + emojisMenu_->setVisible(false); + } +} + +void QtChatWindow::handleTextInputReceivedFocus() { + lastLineTracker_.setHasFocus(true); + input_->setFocus(); + if (focusTimer_) { + focusTimer_->stop(); + } + else { + focusTimer_ = std::make_unique<QTimer>(this); + focusTimer_->setSingleShot(true); + focusTimer_->setTimerType(Qt::CoarseTimer); + connect(focusTimer_.get(), &QTimer::timeout, this, &QtChatWindow::handleFocusTimerTick); + } + focusTimer_->setInterval(1000); + focusTimer_->start(); +} + +void QtChatWindow::handleTextInputLostFocus() { + lastLineTracker_.setHasFocus(false); } void QtChatWindow::handleActionButtonClicked() { - QMenu contextMenu; - QAction* changeSubject = NULL; - QAction* configure = NULL; - QAction* affiliations = NULL; - QAction* destroy = NULL; - QAction* invite = NULL; - - QAction* block = NULL; - QAction* unblock = NULL; - - if (availableRoomActions_.empty()) { - if (blockingState_ == IsBlocked) { - unblock = contextMenu.addAction(tr("Unblock")); - } else if (blockingState_ == IsUnblocked) { - block = contextMenu.addAction(tr("Block")); - } - - if (supportsImpromptuChat_) { - invite = contextMenu.addAction(tr("Invite person to this chat…")); - } - - } else { - foreach(ChatWindow::RoomAction availableAction, availableRoomActions_) - { - 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; - } - } - } - - QAction* result = contextMenu.exec(QCursor::pos()); - if (result == NULL) { - /* Skip processing. Note that otherwise, because the actions could be null they could match */ - } - else if (result == changeSubject) { - bool ok; - QString subject = QInputDialog::getText(this, tr("Change room subject"), tr("New subject:"), QLineEdit::Normal, subject_->text(), &ok); - if (ok) { - onChangeSubjectRequest(Q2PSTRING(subject)); - } - } - else if (result == configure) { - onConfigureRequest(Form::ref()); - } - else if (result == affiliations) { - if (!affiliationEditor_) { - onGetAffiliationsRequest(); - affiliationEditor_ = new QtAffiliationEditor(this); - connect(affiliationEditor_, SIGNAL(accepted()), this, SLOT(handleAffiliationEditorAccepted())); - } - affiliationEditor_->show(); - } - else if (result == destroy) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Confirm room destruction")); - msgBox.setText(tr("Are you sure you want to destroy the room?")); - msgBox.setInformativeText(tr("This will destroy the room.")); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - if (msgBox.exec() == QMessageBox::Yes) { - onDestroyRequest(); - } - } - else if (result == invite) { - onInviteToChat(std::vector<JID>()); - } - else if (result == block) { - onBlockUserRequest(); - } - else if (result == unblock) { - onUnblockUserRequest(); - } + QMenu contextMenu; + QAction* changeSubject = nullptr; + QAction* configure = nullptr; + QAction* affiliations = nullptr; + QAction* destroy = nullptr; + QAction* invite = nullptr; + QAction* leave = nullptr; + + QAction* block = nullptr; + QAction* unblock = nullptr; + + if (availableRoomActions_.empty()) { + if (blockingState_ == IsBlocked) { + unblock = contextMenu.addAction(tr("Unblock")); + unblock->setEnabled(isOnline_); + } + else if (!isMUC_ && blockingState_ == IsUnblocked) { + block = contextMenu.addAction(tr("Block")); + block->setEnabled(isOnline_); + } + + if (supportsImpromptuChat_) { + invite = contextMenu.addAction(tr("Invite person to this chat…")); + invite->setEnabled(isOnline_ && (blockingState_ != IsBlocked)); + } + + } + else { + for (auto&& 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…")); + changeSubject->setEnabled(isOnline_); + break; + case ChatWindow::Configure: + configure = contextMenu.addAction(tr("Configure room…")); + configure->setEnabled(isOnline_); + break; + case ChatWindow::Affiliations: + affiliations = contextMenu.addAction(tr("Edit affiliations…")); + affiliations->setEnabled(isOnline_); + break; + case ChatWindow::Destroy: + destroy = contextMenu.addAction(tr("Destroy room")); + destroy->setEnabled(isOnline_); + break; + case ChatWindow::Invite: + invite = contextMenu.addAction(tr("Invite person to this room…")); + invite->setEnabled(isOnline_); + break; + case ChatWindow::Leave: + leave = contextMenu.addAction(tr("Leave room")); + leave->setEnabled(isOnline_); + break; + } + } + } + + QAction* bookmark = nullptr; + if (isMUC_) { + if (roomBookmarkState_ == RoomNotBookmarked) { + bookmark = contextMenu.addAction(tr("Bookmark this room...")); + } + else { + bookmark = contextMenu.addAction(tr("Edit bookmark...")); + } + bookmark->setEnabled(isOnline_); + } + + QAction* result = contextMenu.exec(QCursor::pos()); + if (result == nullptr) { + /* Skip processing. Note that otherwise, because the actions could be null they could match */ + } + else if (result == changeSubject) { + bool ok; + QString subject = QInputDialog::getText(this, tr("Change room subject"), tr("New subject:"), QLineEdit::Normal, subject_->text(), &ok); + if (ok) { + onChangeSubjectRequest(Q2PSTRING(subject)); + } + } + else if (result == configure) { + onConfigureRequest(Form::ref()); + } + else if (result == affiliations) { + if (!affiliationEditor_) { + onGetAffiliationsRequest(); + affiliationEditor_ = new QtAffiliationEditor(this); + connect(affiliationEditor_, SIGNAL(accepted()), this, SLOT(handleAffiliationEditorAccepted())); + } + affiliationEditor_->show(); + } + else if (result == destroy) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Confirm room destruction")); + msgBox.setText(tr("Are you sure you want to destroy the room?")); + msgBox.setInformativeText(tr("This will destroy the room.")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + if (msgBox.exec() == QMessageBox::Yes) { + onDestroyRequest(); + } + } + else if (result == invite) { + onInviteToChat(std::vector<JID>()); + } + else if (result == leave) { + close(); + } + else if (result == block) { + onBlockUserRequest(); + } + else if (result == unblock) { + onUnblockUserRequest(); + } + else if (result == bookmark) { + onBookmarkRequest(); + } } void QtChatWindow::handleAffiliationEditorAccepted() { - onChangeAffiliationsRequest(affiliationEditor_->getChanges()); + onChangeAffiliationsRequest(affiliationEditor_->getChanges()); } void QtChatWindow::setAffiliations(MUCOccupant::Affiliation affiliation, const std::vector<JID>& jids) { - if (!affiliationEditor_) return; - affiliationEditor_->setAffiliations(affiliation, jids); + if (!affiliationEditor_) return; + affiliationEditor_->setAffiliations(affiliation, jids); } void QtChatWindow::setAvailableRoomActions(const std::vector<RoomAction>& actions) { - availableRoomActions_ = actions; + availableRoomActions_ = actions; } void QtChatWindow::setBlockingState(BlockingState state) { - blockingState_ = state; + blockingState_ = state; } void QtChatWindow::setCanInitiateImpromptuChats(bool supportsImpromptu) { - supportsImpromptuChat_ = supportsImpromptu; + supportsImpromptuChat_ = supportsImpromptu; +} + +void QtChatWindow::showBookmarkWindow(const MUCBookmark& bookmark) { + if (roomBookmarkState_ != RoomNotBookmarked) { + QtEditBookmarkWindow* window = new QtEditBookmarkWindow(eventStream_, bookmark); + window->show(); + } +#ifndef NOT_YET + else { + QtAddBookmarkWindow* window = new QtAddBookmarkWindow(eventStream_, bookmark); + window->show(); + } +#endif // ! NOT_YET +} + +std::string QtChatWindow::getID() const { + return id_; +} + +void QtChatWindow::setEmphasiseFocus(bool emphasise) { + input_->setEmphasiseFocus(emphasise); } void QtChatWindow::showRoomConfigurationForm(Form::ref form) { - if (mucConfigurationWindow_) { - delete mucConfigurationWindow_.data(); - } - mucConfigurationWindow_ = new QtMUCConfigurationWindow(form); - mucConfigurationWindow_->onFormComplete.connect(boost::bind(boost::ref(onConfigureRequest), _1)); - mucConfigurationWindow_->onFormCancelled.connect(boost::bind(boost::ref(onConfigurationFormCancelled))); + if (mucConfigurationWindow_) { + delete mucConfigurationWindow_.data(); + } + mucConfigurationWindow_ = new QtMUCConfigurationWindow(form); + mucConfigurationWindow_->onFormComplete.connect(boost::bind(boost::ref(onConfigureRequest), _1)); + mucConfigurationWindow_->onFormCancelled.connect(boost::bind(boost::ref(onConfigurationFormCancelled))); } void QtChatWindow::handleAppendedToLog() { - if (lastLineTracker_.getShouldMoveLastLine()) { - /* should this be queued? */ - messageLog_->addLastSeenLine(); - } - if (isWidgetSelected()) { - onAllMessagesRead(); - } + if (lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + messageLog_->addLastSeenLine(); + } + if (isWidgetSelected()) { + onAllMessagesRead(); + } } 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); + handleAppendedToLog(); + messageLog_->addMUCInvitation(senderName, jid, reason, password, direct, isImpromptu, isContinuation); } -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); +std::string QtChatWindow::addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { + handleAppendedToLog(); + return messageLog_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time); } -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); +std::string QtChatWindow::addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { + handleAppendedToLog(); + return messageLog_->addAction(message, senderName, senderIsSelf, label, avatarPath, time); } -void QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) { - handleAppendedToLog(); - messageLog_->addSystemMessage(message, direction); +std::string QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) { + handleAppendedToLog(); + return messageLog_->addSystemMessage(message, direction); } void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) { - handleAppendedToLog(); - messageLog_->addPresenceMessage(message, direction); + handleAppendedToLog(); + messageLog_->addPresenceMessage(message, direction); } void QtChatWindow::addErrorMessage(const ChatMessage& message) { - handleAppendedToLog(); - messageLog_->addErrorMessage(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::replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) { + handleAppendedToLog(); + messageLog_->replaceMessage(message, id, time); } -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); +void QtChatWindow::replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) { + handleAppendedToLog(); + messageLog_->replaceWithAction(message, id, time); } -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); +std::string QtChatWindow::addFileTransfer(const std::string& senderName, const std::string& avatarPath, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description) { + handleAppendedToLog(); + return messageLog_->addFileTransfer(senderName, avatarPath, senderIsSelf, filename, sizeInBytes, description); } void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) { - messageLog_->setFileTransferProgress(id, percentageDone); + messageLog_->setFileTransferProgress(id, percentageDone); } void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) { - messageLog_->setFileTransferStatus(id, state, msg); + messageLog_->setFileTransferStatus(id, state, msg); } - std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) { - handleAppendedToLog(); - return messageLog_->addWhiteboardRequest(contact_, senderIsSelf); + handleAppendedToLog(); + return messageLog_->addWhiteboardRequest(contact_, senderIsSelf); } void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) { - messageLog_->setWhiteboardSessionStatus(id, state); + messageLog_->setWhiteboardSessionStatus(id, state); } -void QtChatWindow::replaceLastMessage(const ChatMessage& message) { - messageLog_->replaceLastMessage(message); +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); + messageLog_->setAckState(id, state); } void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { - messageLog_->setMessageReceiptState(id, state); + messageLog_->setMessageReceiptState(id, state); +} + +void QtChatWindow::setBookmarkState(RoomBookmarkState bookmarkState) { + roomBookmarkState_ = bookmarkState; +} + +void QtChatWindow::setChatSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue) { + auto layout = static_cast<QBoxLayout*>(this->layout()); + + if (securityMarkingLayout_) { + layout->removeItem(securityMarkingLayout_); + securityMarkingLayout_->removeWidget(securityMarkingDisplay_); + delete securityMarkingLayout_; + } + delete securityMarkingDisplay_; + + securityMarkingLayout_ = new QHBoxLayout(); + securityMarkingDisplay_ = new QLabel(P2QSTRING(markingValue)); + + securityMarkingLayout_->addWidget(securityMarkingDisplay_); + layout->insertLayout(1, securityMarkingLayout_); + + auto palette = securityMarkingDisplay_->palette(); + palette.setColor(securityMarkingDisplay_->foregroundRole(), P2QSTRING(markingForegroundColorValue)); + palette.setColor(securityMarkingDisplay_->backgroundRole(), P2QSTRING(markingBackgroundColorValue)); + + securityMarkingDisplay_->setPalette(palette); + securityMarkingDisplay_->setContentsMargins(4,4,4,4); + securityMarkingDisplay_->setAutoFillBackground(true); + securityMarkingDisplay_->setAlignment(Qt::AlignCenter); +} + +void QtChatWindow::removeChatSecurityMarking() { + if (!securityMarkingLayout_) { + return; + } + + auto layout = static_cast<QBoxLayout*>(this->layout()); + + layout->removeItem(securityMarkingLayout_); + securityMarkingLayout_->removeWidget(securityMarkingDisplay_); + + delete securityMarkingDisplay_; + delete securityMarkingLayout_; + securityMarkingDisplay_ = nullptr; + securityMarkingLayout_ = nullptr; +} + +void QtChatWindow::handleFocusTimerTick() { + if (hasFocus()) { + onAllMessagesRead(); + } + focusTimer_.reset(); +} + +void QtChatWindow::resendMessage(const std::string& id) { + if (!isOnline_ || (blockingState_ == IsBlocked)) { + return; + } + onResendMessageRequest(id); } } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index ca0ecad..b876d1e 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -1,220 +1,254 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <map> +#include <string> +#include <QMap> +#include <QMenu> #include <QPointer> +#include <QString> #include <QTextCursor> -#include <QMap> - -#include <SwifTools/LastLineTracker.h> +#include <QVBoxLayout> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <SwifTools/EmojiMapper.h> +#include <SwifTools/LastLineTracker.h> + #include <Swift/QtUI/ChatSnippet.h> #include <Swift/QtUI/QtAffiliationEditor.h> +#include <Swift/QtUI/QtEmojisSelector.h> #include <Swift/QtUI/QtMUCConfigurationWindow.h> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtTabbable.h> - - class QTextEdit; class QLineEdit; class QComboBox; class QLabel; class QSplitter; class QPushButton; +class QTimer; namespace Swift { - class QtChatView; - class QtOccupantListWidget; - class QtChatTheme; - class TreeWidget; - class QtTextEdit; - class UIEventStream; - class QtChatWindowJSBridge; - class SettingsProvider; - - // FIXME: Move this to a different file - std::string formatSize(const boost::uintmax_t bytes); - - class LabelModel : public QAbstractListModel { - Q_OBJECT - public: - 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(); - } - SecurityLabel::ref 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 ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); - std::string addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight); - - void addSystemMessage(const ChatMessage& message, Direction direction); - void addPresenceMessage(const ChatMessage& message, Direction direction); - void addErrorMessage(const ChatMessage& message); - - void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); - void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight); - // File transfer related stuff - std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes); - void setFileTransferProgress(std::string id, const int percentageDone); - 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(); - void activate(); - void setUnreadMessageCount(int count); - void convertToMUC(bool impromptuMUC = false); -// TreeWidget *getTreeWidget(); - void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels); - void setSecurityLabelsEnabled(bool enabled); - void setSecurityLabelsError(); - SecurityLabelsCatalog::Item getSelectedSecurityLabel(); - void setName(const std::string& name); - void setInputEnabled(bool enabled); - QtTabbable::AlertType getWidgetAlertState(); - void setContactChatState(ChatState::ChatStateType state); - void setRosterModel(Roster* roster); - void setTabComplete(TabComplete* completer); - int getCount(); - void replaceLastMessage(const ChatMessage& message); - void setAckState(const std::string& id, AckState state); - - // message receipts - void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state); - - void flash(); - QByteArray getSplitterState(); - virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions); - void setSubject(const std::string& subject); - void showRoomConfigurationForm(Form::ref); - void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false); - void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&); - void setAvailableRoomActions(const std::vector<RoomAction>& actions); - void setBlockingState(BlockingState state); - virtual void setCanInitiateImpromptuChats(bool supportsImpromptu); - - public slots: - void handleChangeSplitterState(QByteArray state); - void handleFontResized(int fontSizeSteps); - void setAlert(const std::string& alertText, const std::string& buttonText = ""); - void cancelAlert(); - void setCorrectionEnabled(Tristate enabled); - - signals: - void geometryChanged(); - void splitterMoved(); - void fontResized(int); - - protected slots: - void qAppFocusChanged(QWidget* old, QWidget* now); - void closeEvent(QCloseEvent* event); - void resizeEvent(QResizeEvent* event); - void moveEvent(QMoveEvent* event); - - void dragEnterEvent(QDragEnterEvent *event); - void dropEvent(QDropEvent *event); - - protected: - void showEvent(QShowEvent* event); - - private slots: - void handleLogCleared(); - void returnPressed(); - void handleInputChanged(); - void handleCursorPositionChanged(); - void handleKeyPressEvent(QKeyEvent* event); - void handleSplitterMoved(int pos, int index); - void handleAlertButtonClicked(); - void handleActionButtonClicked(); - void handleAffiliationEditorAccepted(); - void handleCurrentLabelChanged(int); - - private: - void updateTitleWithUnreadCount(); - void tabComplete(); - void beginCorrection(); - void cancelCorrection(); - void handleSettingChanged(const std::string& setting); - - void handleOccupantSelectionChanged(RosterItem* item); - void handleAppendedToLog(); - - - int unreadCount_; - bool contactIsTyping_; - LastLineTracker lastLineTracker_; - QString contact_; - QString lastSentMessage_; - QTextCursor tabCompleteCursor_; - QtChatView* messageLog_; - QtChatTheme* theme_; - QtTextEdit* input_; - QWidget* midBar_; - QBoxLayout* subjectLayout_; - QComboBox* labelsWidget_; - QtOccupantListWidget* treeWidget_; - QLabel* correctingLabel_; - QLabel* alertLabel_; - QWidget* alertWidget_; - QPushButton* alertButton_; - TabComplete* completer_; - QLineEdit* subject_; - bool isCorrection_; - bool inputClearing_; - bool tabCompletion_; - UIEventStream* eventStream_; - bool inputEnabled_; - QSplitter *logRosterSplitter_; - Tristate correctionEnabled_; - QString alertStyleSheet_; - QPointer<QtMUCConfigurationWindow> mucConfigurationWindow_; - QPointer<QtAffiliationEditor> affiliationEditor_; - SettingsProvider* settings_; - std::vector<ChatWindow::RoomAction> availableRoomActions_; - QPalette defaultLabelsPalette_; - LabelModel* labelModel_; - BlockingState blockingState_; - bool impromptu_; - bool isMUC_; - bool supportsImpromptuChat_; - }; + class QtChatView; + class QtOccupantListWidget; + class QtChatTheme; + class TreeWidget; + class QtTextEdit; + class UIEventStream; + class QtChatWindowJSBridge; + class SettingsProvider; + class QtSettingsProvider; + + class LabelModel : public QAbstractListModel { + Q_OBJECT + public: + LabelModel(QObject* parent = nullptr) : 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(); + } + std::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, QtSettingsProvider* qtOnlySettings, const std::map<std::string, std::string>& emoticonsMap); + virtual ~QtChatWindow(); + std::string addMessage(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); + std::string addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); + + std::string 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); + void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time); + void resendMessage(const std::string& id); + // File transfer related stuff + std::string addFileTransfer(const std::string& senderName, const std::string& avatarPath, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description); + void setFileTransferProgress(std::string id, const int percentageDone); + 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(size_t count); + void convertToMUC(MUCType mucType); +// TreeWidget *getTreeWidget(); + void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels); + void setSecurityLabelsEnabled(bool enabled); + void setSecurityLabelsError(); + SecurityLabelsCatalog::Item getSelectedSecurityLabel(); + void setName(const std::string& name); + void setOnline(bool online); + QtTabbable::AlertType getWidgetAlertState(); + void setContactChatState(ChatState::ChatStateType state); + void setRosterModel(Roster* roster); + void setTabComplete(TabComplete* completer); + size_t getCount(); + virtual void replaceSystemMessage(const ChatMessage& message, const std::string& id, const TimestampBehaviour timestampBehaviour); + void replaceLastMessage(const ChatMessage& message, const TimestampBehaviour timestampBehaviour); + void setAckState(const std::string& id, AckState state); + + // message receipts + void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state); + + void flash(); + QByteArray getSplitterState(); + virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions); + void setSubject(const std::string& subject); + void showRoomConfigurationForm(Form::ref); + void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false); + void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&); + void setAvailableRoomActions(const std::vector<RoomAction>& actions); + void setBlockingState(BlockingState state); + virtual void setCanInitiateImpromptuChats(bool supportsImpromptu); + virtual void showBookmarkWindow(const MUCBookmark& bookmark); + virtual void setBookmarkState(RoomBookmarkState bookmarkState); + virtual std::string getID() const; + virtual void setEmphasiseFocus(bool emphasise); + + public slots: + void handleChangeSplitterState(QByteArray state); + void handleEmojiClicked(QString emoji); + void handleFontResized(int fontSizeSteps); + AlertID addAlert(const std::string& alertText); + void removeAlert(const AlertID id); + void setCorrectionEnabled(Tristate enabled); + void setFileTransferEnabled(Tristate enabled); + + signals: + void geometryChanged(); + void splitterMoved(); + void fontResized(int); + + protected slots: + void closeEvent(QCloseEvent* event); + void resizeEvent(QResizeEvent* event); + void moveEvent(QMoveEvent* event); + + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + + protected: + void showEvent(QShowEvent* event); + + private slots: + void handleLogCleared(); + void returnPressed(); + void handleInputChanged(); + void handleCursorPositionChanged(); + void handleKeyPressEvent(QKeyEvent* event); + void handleSplitterMoved(int pos, int index); + void handleAlertButtonClicked(); + void handleActionButtonClicked(); + void handleAffiliationEditorAccepted(); + void handleCurrentLabelChanged(int); + void handleEmojisButtonClicked(); + void handleTextInputReceivedFocus(); + void handleTextInputLostFocus(); + + private: + void updateTitleWithUnreadCount(); + void tabComplete(); + void beginCorrection(); + void cancelCorrection(); + void handleSettingChanged(const std::string& setting); + + void handleOccupantSelectionChanged(RosterItem* item); + void handleAppendedToLog(); + + static std::vector<JID> jidListFromQByteArray(const QByteArray& dataBytes); + + void resetDayChangeTimer(); + + void setChatSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue); + void removeChatSecurityMarking(); + void handleFocusTimerTick(); + + private: + size_t unreadCount_; + bool contactIsTyping_; + LastLineTracker lastLineTracker_; + std::string id_; + QString contact_; + QString lastSentMessage_; + QTextCursor tabCompleteCursor_; + QtChatView* messageLog_; + QtChatTheme* theme_; + QtTextEdit* input_; + QWidget* midBar_; + QBoxLayout* subjectLayout_; + QComboBox* labelsWidget_; + QtOccupantListWidget* treeWidget_; + QLabel* correctingLabel_; + boost::optional<AlertID> correctingAlert_; + QVBoxLayout* alertLayout_; + std::map<AlertID, QWidget*> alertWidgets_; + AlertID nextAlertId_; + TabComplete* completer_; + QLineEdit* subject_; + bool isCorrection_; + bool inputClearing_; + bool tabCompletion_; + UIEventStream* eventStream_; + bool isOnline_; + QSplitter* logRosterSplitter_; + Tristate correctionEnabled_; + Tristate fileTransferEnabled_; + QString alertStyleSheet_; + QPointer<QtMUCConfigurationWindow> mucConfigurationWindow_; + QPointer<QtAffiliationEditor> affiliationEditor_; + SettingsProvider* settings_ = nullptr; + QtSettingsProvider* qtOnlySettings_ = nullptr; + std::vector<ChatWindow::RoomAction> availableRoomActions_; + QPalette defaultLabelsPalette_; + LabelModel* labelModel_; + BlockingState blockingState_; + bool impromptu_; + bool isMUC_; + bool supportsImpromptuChat_; + RoomBookmarkState roomBookmarkState_; + std::unique_ptr<QMenu> emojisMenu_; + QPointer<QtEmojisSelector> emojisGrid_; + std::map<std::string, std::string> emoticonsMap_; + QTimer* dayChangeTimer = nullptr; + QHBoxLayout* securityMarkingLayout_ = nullptr; + QLabel* securityMarkingDisplay_ = nullptr; + std::unique_ptr<QTimer> focusTimer_; + }; } diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp index 78c04c9..33c8b94 100644 --- a/Swift/QtUI/QtChatWindowFactory.cpp +++ b/Swift/QtUI/QtChatWindowFactory.cpp @@ -1,84 +1,83 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtChatWindowFactory.h> #include <QDesktopWidget> -#include <qdebug.h> + +#include <SwifTools/EmojiMapper.h> #include <Swift/QtUI/QtChatTabs.h> -#include <Swift/QtUI/QtChatWindow.h> -#include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtChatTheme.h> +#include <Swift/QtUI/QtChatWindow.h> +#include <Swift/QtUI/QtEmojisSelector.h> #include <Swift/QtUI/QtSingleWindow.h> - +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { static const QString SPLITTER_STATE = "mucSplitterState"; static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry"; -QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) { - qtOnlySettings_ = qtSettings; - settings_ = settings; - tabs_ = tabs; - theme_ = NULL; - if (splitter) { - splitter->addWidget(tabs_); - } else if (tabs_) { - QVariant chatTabsGeometryVariant = qtOnlySettings_->getQSettings()->value(CHAT_TABS_GEOMETRY); - if (chatTabsGeometryVariant.isValid()) { - tabs_->restoreGeometry(chatTabsGeometryVariant.toByteArray()); - } - connect(tabs_, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged())); - } +QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticonsMap) : themePath_(themePath), emoticonsMap_(emoticonsMap) { + qtOnlySettings_ = qtSettings; + settings_ = settings; + tabs_ = tabs; + theme_ = nullptr; + splitter->addWidget(tabs_); + QVariant chatTabsGeometryVariant = qtOnlySettings_->getQSettings()->value(CHAT_TABS_GEOMETRY); + if (chatTabsGeometryVariant.isValid()) { + tabs_->restoreGeometry(chatTabsGeometryVariant.toByteArray()); + } + connect(tabs_, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged())); } QtChatWindowFactory::~QtChatWindowFactory() { - delete theme_; + delete theme_; } ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStream* eventStream) { - if (!theme_) { - theme_ = new QtChatTheme(themePath_); - if (theme_->getIncomingContent().isEmpty()) { - delete theme_; - theme_ = new QtChatTheme(""); /* Use the inbuilt theme */ - } - } - - QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_); - connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved())); - connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray))); - - QVariant splitterState = qtOnlySettings_->getQSettings()->value(SPLITTER_STATE); - if(splitterState.isValid()) { - chatWindow->handleChangeSplitterState(splitterState.toByteArray()); - } - - if (tabs_) { - tabs_->addTab(chatWindow); - } else { - QVariant chatGeometryVariant = qtOnlySettings_->getQSettings()->value(CHAT_TABS_GEOMETRY); - if (chatGeometryVariant.isValid()) { - chatWindow->restoreGeometry(chatGeometryVariant.toByteArray()); - } - connect(chatWindow, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged())); - } - return chatWindow; + if (!theme_) { + theme_ = new QtChatTheme(themePath_); + if (theme_->getIncomingContent().isEmpty()) { + delete theme_; + theme_ = new QtChatTheme(":/themes/Default/"); /* Use the inbuilt theme */ + } + } + + QtChatWindow* chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_, qtOnlySettings_, emoticonsMap_); + + connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved())); + connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray))); + + QVariant splitterState = qtOnlySettings_->getQSettings()->value(SPLITTER_STATE); + if (splitterState.isValid()) { + chatWindow->handleChangeSplitterState(splitterState.toByteArray()); + } + + if (tabs_) { + tabs_->addTab(chatWindow); + } else { + QVariant chatGeometryVariant = qtOnlySettings_->getQSettings()->value(CHAT_TABS_GEOMETRY); + if (chatGeometryVariant.isValid()) { + chatWindow->restoreGeometry(chatGeometryVariant.toByteArray()); + } + connect(chatWindow, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged())); + } + return chatWindow; } void QtChatWindowFactory::handleWindowGeometryChanged() { - qtOnlySettings_->getQSettings()->setValue(CHAT_TABS_GEOMETRY, qobject_cast<QWidget*>(sender())->saveGeometry()); + qtOnlySettings_->getQSettings()->setValue(CHAT_TABS_GEOMETRY, qobject_cast<QWidget*>(sender())->saveGeometry()); } void QtChatWindowFactory::handleSplitterMoved() { - QByteArray splitterState = qobject_cast<QtChatWindow*>(sender())->getSplitterState(); - qtOnlySettings_->getQSettings()->setValue(SPLITTER_STATE, QVariant(splitterState)); - emit changeSplitterState(splitterState); + QByteArray splitterState = qobject_cast<QtChatWindow*>(sender())->getSplitterState(); + qtOnlySettings_->getQSettings()->setValue(SPLITTER_STATE, QVariant(splitterState)); + emit changeSplitterState(splitterState); } } diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h index 63da514..3e4dca3 100644 --- a/Swift/QtUI/QtChatWindowFactory.h +++ b/Swift/QtUI/QtChatWindowFactory.h @@ -1,42 +1,51 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <map> +#include <string> +#include <QMenu> #include <QObject> #include <QSplitter> +#include <QVBoxLayout> #include <Swiften/JID/JID.h> + +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/QtEmojisSelector.h> #include <Swift/QtUI/QtSettingsProvider.h> namespace Swift { - class QtChatTabs; - class QtChatTheme; - class UIEventStream; - class QtUIPreferences; - class QtSingleWindow; - class QtChatWindowFactory : public QObject, public ChatWindowFactory { - Q_OBJECT - public: - QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath); - ~QtChatWindowFactory(); - ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); - signals: - void changeSplitterState(QByteArray); - private slots: - void handleWindowGeometryChanged(); - void handleSplitterMoved(); - private: - QString themePath_; - SettingsProvider* settings_; - QtSettingsProvider* qtOnlySettings_; - QtChatTabs* tabs_; - QtChatTheme* theme_; - }; + class QtChatTabs; + class QtChatTheme; + class UIEventStream; + class QtUIPreferences; + class QtSingleWindow; + class QtChatWindowFactory : public QObject, public ChatWindowFactory { + Q_OBJECT + public: + QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticonsMap); + ~QtChatWindowFactory(); + ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); + signals: + void changeSplitterState(QByteArray); + private slots: + void handleWindowGeometryChanged(); + void handleSplitterMoved(); + private: + QString themePath_; + SettingsProvider* settings_; + QtSettingsProvider* qtOnlySettings_; + QtChatTabs* tabs_; + QtChatTheme* theme_; + std::map<std::string, std::string> emoticonsMap_; + }; } - diff --git a/Swift/QtUI/QtChatWindowJSBridge.cpp b/Swift/QtUI/QtChatWindowJSBridge.cpp index db67d79..3030689 100644 --- a/Swift/QtUI/QtChatWindowJSBridge.cpp +++ b/Swift/QtUI/QtChatWindowJSBridge.cpp @@ -4,16 +4,22 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtChatWindowJSBridge.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtChatWindowJSBridge.h> namespace Swift { QtChatWindowJSBridge::QtChatWindowJSBridge() { - + } QtChatWindowJSBridge::~QtChatWindowJSBridge() { - + } } diff --git a/Swift/QtUI/QtChatWindowJSBridge.h b/Swift/QtUI/QtChatWindowJSBridge.h index 5a26302..553e929 100644 --- a/Swift/QtUI/QtChatWindowJSBridge.h +++ b/Swift/QtUI/QtChatWindowJSBridge.h @@ -4,23 +4,30 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#pragma once +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <QObject> +#pragma once #include <map> +#include <QObject> + namespace Swift { class FileTransferController; class QtChatWindowJSBridge : public QObject { - Q_OBJECT + Q_OBJECT public: - QtChatWindowJSBridge(); - virtual ~QtChatWindowJSBridge(); + QtChatWindowJSBridge(); + virtual ~QtChatWindowJSBridge(); signals: - void buttonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); + void buttonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); + void verticalScrollBarPositionChanged(double scrollbarPosition); }; } diff --git a/Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp b/Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp new file mode 100644 index 0000000..5f71ed4 --- /dev/null +++ b/Swift/QtUI/QtCheckBoxStyledItemDelegate.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtCheckBoxStyledItemDelegate.h> + +#include <array> + +#include <QApplication> +#include <QEvent> +#include <QPainter> + +namespace Swift { + +QtCheckBoxStyledItemDelegate::QtCheckBoxStyledItemDelegate(QObject* parent) : QStyledItemDelegate(parent) { + +} + +void QtCheckBoxStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + QStyleOptionButton cbOpt; + cbOpt.rect = option.rect; + cbOpt.state = QStyle::State_Active; + + auto copyFlags = std::array<QStyle::StateFlag, 2>({{QStyle::State_Enabled/*, QStyle::State_Sunken*/}}); + for (auto flag : copyFlags) { + if (option.state && flag) { + cbOpt.state |= flag; + } + } + + bool isChecked = index.data(DATA_ROLE).toBool(); + if (isChecked) { + cbOpt.state |= QStyle::State_On; + } + else { + cbOpt.state |= QStyle::State_Off; + } + painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base()); + QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter); +} + +bool QtCheckBoxStyledItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) { + if (event->type() == QEvent::MouseButtonRelease) { + model->setData(index, !index.data(DATA_ROLE).toBool(), DATA_ROLE); + } + return true; +} + +}; diff --git a/Swift/QtUI/QtCheckBoxStyledItemDelegate.h b/Swift/QtUI/QtCheckBoxStyledItemDelegate.h new file mode 100644 index 0000000..1d8db62 --- /dev/null +++ b/Swift/QtUI/QtCheckBoxStyledItemDelegate.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QStyledItemDelegate> + +namespace Swift { + +class QtCheckBoxStyledItemDelegate : public QStyledItemDelegate { + public: + QtCheckBoxStyledItemDelegate(QObject* parent = nullptr); + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const; + + public: + static const int DATA_ROLE = Qt::UserRole + 1; + + protected: + virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); +}; + +} diff --git a/Swift/QtUI/QtClickableLabel.cpp b/Swift/QtUI/QtClickableLabel.cpp index f040f5f..7ce3325 100644 --- a/Swift/QtUI/QtClickableLabel.cpp +++ b/Swift/QtUI/QtClickableLabel.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtClickableLabel.h" +#include <Swift/QtUI/QtClickableLabel.h> namespace Swift { @@ -12,7 +12,7 @@ QtClickableLabel::QtClickableLabel(QWidget* parent) : QLabel(parent) { } void QtClickableLabel::mousePressEvent(QMouseEvent*) { - emit clicked(); + emit clicked(); } } diff --git a/Swift/QtUI/QtClickableLabel.h b/Swift/QtUI/QtClickableLabel.h index d56e9b4..83ed3f1 100644 --- a/Swift/QtUI/QtClickableLabel.h +++ b/Swift/QtUI/QtClickableLabel.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,14 +9,14 @@ #include <QLabel> namespace Swift { - class QtClickableLabel : public QLabel { - Q_OBJECT - public: - QtClickableLabel(QWidget* parent); + class QtClickableLabel : public QLabel { + Q_OBJECT + public: + QtClickableLabel(QWidget* parent); - void mousePressEvent(QMouseEvent* event); + void mousePressEvent(QMouseEvent* event); - signals: - void clicked(); - }; + signals: + void clicked(); + }; } diff --git a/Swift/QtUI/QtClosableLineEdit.cpp b/Swift/QtUI/QtClosableLineEdit.cpp new file mode 100644 index 0000000..033db78 --- /dev/null +++ b/Swift/QtUI/QtClosableLineEdit.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014-2015 Isode Limited. + * All rights reserved. + * See the COPYING file 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 <QKeyEvent> +#include <QStyle> +#include <QToolButton> +#include <QtGlobal> + +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); + int verticalAdjustment = 1; +#if defined(Q_OS_WIN32) + // This vertical adjustment is required on Windows so the close button is vertically centered in the line edit. + verticalAdjustment += 2; +#endif + clearButton->move(rect().right() - frameWidth - size.width(), (rect().bottom() + verticalAdjustment - 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..0b195dd --- /dev/null +++ b/Swift/QtUI/QtClosableLineEdit.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file 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 = nullptr); + + 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/QtColorSelectionStyledItemDelegate.cpp b/Swift/QtUI/QtColorSelectionStyledItemDelegate.cpp new file mode 100644 index 0000000..c3fef5a --- /dev/null +++ b/Swift/QtUI/QtColorSelectionStyledItemDelegate.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtColorSelectionStyledItemDelegate.h> + +#include <QApplication> +#include <QColorDialog> +#include <QEvent> +#include <QPainter> +#include <QStyleOptionComboBox> + +namespace Swift { + +QtColorSelectionStyledItemDelegate::QtColorSelectionStyledItemDelegate(QObject* parent) : QStyledItemDelegate(parent) { + +} + +void QtColorSelectionStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + // draw item selected background + painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base()); + + // draw combo box + QStyleOptionComboBox opt; + opt.rect = option.rect; + opt.state = QStyle::State_Active | QStyle::State_Enabled; + QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &opt, painter); + + // draw currently selected color + auto contentRect = QApplication::style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField); + auto currentColor = index.data(DATA_ROLE).value<QColor>(); + painter->save(); + painter->setClipRect(contentRect); + painter->fillRect(contentRect.adjusted(1, -4, -1, -3), currentColor); + painter->restore(); +} + +bool QtColorSelectionStyledItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) { + if (event->type() == QEvent::MouseButtonRelease) { + auto currentColor = index.data(DATA_ROLE).value<QColor>(); + auto newColor = QColorDialog::getColor(currentColor); + if (newColor.isValid()) { + model->setData(index, newColor, DATA_ROLE); + } + + auto correspondingDialog = qobject_cast<QDialog*>(parent()); + if (correspondingDialog) { + correspondingDialog->raise(); + } + } + return true; +} + +}; diff --git a/Swift/QtUI/QtColorSelectionStyledItemDelegate.h b/Swift/QtUI/QtColorSelectionStyledItemDelegate.h new file mode 100644 index 0000000..d6b3336 --- /dev/null +++ b/Swift/QtUI/QtColorSelectionStyledItemDelegate.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QStyledItemDelegate> + +namespace Swift { + +class QtColorSelectionStyledItemDelegate : public QStyledItemDelegate { + public: + QtColorSelectionStyledItemDelegate(QObject* parent = nullptr); + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const; + + public: + static const int DATA_ROLE = Qt::UserRole + 1; + + protected: + virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); +}; + +} diff --git a/Swift/QtUI/QtColorToolButton.cpp b/Swift/QtUI/QtColorToolButton.cpp index 1d379a3..6452cf4 100644 --- a/Swift/QtUI/QtColorToolButton.cpp +++ b/Swift/QtUI/QtColorToolButton.cpp @@ -4,42 +4,49 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include <QColorDialog> -#include <QPainter> +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ #include <Swift/QtUI/QtColorToolButton.h> +#include <QColorDialog> +#include <QPainter> + namespace Swift { QtColorToolButton::QtColorToolButton(QWidget* parent) : - QToolButton(parent) + QToolButton(parent) { - connect(this, SIGNAL(clicked()), SLOT(onClicked())); - setColorIcon(Qt::transparent); + 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_); - } + 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); - } + QColor c = QColorDialog::getColor(color_, this); + window()->raise(); + if (c.isValid()) { + setColor(c); + } } void QtColorToolButton::setColorIcon(const QColor& color) { - QPixmap pix(iconSize()); - pix.fill(color.isValid() ? color : Qt::transparent); - setIcon(pix); + QPixmap pix(iconSize()); + pix.fill(color.isValid() ? color : Qt::transparent); + setIcon(pix); } } diff --git a/Swift/QtUI/QtColorToolButton.h b/Swift/QtUI/QtColorToolButton.h index 33d195d..babafc5 100644 --- a/Swift/QtUI/QtColorToolButton.h +++ b/Swift/QtUI/QtColorToolButton.h @@ -4,29 +4,35 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file 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_; } + class QtColorToolButton : public QToolButton { + Q_OBJECT + Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged) + public: + explicit QtColorToolButton(QWidget* parent = nullptr); + void setColor(const QColor& color); + const QColor& getColor() const { return color_; } - signals: - void colorChanged(const QColor&); + signals: + void colorChanged(const QColor&); - private slots: - void onClicked(); + private slots: + void onClicked(); - private: - void setColorIcon(const QColor& color); - QColor color_; - }; + private: + void setColorIcon(const QColor& color); + QColor color_; + }; } diff --git a/Swift/QtUI/QtConnectionSettings.ui b/Swift/QtUI/QtConnectionSettings.ui index 2dc46d1..cce60fe 100644 --- a/Swift/QtUI/QtConnectionSettings.ui +++ b/Swift/QtUI/QtConnectionSettings.ui @@ -136,6 +136,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="manual_forceTLS1_0"> + <property name="text"> + <string>Limit encryption to TLS 1.0</string> + </property> + </widget> + </item> + <item> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/Swift/QtUI/QtConnectionSettingsWindow.cpp b/Swift/QtUI/QtConnectionSettingsWindow.cpp index 737a79c..a58bc5c 100644 --- a/Swift/QtUI/QtConnectionSettingsWindow.cpp +++ b/Swift/QtUI/QtConnectionSettingsWindow.cpp @@ -1,23 +1,23 @@ /* - * Copyright (c) 2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/QtConnectionSettingsWindow.h" +#include <Swift/QtUI/QtConnectionSettingsWindow.h> #include <boost/lexical_cast.hpp> #include <QCoreApplication> +#include <QFile> #include <QIcon> #include <QLabel> -#include <QVBoxLayout> -#include <QtGlobal> +#include <QMessageBox> #include <QPushButton> #include <QTextEdit> -#include <QFile> #include <QTextStream> -#include <QMessageBox> +#include <QVBoxLayout> +#include <QtGlobal> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtURLValidator.h> @@ -25,141 +25,147 @@ 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()))); - } - } - } + 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; + isDefault &= options.tlsOptions.schannelTLS1_0Workaround == defaults.tlsOptions.schannelTLS1_0Workaround; + 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))); + } + ui.manual_forceTLS1_0->setChecked(options.tlsOptions.schannelTLS1_0Workaround); + } + } 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()))); + } + } + } +#ifndef HAVE_SCHANNEL + ui.manual_forceTLS1_0->hide(); +#endif } 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); + 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.")); - } + 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; + 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(); + options.tlsOptions.schannelTLS1_0Workaround = ui.manual_forceTLS1_0->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 index 3b017ab..f9fad84 100644 --- a/Swift/QtUI/QtConnectionSettingsWindow.h +++ b/Swift/QtUI/QtConnectionSettingsWindow.h @@ -1,37 +1,37 @@ /* - * Copyright (c) 2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QDialog> -#include "ui_QtConnectionSettings.h" - #include <Swiften/Client/ClientOptions.h> +#include <Swift/QtUI/ui_QtConnectionSettings.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; - }; + 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 a347a00..17f5ccf 100644 --- a/Swift/QtUI/QtContactEditWidget.cpp +++ b/Swift/QtUI/QtContactEditWidget.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtContactEditWidget.h" +#include <Swift/QtUI/QtContactEditWidget.h> #include <algorithm> @@ -13,154 +13,163 @@ #include <QLabel> #include <QLineEdit> #include <QMovie> -#include <QScrollArea> #include <QRadioButton> +#include <QScrollArea> -#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtContactEditWidget::QtContactEditWidget(const std::set<std::string>& allGroups, QWidget* parent) : QWidget(parent), nameRadioButton_(NULL), groups_(NULL) { - QBoxLayout* layout = new QVBoxLayout(this); - setContentsMargins(0,0,0,0); - layout->setContentsMargins(0,0,0,0); - - nameLayout_ = new QHBoxLayout(); - suggestionsLayout_ = new QHBoxLayout(); - nameLayout_->addLayout(suggestionsLayout_); - - name_ = new QLineEdit(this); - nameLayout_->addWidget(name_); - - throbberLabel_ = new QLabel(this); - throbberLabel_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); - throbberLabel_->movie()->start(); - nameLayout_->addWidget(throbberLabel_); - - layout->addLayout(nameLayout_); - - layout->addWidget(new QLabel(tr("Groups:"), this)); - - QScrollArea* groupsArea = new QScrollArea(this); - layout->addWidget(groupsArea); - groupsArea->setWidgetResizable(true); - groupsArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - groupsArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - - QWidget* groups = new QWidget(groupsArea); - groupsArea->setWidget(groups); - QVBoxLayout* scrollLayout = new QVBoxLayout(groups); - - foreach (std::string group, allGroups) { - QString groupName = doubleAmpersand(group); - QCheckBox* check = new QCheckBox(groups); - check->setText(groupName); - check->setCheckState(Qt::Unchecked); - checkBoxes_[group] = check; - scrollLayout->addWidget(check); - } - - QHBoxLayout* newGroupLayout = new QHBoxLayout(); - newGroup_ = new QCheckBox(groups); - newGroup_->setText(tr("New Group:")); - newGroup_->setCheckState(Qt::Unchecked); - newGroupLayout->addWidget(newGroup_); - newGroupName_ = new QLineEdit(groups); - newGroupLayout->addWidget(newGroupName_); - scrollLayout->addLayout(newGroupLayout); - - scrollLayout->addItem(new QSpacerItem(20, 73, QSizePolicy::Minimum, QSizePolicy::Expanding)); +QtContactEditWidget::QtContactEditWidget(const std::set<std::string>& allGroups, QWidget* parent) : QWidget(parent), nameRadioButton_(nullptr), groups_(nullptr) { + QBoxLayout* layout = new QVBoxLayout(this); + setContentsMargins(0,0,0,0); + layout->setContentsMargins(0,0,0,0); + + nameLayout_ = new QHBoxLayout(); + suggestionsLayout_ = new QHBoxLayout(); + nameLayout_->addLayout(suggestionsLayout_); + + name_ = new QLineEdit(this); + nameLayout_->addWidget(name_); + + throbberLabel_ = new QLabel(this); + throbberLabel_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); + throbberLabel_->movie()->start(); + nameLayout_->addWidget(throbberLabel_); + + layout->addLayout(nameLayout_); + + layout->addWidget(new QLabel(tr("Groups:"), this)); + + QScrollArea* groupsArea = new QScrollArea(this); + layout->addWidget(groupsArea); + groupsArea->setWidgetResizable(true); + groupsArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + groupsArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + QWidget* groups = new QWidget(groupsArea); + groupsArea->setWidget(groups); + QVBoxLayout* scrollLayout = new QVBoxLayout(groups); + + for (const auto& group : allGroups) { + QString groupName = doubleAmpersand(group); + QCheckBox* check = new QCheckBox(groups); + check->setText(groupName); + check->setCheckState(Qt::Unchecked); + checkBoxes_[group] = check; + scrollLayout->addWidget(check); + } + + QHBoxLayout* newGroupLayout = new QHBoxLayout(); + newGroup_ = new QCheckBox(groups); + newGroup_->setText(tr("New Group:")); + newGroup_->setCheckState(Qt::Unchecked); + newGroupLayout->addWidget(newGroup_); + newGroupName_ = new QLineEdit(groups); + newGroupLayout->addWidget(newGroupName_); + scrollLayout->addLayout(newGroupLayout); + + scrollLayout->addItem(new QSpacerItem(20, 73, QSizePolicy::Minimum, QSizePolicy::Expanding)); } void QtContactEditWidget::setName(const std::string& name) { - name_->setText(P2QSTRING(name)); + name_->setText(P2QSTRING(name)); } std::string QtContactEditWidget::getName() const { - std::string name = Q2PSTRING(name_->text()); - QList<QRadioButton*> buttons = findChildren<QRadioButton*>(); - foreach(const QRadioButton* button, buttons) { - if (button->isChecked()) { - if (button == nameRadioButton_) { - name = Q2PSTRING(name_->text()); - } else { - name = singleAmpersand(button->text()); - } - break; - } - } - return name; + std::string name = Q2PSTRING(name_->text()); + QList<QRadioButton*> buttons = findChildren<QRadioButton*>(); + for (const auto button : buttons) { + if (button->isChecked()) { + if (button == nameRadioButton_) { + name = Q2PSTRING(name_->text()); + } else { + name = singleAmpersand(button->text()); + } + break; + } + } + return name; } void QtContactEditWidget::setSelectedGroups(const std::vector<std::string>& groups) { - foreach (std::string group, groups) { - checkBoxes_[group]->setCheckState(Qt::Checked); - } + for (auto&& group : groups) { + checkBoxes_[group]->setCheckState(Qt::Checked); + } } std::set<std::string> QtContactEditWidget::getSelectedGroups() const { - std::set<std::string> groups; - foreach(const CheckBoxMap::value_type& group, checkBoxes_) { - if (group.second->checkState() == Qt::Checked) { - groups.insert(group.first); - } - } - if (newGroup_->checkState() == Qt::Checked && !newGroupName_->text().isEmpty()) { - groups.insert(Q2PSTRING(newGroupName_->text())); - } - return groups; + std::set<std::string> groups; + for (const auto& group : checkBoxes_) { + if (group.second->checkState() == Qt::Checked) { + groups.insert(group.first); + } + } + if (newGroup_->checkState() == Qt::Checked && !newGroupName_->text().isEmpty()) { + groups.insert(Q2PSTRING(newGroupName_->text())); + } + return groups; } void QtContactEditWidget::setNameSuggestions(const std::vector<std::string>& suggestions) { - throbberLabel_->movie()->stop(); - throbberLabel_->hide(); - - foreach(const std::string& name, suggestions) { - suggestionsLayout_->insertWidget(nameLayout_->count() - 2, new QRadioButton(doubleAmpersand(name), this)); - } - - nameRadioButton_ = new QRadioButton(tr("Name:"), this); - suggestionsLayout_->insertWidget(nameLayout_->count(), nameRadioButton_); - - QRadioButton* suggestedRadioButton = 0; - QList<QRadioButton*> radioButtons = findChildren<QRadioButton*>(); - foreach (QRadioButton* candidate, radioButtons) { - if (candidate->text() == name_->text()) { - suggestedRadioButton = candidate; - break; - } - } - if (suggestedRadioButton) { - suggestedRadioButton->setChecked(true); - } else { - nameRadioButton_->setChecked(true); - } + throbberLabel_->movie()->stop(); + throbberLabel_->hide(); + + // remove old suggestions except for the user input text field + QLayoutItem* suggestionItem = nullptr; + while ((suggestionItem = suggestionsLayout_->itemAt(0)) && suggestionItem->widget() != name_) { + QWidget* suggestionWidget = suggestionItem->widget(); + suggestionsLayout_->removeWidget(suggestionWidget); + delete suggestionWidget; + } + + // populate new suggestions + for (const auto& name : suggestions) { + suggestionsLayout_->insertWidget(nameLayout_->count() - 2, new QRadioButton(doubleAmpersand(name), this)); + } + + nameRadioButton_ = new QRadioButton(tr("Name:"), this); + suggestionsLayout_->insertWidget(nameLayout_->count(), nameRadioButton_); + + QRadioButton* suggestedRadioButton = nullptr; + QList<QRadioButton*> radioButtons = findChildren<QRadioButton*>(); + for (auto candidate : radioButtons) { + if (candidate->text() == name_->text()) { + suggestedRadioButton = candidate; + break; + } + } + if (suggestedRadioButton) { + suggestedRadioButton->setChecked(true); + } else { + nameRadioButton_->setChecked(true); + } } QString QtContactEditWidget::doubleAmpersand(const std::string& name) const { - return P2QSTRING(name).replace("&", "&&"); + return P2QSTRING(name).replace("&", "&&"); } std::string QtContactEditWidget::singleAmpersand(const QString& name) const { - return Q2PSTRING(QString(name).replace("&&", "&")); + return Q2PSTRING(QString(name).replace("&&", "&")); } void QtContactEditWidget::clear() { - name_->clear(); - setSelectedGroups(std::vector<std::string>()); - newGroup_->setChecked(false); - newGroupName_->clear(); - throbberLabel_->movie()->start(); - throbberLabel_->show(); - - // clear suggestions - while(suggestionsLayout_->count() != 0) { - QLayoutItem *layoutItem = suggestionsLayout_->takeAt(0); - delete layoutItem->widget(); - delete layoutItem; - } - nameRadioButton_ = NULL; + name_->clear(); + setSelectedGroups(std::vector<std::string>()); + newGroup_->setChecked(false); + newGroupName_->clear(); + throbberLabel_->movie()->start(); + throbberLabel_->show(); + + // clear suggestions + while(suggestionsLayout_->count() != 0) { + QLayoutItem *layoutItem = suggestionsLayout_->takeAt(0); + delete layoutItem->widget(); + delete layoutItem; + } + nameRadioButton_ = nullptr; } } diff --git a/Swift/QtUI/QtContactEditWidget.h b/Swift/QtUI/QtContactEditWidget.h index 6d55b80..0718fee 100644 --- a/Swift/QtUI/QtContactEditWidget.h +++ b/Swift/QtUI/QtContactEditWidget.h @@ -1,20 +1,19 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <map> +#include <memory> #include <set> -#include <boost/shared_ptr.hpp> +#include <string> +#include <vector> #include <QWidget> -#include <vector> -#include <string> - class QLabel; class QLineEdit; class QCheckBox; @@ -22,37 +21,37 @@ class QHBoxLayout; class QRadioButton; namespace Swift { - class QtContactEditWidget : public QWidget { - Q_OBJECT - - public: - QtContactEditWidget(const std::set<std::string>& allGroups, QWidget* parent); - - void setName(const std::string&); - std::string getName() const; - - void setSelectedGroups(const std::vector<std::string>& groups); - std::set<std::string> getSelectedGroups() const; - - void setNameSuggestions(const std::vector<std::string>& suggestions); - - 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; - CheckBoxMap checkBoxes_; - QHBoxLayout* nameLayout_; - QHBoxLayout* suggestionsLayout_; - QRadioButton* nameRadioButton_; - QLineEdit* name_; - QWidget* groups_; - QCheckBox* newGroup_; - QLineEdit* newGroupName_; - QLabel* throbberLabel_; - }; + class QtContactEditWidget : public QWidget { + Q_OBJECT + + public: + QtContactEditWidget(const std::set<std::string>& allGroups, QWidget* parent); + + void setName(const std::string&); + std::string getName() const; + + void setSelectedGroups(const std::vector<std::string>& groups); + std::set<std::string> getSelectedGroups() const; + + void setNameSuggestions(const std::vector<std::string>& suggestions); + + void clear(); + + + private: + QString doubleAmpersand(const std::string& name) const; + std::string singleAmpersand(const QString& name) const; + + private: + std::map<std::string, QCheckBox*> checkBoxes_; + QHBoxLayout* nameLayout_; + QHBoxLayout* suggestionsLayout_; + QRadioButton* nameRadioButton_; + QLineEdit* name_; + QWidget* groups_; + QCheckBox* newGroup_; + QLineEdit* newGroupName_; + QLabel* throbberLabel_; + }; } diff --git a/Swift/QtUI/QtContactEditWindow.cpp b/Swift/QtUI/QtContactEditWindow.cpp index 6520c0a..138a356 100644 --- a/Swift/QtUI/QtContactEditWindow.cpp +++ b/Swift/QtUI/QtContactEditWindow.cpp @@ -1,106 +1,107 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtContactEditWindow.h" +#include <Swift/QtUI/QtContactEditWindow.h> #include <algorithm> #include <boost/bind.hpp> -#include <QScrollArea> #include <QBoxLayout> -#include <QLabel> #include <QCheckBox> +#include <QLabel> #include <QLineEdit> #include <QMessageBox> #include <QPushButton> +#include <QScrollArea> -#include "Swift/QtUI/QtSwiftUtil.h" -#include "QtContactEditWidget.h" +#include <Swift/QtUI/QtContactEditWidget.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtContactEditWindow::QtContactEditWindow() : contactEditWidget_(NULL) { - resize(400,300); - setWindowTitle(tr("Edit contact")); - setContentsMargins(0,0,0,0); - - QBoxLayout* layout = new QVBoxLayout(this); - - jidLabel_ = new QLabel(this); - jidLabel_->setAlignment(Qt::AlignHCenter); - layout->addWidget(jidLabel_); - - groupsLayout_ = new QVBoxLayout(); - groupsLayout_->setContentsMargins(0,0,0,0); - layout->addLayout(groupsLayout_); - - QHBoxLayout* buttonLayout = new QHBoxLayout(); - layout->addLayout(buttonLayout); - QPushButton* removeButton = new QPushButton(tr("Remove contact"), this); - connect(removeButton, SIGNAL(clicked()), this, SLOT(handleRemoveContact())); - buttonLayout->addWidget(removeButton); - QPushButton* okButton = new QPushButton(tr("OK"), this); - okButton->setDefault( true ); - connect(okButton, SIGNAL(clicked()), this, SLOT(handleUpdateContact())); - buttonLayout->addStretch(); - buttonLayout->addWidget(okButton); +QtContactEditWindow::QtContactEditWindow() : contactEditWidget_(nullptr) { + resize(400,300); + setWindowTitle(tr("Edit contact")); + setContentsMargins(0,0,0,0); + + QBoxLayout* layout = new QVBoxLayout(this); + + jidLabel_ = new QLabel(this); + jidLabel_->setAlignment(Qt::AlignHCenter); + layout->addWidget(jidLabel_); + + groupsLayout_ = new QVBoxLayout(); + groupsLayout_->setContentsMargins(0,0,0,0); + layout->addLayout(groupsLayout_); + + QHBoxLayout* buttonLayout = new QHBoxLayout(); + layout->addLayout(buttonLayout); + QPushButton* removeButton = new QPushButton(tr("Remove contact"), this); + connect(removeButton, SIGNAL(clicked()), this, SLOT(handleRemoveContact())); + buttonLayout->addWidget(removeButton); + QPushButton* okButton = new QPushButton(tr("OK"), this); + okButton->setDefault( true ); + connect(okButton, SIGNAL(clicked()), this, SLOT(handleUpdateContact())); + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); } QtContactEditWindow::~QtContactEditWindow() { } void QtContactEditWindow::setNameSuggestions(const std::vector<std::string>& nameSuggestions) { - if (contactEditWidget_) { - contactEditWidget_->setNameSuggestions(nameSuggestions); - } + if (contactEditWidget_) { + contactEditWidget_->setNameSuggestions(nameSuggestions); + } } void QtContactEditWindow::setContact(const JID& jid, const std::string& name, const std::vector<std::string>& groups, const std::set<std::string>& allGroups) { - delete contactEditWidget_; - jid_ = jid; - jidLabel_->setText("<b>" + P2QSTRING(jid.toString()) + "</b>"); - - contactEditWidget_ = new QtContactEditWidget(allGroups, this); - groupsLayout_->addWidget(contactEditWidget_); - contactEditWidget_->setName(name); - contactEditWidget_->setSelectedGroups(groups); + delete contactEditWidget_; + jid_ = jid; + jidLabel_->setText("<b>" + P2QSTRING(jid.toString()) + "</b>"); + + contactEditWidget_ = new QtContactEditWidget(allGroups, this); + groupsLayout_->addWidget(contactEditWidget_); + contactEditWidget_->setName(name); + contactEditWidget_->setSelectedGroups(groups); } void QtContactEditWindow::setEnabled(bool b) { - QWidget::setEnabled(b); + QWidget::setEnabled(b); } void QtContactEditWindow::show() { - QWidget::show(); - QWidget::activateWindow(); + QWidget::showNormal(); + QWidget::activateWindow(); + QWidget::raise(); } void QtContactEditWindow::hide() { - QWidget::hide(); + QWidget::hide(); } void QtContactEditWindow::handleRemoveContact() { - if (confirmContactDeletion(jid_)) { - onRemoveContactRequest(); - } + if (confirmContactDeletion(jid_)) { + onRemoveContactRequest(); + } } bool QtContactEditWindow::confirmContactDeletion(const JID& jid) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Confirm contact deletion")); - msgBox.setText(tr("Are you sure you want to delete this contact?")); - msgBox.setInformativeText(QString(tr("This will remove the contact '%1' from all groups they may be in.")).arg(P2QSTRING(jid.toString()))); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::Yes); - return msgBox.exec() == QMessageBox::Yes; + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Confirm contact deletion")); + msgBox.setText(tr("Are you sure you want to delete this contact?")); + msgBox.setInformativeText(QString(tr("This will remove the contact '%1' from all groups they may be in.")).arg(P2QSTRING(jid.toString()))); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + return msgBox.exec() == QMessageBox::Yes; } void QtContactEditWindow::handleUpdateContact() { - onChangeContactRequest(contactEditWidget_->getName(), contactEditWidget_->getSelectedGroups()); + onChangeContactRequest(contactEditWidget_->getName(), contactEditWidget_->getSelectedGroups()); } } diff --git a/Swift/QtUI/QtContactEditWindow.h b/Swift/QtUI/QtContactEditWindow.h index 5392b10..655fbb4 100644 --- a/Swift/QtUI/QtContactEditWindow.h +++ b/Swift/QtUI/QtContactEditWindow.h @@ -1,49 +1,51 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <string> + #include <QWidget> -#include <Swift/Controllers/UIInterfaces/ContactEditWindow.h> -#include <string> -#include <Swiften/JID/JID.h> #include <Swiften/Elements/VCard.h> +#include <Swiften/JID/JID.h> + +#include <Swift/Controllers/UIInterfaces/ContactEditWindow.h> class QLabel; class QVBoxLayout; namespace Swift { - class QtContactEditWidget; + class QtContactEditWidget; - class QtContactEditWindow : public QWidget, public ContactEditWindow { - Q_OBJECT + class QtContactEditWindow : public QWidget, public ContactEditWindow { + Q_OBJECT - public: - QtContactEditWindow(); - virtual ~QtContactEditWindow(); + public: + QtContactEditWindow(); + virtual ~QtContactEditWindow(); - virtual void setNameSuggestions(const std::vector<std::string>& nameSuggestions); - virtual void setContact(const JID& jid, const std::string& name, const std::vector<std::string>& groups, const std::set<std::string>& allGroups); + virtual void setNameSuggestions(const std::vector<std::string>& nameSuggestions); + virtual void setContact(const JID& jid, const std::string& name, const std::vector<std::string>& groups, const std::set<std::string>& allGroups); - void setEnabled(bool); - void show(); - void hide(); + void setEnabled(bool); + void show(); + void hide(); - static bool confirmContactDeletion(const JID& jid); + static bool confirmContactDeletion(const JID& jid); - private slots: - void handleRemoveContact(); - void handleUpdateContact(); + private slots: + void handleRemoveContact(); + void handleUpdateContact(); - private: - JID jid_; - QVBoxLayout* groupsLayout_; - QLabel* jidLabel_; - QtContactEditWidget* contactEditWidget_; - }; + private: + JID jid_; + QVBoxLayout* groupsLayout_; + QLabel* jidLabel_; + QtContactEditWidget* contactEditWidget_; + }; } diff --git a/Swift/QtUI/QtDBUSURIHandler.cpp b/Swift/QtUI/QtDBUSURIHandler.cpp index 9b69ca6..a1446c3 100644 --- a/Swift/QtUI/QtDBUSURIHandler.cpp +++ b/Swift/QtUI/QtDBUSURIHandler.cpp @@ -1,41 +1,46 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtDBUSURIHandler.h" +#include <Swift/QtUI/QtDBUSURIHandler.h> #include <QDBusAbstractAdaptor> #include <QDBusConnection> -#include "QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> using namespace Swift; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-member-function" + namespace { - class DBUSAdaptor: public QDBusAbstractAdaptor { - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler"); - public: - DBUSAdaptor(QtDBUSURIHandler* uriHandler) : QDBusAbstractAdaptor(uriHandler), uriHandler(uriHandler) { - } - - public slots: - void openURI(const QString& uri) { - uriHandler->onURI(Q2PSTRING(uri)); - } - - private: - QtDBUSURIHandler* uriHandler; - }; + class DBUSAdaptor: public QDBusAbstractAdaptor { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler") + public: + DBUSAdaptor(QtDBUSURIHandler* uriHandler) : QDBusAbstractAdaptor(uriHandler), uriHandler(uriHandler) { + } + + public slots: + void openURI(const QString& uri) { + uriHandler->onURI(Q2PSTRING(uri)); + } + + private: + QtDBUSURIHandler* uriHandler; + }; } QtDBUSURIHandler::QtDBUSURIHandler() { - new DBUSAdaptor(this); - QDBusConnection connection = QDBusConnection::sessionBus(); - connection.registerService("im.swift.Swift.URIHandler"); - connection.registerObject("/", this); + new DBUSAdaptor(this); + QDBusConnection connection = QDBusConnection::sessionBus(); + connection.registerService("im.swift.Swift.URIHandler"); + connection.registerObject("/", this); } +#pragma clang diagnostic pop + #include "QtDBUSURIHandler.moc" diff --git a/Swift/QtUI/QtDBUSURIHandler.h b/Swift/QtUI/QtDBUSURIHandler.h index be1872e..3cd12f7 100644 --- a/Swift/QtUI/QtDBUSURIHandler.h +++ b/Swift/QtUI/QtDBUSURIHandler.h @@ -1,17 +1,18 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QObject> + #include <SwifTools/URIHandler/URIHandler.h> namespace Swift { - class QtDBUSURIHandler : public QObject, public URIHandler { - public: - QtDBUSURIHandler(); - }; + class QtDBUSURIHandler : public QObject, public URIHandler { + public: + QtDBUSURIHandler(); + }; } diff --git a/Swift/QtUI/QtEditBookmarkWindow.cpp b/Swift/QtUI/QtEditBookmarkWindow.cpp index 1d126cd..c724c97 100644 --- a/Swift/QtUI/QtEditBookmarkWindow.cpp +++ b/Swift/QtUI/QtEditBookmarkWindow.cpp @@ -1,29 +1,28 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtEditBookmarkWindow.h" +#include <Swift/QtUI/QtEditBookmarkWindow.h> -#include "QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtEditBookmarkWindow::QtEditBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark) : eventStream_(eventStream), bookmark_(bookmark) { - name_->setText(P2QSTRING(bookmark.getName())); - room_->setText(P2QSTRING(bookmark.getRoom().toString())); - autojoin_->setChecked(bookmark.getAutojoin()); - nick_->setText(bookmark.getNick() ? P2QSTRING(bookmark.getNick().get()) : ""); - password_->setText(bookmark.getPassword() ? P2QSTRING(bookmark.getPassword().get()) : ""); + name_->setText(P2QSTRING(bookmark.getName())); + room_->setText(P2QSTRING(bookmark.getRoom().toString())); + nick_->setText(bookmark.getNick() ? P2QSTRING(bookmark.getNick().get()) : ""); + password_->setText(bookmark.getPassword() ? P2QSTRING(bookmark.getPassword().get()) : ""); } bool QtEditBookmarkWindow::commit() { - boost::optional<MUCBookmark> bookmark = createBookmarkFromForm(); - if (!bookmark) { - return false; - } - eventStream_->send(boost::shared_ptr<UIEvent>(new EditMUCBookmarkUIEvent(bookmark_, *bookmark))); - return true; + boost::optional<MUCBookmark> bookmark = createBookmarkFromForm(); + if (!bookmark) { + return false; + } + eventStream_->send(std::make_shared<EditMUCBookmarkUIEvent>(bookmark_, *bookmark)); + return true; } } diff --git a/Swift/QtUI/QtEditBookmarkWindow.h b/Swift/QtUI/QtEditBookmarkWindow.h index 537a7a7..337ba1f 100644 --- a/Swift/QtUI/QtEditBookmarkWindow.h +++ b/Swift/QtUI/QtEditBookmarkWindow.h @@ -1,24 +1,25 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "QtBookmarkDetailWindow.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h" +#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <Swift/QtUI/QtBookmarkDetailWindow.h> namespace Swift { - class QtEditBookmarkWindow : public QtBookmarkDetailWindow { - Q_OBJECT - public: - QtEditBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark); - bool commit(); - - private: - UIEventStream* eventStream_; - MUCBookmark bookmark_; - }; + class QtEditBookmarkWindow : public QtBookmarkDetailWindow { + Q_OBJECT + public: + QtEditBookmarkWindow(UIEventStream* eventStream, const MUCBookmark& bookmark); + bool commit(); + + private: + UIEventStream* eventStream_; + MUCBookmark bookmark_; + }; } diff --git a/Swift/QtUI/QtElidingLabel.cpp b/Swift/QtUI/QtElidingLabel.cpp index 4a1c37d..4707f9f 100644 --- a/Swift/QtUI/QtElidingLabel.cpp +++ b/Swift/QtUI/QtElidingLabel.cpp @@ -1,55 +1,60 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/QtElidingLabel.h" +#include <Swift/QtUI/QtElidingLabel.h> namespace Swift { QtElidingLabel::QtElidingLabel(QWidget* parent, Qt::WindowFlags f) : QLabel(parent, f) { - fullText_ = ""; - dirty_ = true; - setSizes(); - setTextFormat(Qt::PlainText); + fullText_ = ""; + dirty_ = true; + setSizes(); + setTextFormat(Qt::PlainText); } QtElidingLabel::QtElidingLabel(const QString& text, QWidget* parent, Qt::WindowFlags f) : QLabel(text, parent, f) { - fullText_ = text; - dirty_ = true; - setSizes(); - setTextFormat(Qt::PlainText); + fullText_ = text; + dirty_ = true; + setSizes(); + setTextFormat(Qt::PlainText); } QtElidingLabel::~QtElidingLabel() { } +QSize QtElidingLabel::sizeHint() const { + return sizeHint_; +} + void QtElidingLabel::setSizes() { - setMinimumSize(1, minimumHeight()); + setMinimumSize(1, minimumHeight()); } void QtElidingLabel::setText(const QString& text) { - fullText_ = text; - QLabel::setText(text); - dirty_ = true; + fullText_ = text; + QLabel::setText(text); + sizeHint_ = QLabel::sizeHint(); + dirty_ = true; } void QtElidingLabel::paintEvent(QPaintEvent* event) { - QRect rect = contentsRect(); - dirty_ = dirty_ || rect != lastRect_; - if (dirty_) { - lastRect_ = rect; - int fontWidth = fontMetrics().width(fullText_); - if (fontWidth > rect.width()) { - QString elidedText(fontMetrics().elidedText(fullText_, Qt::ElideRight, rect.width(), Qt::TextShowMnemonic)); - QLabel::setText(elidedText); - } else { - QLabel::setText(fullText_); - } - dirty_ = false; - } - QLabel::paintEvent(event); + QRect rect = contentsRect(); + dirty_ = dirty_ || rect != lastRect_; + if (dirty_) { + lastRect_ = rect; + int fontWidth = fontMetrics().width(fullText_); + if (fontWidth > rect.width()) { + QString elidedText(fontMetrics().elidedText(fullText_, Qt::ElideRight, rect.width(), Qt::TextShowMnemonic)); + QLabel::setText(elidedText); + } else { + QLabel::setText(fullText_); + } + dirty_ = false; + } + QLabel::paintEvent(event); } } diff --git a/Swift/QtUI/QtElidingLabel.h b/Swift/QtUI/QtElidingLabel.h index 0bf2231..e10c39c 100644 --- a/Swift/QtUI/QtElidingLabel.h +++ b/Swift/QtUI/QtElidingLabel.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,20 +9,23 @@ #include <QLabel> namespace Swift { - class QtElidingLabel : public QLabel { - Q_OBJECT - public: - QtElidingLabel(QWidget* parent = NULL, Qt::WindowFlags f = 0); - QtElidingLabel(const QString &text, QWidget* parent = NULL, Qt::WindowFlags f = 0); - void setText(const QString& text); - virtual ~QtElidingLabel(); - - virtual void paintEvent(QPaintEvent* event); + class QtElidingLabel : public QLabel { + Q_OBJECT + public: + QtElidingLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::Widget); + QtElidingLabel(const QString &text, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::Widget); + void setText(const QString& text); + virtual ~QtElidingLabel(); - private: - void setSizes(); - bool dirty_; - QString fullText_; - QRect lastRect_; - }; + virtual QSize sizeHint() const; + + virtual void paintEvent(QPaintEvent* event); + + private: + void setSizes(); + bool dirty_; + QString fullText_; + QRect lastRect_; + QSize sizeHint_; + }; } diff --git a/Swift/QtUI/QtEmojiCell.cpp b/Swift/QtUI/QtEmojiCell.cpp new file mode 100644 index 0000000..106e968 --- /dev/null +++ b/Swift/QtUI/QtEmojiCell.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2016-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtEmojiCell.h> + +#include <Swiften/Base/Platform.h> + +#include <QFont> +#include <QFontMetrics> +#include <QPushButton> +#include <QString> + +#include <SwifTools/EmojiMapper.h> + +namespace Swift { + QtEmojiCell::QtEmojiCell(QString shortname, QString text, QWidget* parent) : QPushButton(parent) { + setText(text); + QFont font = this->font(); +#ifdef SWIFTEN_PLATFORM_WINDOWS + //Windows emoji font miscalculates the bounding rectangular that surrounds the emoji. We set a multiplier value to make it look consistent with linux & Mac + const float sizeMultiplier = 1.3; + font.setPointSize(18); +#else + font.setPointSize(22); + const float sizeMultiplier = 1; +#endif + font.setBold(true); + setFont(font); + + const auto boundingRect = fontMetrics().boundingRect("\xF0\x9F\x98\x83"); + setFixedWidth(qMax(sizeMultiplier*boundingRect.width(), sizeMultiplier*boundingRect.height())); + setFixedHeight(qMax(sizeMultiplier*boundingRect.width(), sizeMultiplier*boundingRect.height())); + + setFlat(true); + setToolTip(shortname); + connect(this, SIGNAL(clicked()), this, SLOT(handleEmojiClicked())); + } + + QtEmojiCell::QtEmojiCell(QIcon icon, QString text, QWidget* parent) : QPushButton(parent), text_(text) { + setIcon(icon); + setFlat(true); + connect(this, SIGNAL(clicked()), this, SLOT(handleEmojiClicked())); + setFixedSize(icon.availableSizes()[0] * 1.5); + } + + QtEmojiCell::QtEmojiCell(const QtEmojiCell& b) : QtEmojiCell(b.toolTip(), b.text()) { + + } + + QtEmojiCell::~QtEmojiCell() { + } + + void QtEmojiCell::handleEmojiClicked () { + if (text_.isEmpty()) { + emit emojiClicked(text()); + } + else { + emit emojiClicked(text_); + } + } + +} diff --git a/Swift/QtUI/QtEmojiCell.h b/Swift/QtUI/QtEmojiCell.h new file mode 100644 index 0000000..250b10d --- /dev/null +++ b/Swift/QtUI/QtEmojiCell.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QPushButton> +#include <QString> + +#include <SwifTools/EmojiMapper.h> + +class QWidget; +class QMouseEvent; + +namespace Swift { + class QtEmojiCell : public QPushButton { + Q_OBJECT + public: + QtEmojiCell(const QtEmojiCell& b); + QtEmojiCell(QString shortname, QString text, QWidget* parent = nullptr); + QtEmojiCell(QIcon icon, QString text, QWidget* parent = nullptr); + ~QtEmojiCell(); + + signals: + void emojiClicked(QString emojiAsText); + + private slots: + void handleEmojiClicked(); + + private: + QString text_; + }; +} diff --git a/Swift/QtUI/QtEmojisGrid.cpp b/Swift/QtUI/QtEmojisGrid.cpp new file mode 100644 index 0000000..981dd67 --- /dev/null +++ b/Swift/QtUI/QtEmojisGrid.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtEmojisGrid.h> + +#include <string> + +#include <QString> +#include <QVector> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/QtEmojiCell.h> +#include <Swift/QtUI/QtRecentEmojisGrid.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + static const int emojiCellSpacing = 2; + + QtEmojisGrid::QtEmojisGrid() : FlowLayout(0, emojiCellSpacing, emojiCellSpacing) { + + } + + QtEmojisGrid::QtEmojisGrid(QString categoryName) : FlowLayout(0, emojiCellSpacing, emojiCellSpacing) { + auto category = EmojiMapper::categoryNameToEmojis(Q2PSTRING(categoryName)); + + QVector<QString> categoryEmojis; + + for (const auto& emoji : category) { + categoryEmojis.push_back(P2QSTRING(emoji)); + } + + setEmojis(categoryEmojis); + } + + void QtEmojisGrid::setEmojis(const QVector<QString>& emojis) { + clearEmojis(); + + for (const auto& unicodeEmoji : emojis) { + QString shortname = QString::fromStdString(EmojiMapper::unicodeToShortname(Q2PSTRING(unicodeEmoji))); + auto emoji = new QtEmojiCell(shortname, unicodeEmoji); + connect(emoji, SIGNAL(emojiClicked(QString)), this, SIGNAL(onEmojiSelected(QString))); + addItem(new QWidgetItem(emoji)); + } + } + + void QtEmojisGrid::addEmoticon(QIcon icon, QString text) { + auto emoji = new QtEmojiCell(icon, text); + connect(emoji, SIGNAL(emojiClicked(QString)), this, SIGNAL(onEmojiSelected(QString))); + addItem(new QWidgetItem(emoji)); + } + + void QtEmojisGrid::clearEmojis() { + QLayoutItem* child = nullptr; + while ((child = this->takeAt(0)) != nullptr) { + if (child->widget()) { + child->widget()->hide(); + removeWidget(child->widget()); + } + else { + removeItem(child); + } + } + } +} diff --git a/Swift/QtUI/QtEmojisGrid.h b/Swift/QtUI/QtEmojisGrid.h new file mode 100644 index 0000000..5e6c770 --- /dev/null +++ b/Swift/QtUI/QtEmojisGrid.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QString> +#include <QVector> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/FlowLayout.h> + +namespace Swift { + class QtEmojisGrid : public FlowLayout { + Q_OBJECT + public: + explicit QtEmojisGrid(); + explicit QtEmojisGrid(QString categoryName); + + protected: + void setEmojis(const QVector<QString>& emojis); + void addEmoticon(QIcon icon, QString text); + + private: + void clearEmojis(); + + signals: + void onEmojiSelected(QString emoji); + }; +} diff --git a/Swift/QtUI/QtEmojisScroll.cpp b/Swift/QtUI/QtEmojisScroll.cpp new file mode 100644 index 0000000..be505b6 --- /dev/null +++ b/Swift/QtUI/QtEmojisScroll.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtEmojisScroll.h> + +#include <QLayout> +#include <QScrollArea> +#include <QStyle> + +#include <Swiften/Base/Platform.h> + +#include <Swift/QtUI/QtEmojisGrid.h> +#include <Swift/QtUI/QtRecentEmojisGrid.h> + +namespace Swift { + QtEmojisScroll::QtEmojisScroll(QLayout* emojiLayout, QWidget *parent) : QWidget(parent) { + auto selector = new QWidget(); + auto scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(selector); + selector->setLayout(emojiLayout); + + this->setLayout(new QVBoxLayout); + this->layout()->addWidget(scrollArea); + this->layout()->setContentsMargins(0,0,0,0); + + if (emojiLayout->itemAt(0)) { +#ifdef SWIFTEN_PLATFORM_MACOSX + setMinimumHeight(emojiLayout->itemAt(0)->minimumSize().height() * 8); +#else + setMinimumHeight(emojiLayout->itemAt(0)->minimumSize().height() * 2); +#endif + } + } +} diff --git a/Swift/QtUI/QtEmojisScroll.h b/Swift/QtUI/QtEmojisScroll.h new file mode 100644 index 0000000..f954c2d --- /dev/null +++ b/Swift/QtUI/QtEmojisScroll.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2016-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QLayout> +#include <QWidget> + +namespace Swift { + class QtEmojisScroll : public QWidget { + Q_OBJECT + public: + QtEmojisScroll(QLayout* emojiLayout, QWidget* parent = nullptr); + }; +} diff --git a/Swift/QtUI/QtEmojisSelector.cpp b/Swift/QtUI/QtEmojisSelector.cpp new file mode 100644 index 0000000..fe2f235 --- /dev/null +++ b/Swift/QtUI/QtEmojisSelector.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtEmojisSelector.h> + +#include <QScrollArea> +#include <QSettings> +#include <QString> +#include <QTabBar> +#include <QWidget> + +#include <Swiften/Base/Platform.h> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/QtEmojisGrid.h> +#include <Swift/QtUI/QtEmojisScroll.h> +#include <Swift/QtUI/QtEmoticonsGrid.h> +#include <Swift/QtUI/QtRecentEmojisGrid.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + QtEmojisSelector::QtEmojisSelector(QSettings* settings, const std::map<std::string, std::string>& emoticonsMap, QWidget* parent) : QTabWidget(parent), settings_(settings), emoticonsMap_(emoticonsMap) { + recentEmojisGrid_ = addRecentTab(); + connect(recentEmojisGrid_, SIGNAL(onEmojiSelected(QString)), this, SLOT(emojiClickedSlot(QString))); + + for (const auto& category : EmojiMapper::getCategories()) { + if (category != "modifier") { + QtEmojisGrid* grid = addTab(P2QSTRING(category)); + connect(grid, SIGNAL(onEmojiSelected(QString)), this, SLOT(emojiClickedSlot(QString))); + } + } + loadSettings(); + //The size of an emoji cell varies depending the OS, 42 is the ceil value. + setFixedSize(QSize(EmojiMapper::emojisInCategory.size() * 42, 300)); + } + + QtEmojisSelector::~QtEmojisSelector() { + writeSettings(); + } + + QtRecentEmojisGrid* QtEmojisSelector::addRecentTab() { + QtRecentEmojisGrid* recent = new QtRecentEmojisGrid(settings_); + QtEmojisScroll* scroll = new QtEmojisScroll(recent); + QTabWidget::addTab(scroll, QString::fromStdString(EmojiMapper::categoryToFlagshipUnicodeEmoji("recent"))); + + setTabToolTip(count()-1, tr("Recent")); + + return recent; + } + + QtEmojisGrid* QtEmojisSelector::addTab(QString categoryName) { + QtEmojisGrid* grid = new QtEmojisGrid(categoryName); + QtEmojisScroll* scroll = new QtEmojisScroll(grid); + QTabWidget::addTab(scroll, QString::fromStdString(EmojiMapper::categoryToFlagshipUnicodeEmoji(Q2PSTRING(categoryName)))); + setTabToolTip(count()-1, categoryName.replace(0, 1, categoryName[0].toUpper())); + + return grid; + } + + void QtEmojisSelector::loadSettings() { + if (settings_->contains("currentEmojiTab")) { + setCurrentIndex(settings_->value("currentEmojiTab").toInt()); + } else { + setCurrentIndex(1); //index of people category + } + } + + void QtEmojisSelector::writeSettings() { + settings_->setValue("currentEmojiTab", currentIndex()); + } + + void QtEmojisSelector::setupEmoticonsTab() { + QtEmojisGrid* grid = new QtEmoticonsGrid(emoticonsMap_); + QtEmojisScroll* scroll = new QtEmojisScroll(grid); + QTabWidget::addTab(scroll, QIcon(":/emoticons/smile.png"),""); + connect(grid, SIGNAL(onEmojiSelected(QString)), this, SLOT(emojiClickedSlot(QString))); + tabBar()->hide(); + } + + void QtEmojisSelector::emojiClickedSlot(QString emoji) { + if (recentEmojisGrid_) { + recentEmojisGrid_->handleEmojiClicked(emoji); + } + emit emojiClicked(emoji); + } +} diff --git a/Swift/QtUI/QtEmojisSelector.h b/Swift/QtUI/QtEmojisSelector.h new file mode 100644 index 0000000..1a64cf4 --- /dev/null +++ b/Swift/QtUI/QtEmojisSelector.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <map> +#include <string> + +#include <QSettings> +#include <QTabWidget> + +#include <Swift/QtUI/QtEmojisGrid.h> +#include <Swift/QtUI/QtRecentEmojisGrid.h> + +namespace Swift { + +class QtEmojisSelector : public QTabWidget { + Q_OBJECT + public: + QtEmojisSelector(QSettings* settings, const std::map<std::string, std::string>& emoticonsMap, QWidget* parent = nullptr); + ~QtEmojisSelector(); + + public slots: + void emojiClickedSlot(QString emoji); + + signals: + void emojiClicked(QString emoji); + + private: + QtRecentEmojisGrid* addRecentTab(); + QtEmojisGrid* addTab(QString categoryName); + void loadSettings(); + void writeSettings(); + + void setupEmoticonsTab(); + + private: + QSettings* settings_ = nullptr; + QtRecentEmojisGrid* recentEmojisGrid_ = nullptr; + std::map<std::string, std::string> emoticonsMap_; +}; + +} diff --git a/Swift/QtUI/QtEmoticonsGrid.cpp b/Swift/QtUI/QtEmoticonsGrid.cpp new file mode 100644 index 0000000..a592e90 --- /dev/null +++ b/Swift/QtUI/QtEmoticonsGrid.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtEmoticonsGrid.h> + +#include <unordered_set> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/QtEmojiCell.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + + QtEmoticonsGrid::QtEmoticonsGrid(const std::map<std::string, std::string>& emoticonsMap) : QtEmojisGrid() { + std::unordered_set<std::string> usedEmoticons; + for (const auto& emoticonPair : emoticonsMap) { + if (usedEmoticons.find(emoticonPair.second) == usedEmoticons.end()) { + usedEmoticons.insert(emoticonPair.second); + + auto filePath = P2QSTRING(emoticonPair.second); + if (filePath.startsWith("qrc:/")) { + filePath.remove(0, 3); + } + auto icon = QIcon(filePath); + auto text = P2QSTRING(emoticonPair.first); + + addEmoticon(icon, text); + } + } + } + + QtEmoticonsGrid::~QtEmoticonsGrid() { + + } + +} diff --git a/Swift/QtUI/QtEmoticonsGrid.h b/Swift/QtUI/QtEmoticonsGrid.h new file mode 100644 index 0000000..2fc2907 --- /dev/null +++ b/Swift/QtUI/QtEmoticonsGrid.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <map> +#include <memory> +#include <string> + +#include <Swift/QtUI/QtEmojisGrid.h> + +namespace Swift { + class QtEmoticonsGrid : public QtEmojisGrid { + Q_OBJECT + public: + explicit QtEmoticonsGrid(const std::map<std::string, std::string>& emoticonsMap); + ~QtEmoticonsGrid(); + + }; +} diff --git a/Swift/QtUI/QtExpandedListView.cpp b/Swift/QtUI/QtExpandedListView.cpp new file mode 100644 index 0000000..84769c5 --- /dev/null +++ b/Swift/QtUI/QtExpandedListView.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtExpandedListView.h> + +#include <QWheelEvent> +#include <QScrollArea> +#include <QDebug> + +namespace Swift { + +QtExpandedListView::QtExpandedListView(QWidget* parent) : QListView(parent) { + // Disable macOS focus rectangle due to bad performance. + setAttribute(Qt::WA_MacShowFocusRect, 0); + setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); +} + +void QtExpandedListView::setModel(QAbstractItemModel* newModel) { + if (model()) { + disconnectFromModel(model()); + } + if (newModel) { + connectToModel(newModel); + } + QListView::setModel(newModel); + adjustHeightToModelChange(); +} + +QtExpandedListView::~QtExpandedListView() { + if (model()) { + disconnectFromModel(model()); + } +} + +bool QtExpandedListView::viewportEvent(QEvent* event) { + // Ignore wheel events for faster mouse scrolling. + if (event && event->type() == QEvent::Wheel) { + return false; + } + + return QListView::viewportEvent(event); +} + +template <typename T> +T getParentOfType(QWidget* start) { + auto parentW = start->parentWidget(); + if (parentW == nullptr) { + return nullptr; + } + T result = dynamic_cast<T>(parentW); + if (result) { + return result; + } + return getParentOfType<T>(parentW); +} + +void QtExpandedListView::currentChanged(const QModelIndex ¤t, const QModelIndex &) { + // Make sure that the current selected index is visible in the parent QScrollArea. + auto scrollArea = getParentOfType<QScrollArea*>(parentWidget()); + if (scrollArea) { + auto scrollWidget = scrollArea->widget(); + QList<QPoint> points; + auto visRect = visualRect(current); + points << mapTo(scrollWidget, visRect.topLeft()); + points << mapTo(scrollWidget, visRect.topRight()); + points << mapTo(scrollWidget, visRect.bottomLeft()); + points << mapTo(scrollWidget, visRect.bottomRight()); + + for (auto&& point : points) { + scrollArea->ensureVisible(point.x(), point.y(), 0, 0); + } + } +} + +void QtExpandedListView::adjustHeightToModelChange() { + updateGeometry(); +} + +QSize QtExpandedListView::minimumSizeHint() const { + auto sh = sizeHint(); + return QSize(0, sh.height()); +} + +QSize QtExpandedListView::sizeHint() const { + auto listViewSH = QListView::sizeHint(); + if (model()) { + auto lastRect = rectForIndex(model()->index(model()->rowCount()-1, 0, rootIndex())); + auto idealHeight = lastRect.y() + lastRect.height() + frameWidth() * 2; + listViewSH.setHeight(idealHeight); + } + return listViewSH; +} + +void QtExpandedListView::connectToModel(QAbstractItemModel* model) { + connect(model, &QAbstractItemModel::dataChanged, this, &QtExpandedListView::adjustHeightToModelChange); + connect(model, &QAbstractItemModel::modelReset, this, &QtExpandedListView::adjustHeightToModelChange); + connect(model, &QAbstractItemModel::rowsInserted, this, &QtExpandedListView::adjustHeightToModelChange); + connect(model, &QAbstractItemModel::rowsRemoved, this, &QtExpandedListView::adjustHeightToModelChange); +} + +void QtExpandedListView::disconnectFromModel(QAbstractItemModel* model) { + disconnect(model, &QAbstractItemModel::dataChanged, this, &QtExpandedListView::adjustHeightToModelChange); + disconnect(model, &QAbstractItemModel::modelReset, this, &QtExpandedListView::adjustHeightToModelChange); + disconnect(model, &QAbstractItemModel::rowsInserted, this, &QtExpandedListView::adjustHeightToModelChange); + disconnect(model, &QAbstractItemModel::rowsRemoved, this, &QtExpandedListView::adjustHeightToModelChange); +} + +} diff --git a/Swift/QtUI/QtExpandedListView.h b/Swift/QtUI/QtExpandedListView.h new file mode 100644 index 0000000..df78376 --- /dev/null +++ b/Swift/QtUI/QtExpandedListView.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QListView> + +namespace Swift { + +class QtExpandedListView : public QListView { +public: + QtExpandedListView(QWidget* parent); + ~QtExpandedListView() override; + + void setModel(QAbstractItemModel* model) override; + bool viewportEvent(QEvent* event) override; + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + +protected slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; + +private slots: + void adjustHeightToModelChange(); + +private: + void connectToModel(QAbstractItemModel* model); + void disconnectFromModel(QAbstractItemModel* model); +}; + +} diff --git a/Swift/QtUI/QtFdpFormSubmitWindow.cpp b/Swift/QtUI/QtFdpFormSubmitWindow.cpp new file mode 100644 index 0000000..5719f87 --- /dev/null +++ b/Swift/QtUI/QtFdpFormSubmitWindow.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtFdpFormSubmitWindow.h> + +#include <QCloseEvent> +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> +#include <QPushButton> +#include <QSpacerItem> +#include <QVBoxLayout> + +#include <Swift/QtUI/QtFormWidget.h> +#include <Swift/QtUI/QtListWidget.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { +QtFdpFormSubmitWindow::QtFdpFormSubmitWindow(QWidget* parent) : QDialog(parent), FdpFormSubmitWindow() { + layout_ = new QVBoxLayout(this); + subLayout_ = new QHBoxLayout(this); + + initNodeViewLayout(); + initFormLayout(); + subLayout_->addLayout(nodeViewLayout_); + subLayout_->addLayout(formLayout_); + subLayout_->setStretchFactor(nodeViewLayout_, 2); + subLayout_->setStretchFactor(formLayout_, 3); + layout_->addLayout(subLayout_); + + submitButton_ = new QPushButton(tr("Submit Form"), this); + submitButton_->setEnabled(false); + okButton_ = new QPushButton(tr("Ok"), this); + okButton_->hide(); + cancelButton_ = new QPushButton(tr("Cancel"), this); + connect(submitButton_, &QPushButton::clicked, this, &QtFdpFormSubmitWindow::handleSubmitClicked); + connect(okButton_, &QPushButton::clicked, this, &QWidget::close); + connect(cancelButton_, &QPushButton::clicked, this, &QWidget::close); + auto buttonSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); + buttonLayout_ = new QHBoxLayout(this); + buttonLayout_->addItem(buttonSpacer); + buttonLayout_->addWidget(submitButton_); + buttonLayout_->addWidget(cancelButton_); + layout_->addLayout(buttonLayout_); + + setMinimumWidth(800); + setMinimumHeight(600); + + this->setWindowTitle(tr("FDP Form Submission")); +} + +QtFdpFormSubmitWindow::~QtFdpFormSubmitWindow() { +} + +void QtFdpFormSubmitWindow::closeEvent(QCloseEvent* event) { + event->ignore(); + onCloseEvent(); +} + +void QtFdpFormSubmitWindow::initNodeViewLayout() { + nodeViewLayout_ = new QVBoxLayout(this); + auto domainSearchLayout = new QHBoxLayout(this); + pubSubDomainEdit_ = new QLineEdit(this); + loadDomainButton_ = new QPushButton(tr("Load"), this); + pubSubNodeView_ = new QtListWidget(this); + pubSubDomainEdit_->setPlaceholderText(tr("Enter pubsub domain here")); + pubSubNodeView_->setMinimumWidth(300); + connect(loadDomainButton_, &QPushButton::clicked, this, &QtFdpFormSubmitWindow::handleLoadDomainButtonClicked); + connect(pubSubNodeView_, &QListWidget::itemDoubleClicked, this, &QtFdpFormSubmitWindow::handlePubSubNodeViewItemDoubleClicked); + domainSearchLayout->addWidget(pubSubDomainEdit_); + domainSearchLayout->addWidget(loadDomainButton_); + nodeViewLayout_->addLayout(domainSearchLayout); + nodeViewLayout_->addWidget(pubSubNodeView_); +} + +void QtFdpFormSubmitWindow::initFormLayout() { + formPlaceholder_ = new QLabel(tr("No form loaded")); + formPlaceholder_->setAlignment(Qt::AlignCenter); + formPlaceholder_->setMinimumWidth(200); + formPlaceholder_->setStyleSheet("QLabel { color : #AAAAAA; }"); + formLayout_ = new QVBoxLayout(this); + formLayout_->addWidget(formPlaceholder_, Qt::AlignCenter); +} + +void QtFdpFormSubmitWindow::show() { + QDialog::show(); +} + +void QtFdpFormSubmitWindow::raise() { + QDialog::raise(); +} + +void QtFdpFormSubmitWindow::addNode(const std::string& node, const std::string& nodeName) { + auto listItem = new QListWidgetItem(P2QSTRING(nodeName)); + listItem->setData(Qt::UserRole, P2QSTRING(node)); + pubSubNodeView_->addItem(listItem); +} + +void QtFdpFormSubmitWindow::clearNodeData() { + pubSubNodeView_->clear(); + pubSubNodeView_->setWordWrap(false); + disconnect(pubSubNodeView_, &QtListWidget::onResize, this, &QtFdpFormSubmitWindow::handleNodeListResize); +} + +void QtFdpFormSubmitWindow::setFormData(const std::shared_ptr<Form>& form) { + if (formWidget_) { + formLayout_->removeWidget(formWidget_); + formWidget_->deleteLater(); + formWidget_ = nullptr; + } + else if (!formPlaceholder_->isHidden()) { + formLayout_->removeWidget(formPlaceholder_); + formPlaceholder_->hide(); + } + formWidget_ = new QtFormWidget(form); + formWidget_->setEditable(true); + formLayout_->addWidget(formWidget_); + + if (!okButton_->isHidden()) { + buttonLayout_->removeWidget(okButton_); + okButton_->hide(); + } + if (submitButton_->isHidden()) { + buttonLayout_->insertWidget(1, submitButton_); + submitButton_->show(); + } + submitButton_->setEnabled(true); + cancelButton_->setEnabled(true); +} + +void QtFdpFormSubmitWindow::showNodePlaceholder(NodeError nodeError) { + pubSubNodeView_->clear(); + pubSubNodeView_->setWordWrap(true); + auto listItem = new QListWidgetItem; + auto placeholderText = P2QSTRING(getNodeErrorText(nodeError)); + listItem->setText(placeholderText); + listItem->setTextAlignment(Qt::AlignCenter); + listItem->setFlags(Qt::NoItemFlags); + listItem->setSizeHint(QSize(listItem->sizeHint().width(), pubSubNodeView_->height())); + connect(pubSubNodeView_, &QtListWidget::onResize, this, &QtFdpFormSubmitWindow::handleNodeListResize); + pubSubNodeView_->addItem(listItem); +} + +void QtFdpFormSubmitWindow::showFormPlaceholder(TemplateError templateError) { + if (formWidget_) { + formLayout_->removeWidget(formWidget_); + formWidget_->deleteLater(); + formWidget_ = nullptr; + } + auto placeholderText = P2QSTRING(getTemplateErrorText(templateError)); + formPlaceholder_->setText(placeholderText); + if (formPlaceholder_->isHidden()) { + formLayout_->addWidget(formPlaceholder_, Qt::AlignCenter); + formPlaceholder_->show(); + } + + if (!okButton_->isHidden()) { + buttonLayout_->removeWidget(okButton_); + okButton_->hide(); + } + if (submitButton_->isHidden()) { + buttonLayout_->insertWidget(1, submitButton_); + submitButton_->show(); + } + submitButton_->setEnabled(false); + cancelButton_->setEnabled(true); +} + +void QtFdpFormSubmitWindow::handleLoadDomainButtonClicked() { + std::string domainUri = Q2PSTRING(pubSubDomainEdit_->text()); + onRequestPubSubNodeData(domainUri); +} + +void QtFdpFormSubmitWindow::handlePubSubListViewTemplateSelected(const std::string& nodeName) { + onRequestTemplateForm(nodeName); +} + +void QtFdpFormSubmitWindow::handlePubSubNodeViewItemDoubleClicked(QListWidgetItem* item) { + handlePubSubListViewTemplateSelected(Q2PSTRING(item->data(Qt::UserRole).toString())); +} + +void QtFdpFormSubmitWindow::handleSubmitClicked() { + auto form = formWidget_->getCompletedForm(); + formWidget_->setDisabled(true); + submitButton_->setEnabled(false); + onSubmitForm(form); +} + +void QtFdpFormSubmitWindow::handleSubmitServerResponse(bool submissionSuccess) { + if (submissionSuccess) { + if (!submitButton_->isHidden()) { + buttonLayout_->removeWidget(submitButton_); + submitButton_->hide(); + } + if (okButton_->isHidden()) { + buttonLayout_->insertWidget(1, okButton_); + okButton_->show(); + } + cancelButton_->setEnabled(false); + } + else { + formWidget_->setDisabled(false); + submitButton_->setEnabled(true); + } +} + +void QtFdpFormSubmitWindow::handleNodeListResize() { + auto placeholderItem = pubSubNodeView_->item(0); + placeholderItem->setSizeHint(QSize(placeholderItem->sizeHint().width(), pubSubNodeView_->height())); +} + +} diff --git a/Swift/QtUI/QtFdpFormSubmitWindow.h b/Swift/QtUI/QtFdpFormSubmitWindow.h new file mode 100644 index 0000000..b429927 --- /dev/null +++ b/Swift/QtUI/QtFdpFormSubmitWindow.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <memory> + +#include <QDialog> + +#include <Swift/Controllers/UIInterfaces/FdpFormSubmitWindow.h> + +class QHBoxLayout; +class QLabel; +class QLineEdit; +class QListWidgetItem; +class QPushButton; +class QTextEdit; +class QVBoxLayout; + +namespace Swift { + + class Form; + class QtFormWidget; + class QtListWidget; + class QtPubSubNodeController; + + class QtFdpFormSubmitWindow : public QDialog, public FdpFormSubmitWindow { + Q_OBJECT + + public: + QtFdpFormSubmitWindow(QWidget* parent = nullptr); + virtual ~QtFdpFormSubmitWindow() override; + + protected: + virtual void closeEvent(QCloseEvent* event) override; + + private: + void initNodeViewLayout(); + void initFormLayout(); + virtual void show() override; + virtual void raise() override; + virtual void addNode(const std::string& node, const std::string& nodeName) override; + virtual void clearNodeData() override; + virtual void setFormData(const std::shared_ptr<Form>& form) override; + virtual void showNodePlaceholder(NodeError nodeError) override; + virtual void showFormPlaceholder(TemplateError templateError) override; + virtual void handleSubmitServerResponse(bool submissionSuccess) override; + + private slots: + void handleLoadDomainButtonClicked(); + void handlePubSubListViewTemplateSelected(const std::string& nodeName); + void handlePubSubNodeViewItemDoubleClicked(QListWidgetItem* item); + void handleSubmitClicked(); + void handleNodeListResize(); + + private: + QVBoxLayout* layout_; + QHBoxLayout* subLayout_; + QHBoxLayout* buttonLayout_; + QVBoxLayout* nodeViewLayout_; + QVBoxLayout* formLayout_; + QLineEdit* pubSubDomainEdit_; + QPushButton* loadDomainButton_; + QtListWidget* pubSubNodeView_; + QLabel* formPlaceholder_; + QTextEdit* nodePlaceholderTextEdit_ = nullptr; + QtFormWidget* formWidget_ = nullptr; + QPushButton* cancelButton_; + QPushButton* submitButton_ = nullptr; + QPushButton* okButton_ = nullptr; + }; +} diff --git a/Swift/QtUI/QtFileTransferListItemModel.cpp b/Swift/QtUI/QtFileTransferListItemModel.cpp index 00afacb..24d6dd1 100644 --- a/Swift/QtUI/QtFileTransferListItemModel.cpp +++ b/Swift/QtUI/QtFileTransferListItemModel.cpp @@ -4,112 +4,140 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtFileTransferListItemModel.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtFileTransferListItemModel.h> #include <boost/bind.hpp> #include <boost/cstdint.hpp> +#include <boost/signals2.hpp> -#include "QtChatWindow.h" // for formatSize +#include <Swiften/Base/FileSize.h> -#include <Swiften/Base/boost_bsignals.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> -#include "QtSwiftUtil.h" + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtFileTransferListItemModel::QtFileTransferListItemModel(QObject *parent) : QAbstractItemModel(parent), fileTransferOverview(0) { +QtFileTransferListItemModel::QtFileTransferListItemModel(QObject *parent) : QAbstractItemModel(parent), fileTransferOverview(nullptr) { +} + +QtFileTransferListItemModel::~QtFileTransferListItemModel() { + if (fileTransferOverview) { + fileTransferOverview->onNewFileTransferController.disconnect(boost::bind(&QtFileTransferListItemModel::handleNewFileTransferController, this, _1)); + fileTransferOverview->onFileTransferListChanged.disconnect(boost::bind(&QtFileTransferListItemModel::handleFileTransferListChanged, this)); + } } void QtFileTransferListItemModel::setFileTransferOverview(FileTransferOverview *overview) { - fileTransferOverview = overview; - fileTransferOverview->onNewFileTransferController.connect(boost::bind(&QtFileTransferListItemModel::handleNewFileTransferController, this, _1)); + if (fileTransferOverview) { + fileTransferOverview->onNewFileTransferController.disconnect(boost::bind(&QtFileTransferListItemModel::handleNewFileTransferController, this, _1)); + fileTransferOverview->onFileTransferListChanged.disconnect(boost::bind(&QtFileTransferListItemModel::handleFileTransferListChanged, this)); + } + fileTransferOverview = overview; + if (fileTransferOverview) { + fileTransferOverview->onNewFileTransferController.connect(boost::bind(&QtFileTransferListItemModel::handleNewFileTransferController, this, _1)); + fileTransferOverview->onFileTransferListChanged.connect(boost::bind(&QtFileTransferListItemModel::handleFileTransferListChanged, this)); + } + emit layoutAboutToBeChanged(); + emit layoutChanged(); } void QtFileTransferListItemModel::handleNewFileTransferController(FileTransferController* newController) { - emit layoutAboutToBeChanged(); - emit layoutChanged(); - dataChanged(createIndex(0,0), createIndex(fileTransferOverview->getFileTransfers().size(),4)); - newController->onStateChage.connect(boost::bind(&QtFileTransferListItemModel::handleStateChange, this, fileTransferOverview->getFileTransfers().size() - 1)); - newController->onProgressChange.connect(boost::bind(&QtFileTransferListItemModel::handleProgressChange, this, fileTransferOverview->getFileTransfers().size() - 1)); + emit layoutAboutToBeChanged(); + emit layoutChanged(); + dataChanged(createIndex(0,0), createIndex(fileTransferOverview->getFileTransfers().size(),4)); + newController->onStateChanged.connect(boost::bind(&QtFileTransferListItemModel::handleStateChange, this, fileTransferOverview->getFileTransfers().size() - 1)); + newController->onProgressChange.connect(boost::bind(&QtFileTransferListItemModel::handleProgressChange, this, fileTransferOverview->getFileTransfers().size() - 1)); +} + +void QtFileTransferListItemModel::handleFileTransferListChanged() { + emit layoutAboutToBeChanged(); + emit layoutChanged(); } void QtFileTransferListItemModel::handleStateChange(int index) { - emit dataChanged(createIndex(index, 2), createIndex(index, 2)); + emit dataChanged(createIndex(index, 2), createIndex(index, 2)); } void QtFileTransferListItemModel::handleProgressChange(int index) { - emit dataChanged(createIndex(index, 3), createIndex(index, 3)); + emit dataChanged(createIndex(index, 3), createIndex(index, 3)); } QVariant QtFileTransferListItemModel::headerData(int section, Qt::Orientation /* orientation */, int role) const { - if (role != Qt::DisplayRole) return QVariant(); - if (section == Direction) return QVariant(QObject::tr("Direction")); - if (section == OtherParty) return QVariant(QObject::tr("Other Party")); - if (section == State) return QVariant(QObject::tr("State")); - if (section == Progress) return QVariant(QObject::tr("Progress")); - if (section == OverallSize) return QVariant(QObject::tr("Size")); - return QVariant(); + if (role != Qt::DisplayRole) return QVariant(); + if (section == Direction) return QVariant(QObject::tr("Direction")); + if (section == OtherParty) return QVariant(QObject::tr("Other Party")); + if (section == State) return QVariant(QObject::tr("State")); + if (section == Progress) return QVariant(QObject::tr("Progress")); + if (section == OverallSize) return QVariant(QObject::tr("Size")); + return QVariant(); } int QtFileTransferListItemModel::columnCount(const QModelIndex& /* parent */) const { - return NoOfColumns; + return NoOfColumns; } QVariant QtFileTransferListItemModel::data(const QModelIndex &index, int role) const { - if (role != Qt::DisplayRole || !index.isValid() || - !fileTransferOverview || static_cast<size_t>(index.row()) >= fileTransferOverview->getFileTransfers().size()) { - return QVariant(); - } - FileTransferController* controller = fileTransferOverview->getFileTransfers().at(index.row()); - if (index.column() == Direction) { - return controller->isIncoming() ? QVariant(QObject::tr("Incoming")) : QVariant(QObject::tr("Outgoing")); - } - if (index.column() == OtherParty) { - return QVariant(P2QSTRING(controller->getOtherParty().toString())); - } - if (index.column() == State) { - FileTransfer::State state = controller->getState(); - switch(state.type) { - case FileTransfer::State::Initial: - assert(false); - return QVariant(""); - case FileTransfer::State::WaitingForStart: - return QVariant(QObject::tr("Waiting for start")); - case FileTransfer::State::WaitingForAccept: - return QVariant(QObject::tr("Waiting for other side to accept")); - case FileTransfer::State::Negotiating: - return QVariant(QObject::tr("Negotiating")); - case FileTransfer::State::Transferring: - return QVariant(QObject::tr("Transferring")); - case FileTransfer::State::Finished: - return QVariant(QObject::tr("Finished")); - case FileTransfer::State::Failed: - return QVariant(QObject::tr("Failed")); - case FileTransfer::State::Canceled: - return QVariant(QObject::tr("Canceled")); - } - } - - if (index.column() == Progress) { - return QVariant(QString::number(controller->getProgress())); - } - if (index.column() == OverallSize) { - return QVariant(P2QSTRING(formatSize((controller->getSize())))); - } - return QVariant(); + if (role != Qt::DisplayRole || !index.isValid() || + !fileTransferOverview || static_cast<size_t>(index.row()) >= fileTransferOverview->getFileTransfers().size()) { + return QVariant(); + } + FileTransferController* controller = fileTransferOverview->getFileTransfers().at(index.row()); + if (index.column() == Direction) { + return controller->isIncoming() ? QVariant(QObject::tr("Incoming")) : QVariant(QObject::tr("Outgoing")); + } + if (index.column() == OtherParty) { + return QVariant(P2QSTRING(controller->getOtherParty().toString())); + } + if (index.column() == State) { + FileTransfer::State state = controller->getState(); + switch(state.type) { + case FileTransfer::State::Initial: + assert(false); + return QVariant(""); + case FileTransfer::State::WaitingForStart: + return QVariant(QObject::tr("Waiting for start")); + case FileTransfer::State::WaitingForAccept: + return QVariant(QObject::tr("Waiting for other side to accept")); + case FileTransfer::State::Negotiating: + return QVariant(QObject::tr("Negotiating")); + case FileTransfer::State::Transferring: + return QVariant(QObject::tr("Transferring")); + case FileTransfer::State::Finished: + return QVariant(QObject::tr("Finished")); + case FileTransfer::State::Failed: + return QVariant(QObject::tr("Failed")); + case FileTransfer::State::Canceled: + return QVariant(QObject::tr("Canceled")); + } + } + + if (index.column() == Progress) { + return QVariant(QString::number(controller->getProgress())); + } + if (index.column() == OverallSize) { + return QVariant(P2QSTRING(formatSize((controller->getSize())))); + } + return QVariant(); } QModelIndex QtFileTransferListItemModel::parent(const QModelIndex& /* child */) const { - return createIndex(0,0); + return createIndex(0,0); } int QtFileTransferListItemModel::rowCount(const QModelIndex& /* parent */) const { - return fileTransferOverview ? fileTransferOverview->getFileTransfers().size() : 0; + return fileTransferOverview ? fileTransferOverview->getFileTransfers().size() : 0; } QModelIndex QtFileTransferListItemModel::index(int row, int column, const QModelIndex& /* parent */) const { - return createIndex(row, column, (void*) 0); + return createIndex(row, column, static_cast<void*>(nullptr)); } } diff --git a/Swift/QtUI/QtFileTransferListItemModel.h b/Swift/QtUI/QtFileTransferListItemModel.h index 28f13f8..4d1e48b 100644 --- a/Swift/QtUI/QtFileTransferListItemModel.h +++ b/Swift/QtUI/QtFileTransferListItemModel.h @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QAbstractItemModel> @@ -14,40 +20,42 @@ class FileTransferController; class FileTransferOverview; class QtFileTransferListItemModel : public QAbstractItemModel { - Q_OBJECT + Q_OBJECT public: - explicit QtFileTransferListItemModel(QObject *parent = 0); + explicit QtFileTransferListItemModel(QObject *parent = nullptr); + virtual ~QtFileTransferListItemModel(); - void setFileTransferOverview(FileTransferOverview*); + void setFileTransferOverview(FileTransferOverview*); - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - int columnCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; - QModelIndex parent(const QModelIndex &child) const; - int rowCount(const QModelIndex &parent) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; private: - enum Columns { - Direction = 0, - OtherParty, - State, - Progress, - OverallSize, - NoOfColumns - }; + enum Columns { + Direction = 0, + OtherParty, + State, + Progress, + OverallSize, + NoOfColumns + }; private: - void handleNewFileTransferController(FileTransferController*); - void handleStateChange(int index); - void handleProgressChange(int index); + void handleNewFileTransferController(FileTransferController*); + void handleFileTransferListChanged(); + void handleStateChange(int index); + void handleProgressChange(int index); signals: public slots: private: - FileTransferOverview *fileTransferOverview; + FileTransferOverview *fileTransferOverview; }; diff --git a/Swift/QtUI/QtFileTransferListWidget.cpp b/Swift/QtUI/QtFileTransferListWidget.cpp index 01c632f..8b855b0 100644 --- a/Swift/QtUI/QtFileTransferListWidget.cpp +++ b/Swift/QtUI/QtFileTransferListWidget.cpp @@ -4,74 +4,108 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtFileTransferListWidget.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swift/QtUI/QtFileTransferListItemModel.h> +#include <Swift/QtUI/QtFileTransferListWidget.h> + +#include <boost/bind.hpp> -#include <QVBoxLayout> #include <QHBoxLayout> -#include <QWidget> #include <QPushButton> +#include <QVBoxLayout> +#include <QWidget> + +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> + +#include <Swift/QtUI/QtFileTransferListItemModel.h> namespace Swift { -QtFileTransferListWidget::QtFileTransferListWidget() : fileTransferOverview(0) { - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setSpacing(0); - layout->setContentsMargins(0,0,0,0); +QtFileTransferListWidget::QtFileTransferListWidget() : fileTransferOverview(nullptr) { + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(0,0,0,0); - treeView = new QTreeView(this); - treeView->setRootIsDecorated(false); - treeView->setItemsExpandable(false); - layout->addWidget(treeView); + treeView = new QTreeView(this); + treeView->setRootIsDecorated(false); + treeView->setItemsExpandable(false); + layout->addWidget(treeView); - itemModel = new QtFileTransferListItemModel(); - treeView->setModel(itemModel); + itemModel = new QtFileTransferListItemModel(); + treeView->setModel(itemModel); - QWidget* bottom = new QWidget(this); - layout->addWidget(bottom); - bottom->setAutoFillBackground(true); + QWidget* bottom = new QWidget(this); + layout->addWidget(bottom); + bottom->setAutoFillBackground(true); - QHBoxLayout* buttonLayout = new QHBoxLayout(bottom); - buttonLayout->setContentsMargins(10,0,20,0); - buttonLayout->setSpacing(0); + QHBoxLayout* buttonLayout = new QHBoxLayout(bottom); + buttonLayout->setContentsMargins(10,0,20,0); + buttonLayout->setSpacing(0); - QPushButton* clearFinished = new QPushButton(tr("Clear Finished Transfers"), bottom); - clearFinished->setEnabled(false); - //connect(clearButton, SIGNAL(clicked()), textEdit, SLOT(clear())); - buttonLayout->addWidget(clearFinished); + clearFinished = new QPushButton(tr("Clear all"), bottom); + clearFinished->setEnabled(false); + connect(clearFinished, SIGNAL(clicked()), this, SLOT(clearInactiveTransfers())); + buttonLayout->addWidget(clearFinished); - setWindowTitle(tr("File Transfer List")); - emit titleUpdated(); + setWindowTitle(tr("File Transfer List")); + emit titleUpdated(); } QtFileTransferListWidget::~QtFileTransferListWidget() { - delete itemModel; + if (fileTransferOverview) { + fileTransferOverview->onFileTransferListChanged.disconnect(boost::bind(&QtFileTransferListWidget::handleFileTransferListChanged, this)); + fileTransferOverview = nullptr; + } + delete itemModel; } void QtFileTransferListWidget::showEvent(QShowEvent* event) { - emit windowOpening(); - emit titleUpdated(); /* This just needs to be somewhere after construction */ - QWidget::showEvent(event); + emit windowOpening(); + emit titleUpdated(); /* This just needs to be somewhere after construction */ + QWidget::showEvent(event); +} + +void QtFileTransferListWidget::handleFileTransferListChanged() { + clearFinished->setEnabled(fileTransferOverview->isClearable()); +} + +void QtFileTransferListWidget::clearInactiveTransfers() { + fileTransferOverview->clearFinished(); } void QtFileTransferListWidget::show() { - QWidget::show(); - emit windowOpening(); + QWidget::show(); + emit windowOpening(); } void QtFileTransferListWidget::activate() { - emit wantsToActivate(); + emit wantsToActivate(); } void QtFileTransferListWidget::setFileTransferOverview(FileTransferOverview *overview) { - fileTransferOverview = overview; - itemModel->setFileTransferOverview(overview); + if (fileTransferOverview) { + fileTransferOverview->onFileTransferListChanged.disconnect(boost::bind(&QtFileTransferListWidget::handleFileTransferListChanged, this)); + fileTransferOverview = nullptr; + } + if (overview) { + fileTransferOverview = overview; + fileTransferOverview->onFileTransferListChanged.connect(boost::bind(&QtFileTransferListWidget::handleFileTransferListChanged, this)); + clearFinished->setEnabled(fileTransferOverview->isClearable()); + } + itemModel->setFileTransferOverview(overview); +} + +std::string QtFileTransferListWidget::getID() const { + return "QtFileTransferListWidget"; } void QtFileTransferListWidget::closeEvent(QCloseEvent* event) { - emit windowClosing(); - event->accept(); + emit windowClosing(); + event->accept(); } } diff --git a/Swift/QtUI/QtFileTransferListWidget.h b/Swift/QtUI/QtFileTransferListWidget.h index c828d4e..cfc7dd2 100644 --- a/Swift/QtUI/QtFileTransferListWidget.h +++ b/Swift/QtUI/QtFileTransferListWidget.h @@ -4,42 +4,56 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#pragma once - -#include "Swift/Controllers/UIInterfaces/FileTransferListWidget.h" +/* + * Copyright (c) 2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include "QtTabbable.h" +#pragma once #include <QCloseEvent> +#include <QPushButton> #include <QShowEvent> #include <QTreeView> +#include <Swift/Controllers/UIInterfaces/FileTransferListWidget.h> + +#include <Swift/QtUI/QtTabbable.h> + namespace Swift { class FileTransferOverview; class QtFileTransferListItemModel; class QtFileTransferListWidget : public QtTabbable, public FileTransferListWidget { - Q_OBJECT + Q_OBJECT public: - QtFileTransferListWidget(); - virtual ~QtFileTransferListWidget(); + QtFileTransferListWidget(); + virtual ~QtFileTransferListWidget(); - void show(); - void activate(); + void show(); + void activate(); - void setFileTransferOverview(FileTransferOverview *); + void setFileTransferOverview(FileTransferOverview *); + + virtual std::string getID() const; private: - virtual void closeEvent(QCloseEvent* event); - virtual void showEvent(QShowEvent* event); + virtual void closeEvent(QCloseEvent* event); + virtual void showEvent(QShowEvent* event); + void handleFileTransferListChanged(); + +private slots: + void clearInactiveTransfers(); private: - QTreeView* treeView; + QTreeView* treeView; - QtFileTransferListItemModel* itemModel; - FileTransferOverview* fileTransferOverview; + QtFileTransferListItemModel* itemModel; + FileTransferOverview* fileTransferOverview; + QPushButton* clearFinished; }; } diff --git a/Swift/QtUI/QtFormResultItemModel.cpp b/Swift/QtUI/QtFormResultItemModel.cpp index 8920128..b35bb4f 100644 --- a/Swift/QtUI/QtFormResultItemModel.cpp +++ b/Swift/QtUI/QtFormResultItemModel.cpp @@ -5,17 +5,16 @@ */ /* - * Copyright (c) 2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2013-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtFormResultItemModel.h" +#include <Swift/QtUI/QtFormResultItemModel.h> #include <boost/algorithm/string/join.hpp> #include <Swift/QtUI/QtSwiftUtil.h> -#include <Swiften/Base/foreach.h> namespace Swift { @@ -28,67 +27,67 @@ QtFormResultItemModel::QtFormResultItemModel(QObject* parent, Form::ref formResu } void QtFormResultItemModel::setForm(Form::ref formResult) { - emit layoutAboutToBeChanged(); - formResult_ = formResult; - emit layoutChanged(); + emit layoutAboutToBeChanged(); + formResult_ = formResult; + emit layoutChanged(); } const Form::ref QtFormResultItemModel::getForm() const { - return formResult_; + return formResult_; } QVariant QtFormResultItemModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const { - if (!formResult_) return QVariant(); - if (role != Qt::DisplayRole) return QVariant(); - if (static_cast<size_t>(section) >= formResult_->getReportedFields().size()) return QVariant(); - return QVariant(P2QSTRING(formResult_->getReportedFields().at(section)->getLabel())); + if (!formResult_) return QVariant(); + if (role != Qt::DisplayRole) return QVariant(); + if (static_cast<size_t>(section) >= formResult_->getReportedFields().size()) return QVariant(); + return QVariant(P2QSTRING(formResult_->getReportedFields().at(section)->getLabel())); } int QtFormResultItemModel::rowCount(const QModelIndex &/*parent*/) const { - if (!formResult_) return 0; - return formResult_->getItems().size(); + if (!formResult_) return 0; + return formResult_->getItems().size(); } int QtFormResultItemModel::columnCount(const QModelIndex &/*parent*/) const { - if (!formResult_) return 0; - return formResult_->getReportedFields().size(); + if (!formResult_) return 0; + return formResult_->getReportedFields().size(); } QVariant QtFormResultItemModel::data(const QModelIndex &index, int role) const { - if (!formResult_) return QVariant(); - if (role != Qt::DisplayRole || !index.isValid()) { - return QVariant(); - } + if (!formResult_) return QVariant(); + if (role != Qt::DisplayRole || !index.isValid()) { + return QVariant(); + } - if (static_cast<size_t>(index.row()) >= formResult_->getItems().size()) return QVariant(); - if (static_cast<size_t>(index.column()) >= formResult_->getReportedFields().size()) return QVariant(); + if (static_cast<size_t>(index.row()) >= formResult_->getItems().size()) return QVariant(); + if (static_cast<size_t>(index.column()) >= formResult_->getReportedFields().size()) return QVariant(); - Form::FormItem item = formResult_->getItems().at(index.row()); + Form::FormItem item = formResult_->getItems().at(index.row()); - return QVariant(P2QSTRING(getFieldValue(item, index.column()))); + return QVariant(P2QSTRING(getFieldValue(item, index.column()))); } const std::string QtFormResultItemModel::getFieldValue(const Form::FormItem& item, const int column) const { - // determine field name - std::string name = formResult_->getReportedFields().at(column)->getName(); - - foreach(FormField::ref field, item) { - if (field->getName() == name) { - std::string delimiter = ""; - if (field->getType() == FormField::TextMultiType) { - delimiter = "\n"; - } - else if (field->getType() == FormField::JIDMultiType) { - delimiter = ", "; - } - else if (field->getType() == FormField::ListMultiType) { - delimiter = ", "; - } - return boost::algorithm::join(field->getValues(), delimiter); - } - } - - return ""; + // determine field name + std::string name = formResult_->getReportedFields().at(column)->getName(); + + for (auto&& field : item) { + if (field->getName() == name) { + std::string delimiter = ""; + if (field->getType() == FormField::TextMultiType) { + delimiter = "\n"; + } + else if (field->getType() == FormField::JIDMultiType) { + delimiter = ", "; + } + else if (field->getType() == FormField::ListMultiType) { + delimiter = ", "; + } + return boost::algorithm::join(field->getValues(), delimiter); + } + } + + return ""; } } diff --git a/Swift/QtUI/QtFormResultItemModel.h b/Swift/QtUI/QtFormResultItemModel.h index f383f74..47f3549 100644 --- a/Swift/QtUI/QtFormResultItemModel.h +++ b/Swift/QtUI/QtFormResultItemModel.h @@ -13,24 +13,24 @@ namespace Swift { class QtFormResultItemModel : public QAbstractTableModel { - Q_OBJECT - public: - QtFormResultItemModel(QObject* parent); - QtFormResultItemModel(QObject* parent, Form::ref formResult); + Q_OBJECT + public: + QtFormResultItemModel(QObject* parent); + QtFormResultItemModel(QObject* parent, Form::ref formResult); - void setForm(Form::ref formResult); - const Form::ref getForm() const; + void setForm(Form::ref formResult); + const Form::ref getForm() const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - private: - const std::string getFieldValue(const Form::FormItem& item, const int column) const; + private: + const std::string getFieldValue(const Form::FormItem& item, const int column) const; - private: - Form::ref formResult_; + private: + Form::ref formResult_; }; } diff --git a/Swift/QtUI/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp index 117696d..860ddfa 100644 --- a/Swift/QtUI/QtFormWidget.cpp +++ b/Swift/QtUI/QtFormWidget.cpp @@ -1,52 +1,70 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtFormWidget.h> +#include <memory> + +#include <boost/algorithm/string/join.hpp> + +#include <QCheckBox> #include <QGridLayout> #include <QLabel> -#include <QListWidget> #include <QLineEdit> -#include <QTextEdit> -#include <QCheckBox> +#include <QListWidget> #include <QScrollArea> +#include <QTextEdit> + #include <qdebug.h> #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 { QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), form_(form) { - QGridLayout* thisLayout = new QGridLayout(this); - int row = 0; - if (!form->getTitle().empty()) { - QLabel* instructions = new QLabel(("<b>" + form->getTitle() + "</b>").c_str(), this); - thisLayout->addWidget(instructions, row++, 0, 1, 2); - } - if (!form->getInstructions().empty()) { - QLabel* instructions = new QLabel(form->getInstructions().c_str(), this); - thisLayout->addWidget(instructions, row++, 0, 1, 2); - } - QScrollArea* scrollArea = new QScrollArea(this); - thisLayout->addWidget(scrollArea); - QWidget* scroll = new QWidget(this); - QGridLayout* layout = new QGridLayout(scroll); - foreach (boost::shared_ptr<FormField> field, form->getFields()) { - QWidget* widget = createWidget(field); - if (widget) { - layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0); - layout->addWidget(widget, row++, 1); - } - } - scrollArea->setWidget(scroll); - scrollArea->setWidgetResizable(true); - setEditable(form->getType() != Form::CancelType && form->getType() != Form::ResultType); + QGridLayout* thisLayout = new QGridLayout(this); + int row = 0; + if (!form->getTitle().empty()) { + QLabel* instructions = new QLabel(("<b>" + form->getTitle() + "</b>").c_str(), this); + thisLayout->addWidget(instructions, row++, 0, 1, 2); + } + if (!form->getInstructions().empty()) { + QLabel* instructions = new QLabel(form->getInstructions().c_str(), this); + thisLayout->addWidget(instructions, row++, 0, 1, 2); + } + QScrollArea* scrollArea = new QScrollArea(this); + thisLayout->addWidget(scrollArea); + QWidget* scroll = new QWidget(this); + QGridLayout* layout = new QGridLayout(scroll); + const std::vector<Form::FormItem> items = form->getItems(); + if (items.empty()) { /* single item forms */ + for (auto&& field : form->getFields()) { + QWidget* widget = createWidget(field, field->getType(), 0); + if (widget) { + layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0); + layout->addWidget(widget, row++, 1); + } + } + } 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); } QtFormWidget::~QtFormWidget() { @@ -54,156 +72,168 @@ QtFormWidget::~QtFormWidget() { } QListWidget* QtFormWidget::createList(FormField::ref field) { - QListWidget* listWidget = new QListWidget(this); - listWidget->setSortingEnabled(false); - listWidget->setSelectionMode(field->getType() == FormField::ListMultiType ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection); - std::vector<bool> selected; - foreach (FormField::Option option, field->getOptions()) { - listWidget->addItem(option.label.c_str()); - if (field->getType() == FormField::ListSingleType) { - selected.push_back(!field->getValues().empty() && option.value == field->getValues()[0]); - } - else if (field->getType() == FormField::ListMultiType) { - std::string text = option.value; - selected.push_back(std::find(field->getValues().begin(), field->getValues().end(), text) != field->getValues().end()); - } - - } - for (int i = 0; i < listWidget->count(); i++) { - QListWidgetItem* item = listWidget->item(i); - item->setSelected(selected[i]); - } - return listWidget; + QListWidget* listWidget = new QListWidget(this); + listWidget->setSortingEnabled(false); + 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) { + for (const auto& value : field->getValues()) { + listWidget->addItem(P2QSTRING(value)); + selected.push_back(false); + } + } else { + for (auto&& option : field->getOptions()) { + listWidget->addItem(option.label.c_str()); + if (field->getType() == FormField::ListSingleType) { + selected.push_back(!field->getValues().empty() && option.value == field->getValues()[0]); + } + else if (field->getType() == FormField::ListMultiType) { + std::string text = option.value; + selected.push_back(std::find(field->getValues().begin(), field->getValues().end(), text) != field->getValues().end()); + } + } + } + for (int i = 0; i < listWidget->count(); i++) { + QListWidgetItem* item = listWidget->item(i); + item->setSelected(selected[i]); + } + return listWidget; } -QWidget* QtFormWidget::createWidget(FormField::ref field) { - QWidget* widget = NULL; - if (field->getType() == FormField::BooleanType) { - QCheckBox* checkWidget = new QCheckBox(this); - checkWidget->setCheckState(field->getBoolValue() ? Qt::Checked : Qt::Unchecked); - widget = checkWidget; - } - if (field->getType() == FormField::FixedType) { - QString value = field->getFixedValue().c_str(); - widget = new QLabel(value, this); - } - if (field->getType() == FormField::ListSingleType) { - widget = createList(field); - } - if (field->getType() == FormField::TextMultiType) { - QString value = field->getTextMultiValue().c_str(); - QTextEdit* textWidget = new QTextEdit(this); - textWidget->setPlainText(value); - widget = textWidget; - } - if (field->getType() == FormField::TextPrivateType) { - QString value = field->getTextPrivateValue().c_str(); - QLineEdit* lineWidget = new QLineEdit(value, this); - lineWidget->setEchoMode(QLineEdit::Password); - widget = lineWidget; - } - if (field->getType() == FormField::TextSingleType) { - QString value = field->getTextSingleValue().c_str(); - widget = new QLineEdit(value, this); - } - if (field->getType() == FormField::JIDSingleType) { - QString value = field->getJIDSingleValue().toString().c_str(); - widget = new QLineEdit(value, this); - } - if (field->getType() == FormField::JIDMultiType) { - QString text = boost::join(field->getValues(), "\n").c_str(); - QTextEdit* textWidget = new QTextEdit(this); - textWidget->setPlainText(text); - widget = textWidget; - } - if (field->getType() == FormField::ListMultiType) { - widget = createList(field); - } - fields_[field->getName()] = widget; - return widget; +QWidget* QtFormWidget::createWidget(FormField::ref field, const FormField::Type type, const size_t index) { + QWidget* widget = nullptr; + if (type == FormField::BooleanType) { + QCheckBox* checkWidget = new QCheckBox(this); + checkWidget->setCheckState(field->getBoolValue() ? Qt::Checked : Qt::Unchecked); + widget = checkWidget; + } + if (type == FormField::FixedType) { + QString value = field->getFixedValue().c_str(); + widget = new QLabel(value, this); + } + if (type == FormField::ListSingleType) { + widget = createList(field); + } + if (type == FormField::TextMultiType) { + QString value = field->getTextMultiValue().c_str(); + QTextEdit* textWidget = new QTextEdit(this); + textWidget->setPlainText(value); + widget = textWidget; + } + if (type == FormField::TextPrivateType) { + QString value = field->getTextPrivateValue().c_str(); + QLineEdit* lineWidget = new QLineEdit(value, this); + lineWidget->setEchoMode(QLineEdit::Password); + widget = lineWidget; + } + if (type == FormField::TextSingleType) { + QString value = field->getTextSingleValue().c_str(); + widget = new QLineEdit(value, this); + } + if (type == FormField::JIDSingleType) { + QString value = field->getJIDSingleValue().toString().c_str(); + widget = new QLineEdit(value, this); + } + if (type == FormField::JIDMultiType) { + QString text = boost::join(field->getValues(), "\n").c_str(); + QTextEdit* textWidget = new QTextEdit(this); + textWidget->setPlainText(text); + widget = textWidget; + } + if (type == FormField::ListMultiType) { + widget = createList(field); + } + std::string indexString; + if (index) { + /* for multi-item forms we need to distinguish between the different rows */ + indexString = std::to_string(index); + } + fields_[field->getName() + indexString] = widget; + return widget; } Form::ref QtFormWidget::getCompletedForm() { - Form::ref result(new Form(Form::SubmitType)); - foreach (boost::shared_ptr<FormField> field, form_->getFields()) { - boost::shared_ptr<FormField> resultField = boost::make_shared<FormField>(field->getType()); - if (field->getType() == FormField::BooleanType) { - resultField->setBoolValue(qobject_cast<QCheckBox*>(fields_[field->getName()])->checkState() == Qt::Checked); - } - if (field->getType() == FormField::FixedType || field->getType() == FormField::HiddenType) { - resultField->addValue(field->getValues().empty() ? "" : field->getValues()[0]); - } - 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->addValue(field->getOptions()[i].value); - } - } - if (field->getType() == FormField::TextMultiType) { - QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); - QString string = widget->toPlainText(); - if (!string.isEmpty()) { - resultField->setTextMultiValue(Q2PSTRING(string)); - } - } - 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->addValue(Q2PSTRING(string)); - } - } - if (field->getType() == FormField::JIDMultiType) { - QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); - QString string = widget->toPlainText(); - if (!string.isEmpty()) { - QStringList lines = string.split("\n"); - foreach (QString line, lines) { - resultField->addValue(Q2PSTRING(line)); - } - } - } - if (field->getType() == FormField::ListMultiType) { - QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]); - foreach (QListWidgetItem* item, listWidget->selectedItems()) { - resultField->addValue(field->getOptions()[listWidget->row(item)].value); - } - } - resultField->setName(field->getName()); - result->addField(resultField); - } - return result; + Form::ref result(new Form(Form::SubmitType)); + for (auto&& field : form_->getFields()) { + std::shared_ptr<FormField> resultField = std::make_shared<FormField>(field->getType()); + if (field->getType() == FormField::BooleanType) { + resultField->setBoolValue(qobject_cast<QCheckBox*>(fields_[field->getName()])->checkState() == Qt::Checked); + } + if (field->getType() == FormField::FixedType || field->getType() == FormField::HiddenType) { + resultField->addValue(field->getValues().empty() ? "" : field->getValues()[0]); + } + 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->addValue(field->getOptions()[i].value); + } + } + if (field->getType() == FormField::TextMultiType) { + QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); + QString string = widget->toPlainText(); + if (!string.isEmpty()) { + resultField->setTextMultiValue(Q2PSTRING(string)); + } + } + 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->addValue(Q2PSTRING(string)); + } + } + if (field->getType() == FormField::JIDMultiType) { + QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); + QString string = widget->toPlainText(); + if (!string.isEmpty()) { + QStringList lines = string.split("\n"); + for (auto&& line : lines) { + resultField->addValue(Q2PSTRING(line)); + } + } + } + if (field->getType() == FormField::ListMultiType) { + QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]); + for (auto item : listWidget->selectedItems()) { + resultField->addValue(field->getOptions()[listWidget->row(item)].value); + } + } + resultField->setName(field->getName()); + result->addField(resultField); + } + return result; } template<class T> void QtFormWidget::setEnabled(QWidget* rawWidget, bool editable) { - T* widget = qobject_cast<T*>(rawWidget); - if (widget) { - widget->setEnabled(editable); - } + T* widget = qobject_cast<T*>(rawWidget); + if (widget) { + widget->setEnabled(editable); + } } template<class T> void QtFormWidget::setEditable(QWidget* rawWidget, bool editable) { - T* widget = qobject_cast<T*>(rawWidget); - if (widget) { - widget->setReadOnly(!editable); - } + T* widget = qobject_cast<T*>(rawWidget); + if (widget) { + widget->setReadOnly(!editable); + } } void QtFormWidget::setEditable(bool editable) { - if (!form_) { - return; - } - foreach (boost::shared_ptr<FormField> field, form_->getFields()) { - QWidget* widget = NULL; - if (field) { - widget = fields_[field->getName()]; - } - setEnabled<QCheckBox>(widget, editable); - setEnabled<QListWidget>(widget, editable); - setEditable<QTextEdit>(widget, editable); - setEditable<QLineEdit>(widget, editable); - } + if (!form_) { + return; + } + for (auto&& field : form_->getFields()) { + QWidget* widget = nullptr; + if (field) { + widget = fields_[field->getName()]; + } + setEnabled<QCheckBox>(widget, editable); + setEnabled<QListWidget>(widget, editable); + setEditable<QTextEdit>(widget, editable); + setEditable<QLineEdit>(widget, editable); + } } } diff --git a/Swift/QtUI/QtFormWidget.h b/Swift/QtUI/QtFormWidget.h index c7aae73..f228ccb 100644 --- a/Swift/QtUI/QtFormWidget.h +++ b/Swift/QtUI/QtFormWidget.h @@ -1,14 +1,15 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <map> + #include <QWidget> -#include <map> #include <Swiften/Elements/Form.h> class QListWidget; @@ -16,19 +17,19 @@ class QListWidget; namespace Swift { class QtFormWidget : public QWidget { - Q_OBJECT - public: - QtFormWidget(Form::ref form, QWidget* parent = NULL); - virtual ~QtFormWidget(); - Form::ref getCompletedForm(); - void setEditable(bool editable); - private: - QWidget* createWidget(FormField::ref field); - QListWidget* createList(FormField::ref field); - template<class T> void setEnabled(QWidget* rawWidget, bool editable); - template<class T> void setEditable(QWidget* rawWidget, bool editable); - std::map<std::string, QWidget*> fields_; - Form::ref form_; + Q_OBJECT + public: + QtFormWidget(Form::ref form, QWidget* parent = nullptr); + virtual ~QtFormWidget(); + Form::ref getCompletedForm(); + void setEditable(bool editable); + private: + 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); + template<class T> void setEditable(QWidget* rawWidget, bool editable); + std::map<std::string, QWidget*> fields_; + Form::ref form_; }; } diff --git a/Swift/QtUI/QtHighlightEditorWidget.cpp b/Swift/QtUI/QtHighlightEditorWidget.cpp deleted file mode 100644 index 7ff094e..0000000 --- a/Swift/QtUI/QtHighlightEditorWidget.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 deleted file mode 100644 index 1293c87..0000000 --- a/Swift/QtUI/QtHighlightEditorWidget.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0f39168..0000000 --- a/Swift/QtUI/QtHighlightEditorWidget.ui +++ /dev/null @@ -1,124 +0,0 @@ -<?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/QtHighlightNotificationConfigDialog.cpp b/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp new file mode 100644 index 0000000..0521a2d --- /dev/null +++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2016-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtHighlightNotificationConfigDialog.h> + +#include <Swiften/Base/Log.h> + +#include <Swift/Controllers/Highlighting/HighlightManager.h> + +#include <Swift/QtUI/QtCheckBoxStyledItemDelegate.h> +#include <Swift/QtUI/QtColorSelectionStyledItemDelegate.h> +#include <Swift/QtUI/QtSoundSelectionStyledItemDelegate.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtHighlightNotificationConfigDialog::QtHighlightNotificationConfigDialog(QtSettingsProvider* settings, QWidget* parent) : QDialog(parent), settings_(settings) { + ui_.setupUi(this); + + // setup custom delegates for checkboxes, color selection, and sound selection + ui_.userHighlightTreeWidget->setItemDelegateForColumn(1, new QtColorSelectionStyledItemDelegate(this)); + ui_.userHighlightTreeWidget->setItemDelegateForColumn(2, new QtColorSelectionStyledItemDelegate(this)); + ui_.userHighlightTreeWidget->setItemDelegateForColumn(3, new QtSoundSelectionStyledItemDelegate(this)); + ui_.userHighlightTreeWidget->setItemDelegateForColumn(4, new QtCheckBoxStyledItemDelegate(this)); + + ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(1, new QtCheckBoxStyledItemDelegate(this)); + ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(2, new QtColorSelectionStyledItemDelegate(this)); + ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(3, new QtColorSelectionStyledItemDelegate(this)); + ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(4, new QtSoundSelectionStyledItemDelegate(this)); + ui_.keywordHighlightTreeWidget->setItemDelegateForColumn(5, new QtCheckBoxStyledItemDelegate(this)); + + // user highlight edit slots + connect(ui_.addUserHighlightPushButton, &QPushButton::clicked, [&](bool) { + auto item = new QTreeWidgetItem(); + item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + item->setData(0, Qt::EditRole, ""); + item->setData(1, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#000000")); + item->setData(2, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#ffff00")); + item->setData(3, Qt::EditRole, ""); + item->setData(4, QtCheckBoxStyledItemDelegate::DATA_ROLE, QVariant(true)); + ui_.userHighlightTreeWidget->addTopLevelItem(item); + }); + connect(ui_.removeUserHighlightPushButton, &QPushButton::clicked, [&](bool) { + auto currentItem = ui_.userHighlightTreeWidget->currentItem(); + if (currentItem) { + ui_.userHighlightTreeWidget->takeTopLevelItem(ui_.userHighlightTreeWidget->indexOfTopLevelItem(currentItem)); + } + }); + connect(ui_.userHighlightTreeWidget, &QTreeWidget::currentItemChanged, [&](QTreeWidgetItem* current, QTreeWidgetItem* ) { + ui_.removeUserHighlightPushButton->setEnabled(current != nullptr); + }); + + // keyword highlight edit slots + connect(ui_.addKeywordHighlightPushButton, &QPushButton::clicked, [&](bool) { + auto item = new QTreeWidgetItem(); + item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + item->setData(0, Qt::EditRole, ""); + item->setData(1, QtCheckBoxStyledItemDelegate::DATA_ROLE, QVariant(false)); + item->setData(2, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#000000")); + item->setData(3, QtColorSelectionStyledItemDelegate::DATA_ROLE, QColor("#ffff00")); + item->setData(4, Qt::EditRole, ""); + item->setData(5, QtCheckBoxStyledItemDelegate::DATA_ROLE, QVariant(true)); + ui_.keywordHighlightTreeWidget->addTopLevelItem(item); + }); + connect(ui_.removeKeywordHighlightPushButton, &QPushButton::clicked, [&](bool) { + auto currentItem = ui_.keywordHighlightTreeWidget->currentItem(); + if (currentItem) { + ui_.keywordHighlightTreeWidget->takeTopLevelItem(ui_.keywordHighlightTreeWidget->indexOfTopLevelItem(currentItem)); + } + }); + connect(ui_.keywordHighlightTreeWidget, &QTreeWidget::currentItemChanged, [&](QTreeWidgetItem* current, QTreeWidgetItem* ) { + ui_.removeKeywordHighlightPushButton->setEnabled(current != nullptr); + }); + + // setup slots for main dialog buttons + connect(ui_.buttonBox, &QDialogButtonBox::clicked, [&](QAbstractButton* clickedButton) { + if (clickedButton == ui_.buttonBox->button(QDialogButtonBox::RestoreDefaults)) { + if (highlightManager_) { + highlightManager_->resetToDefaultConfiguration(); + setHighlightConfigurationToDialog(*highlightManager_->getConfiguration()); + } + } + }); + connect(this, &QDialog::accepted, [&]() { + if (highlightManager_) { + highlightManager_->setConfiguration(getHighlightConfigurationFromDialog()); + } + }); +} + +QtHighlightNotificationConfigDialog::~QtHighlightNotificationConfigDialog() { +} + +void QtHighlightNotificationConfigDialog::show() { + if (highlightManager_) { + setHighlightConfigurationToDialog(*(highlightManager_->getConfiguration())); + } + QWidget::show(); + QWidget::activateWindow(); +} + +void QtHighlightNotificationConfigDialog::setHighlightManager(HighlightManager* highlightManager) { + highlightManager_ = highlightManager; +} + +void QtHighlightNotificationConfigDialog::setContactSuggestions(const std::vector<Contact::ref>& /*suggestions*/) { + +} + +HighlightConfiguration QtHighlightNotificationConfigDialog::getHighlightConfigurationFromDialog() const { + auto qtColorToOptionalString = [&](const QColor& color) { + boost::optional<std::string> colorString; + if (color.isValid()) { + colorString = Q2PSTRING(color.name(QColor::HexRgb)); + } + return colorString; + }; + + auto getHighlightActionFromWidgetItem = [&](const QTreeWidgetItem* item, int startingColumn) { + HighlightAction action; + + action.setFrontColor(qtColorToOptionalString(item->data(startingColumn, QtColorSelectionStyledItemDelegate::DATA_ROLE).value<QColor>())); + action.setBackColor(qtColorToOptionalString(item->data(startingColumn + 1, QtColorSelectionStyledItemDelegate::DATA_ROLE).value<QColor>())); + + std::string soundFilePath = Q2PSTRING(item->data(startingColumn + 2, Qt::EditRole).toString()); + if (soundFilePath == "defaultSound") { + action.setSoundFilePath(boost::optional<std::string>("")); + } + else if (soundFilePath.empty()) { + action.setSoundFilePath(boost::optional<std::string>()); + } + else { + action.setSoundFilePath(boost::optional<std::string>("")); + } + + action.setSystemNotificationEnabled(item->data(startingColumn + 3, QtCheckBoxStyledItemDelegate::DATA_ROLE).toBool()); + return action; + }; + + HighlightConfiguration uiConfiguration; + + // contact highlights + for (int i = 0; i < ui_.userHighlightTreeWidget->topLevelItemCount(); i++) { + auto item = ui_.userHighlightTreeWidget->topLevelItem(i); + HighlightConfiguration::ContactHighlight contactHighlight; + contactHighlight.name = Q2PSTRING(item->data(0, Qt::EditRole).toString()); + contactHighlight.action = getHighlightActionFromWidgetItem(item, 1); + uiConfiguration.contactHighlights.push_back(contactHighlight); + } + + // keyword highlights + for (int i = 0; i < ui_.keywordHighlightTreeWidget->topLevelItemCount(); i++) { + auto item = ui_.keywordHighlightTreeWidget->topLevelItem(i); + HighlightConfiguration::KeywordHightlight keywordHighlight; + keywordHighlight.keyword = Q2PSTRING(item->data(0, Qt::EditRole).toString()); + keywordHighlight.matchCaseSensitive = item->data(1, QtCheckBoxStyledItemDelegate::DATA_ROLE).toBool(); + keywordHighlight.action = getHighlightActionFromWidgetItem(item, 2); + uiConfiguration.keywordHighlights.push_back(keywordHighlight); + } + + // general configuration + uiConfiguration.playSoundOnIncomingDirectMessages = ui_.playSoundOnDirectMessagesCheckBox->isChecked(); + uiConfiguration.showNotificationOnIncomingDirectMessages = ui_.notificationOnDirectMessagesCheckBox->isChecked(); + uiConfiguration.playSoundOnIncomingGroupchatMessages = ui_.playSoundOnGroupMessagesCheckBox->isChecked(); + uiConfiguration.showNotificationOnIncomingGroupchatMessages = ui_.notificationOnGroupMessagesCheckBox->isChecked(); + + uiConfiguration.ownMentionAction.setFrontColor(qtColorToOptionalString(ui_.mentionTextColorColorButton->getColor())); + uiConfiguration.ownMentionAction.setBackColor(qtColorToOptionalString(ui_.mentionBackgroundColorButton->getColor())); + uiConfiguration.ownMentionAction.setSoundFilePath(ui_.playSoundOnMentionCheckBox->isChecked() ? boost::optional<std::string>("") : boost::optional<std::string>()); + uiConfiguration.ownMentionAction.setSystemNotificationEnabled(ui_.notificationOnMentionCheckBox->isChecked()); + return uiConfiguration; +} + +void QtHighlightNotificationConfigDialog::setHighlightConfigurationToDialog(const HighlightConfiguration& config) { + auto optionalStringToQtColor = [](const boost::optional<std::string>& colorString) { + QColor qtColor; + if (colorString) { + qtColor = QColor(P2QSTRING(colorString.get_value_or(std::string("")))); + } + return qtColor; + }; + + auto optionalSoundPathStringToQString = [](const boost::optional<std::string>& soundPath) { + QString ret; + if (soundPath) { + if (soundPath.get_value_or("").empty()) { + ret = "defaultSound"; + } + else { + ret = P2QSTRING(soundPath.get_value_or("")); + } + } + return ret; + }; + + auto setHighlightActionOnTreeWidgetItem = [&](QTreeWidgetItem* item, int startingColumn, const HighlightAction& action) { + item->setData(startingColumn, QtColorSelectionStyledItemDelegate::DATA_ROLE, optionalStringToQtColor(action.getFrontColor())); + item->setData(startingColumn + 1, QtColorSelectionStyledItemDelegate::DATA_ROLE, optionalStringToQtColor(action.getBackColor())); + item->setData(startingColumn + 2, Qt::DisplayRole, P2QSTRING(action.getSoundFilePath().get_value_or(std::string("")))); + item->setData(startingColumn + 2, Qt::EditRole, optionalSoundPathStringToQString(action.getSoundFilePath())); + item->setData(startingColumn + 3, QtCheckBoxStyledItemDelegate::DATA_ROLE, action.isSystemNotificationEnabled()); + }; + + // contact highlights + ui_.userHighlightTreeWidget->clear(); + for (const auto& contactHighlight : config.contactHighlights) { + auto item = new QTreeWidgetItem(); + item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); + item->setData(0, Qt::DisplayRole, P2QSTRING(contactHighlight.name)); + item->setData(0, Qt::EditRole, P2QSTRING(contactHighlight.name)); + + setHighlightActionOnTreeWidgetItem(item, 1, contactHighlight.action); + + ui_.userHighlightTreeWidget->addTopLevelItem(item); + } + + // keyword highlights + ui_.keywordHighlightTreeWidget->clear(); + for (const auto& keywordHighlight : config.keywordHighlights) { + auto item = new QTreeWidgetItem(); + item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); + item->setData(0, Qt::DisplayRole, P2QSTRING(keywordHighlight.keyword)); + item->setData(0, Qt::EditRole, P2QSTRING(keywordHighlight.keyword)); + item->setData(1, QtCheckBoxStyledItemDelegate::DATA_ROLE, keywordHighlight.matchCaseSensitive); + + setHighlightActionOnTreeWidgetItem(item, 2, keywordHighlight.action); + + ui_.keywordHighlightTreeWidget->addTopLevelItem(item); + } + + // general configuration + ui_.playSoundOnDirectMessagesCheckBox->setChecked(config.playSoundOnIncomingDirectMessages); + ui_.notificationOnDirectMessagesCheckBox->setChecked(config.showNotificationOnIncomingDirectMessages); + ui_.playSoundOnGroupMessagesCheckBox->setChecked(config.playSoundOnIncomingGroupchatMessages); + ui_.notificationOnGroupMessagesCheckBox->setChecked(config.showNotificationOnIncomingGroupchatMessages); + + ui_.mentionTextColorColorButton->setColor(optionalStringToQtColor(config.ownMentionAction.getFrontColor())); + ui_.mentionBackgroundColorButton->setColor(optionalStringToQtColor(config.ownMentionAction.getBackColor())); + ui_.playSoundOnMentionCheckBox->setChecked(config.ownMentionAction.getSoundFilePath().is_initialized()); + ui_.notificationOnMentionCheckBox->setChecked(config.ownMentionAction.isSystemNotificationEnabled()); +} + +} diff --git a/Swift/QtUI/QtHighlightNotificationConfigDialog.h b/Swift/QtUI/QtHighlightNotificationConfigDialog.h new file mode 100644 index 0000000..03044eb --- /dev/null +++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <Swift/Controllers/Highlighting/HighlightConfiguration.h> +#include <Swift/Controllers/UIInterfaces/HighlightEditorWindow.h> + +#include <Swift/QtUI/ui_QtHighlightNotificationConfigDialog.h> + +namespace Swift { + + class QtSettingsProvider; + class QtSuggestingJIDInput; + + class QtHighlightNotificationConfigDialog : public QDialog, public HighlightEditorWindow { + Q_OBJECT + + public: + QtHighlightNotificationConfigDialog(QtSettingsProvider* settings, QWidget* parent = nullptr); + virtual ~QtHighlightNotificationConfigDialog(); + + virtual void show(); + virtual void setHighlightManager(HighlightManager* highlightManager); + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions); + + private: + HighlightConfiguration getHighlightConfigurationFromDialog() const; + void setHighlightConfigurationToDialog(const HighlightConfiguration& config); + + private: + Ui::QtHighlightNotificationConfigDialog ui_; + QtSettingsProvider* settings_; + HighlightManager* highlightManager_ = nullptr; + QtSuggestingJIDInput* jid_ = nullptr; + }; + +} diff --git a/Swift/QtUI/QtHighlightNotificationConfigDialog.ui b/Swift/QtUI/QtHighlightNotificationConfigDialog.ui new file mode 100644 index 0000000..8ee0d6f --- /dev/null +++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.ui @@ -0,0 +1,537 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtHighlightNotificationConfigDialog</class> + <widget class="QDialog" name="QtHighlightNotificationConfigDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>629</width> + <height>515</height> + </rect> + </property> + <property name="windowTitle"> + <string>Highlight and Notification Configuration</string> + </property> + <property name="sizeGripEnabled"> + <bool>false</bool> + </property> + <property name="modal"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Highlight messages from these people</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <item> + <widget class="QTreeWidget" name="userHighlightTreeWidget"> + <property name="tabKeyNavigation"> + <bool>true</bool> + </property> + <property name="showDropIndicator" stdset="0"> + <bool>false</bool> + </property> + <property name="dragEnabled"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::InternalMove</enum> + </property> + <property name="defaultDropAction"> + <enum>Qt::MoveAction</enum> + </property> + <property name="indentation"> + <number>0</number> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="animated"> + <bool>true</bool> + </property> + <property name="headerHidden"> + <bool>false</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerCascadingSectionResizes"> + <bool>false</bool> + </attribute> + <attribute name="headerDefaultSectionSize"> + <number>80</number> + </attribute> + <attribute name="headerHighlightSections"> + <bool>false</bool> + </attribute> + <attribute name="headerMinimumSectionSize"> + <number>15</number> + </attribute> + <column> + <property name="text"> + <string>Nickname</string> + </property> + </column> + <column> + <property name="text"> + <string>Text color</string> + </property> + </column> + <column> + <property name="text"> + <string>Background color</string> + </property> + </column> + <column> + <property name="text"> + <string>Play sound</string> + </property> + </column> + <column> + <property name="text"> + <string>Create notification</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>12</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="addUserHighlightPushButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="sizeIncrement"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>13</pointsize> + <weight>50</weight> + <italic>false</italic> + <bold>false</bold> + <underline>false</underline> + <strikeout>false</strikeout> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string>+</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeUserHighlightPushButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximumSize"> + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="text"> + <string>-</string> + </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> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Highlight messages containing these keywords</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>6</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <item> + <widget class="QTreeWidget" name="keywordHighlightTreeWidget"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>300</height> + </size> + </property> + <property name="tabKeyNavigation"> + <bool>true</bool> + </property> + <property name="showDropIndicator" stdset="0"> + <bool>false</bool> + </property> + <property name="dragEnabled"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::InternalMove</enum> + </property> + <property name="defaultDropAction"> + <enum>Qt::MoveAction</enum> + </property> + <property name="indentation"> + <number>0</number> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="animated"> + <bool>true</bool> + </property> + <column> + <property name="text"> + <string>Keyword</string> + </property> + </column> + <column> + <property name="text"> + <string>Match case sensitive</string> + </property> + </column> + <column> + <property name="text"> + <string>Text color</string> + </property> + </column> + <column> + <property name="text"> + <string>Background color</string> + </property> + </column> + <column> + <property name="text"> + <string>Play sound</string> + </property> + </column> + <column> + <property name="text"> + <string>Create notification</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="spacing"> + <number>12</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="addKeywordHighlightPushButton"> + <property name="maximumSize"> + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="text"> + <string>+</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="removeKeywordHighlightPushButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximumSize"> + <size> + <width>26</width> + <height>26</height> + </size> + </property> + <property name="text"> + <string>-</string> + </property> + </widget> + </item> + <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> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>General notification settings</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <item row="3" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="Swift::QtColorToolButton" name="mentionBackgroundColorButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Highlight background color on own mention</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"> + <widget class="QCheckBox" name="notificationOnGroupMessagesCheckBox"> + <property name="text"> + <string>Create notification on incoming group messages</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="notificationOnMentionCheckBox"> + <property name="text"> + <string>Create notification when my name is mentioned</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="playSoundOnDirectMessagesCheckBox"> + <property name="text"> + <string>Play sound on incoming direct messages</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="playSoundOnGroupMessagesCheckBox"> + <property name="text"> + <string>Play sound on incoming group messages</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="notificationOnDirectMessagesCheckBox"> + <property name="text"> + <string>Create notification on incoming direct messages</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="playSoundOnMentionCheckBox"> + <property name="text"> + <string>Play sound when my name is mentioned</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <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> + <widget class="Swift::QtColorToolButton" name="mentionTextColorColorButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Highlight text color on own mention</string> + </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> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set> + </property> + <property name="centerButtons"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Swift::QtColorToolButton</class> + <extends>QToolButton</extends> + <header>QtColorToolButton.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>userHighlightTreeWidget</tabstop> + <tabstop>addUserHighlightPushButton</tabstop> + <tabstop>removeUserHighlightPushButton</tabstop> + <tabstop>keywordHighlightTreeWidget</tabstop> + <tabstop>addKeywordHighlightPushButton</tabstop> + <tabstop>removeKeywordHighlightPushButton</tabstop> + <tabstop>playSoundOnDirectMessagesCheckBox</tabstop> + <tabstop>notificationOnDirectMessagesCheckBox</tabstop> + <tabstop>playSoundOnGroupMessagesCheckBox</tabstop> + <tabstop>notificationOnGroupMessagesCheckBox</tabstop> + <tabstop>playSoundOnMentionCheckBox</tabstop> + <tabstop>notificationOnMentionCheckBox</tabstop> + <tabstop>mentionTextColorColorButton</tabstop> + <tabstop>mentionBackgroundColorButton</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>QtHighlightNotificationConfigDialog</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>QtHighlightNotificationConfigDialog</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/QtHighlightRuleWidget.cpp b/Swift/QtUI/QtHighlightRuleWidget.cpp deleted file mode 100644 index 9c0df5e..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#include <QDataWidgetMapper> -#include <QStringListModel> -#include <QFileDialog> - -#include <Swift/QtUI/QtHighlightRuleWidget.h> -#include <Swift/QtUI/QtHighlightRulesItemModel.h> - -namespace Swift { - -QtHighlightRuleWidget::QtHighlightRuleWidget(QWidget* parent) - : QWidget(parent) -{ - ui_.setupUi(this); - - QStringList applyToItems; - for (int i = 0; i < QtHighlightRulesItemModel::ApplyToEOL; ++i) { - applyToItems << QtHighlightRulesItemModel::getApplyToString(i); - } - QStringListModel * applyToModel = new QStringListModel(applyToItems, this); - ui_.applyTo->setModel(applyToModel); - - connect(ui_.highlightText, SIGNAL(toggled(bool)), SLOT(onHighlightTextToggled(bool))); - connect(ui_.customColors, SIGNAL(toggled(bool)), SLOT(onCustomColorsToggled(bool))); - connect(ui_.playSound, SIGNAL(toggled(bool)), SLOT(onPlaySoundToggled(bool))); - connect(ui_.customSound, SIGNAL(toggled(bool)), SLOT(onCustomSoundToggled(bool))); - connect(ui_.soundFileButton, SIGNAL(clicked()), SLOT(onSoundFileButtonClicked())); - - mapper_ = new QDataWidgetMapper(this); - hasValidIndex_ = false; - model_ = NULL; -} - -QtHighlightRuleWidget::~QtHighlightRuleWidget() -{ -} - -/** Widget does not gain ownership over the model */ -void QtHighlightRuleWidget::setModel(QtHighlightRulesItemModel* model) -{ - model_ = model; - mapper_->setModel(model_); -} - -void QtHighlightRuleWidget::setActiveIndex(const QModelIndex& index) -{ - if (index.isValid()) { - if (!hasValidIndex_) { - mapper_->addMapping(ui_.applyTo, QtHighlightRulesItemModel::ApplyTo, "currentIndex"); - mapper_->addMapping(ui_.senders, QtHighlightRulesItemModel::Sender, "plainText"); - mapper_->addMapping(ui_.keywords, QtHighlightRulesItemModel::Keyword, "plainText"); - mapper_->addMapping(ui_.nickIsKeyword, QtHighlightRulesItemModel::NickIsKeyword); - mapper_->addMapping(ui_.matchCase, QtHighlightRulesItemModel::MatchCase); - mapper_->addMapping(ui_.matchWholeWords, QtHighlightRulesItemModel::MatchWholeWords); - mapper_->addMapping(ui_.highlightText, QtHighlightRulesItemModel::HighlightText); - mapper_->addMapping(ui_.foreground, QtHighlightRulesItemModel::TextColor, "color"); - mapper_->addMapping(ui_.background, QtHighlightRulesItemModel::TextBackground, "color"); - mapper_->addMapping(ui_.playSound, QtHighlightRulesItemModel::PlaySound); - mapper_->addMapping(ui_.soundFile, QtHighlightRulesItemModel::SoundFile); - } - mapper_->setCurrentModelIndex(index); - ui_.customColors->setChecked(ui_.foreground->getColor().isValid() || ui_.background->getColor().isValid()); - ui_.customSound->setChecked(!ui_.soundFile->text().isEmpty()); - ui_.applyTo->focusWidget(); - } else { - if (hasValidIndex_) { - mapper_->clearMapping(); - } - } - - hasValidIndex_ = index.isValid(); -} - -void QtHighlightRuleWidget::onCustomColorsToggled(bool enabled) -{ - if (!enabled) { - ui_.foreground->setColor(QColor()); - ui_.background->setColor(QColor()); - } - ui_.foreground->setEnabled(enabled); - ui_.background->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onCustomSoundToggled(bool enabled) -{ - if (enabled) { - if (ui_.soundFile->text().isEmpty()) { - onSoundFileButtonClicked(); - } - } else { - ui_.soundFile->clear(); - } - ui_.soundFile->setEnabled(enabled); - ui_.soundFileButton->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onSoundFileButtonClicked() -{ - QString s = QFileDialog::getOpenFileName(this, tr("Choose sound file"), QString(), tr("Sound files (*.wav)")); - if (!s.isEmpty()) { - ui_.soundFile->setText(s); - } -} - -void QtHighlightRuleWidget::onHighlightTextToggled(bool enabled) -{ - ui_.customColors->setEnabled(enabled); -} - -void QtHighlightRuleWidget::onPlaySoundToggled(bool enabled) -{ - ui_.customSound->setEnabled(enabled); -} - -void QtHighlightRuleWidget::save() -{ - if (hasValidIndex_) { - mapper_->submit(); - } -} - -void QtHighlightRuleWidget::revert() -{ - if (hasValidIndex_) { - mapper_->revert(); - } -} - -} diff --git a/Swift/QtUI/QtHighlightRuleWidget.h b/Swift/QtUI/QtHighlightRuleWidget.h deleted file mode 100644 index 8a59a14..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#pragma once - -#include <QWidget> -#include <QModelIndex> - -#include <Swift/QtUI/ui_QtHighlightRuleWidget.h> - -class QDataWidgetMapper; - -namespace Swift { - - class QtHighlightRulesItemModel; - - class QtHighlightRuleWidget : public QWidget - { - Q_OBJECT - - public: - explicit QtHighlightRuleWidget(QWidget* parent = NULL); - ~QtHighlightRuleWidget(); - - void setModel(QtHighlightRulesItemModel* model); - - public slots: - void setActiveIndex(const QModelIndex&); - void save(); - void revert(); - - private slots: - void onHighlightTextToggled(bool); - void onCustomColorsToggled(bool); - void onPlaySoundToggled(bool); - void onCustomSoundToggled(bool); - void onSoundFileButtonClicked(); - - private: - QDataWidgetMapper * mapper_; - QtHighlightRulesItemModel * model_; - bool hasValidIndex_; - Ui::QtHighlightRuleWidget ui_; - }; - -} diff --git a/Swift/QtUI/QtHighlightRuleWidget.ui b/Swift/QtUI/QtHighlightRuleWidget.ui deleted file mode 100644 index 9c465f9..0000000 --- a/Swift/QtUI/QtHighlightRuleWidget.ui +++ /dev/null @@ -1,260 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>QtHighlightRuleWidget</class> - <widget class="QWidget" name="QtHighlightRuleWidget"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>361</width> - <height>524</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QGroupBox" name="groupBox"> - <property name="title"> - <string>Rule conditions</string> - </property> - <layout class="QFormLayout" name="formLayout"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::ExpandingFieldsGrow</enum> - </property> - <item row="0" column="0" colspan="2"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Choose when this rule should be applied. -If you want to provide more than one sender or keyword, input them in separate lines.</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0" colspan="2"> - <widget class="Line" name="line"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>&Apply to:</string> - </property> - <property name="buddy"> - <cstring>applyTo</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QComboBox" name="applyTo"/> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>&Senders:</string> - </property> - <property name="buddy"> - <cstring>senders</cstring> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QPlainTextEdit" name="senders"/> - </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>&Keywords:</string> - </property> - <property name="buddy"> - <cstring>keywords</cstring> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QPlainTextEdit" name="keywords"/> - </item> - <item row="5" column="1"> - <widget class="QCheckBox" name="nickIsKeyword"> - <property name="text"> - <string>Treat &nick as a keyword (in MUC)</string> - </property> - </widget> - </item> - <item row="6" column="1"> - <widget class="QCheckBox" name="matchWholeWords"> - <property name="text"> - <string>Match whole &words</string> - </property> - </widget> - </item> - <item row="7" column="1"> - <widget class="QCheckBox" name="matchCase"> - <property name="text"> - <string>Match &case</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Actions</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QCheckBox" name="highlightText"> - <property name="text"> - <string>&Highlight text</string> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>28</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QCheckBox" name="customColors"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Custom c&olors:</string> - </property> - </widget> - </item> - <item> - <widget class="Swift::QtColorToolButton" name="foreground"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>&Foreground</string> - </property> - <property name="toolButtonStyle"> - <enum>Qt::ToolButtonTextBesideIcon</enum> - </property> - </widget> - </item> - <item> - <widget class="Swift::QtColorToolButton" name="background"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>&Background</string> - </property> - <property name="toolButtonStyle"> - <enum>Qt::ToolButtonTextBesideIcon</enum> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QCheckBox" name="playSound"> - <property name="text"> - <string>&Play sound</string> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>28</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QCheckBox" name="customSound"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>Custom soun&d:</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="soundFile"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QToolButton" name="soundFileButton"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>...</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>101</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>Swift::QtColorToolButton</class> - <extends>QToolButton</extends> - <header>QtColorToolButton.h</header> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/Swift/QtUI/QtHighlightRulesItemModel.cpp b/Swift/QtUI/QtHighlightRulesItemModel.cpp deleted file mode 100644 index 4efa712..0000000 --- a/Swift/QtUI/QtHighlightRulesItemModel.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#include <boost/algorithm/string.hpp> -#include <boost/lambda/lambda.hpp> -#include <boost/numeric/conversion/cast.hpp> - -#include <QStringList> -#include <QColor> - -#include <Swift/Controllers/HighlightManager.h> -#include <Swift/QtUI/QtHighlightRulesItemModel.h> -#include <Swift/QtUI/QtSwiftUtil.h> - -namespace Swift { - -QtHighlightRulesItemModel::QtHighlightRulesItemModel(QObject* parent) : QAbstractItemModel(parent), highlightManager_(NULL) -{ -} - -void QtHighlightRulesItemModel::setHighlightManager(HighlightManager* highlightManager) -{ - emit layoutAboutToBeChanged(); - highlightManager_ = highlightManager; - emit layoutChanged(); -} - -QVariant QtHighlightRulesItemModel::headerData(int section, Qt::Orientation /* orientation */, int role) const -{ - if (role == Qt::DisplayRole) { - switch (section) { - case ApplyTo: return QVariant(tr("Apply to")); - case Sender: return QVariant(tr("Sender")); - case Keyword: return QVariant(tr("Keyword")); - case Action: return QVariant(tr("Action")); - case NickIsKeyword: return QVariant(tr("Nick Is Keyword")); - case MatchCase: return QVariant(tr("Match Case")); - case MatchWholeWords: return QVariant(tr("Match Whole Words")); - case HighlightText: return QVariant(tr("Highlight Text")); - case TextColor: return QVariant(tr("Text Color")); - case TextBackground: return QVariant(tr("Text Background")); - case PlaySound: return QVariant(tr("Play Sounds")); - case SoundFile: return QVariant(tr("Sound File")); - } - } - - return QVariant(); -} - -int QtHighlightRulesItemModel::columnCount(const QModelIndex& /* parent */) const -{ - return NumberOfColumns; -} - -QVariant QtHighlightRulesItemModel::data(const QModelIndex &index, int role) const -{ - if (index.isValid() && highlightManager_ && (role == Qt::DisplayRole || role == Qt::EditRole)) { - - const char* separator = (role == Qt::DisplayRole) ? " ; " : "\n"; - - if (boost::numeric_cast<std::vector<std::string>::size_type>(index.row()) < highlightManager_->getRules().size()) { - const HighlightRule& r = highlightManager_->getRules()[index.row()]; - switch (index.column()) { - case ApplyTo: { - int applyTo = 0; - if (r.getMatchChat() && r.getMatchMUC()) { - applyTo = 1; - } else if (r.getMatchChat()) { - applyTo = 2; - } else if (r.getMatchMUC()) { - applyTo = 3; - } - - if (role == Qt::DisplayRole) { - return QVariant(getApplyToString(applyTo)); - } else { - return QVariant(applyTo); - } - } - case Sender: { - std::string s = boost::join(r.getSenders(), separator); - return QVariant(P2QSTRING(s)); - } - case Keyword: { - std::string s = boost::join(r.getKeywords(), separator); - QString qs(P2QSTRING(s)); - if (role == Qt::DisplayRole && r.getNickIsKeyword()) { - qs = tr("<nick>") + (qs.isEmpty() ? "" : separator + qs); - } - return QVariant(qs); - } - case Action: { - std::vector<std::string> v; - const HighlightAction & action = r.getAction(); - if (action.highlightText()) { - v.push_back(Q2PSTRING(tr("Highlight text"))); - } - if (action.playSound()) { - v.push_back(Q2PSTRING(tr("Play sound"))); - } - std::string s = boost::join(v, separator); - return QVariant(P2QSTRING(s)); - } - case NickIsKeyword: { - return QVariant(r.getNickIsKeyword()); - } - case MatchCase: { - return QVariant(r.getMatchCase()); - } - case MatchWholeWords: { - return QVariant(r.getMatchWholeWords()); - } - case HighlightText: { - return QVariant(r.getAction().highlightText()); - } - case TextColor: { - return QVariant(QColor(P2QSTRING(r.getAction().getTextColor()))); - } - case TextBackground: { - return QVariant(QColor(P2QSTRING(r.getAction().getTextBackground()))); - } - case PlaySound: { - return QVariant(r.getAction().playSound()); - } - case SoundFile: { - return QVariant(P2QSTRING(r.getAction().getSoundFile())); - } - } - } - } - return QVariant(); -} - -bool QtHighlightRulesItemModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.isValid() && highlightManager_ && role == Qt::EditRole) { - if (boost::numeric_cast<std::vector<std::string>::size_type>(index.row()) < highlightManager_->getRules().size()) { - HighlightRule r = highlightManager_->getRule(index.row()); - std::vector<int> changedColumns; - switch (index.column()) { - case ApplyTo: { - bool ok = false; - int applyTo = value.toInt(&ok); - if (!ok) { - return false; - } - r.setMatchChat(applyTo == ApplyToAll || applyTo == ApplyToChat); - r.setMatchMUC(applyTo == ApplyToAll || applyTo == ApplyToMUC); - break; - } - case Sender: - case Keyword: { - std::string s = Q2PSTRING(value.toString()); - std::vector<std::string> v; - boost::split(v, s, boost::is_any_of("\n")); - v.erase(std::remove_if(v.begin(), v.end(), boost::lambda::_1 == ""), v.end()); - if (index.column() == Sender) { - r.setSenders(v); - } else { - r.setKeywords(v); - } - break; - } - case NickIsKeyword: { - r.setNickIsKeyword(value.toBool()); - changedColumns.push_back(Keyword); // "<nick>" - break; - } - case MatchCase: { - r.setMatchCase(value.toBool()); - break; - } - case MatchWholeWords: { - r.setMatchWholeWords(value.toBool()); - break; - } - case HighlightText: { - r.getAction().setHighlightText(value.toBool()); - changedColumns.push_back(Action); - break; - } - case TextColor: { - QColor c = value.value<QColor>(); - r.getAction().setTextColor(c.isValid() ? Q2PSTRING(c.name()) : ""); - break; - } - case TextBackground: { - QColor c = value.value<QColor>(); - r.getAction().setTextBackground(c.isValid() ? Q2PSTRING(c.name()) : ""); - break; - } - case PlaySound: { - r.getAction().setPlaySound(value.toBool()); - changedColumns.push_back(Action); - break; - } - case SoundFile: { - r.getAction().setSoundFile(Q2PSTRING(value.toString())); - break; - } - } - - highlightManager_->setRule(index.row(), r); - emit dataChanged(index, index); - foreach (int column, changedColumns) { - QModelIndex i = createIndex(index.row(), column, (void*) 0); - emit dataChanged(i, i); - } - } - } - - return false; -} - -QModelIndex QtHighlightRulesItemModel::parent(const QModelIndex& /* child */) const -{ - return QModelIndex(); -} - -int QtHighlightRulesItemModel::rowCount(const QModelIndex& /* parent */) const -{ - return highlightManager_ ? highlightManager_->getRules().size() : 0; -} - -QModelIndex QtHighlightRulesItemModel::index(int row, int column, const QModelIndex& /* parent */) const -{ - return createIndex(row, column, (void*) 0); -} - -bool QtHighlightRulesItemModel::insertRows(int row, int count, const QModelIndex& /* parent */) -{ - if (highlightManager_) { - beginInsertRows(QModelIndex(), row, row + count); - while (count--) { - highlightManager_->insertRule(row, HighlightRule()); - } - endInsertRows(); - return true; - } - return false; -} - -bool QtHighlightRulesItemModel::removeRows(int row, int count, const QModelIndex& /* parent */) -{ - if (highlightManager_) { - beginRemoveRows(QModelIndex(), row, row + count); - while (count--) { - highlightManager_->removeRule(row); - } - endRemoveRows(); - return true; - } - return false; -} - -bool QtHighlightRulesItemModel::swapRows(int row1, int row2) -{ - if (highlightManager_) { - assert(row1 >= 0 && row2 >= 0 && boost::numeric_cast<std::vector<std::string>::size_type>(row1) < highlightManager_->getRules().size() && boost::numeric_cast<std::vector<std::string>::size_type>(row2) < highlightManager_->getRules().size()); - HighlightRule r = highlightManager_->getRule(row1); - highlightManager_->setRule(row1, highlightManager_->getRule(row2)); - highlightManager_->setRule(row2, r); - emit dataChanged(index(row1, 0, QModelIndex()), index(row1, 0, QModelIndex())); - emit dataChanged(index(row2, 0, QModelIndex()), index(row2, 0, QModelIndex())); - return true; - } - return false; -} - -QString QtHighlightRulesItemModel::getApplyToString(int applyTo) -{ - switch (applyTo) { - case ApplyToNone: return tr("None"); - case ApplyToAll: return tr("Chat or MUC"); - case ApplyToChat: return tr("Chat"); - case ApplyToMUC: return tr("MUC"); - default: return ""; - } -} - -} diff --git a/Swift/QtUI/QtHighlightRulesItemModel.h b/Swift/QtUI/QtHighlightRulesItemModel.h deleted file mode 100644 index ac85628..0000000 --- a/Swift/QtUI/QtHighlightRulesItemModel.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2012 Maciej Niedzielski - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#pragma once - -#include <QAbstractItemModel> - -namespace Swift { - - class HighlightManager; - - class QtHighlightRulesItemModel : public QAbstractItemModel { - Q_OBJECT - - public: - QtHighlightRulesItemModel(QObject* parent = NULL); - - void setHighlightManager(HighlightManager* highlightManager); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - int columnCount(const QModelIndex& parent) const; - QVariant data(const QModelIndex& index, int role) const; - bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); - QModelIndex parent(const QModelIndex& child) const; - int rowCount(const QModelIndex& parent) const; - QModelIndex index(int row, int column, const QModelIndex& parent) const; - bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()); - bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); - bool swapRows(int row1, int row2); - - static QString getApplyToString(int); - - enum Columns { - ApplyTo = 0, - Sender, - Keyword, - Action, - NickIsKeyword, - MatchCase, - MatchWholeWords, - HighlightText, - TextColor, - TextBackground, - PlaySound, - SoundFile, - NumberOfColumns // end of list marker - }; - - enum ApplyToValues { - ApplyToNone = 0, - ApplyToAll, - ApplyToChat, - ApplyToMUC, - ApplyToEOL // end of list marker - }; - - private: - HighlightManager* highlightManager_; - }; - -} diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp index 9f88258..0e7e89d 100644 --- a/Swift/QtUI/QtHistoryWindow.cpp +++ b/Swift/QtUI/QtHistoryWindow.cpp @@ -5,260 +5,263 @@ */ /* - * Copyright (c) 2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2013-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <QtHistoryWindow.h> +#include <Swift/QtUI/QtHistoryWindow.h> +#include <memory> #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 <QMenu> +#include <QTextDocument> +#include <QTime> +#include <QUrl> #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/ChatSnippet.h> #include <Swift/QtUI/MessageSnippet.h> #include <Swift/QtUI/QtScaledAvatarCache.h> -#include <Swift/QtUI/ChatSnippet.h> +#include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtUtilities.h> -#include <Swift/QtUI/Roster/QtTreeWidget.h> #include <Swift/QtUI/QtWebKitChatView.h> +#include <Swift/QtUI/Roster/QtTreeWidget.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, 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())); + previousTopMessageWasSelf_(false), + previousBottomMessageWasSelf_(false) { + ui_.setupUi(this); + + theme_ = new QtChatTheme(""); + idCounter_ = 0; + + delete ui_.conversation_; + conversation_ = new QtWebKitChatView(nullptr, nullptr, theme_, this, settings, 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_ + 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(); + emit wantsToActivate(); } void QtHistoryWindow::showEvent(QShowEvent* event) { - emit windowOpening(); - emit titleUpdated(); - QWidget::showEvent(event); + emit windowOpening(); + emit titleUpdated(); + QWidget::showEvent(event); } void QtHistoryWindow::closeEvent(QCloseEvent* event) { - emit windowClosing(); - event->accept(); + emit windowClosing(); + event->accept(); } void QtHistoryWindow::setRosterModel(Roster* model) { - conversationRoster_->setRosterModel(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); - } + 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" + std::to_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(std::make_shared<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(std::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); + onSelectedContactChanged(item); } void QtHistoryWindow::resetConversationView() { - previousTopMessageWasSelf_ = false; - previousBottomMessageWasSelf_ = false; - previousTopSenderName_.clear(); - previousBottomSenderName_.clear(); + previousTopMessageWasSelf_ = false; + previousBottomMessageWasSelf_ = false; + previousTopSenderName_.clear(); + previousBottomSenderName_.clear(); - dates_.clear(); - conversation_->resetView(); + 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); - } + // first message starts with offset 5 + if (pos < 5) { + pos = 5; + } + + QDate currentDate; + for (const auto& 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))); + 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))); + 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()); + 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))); + 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())); + ui_.calendarWidget_->setSelectedDate(QDate::fromJulianDay(date.julian_day())); } void QtHistoryWindow::handleNextButtonClicked() { - onNextButtonClicked(); + onNextButtonClicked(); } void QtHistoryWindow::handlePreviousButtonClicked() { - onPreviousButtonClicked(); + onPreviousButtonClicked(); } void QtHistoryWindow::handleFontResized(int fontSizeSteps) { - conversation_->resizeFont(fontSizeSteps); + conversation_->resizeFont(fontSizeSteps); - emit fontResized(fontSizeSteps); + emit fontResized(fontSizeSteps); } void QtHistoryWindow::resetConversationViewTopInsertPoint() { - previousTopMessageWasSelf_ = false; - previousTopSenderName_ = QString(); - conversation_->resetTopInsertPoint(); + previousTopMessageWasSelf_ = false; + previousTopSenderName_ = QString(); + conversation_->resetTopInsertPoint(); } std::string QtHistoryWindow::getSearchBoxText() { - return ui_.searchBox_->lineEdit()->text().toStdString(); + 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); + 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); +} + +std::string QtHistoryWindow::getID() const { + return "QtHistoryWindow"; } } diff --git a/Swift/QtUI/QtHistoryWindow.h b/Swift/QtUI/QtHistoryWindow.h index fcbfd7e..02d7fb8 100644 --- a/Swift/QtUI/QtHistoryWindow.h +++ b/Swift/QtUI/QtHistoryWindow.h @@ -5,9 +5,9 @@ */ /* - * Copyright (c) 2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2013-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -19,63 +19,64 @@ #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_; - }; + 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(); + + virtual std::string getID() const; + + 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/QtJoinMUCWindow.cpp b/Swift/QtUI/QtJoinMUCWindow.cpp index 4884bbd..550ae4a 100644 --- a/Swift/QtUI/QtJoinMUCWindow.cpp +++ b/Swift/QtUI/QtJoinMUCWindow.cpp @@ -1,68 +1,76 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ +#include <Swift/QtUI/QtJoinMUCWindow.h> + +#include <memory> + +#include <QToolTip> -#include "QtJoinMUCWindow.h" -#include <boost/smart_ptr/make_shared.hpp> -#include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> namespace Swift { QtJoinMUCWindow::QtJoinMUCWindow(UIEventStream* uiEventStream) : uiEventStream(uiEventStream) { - ui.setupUi(this); + ui.setupUi(this); #if QT_VERSION >= 0x040700 - ui.room->setPlaceholderText(tr("someroom@rooms.example.com")); + ui.room->setPlaceholderText(tr("someroom@rooms.example.com")); +#endif + connect(ui.room, SIGNAL(returnPressed()), this, SLOT(handleJoin())); + connect(ui.searchButton, SIGNAL(clicked()), this, SLOT(handleSearch())); + connect(ui.joinButton, SIGNAL(clicked()), this, SLOT(handleJoin())); +#if QT_VERSION < 0x050200 + // FIXME: Temporarily set focus on the nickName field first, so that the + // placeholder for the room is visible. This is just because Qt hides + // the placeholder when a widget is focused for some reason. + // Tracked upstream as QTBUG-33237 and fixed with Qt 5.2.0. + ui.nickName->setFocus(); #endif - connect(ui.room, SIGNAL(returnPressed()), this, SLOT(handleJoin())); - connect(ui.searchButton, SIGNAL(clicked()), this, SLOT(handleSearch())); - connect(ui.joinButton, SIGNAL(clicked()), this, SLOT(handleJoin())); - // FIXME: Temporarily set focus on the nickName field first, so that the - // placeholder for the room is visible. This is just because Qt hides - // the placeholder when a widget is focused for some reason. - ui.nickName->setFocus(); - ui.instantRoom->setChecked(true); - ui.nickName->setValidator(new NickValidator(this)); + ui.instantRoom->setChecked(true); + ui.nickName->setValidator(new NickValidator(this)); + ui.room->setValidator(new RoomJIDValidator(this)); } void QtJoinMUCWindow::handleJoin() { - if (ui.room->text().isEmpty()) { - // TODO: Error - return; - } - if (ui.nickName->text().isEmpty()) { - // TODO: Error - return; - } + if (ui.room->text().isEmpty() || !ui.room->hasAcceptableInput()) { + QToolTip::showText(ui.room->mapToGlobal(QPoint()), tr("Please enter a valid room address."), ui.room); + return; + } + if (ui.nickName->text().isEmpty() || !ui.nickName->hasAcceptableInput()) { + QToolTip::showText(ui.nickName->mapToGlobal(QPoint()), tr("Please enter a valid nickname."), ui.nickName); + return; + } - lastSetNick = Q2PSTRING(ui.nickName->text()); - std::string password = Q2PSTRING(ui.password->text()); - JID room(Q2PSTRING(ui.room->text())); - uiEventStream->send(boost::make_shared<JoinMUCUIEvent>(room, password, lastSetNick, ui.joinAutomatically->isChecked(), !ui.instantRoom->isChecked())); - hide(); + lastSetNick = Q2PSTRING(ui.nickName->text()); + std::string password = Q2PSTRING(ui.password->text()); + JID room(Q2PSTRING(ui.room->text())); + uiEventStream->send(std::make_shared<JoinMUCUIEvent>(room, password, lastSetNick, !ui.instantRoom->isChecked())); + hide(); } void QtJoinMUCWindow::handleSearch() { - onSearchMUC(); + onSearchMUC(); } void QtJoinMUCWindow::setNick(const std::string& nick) { - ui.nickName->setText(P2QSTRING(nick)); - lastSetNick = nick; + ui.nickName->setText(P2QSTRING(nick)); + lastSetNick = nick; } void QtJoinMUCWindow::setMUC(const std::string& nick) { - ui.room->setText(P2QSTRING(nick)); + ui.room->setText(P2QSTRING(nick)); } void QtJoinMUCWindow::show() { - QWidget::show(); - QWidget::activateWindow(); - ui.password->setText(""); + QDialog::show(); + QDialog::activateWindow(); + QDialog::raise(); + ui.password->setText(""); } } diff --git a/Swift/QtUI/QtJoinMUCWindow.h b/Swift/QtUI/QtJoinMUCWindow.h index c7e2d74..2fa1152 100644 --- a/Swift/QtUI/QtJoinMUCWindow.h +++ b/Swift/QtUI/QtJoinMUCWindow.h @@ -1,51 +1,77 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QValidator> #include <string> -#include "QtSwiftUtil.h" + +#include <QDialog> +#include <QValidator> + #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> + +#include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/ui_QtJoinMUCWindow.h> namespace Swift { - class UIEventStream; - class NickValidator : public QValidator { - Q_OBJECT - public: - NickValidator(QObject* parent) : QValidator(parent) { - } - - virtual QValidator::State validate(QString& input, int& /*pos*/) const { - if (input.isEmpty()) { - return QValidator::Acceptable; - } - JID test("alice", "wonderland.lit", Q2PSTRING(input)); - - return test.isValid() ? QValidator::Acceptable : QValidator::Invalid; - } - }; - class QtJoinMUCWindow : public QWidget, public JoinMUCWindow { - Q_OBJECT - public: - QtJoinMUCWindow(UIEventStream* uiEventStream); - - virtual void setNick(const std::string& nick); - virtual void setMUC(const std::string& nick); - - virtual void show(); - - private slots: - void handleJoin(); - void handleSearch(); - - private: - Ui::QtJoinMUCWindow ui; - std::string lastSetNick; - UIEventStream* uiEventStream; - }; + class UIEventStream; + class NickValidator : public QValidator { + Q_OBJECT + public: + NickValidator(QObject* parent) : QValidator(parent) { + } + + virtual QValidator::State validate(QString& input, int& /*pos*/) const { + if (input.isEmpty()) { + return QValidator::Intermediate; + } + JID test("alice", "wonderland.lit", Q2PSTRING(input)); + + return test.isValid() ? QValidator::Acceptable : QValidator::Invalid; + } + }; + + class RoomJIDValidator : public QValidator { + Q_OBJECT + public: + RoomJIDValidator(QObject* parent) : QValidator(parent) { + } + + virtual QValidator::State validate(QString& input, int& /*pos*/) const { + if (input.isEmpty()) { + return QValidator::Intermediate; + } + JID roomJID(Q2PSTRING(input)); + + if (roomJID.getNode().empty() || roomJID.getDomain().empty()) { + return QValidator::Intermediate; + } + + return (roomJID.getResource().empty() && !roomJID.getNode().empty() && !roomJID.getDomain().empty() && roomJID.isValid()) ? QValidator::Acceptable : QValidator::Invalid; + } + }; + + class QtJoinMUCWindow : public QDialog, public JoinMUCWindow { + + Q_OBJECT + public: + QtJoinMUCWindow(UIEventStream* uiEventStream); + + virtual void setNick(const std::string& nick); + virtual void setMUC(const std::string& nick); + + virtual void show(); + + private slots: + void handleJoin(); + void handleSearch(); + + private: + Ui::QtJoinMUCWindow ui; + std::string lastSetNick; + UIEventStream* uiEventStream; + }; } diff --git a/Swift/QtUI/QtJoinMUCWindow.ui b/Swift/QtUI/QtJoinMUCWindow.ui index 9225f6f..96f1d17 100644 --- a/Swift/QtUI/QtJoinMUCWindow.ui +++ b/Swift/QtUI/QtJoinMUCWindow.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>QtJoinMUCWindow</class> - <widget class="QWidget" name="QtJoinMUCWindow"> + <widget class="QDialog" name="QtJoinMUCWindow"> <property name="geometry"> <rect> <x>0</x> @@ -101,13 +101,6 @@ </spacer> </item> <item> - <widget class="QCheckBox" name="joinAutomatically"> - <property name="text"> - <string>Enter automatically in future</string> - </property> - </widget> - </item> - <item> <widget class="QPushButton" name="joinButton"> <property name="text"> <string>Enter Room</string> @@ -120,10 +113,11 @@ </widget> <tabstops> <tabstop>room</tabstop> + <tabstop>searchButton</tabstop> <tabstop>nickName</tabstop> - <tabstop>joinAutomatically</tabstop> + <tabstop>password</tabstop> + <tabstop>instantRoom</tabstop> <tabstop>joinButton</tabstop> - <tabstop>searchButton</tabstop> </tabstops> <resources/> <connections/> diff --git a/Swift/QtUI/QtLineEdit.cpp b/Swift/QtUI/QtLineEdit.cpp index 77134b1..d3ec4c4 100644 --- a/Swift/QtUI/QtLineEdit.cpp +++ b/Swift/QtUI/QtLineEdit.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/QtLineEdit.h" +#include <Swift/QtUI/QtLineEdit.h> #include <QKeyEvent> @@ -14,10 +14,10 @@ QtLineEdit::QtLineEdit(QWidget* parent) : QLineEdit(parent) { } void QtLineEdit::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Escape) { - emit escapePressed(); - } - QLineEdit::keyPressEvent(event); + if (event->key() == Qt::Key_Escape) { + emit escapePressed(); + } + QLineEdit::keyPressEvent(event); } } diff --git a/Swift/QtUI/QtLineEdit.h b/Swift/QtUI/QtLineEdit.h index de4706e..851a2ff 100644 --- a/Swift/QtUI/QtLineEdit.h +++ b/Swift/QtUI/QtLineEdit.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,13 +9,13 @@ #include <QLineEdit> namespace Swift { - class QtLineEdit : public QLineEdit { - Q_OBJECT - public: - QtLineEdit(QWidget* parent = NULL); - signals: - void escapePressed(); - protected: - virtual void keyPressEvent(QKeyEvent* event); - }; + class QtLineEdit : public QLineEdit { + Q_OBJECT + public: + QtLineEdit(QWidget* parent = nullptr); + signals: + void escapePressed(); + protected: + virtual void keyPressEvent(QKeyEvent* event); + }; } diff --git a/Swift/QtUI/QtListWidget.cpp b/Swift/QtUI/QtListWidget.cpp new file mode 100644 index 0000000..e35bbbb --- /dev/null +++ b/Swift/QtUI/QtListWidget.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtListWidget.h> + +#include <QListWidget> + +namespace Swift { + +QtListWidget::QtListWidget(QWidget* parent) : QListWidget(parent) { +} + +void QtListWidget::resizeEvent(QResizeEvent*) { + emit onResize(); +} + +} diff --git a/Swift/QtUI/QtListWidget.h b/Swift/QtUI/QtListWidget.h new file mode 100644 index 0000000..b7380c4 --- /dev/null +++ b/Swift/QtUI/QtListWidget.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QListWidget> + +namespace Swift { + +class QtListWidget : public QListWidget { + Q_OBJECT + public: + QtListWidget(QWidget* parent = nullptr); + protected: + virtual void resizeEvent(QResizeEvent*); + signals: + void onResize(); +}; + +} diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index 188e55f..8fdce4d 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -1,47 +1,49 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtLoginWindow.h" +#include <Swift/QtUI/QtLoginWindow.h> -#include <boost/bind.hpp> -#include <boost/smart_ptr/make_shared.hpp> #include <algorithm> #include <cassert> +#include <boost/bind.hpp> +#include <memory> + #include <QApplication> #include <QBoxLayout> +#include <QCloseEvent> #include <QComboBox> +#include <QCursor> +#include <QDebug> #include <QDesktopWidget> #include <QFileDialog> -#include <QStatusBar> -#include <QToolButton> +#include <QHBoxLayout> +#include <QKeyEvent> #include <QLabel> #include <QMenuBar> -#include <QHBoxLayout> -#include <qdebug.h> -#include <QCloseEvent> -#include <QCursor> #include <QMessageBox> -#include <QKeyEvent> - -#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include <Swift/Controllers/UIEvents/RequestXMLConsoleUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> -#include <Swift/Controllers/Settings/SettingsProvider.h> -#include <Swift/Controllers/SettingConstants.h> -#include <Swift/QtUI/QtUISettingConstants.h> +#include <QStatusBar> +#include <QToolButton> + #include <Swiften/Base/Platform.h> #include <Swiften/Base/Paths.h> -#include <QtAboutWidget.h> -#include <QtSwiftUtil.h> -#include <QtMainWindow.h> -#include <QtUtilities.h> -#include <QtConnectionSettingsWindow.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestHighlightEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestXMLConsoleUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <Swift/QtUI/QtAboutWidget.h> +#include <Swift/QtUI/QtConnectionSettingsWindow.h> +#include <Swift/QtUI/QtMainWindow.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtUtilities.h> #ifdef HAVE_SCHANNEL #include "CAPICertificateSelector.h" @@ -51,507 +53,528 @@ namespace Swift{ -QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* settings, TimerFactory* timerFactory) : QMainWindow(), settings_(settings), timerFactory_(timerFactory) { - uiEventStream_ = uiEventStream; +QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* settings, TimerFactory* timerFactory, AutoUpdater* autoUpdater) : QMainWindow(), settings_(settings), timerFactory_(timerFactory), autoUpdater_(autoUpdater) { + uiEventStream_ = uiEventStream; - setWindowTitle("Swift"); + setWindowTitle("Swift"); #ifndef Q_OS_MAC - setWindowIcon(QIcon(":/logo-icon-16.png")); +#ifdef Q_OS_WIN32 + setWindowIcon(QIcon(":/logo-icon-16-win.png")); +#else + setWindowIcon(QIcon(":/logo-icon-16.png")); +#endif #endif - QtUtilities::setX11Resource(this, "Main"); - - resize(200, 500); - setContentsMargins(0,0,0,0); - QWidget *centralWidget = new QWidget(this); - setCentralWidget(centralWidget); - QBoxLayout *topLayout = new QBoxLayout(QBoxLayout::TopToBottom, centralWidget); - stack_ = new QStackedWidget(centralWidget); - topLayout->addWidget(stack_); - topLayout->setMargin(0); - loginWidgetWrapper_ = new QWidget(this); - loginWidgetWrapper_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, loginWidgetWrapper_); - layout->addStretch(2); - - QLabel* logo = new QLabel(this); - logo->setPixmap(QPixmap(":/logo-shaded-text.256.png")); - logo->setScaledContents(true); - logo->setFixedSize(192,192); - - QWidget *logoWidget = new QWidget(this); - QHBoxLayout *logoLayout = new QHBoxLayout(); - logoLayout->setMargin(0); - logoLayout->addStretch(0); - logoLayout->addWidget(logo); - logoLayout->addStretch(0); - logoWidget->setLayout(logoLayout); - layout->addWidget(logoWidget); - - layout->addStretch(2); - - QLabel* jidLabel = new QLabel(this); - jidLabel->setText("<font size='-1'>" + tr("User address:") + "</font>"); - layout->addWidget(jidLabel); - - username_ = new QComboBox(this); - username_->setEditable(true); - username_->setWhatsThis(tr("User address - looks like someuser@someserver.com")); - username_->setToolTip(tr("User address - looks like someuser@someserver.com")); - username_->view()->installEventFilter(this); - layout->addWidget(username_); - QLabel* jidHintLabel = new QLabel(this); - jidHintLabel->setText("<font size='-1' color='grey' >" + tr("Example: alice@wonderland.lit") + "</font>"); - jidHintLabel->setAlignment(Qt::AlignRight); - layout->addWidget(jidHintLabel); - - - QLabel* passwordLabel = new QLabel(); - passwordLabel->setText("<font size='-1'>" + tr("Password:") + "</font>"); - layout->addWidget(passwordLabel); - - - QWidget* w = new QWidget(this); - w->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - layout->addWidget(w); - - QHBoxLayout* credentialsLayout = new QHBoxLayout(w); - credentialsLayout->setMargin(0); - credentialsLayout->setSpacing(3); - password_ = new QLineEdit(this); - password_->setEchoMode(QLineEdit::Password); - connect(password_, SIGNAL(returnPressed()), this, SLOT(loginClicked())); - connect(username_->lineEdit(), SIGNAL(returnPressed()), password_, SLOT(setFocus())); - connect(username_, SIGNAL(editTextChanged(const QString&)), this, SLOT(handleUsernameTextChanged())); - credentialsLayout->addWidget(password_); - - certificateButton_ = new QToolButton(this); - certificateButton_->setCheckable(true); - certificateButton_->setIcon(QIcon(":/icons/certificate.png")); - 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.")); - - credentialsLayout->addWidget(certificateButton_); - connect(certificateButton_, SIGNAL(clicked(bool)), SLOT(handleCertficateChecked(bool))); - - loginButton_ = new QPushButton(this); - loginButton_->setText(tr("Connect")); - loginButton_->setAutoDefault(true); - loginButton_->setDefault(true); - 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); - message_->setWordWrap(true); - layout->addWidget(message_); - - layout->addStretch(2); - remember_ = new QCheckBox(tr("Remember Password?"), this); - layout->addWidget(remember_); - loginAutomatically_ = new QCheckBox(tr("Login Automatically?"), this); - layout->addWidget(loginAutomatically_); - - connect(loginButton_, SIGNAL(clicked()), SLOT(loginClicked())); - stack_->addWidget(loginWidgetWrapper_); + 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); + setContentsMargins(0,0,0,0); + QWidget *centralWidget = new QWidget(this); + setCentralWidget(centralWidget); + QBoxLayout *topLayout = new QBoxLayout(QBoxLayout::TopToBottom, centralWidget); + stack_ = new QStackedWidget(centralWidget); + topLayout->addWidget(stack_); + topLayout->setMargin(0); + loginWidgetWrapper_ = new QWidget(this); + loginWidgetWrapper_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, loginWidgetWrapper_); + layout->addStretch(2); + + QLabel* logo = new QLabel(this); + QIcon swiftWithTextLogo = QIcon(":/logo-shaded-text.png"); + logo->setPixmap(swiftWithTextLogo.pixmap(QSize(192,192))); + + QWidget *logoWidget = new QWidget(this); + QHBoxLayout *logoLayout = new QHBoxLayout(); + logoLayout->setMargin(0); + logoLayout->addStretch(0); + logoLayout->addWidget(logo); + logoLayout->addStretch(0); + logoWidget->setLayout(logoLayout); + layout->addWidget(logoWidget); + + layout->addStretch(2); + + QLabel* jidLabel = new QLabel(this); + jidLabel->setText("<font size='-1'>" + tr("User address:") + "</font>"); + layout->addWidget(jidLabel); + + + username_ = new QComboBox(this); + username_->setEditable(true); + username_->setWhatsThis(tr("User address - looks like someuser@someserver.com")); + 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); + jidHintLabel->setText("<font size='-1' color='grey' >" + tr("Example: alice@wonderland.lit") + "</font>"); + jidHintLabel->setAlignment(Qt::AlignRight); + layout->addWidget(jidHintLabel); + + + 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); + + + QWidget* w = new QWidget(this); + w->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + layout->addWidget(w); + + QHBoxLayout* credentialsLayout = new QHBoxLayout(w); + credentialsLayout->setMargin(0); + credentialsLayout->setSpacing(3); + password_ = new QLineEdit(this); + password_->setEchoMode(QLineEdit::Password); + connect(password_, SIGNAL(returnPressed()), this, SLOT(loginClicked())); + connect(username_->lineEdit(), SIGNAL(returnPressed()), password_, SLOT(setFocus())); + connect(username_, SIGNAL(editTextChanged(const QString&)), this, SLOT(handleUsernameTextChanged())); + credentialsLayout->addWidget(password_); + + certificateButton_ = new QToolButton(this); + certificateButton_->setCheckable(true); + certificateButton_->setIcon(QIcon(":/icons/certificate.png")); + 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_); + connect(certificateButton_, SIGNAL(clicked(bool)), SLOT(handleCertficateChecked(bool))); + + loginButton_ = new QPushButton(this); + loginButton_->setText(tr("Connect")); + 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); + message_->setWordWrap(true); + layout->addWidget(message_); + + layout->addStretch(2); + remember_ = new QCheckBox(tr("Remember Password?"), this); + layout->addWidget(remember_); + loginAutomatically_ = new QCheckBox(tr("Login Automatically?"), this); + layout->addWidget(loginAutomatically_); + + connect(loginButton_, SIGNAL(clicked()), SLOT(loginClicked())); + stack_->addWidget(loginWidgetWrapper_); #ifdef SWIFTEN_PLATFORM_MACOSX - menuBar_ = new QMenuBar(NULL); + menuBar_ = new QMenuBar(nullptr); #else - menuBar_ = menuBar(); + menuBar_ = menuBar(); #endif - QApplication::setQuitOnLastWindowClosed(false); + QApplication::setQuitOnLastWindowClosed(false); - swiftMenu_ = new QMenu(tr("&Swift"), this); + swiftMenu_ = new QMenu(tr("&Swift"), this); #ifdef SWIFTEN_PLATFORM_MACOSX - generalMenu_ = new QMenu(tr("&General"), this); + generalMenu_ = new QMenu(tr("&General"), this); #else - generalMenu_ = swiftMenu_; + generalMenu_ = swiftMenu_; #endif #ifdef SWIFTEN_PLATFORM_MACOSX - QAction* aboutAction = new QAction(QString("&About %1").arg("Swift"), this); + QAction* aboutAction = new QAction(QString("&About %1").arg("Swift"), this); #else - QAction* aboutAction = new QAction(QString(tr("&About %1")).arg("Swift"), this); + QAction* aboutAction = new QAction(QString(tr("&About %1")).arg("Swift"), this); #endif - connect(aboutAction, SIGNAL(triggered()), SLOT(handleAbout())); - swiftMenu_->addAction(aboutAction); + connect(aboutAction, SIGNAL(triggered()), SLOT(handleAbout())); + swiftMenu_->addAction(aboutAction); - xmlConsoleAction_ = new QAction(tr("&Show Debug Console"), this); - connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(handleShowXMLConsole())); - generalMenu_->addAction(xmlConsoleAction_); + xmlConsoleAction_ = new QAction(tr("&Show Debug Console"), this); + connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(handleShowXMLConsole())); + generalMenu_->addAction(xmlConsoleAction_); #ifdef SWIFT_EXPERIMENTAL_FT - fileTransferOverviewAction_ = new QAction(tr("Show &File Transfer Overview"), this); - connect(fileTransferOverviewAction_, SIGNAL(triggered()), SLOT(handleShowFileTransferOverview())); - generalMenu_->addAction(fileTransferOverviewAction_); + fileTransferOverviewAction_ = new QAction(tr("Show &File Transfer Overview"), this); + connect(fileTransferOverviewAction_, SIGNAL(triggered()), SLOT(handleShowFileTransferOverview())); + generalMenu_->addAction(fileTransferOverviewAction_); #endif - highlightEditorAction_ = new QAction(tr("&Edit Highlight Rules"), this); - connect(highlightEditorAction_, SIGNAL(triggered()), SLOT(handleShowHighlightEditor())); - generalMenu_->addAction(highlightEditorAction_); + highlightEditorAction_ = new QAction(tr("&Edit Highlight Rules"), this); + connect(highlightEditorAction_, SIGNAL(triggered()), SLOT(handleShowHighlightEditor())); + generalMenu_->addAction(highlightEditorAction_); - toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this); - toggleSoundsAction_->setCheckable(true); - toggleSoundsAction_->setChecked(settings_->getSetting(SettingConstants::PLAY_SOUNDS)); - connect(toggleSoundsAction_, SIGNAL(toggled(bool)), SLOT(handleToggleSounds(bool))); - generalMenu_->addAction(toggleSoundsAction_); + toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this); + toggleSoundsAction_->setCheckable(true); + toggleSoundsAction_->setChecked(settings_->getSetting(SettingConstants::PLAY_SOUNDS)); + connect(toggleSoundsAction_, SIGNAL(toggled(bool)), SLOT(handleToggleSounds(bool))); + generalMenu_->addAction(toggleSoundsAction_); - toggleNotificationsAction_ = new QAction(tr("Display Pop-up &Notifications"), this); - toggleNotificationsAction_->setCheckable(true); - toggleNotificationsAction_->setChecked(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS)); - connect(toggleNotificationsAction_, SIGNAL(toggled(bool)), SLOT(handleToggleNotifications(bool))); + toggleNotificationsAction_ = new QAction(tr("Display Pop-up &Notifications"), this); + toggleNotificationsAction_->setCheckable(true); + toggleNotificationsAction_->setChecked(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS)); + connect(toggleNotificationsAction_, SIGNAL(toggled(bool)), SLOT(handleToggleNotifications(bool))); #ifndef SWIFTEN_PLATFORM_MACOSX - swiftMenu_->addSeparator(); + swiftMenu_->addSeparator(); #endif #ifdef SWIFTEN_PLATFORM_MACOSX - QAction* quitAction = new QAction("&Quit", this); + QAction* quitAction = new QAction("&Quit", this); #else - QAction* quitAction = new QAction(tr("&Quit"), this); + QAction* quitAction = new QAction(tr("&Quit"), this); #endif - connect(quitAction, SIGNAL(triggered()), SLOT(handleQuit())); - swiftMenu_->addAction(quitAction); + connect(quitAction, SIGNAL(triggered()), SLOT(handleQuit())); + swiftMenu_->addAction(quitAction); - setInitialMenus(); - settings_->onSettingChanged.connect(boost::bind(&QtLoginWindow::handleSettingChanged, this, _1)); + setInitialMenus(); + settings_->onSettingChanged.connect(boost::bind(&QtLoginWindow::handleSettingChanged, this, _1)); - bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); - remember_->setEnabled(!eagle); - loginAutomatically_->setEnabled(!eagle); - xmlConsoleAction_->setEnabled(!eagle); - if (eagle) { - remember_->setChecked(false); - loginAutomatically_->setChecked(false); - } + bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); + remember_->setEnabled(!eagle); + loginAutomatically_->setEnabled(!eagle); + xmlConsoleAction_->setEnabled(!eagle); + if (eagle) { + remember_->setChecked(false); + loginAutomatically_->setChecked(false); + } #ifdef SWIFTEN_PLATFORM_MACOSX - // Temporary workaround for case 501. Could be that this code is still - // needed when Qt provides a proper fix - qApp->installEventFilter(this); + // Temporary workaround for case 501. Could be that this code is still + // needed when Qt provides a proper fix + qApp->installEventFilter(this); #endif - this->show(); + this->show(); } void QtLoginWindow::setShowNotificationToggle(bool toggle) { - if (toggle) { - QList< QAction* > generalMenuActions = generalMenu_->actions(); - generalMenu_->insertAction(generalMenuActions.at(generalMenuActions.count()-2), toggleNotificationsAction_); - } - else { - generalMenu_->removeAction(toggleNotificationsAction_); - } + if (toggle) { + QList< QAction* > generalMenuActions = generalMenu_->actions(); + generalMenu_->insertAction(generalMenuActions.at(generalMenuActions.count()-2), toggleNotificationsAction_); + } + else { + generalMenu_->removeAction(toggleNotificationsAction_); + } } bool QtLoginWindow::eventFilter(QObject *obj, QEvent *event) { - if (obj == username_->view() && event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); - if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace) { - QString jid(username_->view()->currentIndex().data().toString()); - int result = QMessageBox::question(this, tr("Remove profile"), tr("Remove the profile '%1'?").arg(jid), QMessageBox::Yes | QMessageBox::No); - if (result == QMessageBox::Yes) { - onPurgeSavedLoginRequest(Q2PSTRING(jid)); - } - return true; - } - } + if (obj == username_->view() && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Delete || keyEvent->key() == Qt::Key_Backspace) { + QString jid(username_->view()->currentIndex().data().toString()); + int result = QMessageBox::question(this, tr("Remove profile"), tr("Remove the profile '%1'?").arg(jid), QMessageBox::Yes | QMessageBox::No); + if (result == QMessageBox::Yes) { + onPurgeSavedLoginRequest(Q2PSTRING(jid)); + } + return true; + } + } #ifdef SWIFTEN_PLATFORM_MACOSX - // Dock clicked - // Temporary workaround for case 501. Could be that this code is still - // needed when Qt provides a proper fix - if (obj == qApp && event->type() == QEvent::ApplicationActivate && !isVisible()) { - bringToFront(); - } + // Dock clicked + // Temporary workaround for case 501. Could be that this code is still + // needed when Qt provides a proper fix + if (obj == qApp && event->type() == QEvent::ApplicationActivate && !isVisible()) { + bringToFront(); + } #endif - return QObject::eventFilter(obj, event); + return QObject::eventFilter(obj, event); } void QtLoginWindow::handleSettingChanged(const std::string& settingPath) { - if (settingPath == SettingConstants::PLAY_SOUNDS.getKey()) { - toggleSoundsAction_->setChecked(settings_->getSetting(SettingConstants::PLAY_SOUNDS)); - } - if (settingPath == SettingConstants::SHOW_NOTIFICATIONS.getKey()) { - toggleNotificationsAction_->setChecked(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS)); - } + if (settingPath == SettingConstants::PLAY_SOUNDS.getKey()) { + toggleSoundsAction_->setChecked(settings_->getSetting(SettingConstants::PLAY_SOUNDS)); + } + if (settingPath == SettingConstants::SHOW_NOTIFICATIONS.getKey()) { + toggleNotificationsAction_->setChecked(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS)); + } } void QtLoginWindow::selectUser(const std::string& username) { - for (int i = 0; i < usernames_.count(); i++) { - if (P2QSTRING(username) == usernames_[i]) { - username_->setCurrentIndex(i); - password_->setFocus(); - break; - } - } + for (int i = 0; i < usernames_.count(); i++) { + if (P2QSTRING(username) == usernames_[i]) { + username_->setCurrentIndex(i); + password_->setFocus(); + break; + } + } } void QtLoginWindow::removeAvailableAccount(const std::string& jid) { - QString username = P2QSTRING(jid); - int index = -1; - for (int i = 0; i < usernames_.count(); i++) { - if (username == usernames_[i]) { - index = i; - } - } - if (index >= 0) { - usernames_.removeAt(index); - passwords_.removeAt(index); - certificateFiles_.removeAt(index); - username_->removeItem(index); - } + QString username = P2QSTRING(jid); + int index = -1; + for (int i = 0; i < usernames_.count(); i++) { + if (username == usernames_[i]) { + index = i; + } + } + if (index >= 0) { + usernames_.removeAt(index); + passwords_.removeAt(index); + certificateFiles_.removeAt(index); + username_->removeItem(index); + } } 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; - for (int i = 0; i < usernames_.count(); i++) { - if (username == usernames_[i]) { - index = i; - } - } - if (index == -1) { - usernames_.append(username); - passwords_.append(P2QSTRING(defaultPassword)); - certificateFiles_.append(P2QSTRING(defaultCertificate)); - options_.push_back(options); - username_->addItem(username); - } else { - usernames_[index] = username; - passwords_[index] = P2QSTRING(defaultPassword); - certificateFiles_[index] = P2QSTRING(defaultCertificate); - options_[index] = options; - } + QString username = P2QSTRING(defaultJID); + int index = -1; + for (int i = 0; i < usernames_.count(); i++) { + if (username == usernames_[i]) { + index = i; + } + } + if (index == -1) { + usernames_.append(username); + passwords_.append(P2QSTRING(defaultPassword)); + certificateFiles_.append(P2QSTRING(defaultCertificate)); + options_.push_back(options); + username_->addItem(username); + } else { + usernames_[index] = username; + passwords_[index] = P2QSTRING(defaultPassword); + certificateFiles_[index] = P2QSTRING(defaultCertificate); + options_[index] = options; + } } void QtLoginWindow::handleUsernameTextChanged() { - QString username = username_->currentText(); - for (int i = 0; i < usernames_.count(); i++) { - if (username_->currentText() == usernames_[i]) { - certificateFile_ = certificateFiles_[i]; - password_->setText(passwords_[i]); - remember_->setChecked(password_->text() != ""); - currentOptions_ = options_[i]; - } - } - certificateButton_->setChecked(!certificateFile_.isEmpty()); + QString username = username_->currentText(); + for (int i = 0; i < usernames_.count(); i++) { + if (username_->currentText() == usernames_[i]) { + certificateFile_ = certificateFiles_[i]; + password_->setText(passwords_[i]); + remember_->setChecked(password_->text() != ""); + currentOptions_ = options_[i]; + } + } + certificateButton_->setChecked(!certificateFile_.isEmpty()); } void QtLoginWindow::loggedOut() { - stack_->removeWidget(stack_->currentWidget()); - stack_->addWidget(loginWidgetWrapper_); - stack_->setCurrentWidget(loginWidgetWrapper_); - setInitialMenus(); - setIsLoggingIn(false); + stack_->removeWidget(stack_->currentWidget()); + stack_->addWidget(loginWidgetWrapper_); + stack_->setCurrentWidget(loginWidgetWrapper_); + setInitialMenus(); + setIsLoggingIn(false); } void QtLoginWindow::setIsLoggingIn(bool loggingIn) { - /* Change the for loop as well if you add to this.*/ - QWidget* widgets[5] = {username_, password_, remember_, loginAutomatically_, certificateButton_}; - loginButton_->setText(loggingIn ? tr("Cancel") : tr("Connect")); - for (int i = 0; i < 5; i++) { - widgets[i]->setEnabled(!loggingIn); - } - bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); - remember_->setEnabled(!eagle); - loginAutomatically_->setEnabled(!eagle); + /* Change the for loop as well if you add to this.*/ + QWidget* widgets[5] = {username_, password_, remember_, loginAutomatically_, certificateButton_}; + loginButton_->setText(loggingIn ? tr("Cancel") : tr("Connect")); + for (auto& widget : widgets) { + widget->setEnabled(!loggingIn); + } + bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); + remember_->setEnabled(!eagle); + loginAutomatically_->setEnabled(!eagle); } void QtLoginWindow::loginClicked() { - if (username_->isEnabled()) { - std::string banner = settings_->getSetting(QtUISettingConstants::CLICKTHROUGH_BANNER); - if (!banner.empty()) { - QMessageBox msgBox; - msgBox.setWindowTitle(tr("Confirm terms of use")); - msgBox.setText(""); - msgBox.setInformativeText(P2QSTRING(banner)); - msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msgBox.setDefaultButton(QMessageBox::No); - if (msgBox.exec() != QMessageBox::Yes) { - return; - } - } - CertificateWithKey::ref certificate; - std::string certificateString = Q2PSTRING(certificateFile_); + if (username_->isEnabled()) { + std::string banner = settings_->getSetting(QtUISettingConstants::CLICKTHROUGH_BANNER); + if (!banner.empty()) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Confirm terms of use")); + msgBox.setText(""); + msgBox.setInformativeText(P2QSTRING(banner)); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + if (msgBox.exec() != QMessageBox::Yes) { + return; + } + } + CertificateWithKey::ref certificate; + std::string certificateString = Q2PSTRING(certificateFile_); + if (!certificateString.empty()) { #if defined(HAVE_SCHANNEL) - if (isCAPIURI(certificateString)) { - certificate = boost::make_shared<CAPICertificate>(certificateString, timerFactory_); - } else { - certificate = boost::make_shared<PKCS12Certificate>(certificateString, createSafeByteArray(Q2PSTRING(password_->text()))); - } + if (isCAPIURI(certificateString)) { + certificate = std::make_shared<CAPICertificate>(certificateString, timerFactory_); + } else { + certificate = std::make_shared<PKCS12Certificate>(certificateString, createSafeByteArray(Q2PSTRING(password_->text()))); + } #else - certificate = boost::make_shared<PKCS12Certificate>(certificateString, createSafeByteArray(Q2PSTRING(password_->text()))); + certificate = std::make_shared<PKCS12Certificate>(certificateString, createSafeByteArray(Q2PSTRING(password_->text()))); #endif + } - 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(); - password_->setText(""); - } - } else { - onCancelLoginRequest(); - } + 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(); + password_->setText(""); + } + } else { + onCancelLoginRequest(); + } } void QtLoginWindow::setLoginAutomatically(bool loginAutomatically) { - loginAutomatically_->setChecked(loginAutomatically); + loginAutomatically_->setChecked(loginAutomatically); } void QtLoginWindow::handleCertficateChecked(bool checked) { - if (checked) { + if (checked) { #ifdef HAVE_SCHANNEL - certificateFile_ = P2QSTRING(selectCAPICertificate()); - if (certificateFile_.isEmpty()) { - certificateButton_->setChecked(false); - } + certificateFile_ = P2QSTRING(selectCAPICertificate()); + if (certificateFile_.isEmpty()) { + certificateButton_->setChecked(false); + } #else - certificateFile_ = QFileDialog::getOpenFileName(this, tr("Select an authentication certificate"), QString(), tr("P12 files (*.cert *.p12 *.pfx);;All files (*.*)")); - if (certificateFile_.isEmpty()) { - certificateButton_->setChecked(false); - } + certificateFile_ = QFileDialog::getOpenFileName(this, tr("Select an authentication certificate"), QString(), tr("P12 files (*.cert *.p12 *.pfx);;All files (*.*)")); + if (certificateFile_.isEmpty()) { + certificateButton_->setChecked(false); + } #endif - } - else { - certificateFile_ = ""; - } + } + else { + certificateFile_ = ""; + } } void QtLoginWindow::handleAbout() { - if (!aboutDialog_) { - aboutDialog_ = new QtAboutWidget(); - aboutDialog_->show(); - } - else { - aboutDialog_->show(); - aboutDialog_->raise(); - aboutDialog_->activateWindow(); - } + if (!aboutDialog_) { + aboutDialog_ = new QtAboutWidget(settings_, autoUpdater_); + aboutDialog_->show(); + } + else { + aboutDialog_->show(); + aboutDialog_->raise(); + aboutDialog_->activateWindow(); + } } void QtLoginWindow::handleShowXMLConsole() { - uiEventStream_->send(boost::make_shared<RequestXMLConsoleUIEvent>()); + uiEventStream_->send(std::make_shared<RequestXMLConsoleUIEvent>()); } void QtLoginWindow::handleShowFileTransferOverview() { - uiEventStream_->send(boost::make_shared<RequestFileTransferListUIEvent>()); + uiEventStream_->send(std::make_shared<RequestFileTransferListUIEvent>()); } void QtLoginWindow::handleShowHighlightEditor() { - uiEventStream_->send(boost::make_shared<RequestHighlightEditorUIEvent>()); + uiEventStream_->send(std::make_shared<RequestHighlightEditorUIEvent>()); } void QtLoginWindow::handleToggleSounds(bool enabled) { - settings_->storeSetting(SettingConstants::PLAY_SOUNDS, enabled); + settings_->storeSetting(SettingConstants::PLAY_SOUNDS, enabled); } void QtLoginWindow::handleToggleNotifications(bool enabled) { - settings_->storeSetting(SettingConstants::SHOW_NOTIFICATIONS, enabled); + settings_->storeSetting(SettingConstants::SHOW_NOTIFICATIONS, enabled); } void QtLoginWindow::handleQuit() { - onQuitRequest(); + onQuitRequest(); } void QtLoginWindow::quit() { - QApplication::quit(); + QApplication::quit(); } void QtLoginWindow::setInitialMenus() { - menuBar_->clear(); - menuBar_->addMenu(swiftMenu_); + menuBar_->clear(); + menuBar_->addMenu(swiftMenu_); #ifdef SWIFTEN_PLATFORM_MACOSX - menuBar_->addMenu(generalMenu_); + menuBar_->addMenu(generalMenu_); #endif } void QtLoginWindow::morphInto(MainWindow *mainWindow) { - setEnabled(false); - QtMainWindow *qtMainWindow = dynamic_cast<QtMainWindow*>(mainWindow); - assert(qtMainWindow); - stack_->removeWidget(loginWidgetWrapper_); - stack_->addWidget(qtMainWindow); - stack_->setCurrentWidget(qtMainWindow); - setEnabled(true); - setInitialMenus(); - foreach (QMenu* menu, qtMainWindow->getMenus()) { - menuBar_->addMenu(menu); - } - setFocus(); + setEnabled(false); + QtMainWindow *qtMainWindow = dynamic_cast<QtMainWindow*>(mainWindow); + assert(qtMainWindow); + stack_->removeWidget(loginWidgetWrapper_); + stack_->addWidget(qtMainWindow); + stack_->setCurrentWidget(qtMainWindow); + setEnabled(true); + setInitialMenus(); + std::vector<QMenu*> mainWindowMenus = qtMainWindow->getMenus(); + viewMenu_ = mainWindowMenus[0]; + for (auto menu : mainWindowMenus) { + menuBar_->addMenu(menu); + } + setFocus(); } void QtLoginWindow::setMessage(const std::string& message) { - if (!message.empty()) { - message_->setText("<center><font color=\"red\">" + P2QSTRING(message) + "</font></center>"); - } - else { - message_->setText(""); - } + if (!message.empty()) { + message_->setText("<center><font color=\"red\">" + P2QSTRING(message) + "</font></center>"); + } + else { + message_->setText(""); + } } void QtLoginWindow::toggleBringToFront() { - if (!isVisible()) { - bringToFront(); - } - else { - window()->hide(); - } + if (!isVisible()) { + bringToFront(); + } + else { + window()->hide(); + } } void QtLoginWindow::bringToFront() { - window()->showNormal(); - window()->raise(); - window()->activateWindow(); + window()->showNormal(); + window()->raise(); + window()->activateWindow(); } void QtLoginWindow::hide() { - window()->hide(); + window()->hide(); } QtLoginWindow::QtMenus QtLoginWindow::getMenus() const { - return QtMenus(swiftMenu_, generalMenu_); + return QtMenus(swiftMenu_, generalMenu_); } void QtLoginWindow::resizeEvent(QResizeEvent*) { - emit geometryChanged(); + emit geometryChanged(); } void QtLoginWindow::moveEvent(QMoveEvent*) { - emit geometryChanged(); + emit geometryChanged(); } bool QtLoginWindow::askUserToTrustCertificatePermanently(const std::string& message, const std::vector<Certificate::ref>& certificates) { - QMessageBox dialog(this); - - dialog.setText(tr("The certificate presented by the server is not valid.")); - 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.")); - - 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); - } + QMessageBox dialog(this); + + dialog.setText(tr("The certificate presented by the server is not valid.")); + 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.")); + + 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); + } } void QtLoginWindow::handleOpenConnectionOptions() { - QtConnectionSettingsWindow connectionSettings(currentOptions_); - if (connectionSettings.exec() == QDialog::Accepted) { - currentOptions_ = connectionSettings.getOptions(); - } + QtConnectionSettingsWindow connectionSettings(currentOptions_); + if (connectionSettings.exec() == QDialog::Accepted) { + currentOptions_ = connectionSettings.getOptions(); + } +} + +QSize QtLoginWindow::sizeHint() const { + return QSize(250, 600); } } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 7415fbf..d3c2601 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -1,111 +1,118 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <QCheckBox> +#include <QLineEdit> #include <QMainWindow> +#include <QMenuBar> #include <QPointer> -#include <QLineEdit> #include <QPushButton> -#include <QCheckBox> #include <QStackedWidget> -#include <QMenuBar> -#include <Swift/Controllers/UIInterfaces/LoginWindow.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/LoginWindow.h> #include <Swift/Controllers/UIInterfaces/MainWindow.h> -#include <QtAboutWidget.h> + +#include <Swift/QtUI/QtAboutWidget.h> class QLabel; class QToolButton; class QComboBox; namespace Swift { - class SettingsProvider; - class TimerFactory; + class AutoUpdater; + class SettingsProvider; + class TimerFactory; + + class QtLoginWindow : public QMainWindow, public LoginWindow { + Q_OBJECT + public: + struct QtMenus { + QtMenus(QMenu* swiftMenu, QMenu* generalMenu) : swiftMenu(swiftMenu), generalMenu(generalMenu) {} + QMenu* swiftMenu; + QMenu* generalMenu; + }; - class QtLoginWindow : public QMainWindow, public LoginWindow { - Q_OBJECT - public: - struct QtMenus { - QtMenus(QMenu* swiftMenu, QMenu* generalMenu) : swiftMenu(swiftMenu), generalMenu(generalMenu) {} - QMenu* swiftMenu; - QMenu* generalMenu; - }; + public: + QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* settings, TimerFactory* timerFactory, AutoUpdater* autoUpdater); - public: - QtLoginWindow(UIEventStream* uiEventStream, SettingsProvider* settings, TimerFactory* timerFactory); + void morphInto(MainWindow *mainWindow); + virtual void loggedOut(); + 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, 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, const std::vector<Certificate::ref>& certificate); + void hide(); + QtMenus getMenus() const; + virtual void quit(); + QSize sizeHint() const; - void morphInto(MainWindow *mainWindow); - virtual void loggedOut(); - 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, 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, const std::vector<Certificate::ref>& certificate); - void hide(); - QtMenus getMenus() const; - virtual void quit(); + signals: + void geometryChanged(); - signals: - void geometryChanged(); + public slots: + void loginClicked(); - private slots: - void loginClicked(); - void handleCertficateChecked(bool); - void handleQuit(); - void handleShowXMLConsole(); - void handleShowFileTransferOverview(); - void handleShowHighlightEditor(); - void handleToggleSounds(bool enabled); - void handleToggleNotifications(bool enabled); - void handleAbout(); - void bringToFront(); - void toggleBringToFront(); - void handleUsernameTextChanged(); - void resizeEvent(QResizeEvent* event); - void moveEvent(QMoveEvent* event); - void handleSettingChanged(const std::string& settingPath); - void handleOpenConnectionOptions(); + private slots: + void handleCertficateChecked(bool); + void handleQuit(); + void handleShowXMLConsole(); + void handleShowFileTransferOverview(); + void handleShowHighlightEditor(); + void handleToggleSounds(bool enabled); + void handleToggleNotifications(bool enabled); + void handleAbout(); + void bringToFront(); + void toggleBringToFront(); + void handleUsernameTextChanged(); + void resizeEvent(QResizeEvent* event); + void moveEvent(QMoveEvent* event); + void handleSettingChanged(const std::string& settingPath); + void handleOpenConnectionOptions(); - protected: - bool eventFilter(QObject *obj, QEvent *event); + protected: + bool eventFilter(QObject *obj, QEvent *event); - private: - void setInitialMenus(); - QWidget* loginWidgetWrapper_; - QStringList usernames_; - QStringList passwords_; - QStringList certificateFiles_; - std::vector<ClientOptions> options_; - QComboBox* username_; - QLineEdit* password_; - QPushButton* loginButton_; - /* If you add a widget here, change setLoggingIn as well.*/ - QCheckBox* remember_; - QCheckBox* loginAutomatically_; - QStackedWidget* stack_; - QLabel* message_; - QString certificateFile_; - QToolButton* certificateButton_; - QMenuBar* menuBar_; - QMenu* swiftMenu_; - QMenu* generalMenu_; - QAction* toggleSoundsAction_; - QAction* toggleNotificationsAction_; - UIEventStream* uiEventStream_; - QPointer<QtAboutWidget> aboutDialog_; - SettingsProvider* settings_; - QAction* xmlConsoleAction_; - QAction* fileTransferOverviewAction_; - QAction* highlightEditorAction_; - TimerFactory* timerFactory_; - ClientOptions currentOptions_; - }; + private: + void setInitialMenus(); + QWidget* loginWidgetWrapper_; + QStringList usernames_; + QStringList passwords_; + QStringList certificateFiles_; + std::vector<ClientOptions> options_; + QComboBox* username_; + QLineEdit* password_; + QPushButton* loginButton_; + /* If you add a widget here, change setLoggingIn as well.*/ + QCheckBox* remember_; + QCheckBox* loginAutomatically_; + QStackedWidget* stack_; + QLabel* message_; + QString certificateFile_; + QToolButton* certificateButton_; + QMenuBar* menuBar_; + QMenu* swiftMenu_; + QMenu* generalMenu_; + QMenu* viewMenu_ = nullptr; + QAction* toggleSoundsAction_; + QAction* toggleNotificationsAction_; + UIEventStream* uiEventStream_; + QPointer<QtAboutWidget> aboutDialog_; + SettingsProvider* settings_; + QAction* xmlConsoleAction_; + QAction* fileTransferOverviewAction_; + QAction* highlightEditorAction_; + TimerFactory* timerFactory_; + ClientOptions currentOptions_; + AutoUpdater* autoUpdater_; + }; } diff --git a/Swift/QtUI/QtMUCConfigurationWindow.cpp b/Swift/QtUI/QtMUCConfigurationWindow.cpp index 83a5f35..e07b8c6 100644 --- a/Swift/QtUI/QtMUCConfigurationWindow.cpp +++ b/Swift/QtUI/QtMUCConfigurationWindow.cpp @@ -1,43 +1,45 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtMUCConfigurationWindow.h> #include <boost/bind.hpp> + #include <QBoxLayout> #include <QCloseEvent> + #include <Swift/QtUI/QtFormWidget.h> namespace Swift { QtMUCConfigurationWindow::QtMUCConfigurationWindow(Form::ref form) : closed_(false) { - setAttribute(Qt::WA_DeleteOnClose); - - QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); - layout->setContentsMargins(0,0,0,0); - layout->setSpacing(2); - //QLabel* label = new QLabel(this); - //label->setText(tr("Room configuration")); - //layout->addWidget(label); - - formWidget_ = NULL; - formWidget_ = new QtFormWidget(form, this); - layout->addWidget(formWidget_); - - QWidget* buttonsWidget = new QWidget(this); - layout->addWidget(buttonsWidget); - - QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget); - cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget); - buttonsLayout->addWidget(cancelButton_); - connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked())); - okButton_ = new QPushButton(tr("OK"), buttonsWidget); - buttonsLayout->addWidget(okButton_); - connect(okButton_, SIGNAL(clicked()), this, SLOT(handleOKClicked())); - show(); + setAttribute(Qt::WA_DeleteOnClose); + + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(2); + //QLabel* label = new QLabel(this); + //label->setText(tr("Room configuration")); + //layout->addWidget(label); + + formWidget_ = nullptr; + formWidget_ = new QtFormWidget(form, this); + layout->addWidget(formWidget_); + + QWidget* buttonsWidget = new QWidget(this); + layout->addWidget(buttonsWidget); + + QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget); + cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget); + buttonsLayout->addWidget(cancelButton_); + connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked())); + okButton_ = new QPushButton(tr("OK"), buttonsWidget); + buttonsLayout->addWidget(okButton_); + connect(okButton_, SIGNAL(clicked()), this, SLOT(handleOKClicked())); + show(); } QtMUCConfigurationWindow::~QtMUCConfigurationWindow() { @@ -45,19 +47,19 @@ QtMUCConfigurationWindow::~QtMUCConfigurationWindow() { } void QtMUCConfigurationWindow::closeEvent(QCloseEvent* /*event*/) { - if (!closed_) { - onFormCancelled(); - } + if (!closed_) { + onFormCancelled(); + } } void QtMUCConfigurationWindow::handleCancelClicked() { - close(); + close(); } void QtMUCConfigurationWindow::handleOKClicked() { - onFormComplete(formWidget_->getCompletedForm()); - closed_ = true; - close(); + onFormComplete(formWidget_->getCompletedForm()); + closed_ = true; + close(); } diff --git a/Swift/QtUI/QtMUCConfigurationWindow.h b/Swift/QtUI/QtMUCConfigurationWindow.h index f6a22be..da3e12a 100644 --- a/Swift/QtUI/QtMUCConfigurationWindow.h +++ b/Swift/QtUI/QtMUCConfigurationWindow.h @@ -1,39 +1,40 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWidget> -#include <QPushButton> +#include <boost/signals2.hpp> + #include <QLabel> +#include <QPushButton> +#include <QWidget> -#include <Swiften/Base/boost_bsignals.h> #include <Swiften/Elements/Form.h> class QBoxLayout; class QCloseEvent; namespace Swift { - class QtFormWidget; - class QtMUCConfigurationWindow : public QWidget { - Q_OBJECT - public: - QtMUCConfigurationWindow(Form::ref form); - virtual ~QtMUCConfigurationWindow(); - boost::signal<void (Form::ref)> onFormComplete; - boost::signal<void ()> onFormCancelled; - private slots: - void handleCancelClicked(); - void handleOKClicked(); - protected: - virtual void closeEvent(QCloseEvent* event); - private: - QtFormWidget* formWidget_; - QPushButton* okButton_; - QPushButton* cancelButton_; - bool closed_; - }; + class QtFormWidget; + class QtMUCConfigurationWindow : public QWidget { + Q_OBJECT + public: + QtMUCConfigurationWindow(Form::ref form); + virtual ~QtMUCConfigurationWindow(); + boost::signals2::signal<void (Form::ref)> onFormComplete; + boost::signals2::signal<void ()> onFormCancelled; + private slots: + void handleCancelClicked(); + void handleOKClicked(); + protected: + virtual void closeEvent(QCloseEvent* event); + private: + QtFormWidget* formWidget_; + QPushButton* okButton_; + QPushButton* cancelButton_; + bool closed_; + }; } diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 6f87a88..92488ae 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -1,43 +1,50 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtMainWindow.h" +#include <Swift/QtUI/QtMainWindow.h> + +#include <memory> -#include <boost/optional.hpp> #include <boost/bind.hpp> -#include <boost/smart_ptr/make_shared.hpp> +#include <boost/optional.hpp> +#include <QAction> #include <QBoxLayout> #include <QComboBox> #include <QLineEdit> #include <QListWidget> #include <QListWidgetItem> -#include <QPushButton> #include <QMenuBar> -#include <QToolBar> -#include <QAction> +#include <QPushButton> +#include <QScrollArea> #include <QTabWidget> +#include <QToolBar> -#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 <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> -#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> +#include <Swiften/Base/Platform.h> + +#include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/FdpFormSubmitWindowOpenUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h> -#include <Swift/QtUI/QtUISettingConstants.h> -#include <Swift/Controllers/SettingConstants.h> -#include <Swiften/Base/Platform.h> +#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestHistoryUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> +#include <Swift/QtUI/QtAdHocCommandWithJIDWindow.h> +#include <Swift/QtUI/QtChatOverview.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTabWidget.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/Roster/QtFilterWidget.h> +#include <Swift/QtUI/Roster/QtRosterWidget.h> #if defined(SWIFTEN_PLATFORM_MACOSX) #include <Swift/QtUI/CocoaUIHelpers.h> #elif defined(SWIFTEN_PLATFORM_WINDOWS) @@ -48,331 +55,398 @@ namespace Swift { -QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { - uiEventStream_ = uiEventStream; - settings_ = settings; - setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); - mainLayout->setContentsMargins(0,0,0,0); - mainLayout->setSpacing(0); - meView_ = new QtRosterHeader(settings, 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); -#if QT_VERSION >= 0x040500 - tabs_->setDocumentMode(true); +QtMainWindow::QtMainWindow(Chattables& chattables, SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist, bool enableAdHocCommandOnJID) : QWidget(), MainWindow(false), chattables_(chattables), loginMenus_(loginMenus) { + uiEventStream_ = uiEventStream; + settings_ = settings; + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); + mainLayout->setContentsMargins(0,0,0,0); + mainLayout->setSpacing(0); + meView_ = new QtRosterHeader(settings, 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); + tabs_->setDocumentMode(true); + tabs_->setTabPosition(QTabWidget::South); + mainLayout->addWidget(tabs_); + + if (settings->getSetting(SettingConstants::FUTURE)) { + chatOverview_ = new QtChatOverview(chattables, this); + auto overviewScroll = new QScrollArea(this); + overviewScroll->setWidgetResizable(true); + overviewScroll->setWidget(chatOverview_); + tabs_->addTab(overviewScroll, tr("&All")); + + // When used with QSplitter and setChildrenCollapsible(false), the following prevents + // this widget to be hidden, i.e. resized to zero width. + chatOverview_->setMinimumWidth(20); + } + + contactsTabWidget_ = new QWidget(this); + contactsTabWidget_->setContentsMargins(0, 0, 0, 0); + QBoxLayout *contactTabLayout = new QBoxLayout(QBoxLayout::TopToBottom, contactsTabWidget_); + contactsTabWidget_->setLayout(contactTabLayout); + contactTabLayout->setSpacing(0); + contactTabLayout->setContentsMargins(0, 0, 0, 0); + + treeWidget_ = new QtRosterWidget(uiEventStream_, settings_, this); + + contactTabLayout->addWidget(treeWidget_); + new QtFilterWidget(this, treeWidget_, uiEventStream_, contactTabLayout); + tabs_->addTab(contactsTabWidget_, tr("&Contacts")); + eventWindow_ = new QtEventWindow(uiEventStream_); + connect(eventWindow_, SIGNAL(onNewEventCountUpdated(int)), this, SLOT(handleEventCountUpdated(int))); + + chatListWindow_ = new QtChatListWindow(uiEventStream_, settings_); + connect(chatListWindow_, SIGNAL(onCountUpdated(int)), this, SLOT(handleChatCountUpdated(int))); + + tabs_->addTab(chatListWindow_, tr("C&hats")); + tabs_->addTab(eventWindow_, tr("&Notices")); + + tabs_->setCurrentIndex(settings_->getSetting(QtUISettingConstants::CURRENT_ROSTER_TAB)); + + connect(tabs_, SIGNAL(currentChanged(int)), this, SLOT(handleTabChanged(int))); + + tabBarCombo_ = nullptr; + if (settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)) { + tabs_->tabBar()->hide(); + tabBarCombo_ = new QComboBox(this); + tabBarCombo_->setAccessibleName("Current View"); + tabBarCombo_->addItem(tr("All")); +#ifndef NOT_YET + tabBarCombo_->addItem(tr("Contacts")); + tabBarCombo_->addItem(tr("Chats")); #endif - tabs_->setTabPosition(QTabWidget::South); - mainLayout->addWidget(tabs_); - contactsTabWidget_ = new QWidget(this); - contactsTabWidget_->setContentsMargins(0, 0, 0, 0); - QBoxLayout *contactTabLayout = new QBoxLayout(QBoxLayout::TopToBottom, contactsTabWidget_); - contactsTabWidget_->setLayout(contactTabLayout); - contactTabLayout->setSpacing(0); - contactTabLayout->setContentsMargins(0, 0, 0, 0); - - treeWidget_ = new QtRosterWidget(uiEventStream_, settings_, this); - contactTabLayout->addWidget(treeWidget_); - - tabs_->addTab(contactsTabWidget_, tr("&Contacts")); - - eventWindow_ = new QtEventWindow(uiEventStream_); - connect(eventWindow_, SIGNAL(onNewEventCountUpdated(int)), this, SLOT(handleEventCountUpdated(int))); - - chatListWindow_ = new QtChatListWindow(uiEventStream_, settings_); - connect(chatListWindow_, SIGNAL(onCountUpdated(int)), this, SLOT(handleChatCountUpdated(int))); - - tabs_->addTab(chatListWindow_, tr("C&hats")); - tabs_->addTab(eventWindow_, tr("&Notices")); - - tabs_->setCurrentIndex(settings_->getSetting(QtUISettingConstants::CURRENT_ROSTER_TAB)); - - connect(tabs_, SIGNAL(currentChanged(int)), this, SLOT(handleTabChanged(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); - showOfflineAction_->setChecked(false); - connect(showOfflineAction_, SIGNAL(toggled(bool)), SLOT(handleShowOfflineToggled(bool))); - viewMenu->addAction(showOfflineAction_); - 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); - //compactRosterAction_->setChecked(false); - //connect(compactRosterAction_, SIGNAL(toggled(bool)), uiPreferences_, SLOT(setCompactRosters(bool))); - //viewMenu->addAction(compactRosterAction_); - - QMenu* actionsMenu = new QMenu(tr("&Actions"), this); - menus_.push_back(actionsMenu); - 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); - connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction())); - actionsMenu->addAction(joinMUCAction); + 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); + showOfflineAction_->setChecked(false); + connect(showOfflineAction_, SIGNAL(toggled(bool)), SLOT(handleShowOfflineToggled(bool))); + viewMenu->addAction(showOfflineAction_); + 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)); + } + + QMenu* actionsMenu = new QMenu(tr("&Actions"), this); + menus_.push_back(actionsMenu); + QAction* editProfileAction = new QAction(tr("Edit &Profile…"), this); + connect(editProfileAction, SIGNAL(triggered()), SLOT(handleEditProfileAction())); + actionsMenu->addAction(editProfileAction); + onlineOnlyActions_ << editProfileAction; + QAction* joinMUCAction = new QAction(tr("Enter &Room…"), this); + connect(joinMUCAction, SIGNAL(triggered()), SLOT(handleJoinMUCAction())); + actionsMenu->addAction(joinMUCAction); + onlineOnlyActions_ << joinMUCAction; #ifdef SWIFT_EXPERIMENTAL_HISTORY - QAction* viewLogsAction = new QAction(tr("&View History…"), this); - connect(viewLogsAction, SIGNAL(triggered()), SLOT(handleViewLogsAction())); - actionsMenu->addAction(viewLogsAction); + 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_); - openBlockingListEditor_->setVisible(false); - addUserAction_ = new QAction(tr("&Add Contact…"), this); - addUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D)); - connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool))); - actionsMenu->addAction(addUserAction_); - editUserAction_ = new QAction(tr("&Edit Selected Contact…"), this); - connect(editUserAction_, SIGNAL(triggered(bool)), treeWidget_, SLOT(handleEditUserActionTriggered(bool))); - actionsMenu->addAction(editUserAction_); - editUserAction_->setEnabled(false); - chatUserAction_ = new QAction(tr("Start &Chat…"), this); - chatUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N)); - connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool))); - actionsMenu->addAction(chatUserAction_); - serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this); - actionsMenu->addMenu(serverAdHocMenu_); - actionsMenu->addSeparator(); - QAction* signOutAction = new QAction(tr("&Sign Out"), this); - connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction())); - actionsMenu->addAction(signOutAction); - - toggleRequestDeliveryReceipts_ = new QAction(tr("&Request Delivery Receipts"), this); - toggleRequestDeliveryReceipts_->setCheckable(true); - toggleRequestDeliveryReceipts_->setChecked(settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS)); - connect(toggleRequestDeliveryReceipts_, SIGNAL(toggled(bool)), SLOT(handleToggleRequestDeliveryReceipts(bool))); - - QList< QAction* > generalMenuActions = loginMenus_.generalMenu->actions(); - loginMenus_.generalMenu->insertAction(generalMenuActions.at(generalMenuActions.count()-2),toggleRequestDeliveryReceipts_); - - treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1)); - - setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); - QAction* adHocAction = new QAction(tr("Collecting commands..."), this); - adHocAction->setEnabled(false); - serverAdHocMenu_->addAction(adHocAction); - serverAdHocCommandActions_.append(adHocAction); - - settings_->onSettingChanged.connect(boost::bind(&QtMainWindow::handleSettingChanged, this, _1)); + 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_); + 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_->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_); + if (settings_->getSetting(SettingConstants::FUTURE)) { + actionsMenu->addSeparator(); + submitFormAction_ = new QAction(tr("Submit Form"), this); + connect(submitFormAction_, &QAction::triggered, this, &QtMainWindow::handleSubmitFormActionTriggered); + actionsMenu->addAction(submitFormAction_); + onlineOnlyActions_ << submitFormAction_; + } + actionsMenu->addSeparator(); + QAction* signOutAction = new QAction(tr("&Sign Out"), this); + connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction())); + actionsMenu->addAction(signOutAction); + + toggleRequestDeliveryReceipts_ = new QAction(tr("&Request Delivery Receipts"), this); + toggleRequestDeliveryReceipts_->setCheckable(true); + toggleRequestDeliveryReceipts_->setChecked(settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS)); + connect(toggleRequestDeliveryReceipts_, SIGNAL(toggled(bool)), SLOT(handleToggleRequestDeliveryReceipts(bool))); + + QList< QAction* > generalMenuActions = loginMenus_.generalMenu->actions(); + loginMenus_.generalMenu->insertAction(generalMenuActions.at(generalMenuActions.count()-2),toggleRequestDeliveryReceipts_); + + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtMainWindow::handleSomethingSelectedChanged, this, _1)); + + setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); + QAction* adHocAction = new QAction(tr("Collecting commands..."), this); + adHocAction->setEnabled(false); + serverAdHocMenu_->addAction(adHocAction); + serverAdHocCommandActions_.append(adHocAction); + + settings_->onSettingChanged.connect(boost::bind(&QtMainWindow::handleSettingChanged, this, _1)); } QtMainWindow::~QtMainWindow() { - settings_->onSettingChanged.disconnect(boost::bind(&QtMainWindow::handleSettingChanged, this, _1)); + settings_->onSettingChanged.disconnect(boost::bind(&QtMainWindow::handleSettingChanged, this, _1)); } void QtMainWindow::handleTabChanged(int index) { - settings_->storeSetting(QtUISettingConstants::CURRENT_ROSTER_TAB, index); + settings_->storeSetting(QtUISettingConstants::CURRENT_ROSTER_TAB, index); } void QtMainWindow::handleToggleRequestDeliveryReceipts(bool enabled) { - settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, enabled); + settings_->storeSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS, enabled); } void QtMainWindow::handleShowCertificateInfo() { - onShowCertificateRequest(); + onShowCertificateRequest(); } void QtMainWindow::handleEditBlockingList() { - uiEventStream_->send(boost::make_shared<RequestBlockListDialogUIEvent>()); + uiEventStream_->send(std::make_shared<RequestBlockListDialogUIEvent>()); +} + +void QtMainWindow::handleSomethingSelectedChanged(bool itemSelected) { + bool isOnline = addUserAction_->isEnabled(); + editUserAction_->setEnabled(isOnline && itemSelected); } QtEventWindow* QtMainWindow::getEventWindow() { - return eventWindow_; + return eventWindow_; } QtChatListWindow* QtMainWindow::getChatListWindow() { - return chatListWindow_; + return chatListWindow_; } void QtMainWindow::setRosterModel(Roster* roster) { - treeWidget_->setRosterModel(roster); + treeWidget_->setRosterModel(roster); } void QtMainWindow::handleEditProfileRequest() { - uiEventStream_->send(boost::make_shared<RequestProfileEditorUIEvent>()); + uiEventStream_->send(std::make_shared<RequestProfileEditorUIEvent>()); } void QtMainWindow::handleEventCountUpdated(int count) { - QColor eventTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default - int eventIndex = 2; - tabs_->tabBar()->setTabTextColor(eventIndex, eventTabColor); - QString text = tr("&Notices"); - if (count > 0) { - text += QString(" (%1)").arg(count); - } - tabs_->setTabText(eventIndex, text); + QColor eventTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default + int eventIndex = 2; + tabs_->tabBar()->setTabTextColor(eventIndex, eventTabColor); + QString text = tr("&Notices"); + if (count > 0) { + text += QString(" (%1)").arg(count); + } + tabs_->setTabText(eventIndex, text); } void QtMainWindow::handleChatCountUpdated(int count) { - QColor chatTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default - int chatIndex = 1; - tabs_->tabBar()->setTabTextColor(chatIndex, chatTabColor); - QString text = tr("&Chats"); - if (count > 0) { - text += QString(" (%1)").arg(count); - } - tabs_->setTabText(chatIndex, text); + QColor chatTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default + int chatIndex = 1; + tabs_->tabBar()->setTabTextColor(chatIndex, chatTabColor); + QString text = tr("C&hats"); + if (count > 0) { + text += QString(" (%1)").arg(count); + } + tabs_->setTabText(chatIndex, text); } void QtMainWindow::handleAddUserActionTriggered(bool /*checked*/) { - boost::shared_ptr<UIEvent> event(new RequestAddUserDialogUIEvent()); - uiEventStream_->send(event); + std::shared_ptr<UIEvent> event(new RequestAddUserDialogUIEvent()); + uiEventStream_->send(event); } void QtMainWindow::handleChatUserActionTriggered(bool /*checked*/) { - boost::shared_ptr<UIEvent> event(new RequestChatWithUserDialogUIEvent()); - uiEventStream_->send(event); + std::shared_ptr<UIEvent> event(new RequestChatWithUserDialogUIEvent()); + uiEventStream_->send(event); +} + +void QtMainWindow::handleOtherAdHocActionTriggered() { + new QtAdHocCommandWithJIDWindow(uiEventStream_); } void QtMainWindow::handleSignOutAction() { - loginMenus_.generalMenu->removeAction(toggleRequestDeliveryReceipts_); - onSignOutRequest(); + loginMenus_.generalMenu->removeAction(toggleRequestDeliveryReceipts_); + onSignOutRequest(); } void QtMainWindow::handleEditProfileAction() { - uiEventStream_->send(boost::make_shared<RequestProfileEditorUIEvent>()); + uiEventStream_->send(std::make_shared<RequestProfileEditorUIEvent>()); } void QtMainWindow::handleJoinMUCAction() { - uiEventStream_->send(boost::make_shared<RequestJoinMUCUIEvent>()); + uiEventStream_->send(std::make_shared<RequestJoinMUCUIEvent>()); } void QtMainWindow::handleViewLogsAction() { - uiEventStream_->send(boost::make_shared<RequestHistoryUIEvent>()); + uiEventStream_->send(std::make_shared<RequestHistoryUIEvent>()); } void QtMainWindow::handleStatusChanged(StatusShow::Type showType, const QString &statusMessage) { - onChangeStatusRequest(showType, Q2PSTRING(statusMessage)); + onChangeStatusRequest(showType, Q2PSTRING(statusMessage)); } void QtMainWindow::handleSettingChanged(const std::string& settingPath) { - if (settingPath == SettingConstants::SHOW_OFFLINE.getKey()) { - 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)); - } + if (settingPath == SettingConstants::SHOW_OFFLINE.getKey()) { + 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)); + settings_->storeSetting(QtUISettingConstants::COMPACT_ROSTER, state); + compactRosterAction_->setChecked(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); } void QtMainWindow::handleShowOfflineToggled(bool state) { - settings_->storeSetting(SettingConstants::SHOW_OFFLINE, state); - showOfflineAction_->setChecked(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); + settings_->storeSetting(SettingConstants::SHOW_OFFLINE, state); + showOfflineAction_->setChecked(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); } void QtMainWindow::handleShowEmoticonsToggled(bool state) { - settings_->storeSetting(QtUISettingConstants::SHOW_EMOTICONS, state); - showEmoticonsAction_->setChecked(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); + settings_->storeSetting(QtUISettingConstants::SHOW_EMOTICONS, state); + showEmoticonsAction_->setChecked(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS)); } void QtMainWindow::setMyNick(const std::string& nick) { - meView_->setNick(P2QSTRING(nick)); + meView_->setNick(P2QSTRING(nick)); } void QtMainWindow::setMyJID(const JID& jid) { - meView_->setJID(P2QSTRING(jid.toBare().toString())); + meView_->setJID(P2QSTRING(jid.toBare().toString())); } void QtMainWindow::setMyAvatarPath(const std::string& path) { - meView_->setAvatar(P2QSTRING(path)); + meView_->setAvatar(P2QSTRING(path)); } void QtMainWindow::setMyStatusText(const std::string& status) { - meView_->setStatusText(P2QSTRING(status)); + meView_->setStatusText(P2QSTRING(status)); } void QtMainWindow::setMyStatusType(StatusShow::Type type) { - meView_->setStatusType(type); + meView_->setStatusType(type); + const bool online = (type != StatusShow::None); + treeWidget_->setOnline(online); + chatListWindow_->setOnline(online); + for (auto action : onlineOnlyActions_) { + action->setEnabled(online); + } + serverAdHocMenu_->setEnabled(online); +} + +void QtMainWindow::setMyContactRosterItem(std::shared_ptr<ContactRosterItem> contact) { + meView_->setContactRosterItem(contact); } void QtMainWindow::setConnecting() { - meView_->setConnecting(); + meView_->setConnecting(); } void QtMainWindow::setStreamEncryptionStatus(bool tlsInPlaceAndValid) { - meView_->setStreamEncryptionStatus(tlsInPlaceAndValid); + meView_->setStreamEncryptionStatus(tlsInPlaceAndValid); } void QtMainWindow::openCertificateDialog(const std::vector<Certificate::ref>& chain) { - openCertificateDialog(chain, this); + openCertificateDialog(chain, this); } void QtMainWindow::openCertificateDialog(const std::vector<Certificate::ref>& chain, QWidget* parent) { #if defined(SWIFTEN_PLATFORM_MACOSX) - CocoaUIHelpers::displayCertificateChainAsSheet(parent, chain); + CocoaUIHelpers::displayCertificateChainAsSheet(parent, chain); #elif defined(SWIFTEN_PLATFORM_WINDOWS) - WinUIHelpers::displayCertificateChainAsSheet(parent, chain); + WinUIHelpers::displayCertificateChainAsSheet(parent, chain); #else - QtCertificateViewerDialog::displayCertificateChainAsSheet(parent, chain); + QtCertificateViewerDialog::displayCertificateChainAsSheet(parent, chain); #endif } void QtMainWindow::handleAdHocActionTriggered(bool /*checked*/) { - QAction* action = qobject_cast<QAction*>(sender()); - assert(action); - DiscoItems::Item command = serverAdHocCommands_[serverAdHocCommandActions_.indexOf(action)]; - uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestAdHocUIEvent(command))); + QAction* action = qobject_cast<QAction*>(sender()); + assert(action); + assert(serverAdHocCommandActions_.indexOf(action) >= 0); + DiscoItems::Item command = serverAdHocCommands_[serverAdHocCommandActions_.indexOf(action)]; + uiEventStream_->send(std::make_shared<RequestAdHocUIEvent>(command)); } void QtMainWindow::setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) { - serverAdHocCommands_ = commands; - foreach (QAction* action, serverAdHocCommandActions_) { - delete action; - } - serverAdHocMenu_->clear(); - serverAdHocCommandActions_.clear(); - foreach (DiscoItems::Item command, commands) { - QAction* action = new QAction(P2QSTRING(command.getName()), this); - connect(action, SIGNAL(triggered(bool)), this, SLOT(handleAdHocActionTriggered(bool))); - serverAdHocMenu_->addAction(action); - serverAdHocCommandActions_.append(action); - } - if (serverAdHocCommandActions_.isEmpty()) { - QAction* action = new QAction(tr("No Available Commands"), this); - action->setEnabled(false); - serverAdHocMenu_->addAction(action); - serverAdHocCommandActions_.append(action); - } + serverAdHocCommands_ = commands; + for (auto action : serverAdHocCommandActions_) { + delete action; + } + serverAdHocMenu_->clear(); + serverAdHocCommandActions_.clear(); + for (const auto& command : commands) { + QAction* action = new QAction(P2QSTRING(command.getName()), this); + connect(action, SIGNAL(triggered(bool)), this, SLOT(handleAdHocActionTriggered(bool))); + serverAdHocMenu_->addAction(action); + serverAdHocCommandActions_.append(action); + } + if (serverAdHocCommandActions_.isEmpty()) { + QAction* action = new QAction(tr("No Available Commands"), this); + action->setEnabled(false); + serverAdHocMenu_->addAction(action); + serverAdHocCommandActions_.append(action); + } } void QtMainWindow::setBlockingCommandAvailable(bool isAvailable) { - openBlockingListEditor_->setVisible(isAvailable); + openBlockingListEditor_->setVisible(isAvailable); } +void QtMainWindow::handleSubmitFormActionTriggered() { + uiEventStream_->send(std::make_shared<FdpFormSubmitWindowOpenUIEvent>()); } +} diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 3e6e1d3..b285831 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -1,102 +1,116 @@ /* - * Copyright (c) 2010-2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWidget> -#include <QMenu> +#include <vector> + #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 <QMenu> +#include <QWidget> -#include <vector> +#include <Swift/Controllers/Chat/Chattables.h> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> +#include <Swift/QtUI/ChatList/QtChatListWindow.h> +#include <Swift/QtUI/EventViewer/QtEventWindow.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtRosterHeader.h> + +class QAction; class QComboBox; class QLineEdit; -class QPushButton; -class QToolBar; -class QAction; class QMenu; +class QPushButton; class QTabWidget; +class QToolBar; namespace Swift { - class QtRosterWidget; - class TreeWidget; - class UIEventStream; - class QtTabWidget; - class SettingsProvider; - class QtUIPreferences; - class StatusCache; + class QtChatOverview; + class QtRosterWidget; + class QtTabWidget; + class QtUIPreferences; + class SettingsProvider; + class StatusCache; + class TreeWidget; + class UIEventStream; - class QtMainWindow : public QWidget, public MainWindow { - Q_OBJECT - public: - QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist); - virtual ~QtMainWindow(); - std::vector<QMenu*> getMenus() {return menus_;} - void setMyNick(const std::string& name); - void setMyJID(const JID& jid); - void setMyAvatarPath(const std::string& path); - void setMyStatusText(const std::string& status); - void setMyStatusType(StatusShow::Type type); - 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 handleAdHocActionTriggered(bool checked); - void handleEventCountUpdated(int count); - void handleChatCountUpdated(int count); - void handleEditProfileRequest(); - void handleTabChanged(int index); - void handleToggleRequestDeliveryReceipts(bool enabled); - void handleShowCertificateInfo(); - void handleEditBlockingList(); + class QtMainWindow : public QWidget, public MainWindow { + Q_OBJECT + public: + QtMainWindow(Chattables&, SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist, bool enableAdHocCommandOnJID); + virtual ~QtMainWindow() override; + std::vector<QMenu*> getMenus() {return menus_;} + void setMyNick(const std::string& name) override; + void setMyJID(const JID& jid) override; + void setMyAvatarPath(const std::string& path) override; + void setMyStatusText(const std::string& status) override; + void setMyStatusType(StatusShow::Type type) override; + void setMyContactRosterItem(std::shared_ptr<ContactRosterItem> contact) override; + void setConnecting() override; + void setStreamEncryptionStatus(bool tlsInPlaceAndValid) override; + void openCertificateDialog(const std::vector<Certificate::ref>& chain) override; + static void openCertificateDialog(const std::vector<Certificate::ref>& chain, QWidget* parent); + QtEventWindow* getEventWindow(); + QtChatListWindow* getChatListWindow(); + void setRosterModel(Roster* roster) override; + void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) override; + void setBlockingCommandAvailable(bool isAvailable) override; + 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); + void handleChatCountUpdated(int count); + void handleEditProfileRequest(); + void handleTabChanged(int index); + void handleToggleRequestDeliveryReceipts(bool enabled); + void handleShowCertificateInfo(); + void handleEditBlockingList(); + void handleSomethingSelectedChanged(bool itemSelected); + void handleSubmitFormActionTriggered(); - private: - SettingsProvider* settings_; - QtLoginWindow::QtMenus loginMenus_; - std::vector<QMenu*> menus_; - QtRosterWidget* treeWidget_; - QtRosterHeader* meView_; - QAction* addUserAction_; - QAction* editUserAction_; - QAction* chatUserAction_; - QAction* showOfflineAction_; - QAction* compactRosterAction_; - QAction* showEmoticonsAction_; - QAction* openBlockingListEditor_; - QAction* toggleRequestDeliveryReceipts_; - QMenu* serverAdHocMenu_; - QtTabWidget* tabs_; - QWidget* contactsTabWidget_; - QWidget* eventsTabWidget_; - QtEventWindow* eventWindow_; - QtChatListWindow* chatListWindow_; - UIEventStream* uiEventStream_; - std::vector<DiscoItems::Item> serverAdHocCommands_; - QList<QAction*> serverAdHocCommandActions_; - }; + private: + Chattables& chattables_; + SettingsProvider* settings_; + QtLoginWindow::QtMenus loginMenus_; + std::vector<QMenu*> menus_; + QtRosterWidget* treeWidget_; + QtRosterHeader* meView_; + QAction* addUserAction_; + QAction* editUserAction_; + QAction* chatUserAction_; + QAction* otherAdHocAction_; + QAction* showOfflineAction_; + QAction* compactRosterAction_; + QAction* showEmoticonsAction_; + QAction* openBlockingListEditor_; + QAction* toggleRequestDeliveryReceipts_; + QMenu* serverAdHocMenu_; + QtTabWidget* tabs_; + QComboBox* tabBarCombo_; + QtChatOverview* chatOverview_; + QWidget* contactsTabWidget_; + QWidget* eventsTabWidget_; + QtEventWindow* eventWindow_; + QtChatListWindow* chatListWindow_; + UIEventStream* uiEventStream_; + std::vector<DiscoItems::Item> serverAdHocCommands_; + QList<QAction*> serverAdHocCommandActions_; + QList<QAction*> onlineOnlyActions_; + QAction* submitFormAction_; + }; } diff --git a/Swift/QtUI/QtNameWidget.cpp b/Swift/QtUI/QtNameWidget.cpp index 08e32f5..0610a00 100644 --- a/Swift/QtUI/QtNameWidget.cpp +++ b/Swift/QtUI/QtNameWidget.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtNameWidget.h" +#include <Swift/QtUI/QtNameWidget.h> #include <QHBoxLayout> #include <QMenu> @@ -17,76 +17,81 @@ namespace Swift { -QtNameWidget::QtNameWidget(SettingsProvider* settings, QWidget *parent) : QWidget(parent), settings(settings) { - QHBoxLayout* mainLayout = new QHBoxLayout(this); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0,0,0,0); +QtNameWidget::QtNameWidget(SettingsProvider* settings, QWidget *parent) : QWidget(parent), settings(settings), isOnline_(false) { + QHBoxLayout* mainLayout = new QHBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); - mode = settings->getSetting(QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER) ? ShowNick : ShowJID; + mode = settings->getSetting(QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER) ? ShowNick : ShowJID; - textLabel = new QtElidingLabel(this); - QFont font = textLabel->font(); - font.setBold(true); - textLabel->setFont(font); - mainLayout->addWidget(textLabel); + textLabel = new QtElidingLabel(this); + QFont font = textLabel->font(); + font.setBold(true); + textLabel->setFont(font); + mainLayout->addWidget(textLabel); } void QtNameWidget::setNick(const QString& nick) { - this->nick = nick; - updateText(); + this->nick = nick; + updateText(); } void QtNameWidget::setJID(const QString& jid) { - this->jid = jid; - updateText(); + this->jid = jid; + updateText(); +} + +void QtNameWidget::setOnline(const bool isOnline) { + isOnline_ = isOnline; } void QtNameWidget::mousePressEvent(QMouseEvent* event) { - QMenu menu; - bool hasNick = !nick.isEmpty(); - - QAction* showAsNick = new QAction(hasNick ? tr("Show Nickname") : tr("(No Nickname Set)"), this); - showAsNick->setCheckable(true); - showAsNick->setEnabled(hasNick); - if (mode == ShowNick && hasNick) { - showAsNick->setChecked(true); - } - menu.addAction(showAsNick); - - QAction* showAsJID = new QAction(tr("Show Address"), this); - showAsJID->setCheckable(true); - if (mode == ShowJID || !hasNick) { - showAsJID->setChecked(true); - } - menu.addAction(showAsJID); - - QAction* editProfile = new QAction(tr("Edit Profile"), this); - menu.addAction(editProfile); - - QAction* result = menu.exec(event->globalPos()); - if (result == showAsJID) { - mode = ShowJID; - } - else if (result == showAsNick) { - mode = ShowNick; - } - else if (result == editProfile) { - emit onChangeNickRequest(); - } - settings->storeSetting(QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER, mode == ShowNick); - updateText(); + QMenu menu; + bool hasNick = !nick.isEmpty(); + + QAction* showAsNick = new QAction(hasNick ? tr("Show Nickname") : tr("(No Nickname Set)"), this); + showAsNick->setCheckable(true); + showAsNick->setEnabled(hasNick); + if (mode == ShowNick && hasNick) { + showAsNick->setChecked(true); + } + menu.addAction(showAsNick); + + QAction* showAsJID = new QAction(tr("Show Address"), this); + showAsJID->setCheckable(true); + if (mode == ShowJID || !hasNick) { + showAsJID->setChecked(true); + } + menu.addAction(showAsJID); + + QAction* editProfile = new QAction(tr("Edit Profile"), this); + menu.addAction(editProfile); + editProfile->setEnabled(isOnline_); + + QAction* result = menu.exec(event->globalPos()); + if (result == showAsJID) { + mode = ShowJID; + } + else if (result == showAsNick) { + mode = ShowNick; + } + else if (result == editProfile) { + emit onChangeNickRequest(); + } + settings->storeSetting(QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER, mode == ShowNick); + updateText(); } void QtNameWidget::updateText() { - QString text; - if (mode == ShowNick && !nick.isEmpty()) { - text = nick; - } - else { - text = jid; - } - text.replace("<","<"); - textLabel->setText(text); + QString text; + if (mode == ShowNick && !nick.isEmpty()) { + text = nick; + } + else { + text = jid; + } + text.replace("<","<"); + textLabel->setText(text); } } diff --git a/Swift/QtUI/QtNameWidget.h b/Swift/QtUI/QtNameWidget.h index 3225879..0da0ff6 100644 --- a/Swift/QtUI/QtNameWidget.h +++ b/Swift/QtUI/QtNameWidget.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2014 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,36 +9,38 @@ #include <QWidget> namespace Swift { - class QtElidingLabel; - class SettingsProvider; - - class QtNameWidget : public QWidget { - Q_OBJECT - - public: - QtNameWidget(SettingsProvider* settings, QWidget *parent); - - void setNick(const QString& text); - void setJID(const QString& jid); - - signals: - void onChangeNickRequest(); - - private: - void updateText(); - virtual void mousePressEvent(QMouseEvent* event); - - private: - enum Mode { - ShowNick, - ShowJID - }; - - SettingsProvider* settings; - Mode mode; - QtElidingLabel* textLabel; - QString jid; - QString nick; - }; + class QtElidingLabel; + class SettingsProvider; + + class QtNameWidget : public QWidget { + Q_OBJECT + + public: + QtNameWidget(SettingsProvider* settings, QWidget *parent); + + void setNick(const QString& text); + void setJID(const QString& jid); + void setOnline(const bool isOnline); + + signals: + void onChangeNickRequest(); + + private: + void updateText(); + virtual void mousePressEvent(QMouseEvent* event); + + private: + enum Mode { + ShowNick, + ShowJID + }; + + SettingsProvider* settings; + Mode mode; + QtElidingLabel* textLabel; + QString jid; + QString nick; + bool isOnline_; + }; } diff --git a/Swift/QtUI/QtPlainChatView.cpp b/Swift/QtUI/QtPlainChatView.cpp new file mode 100644 index 0000000..cdee1e6 --- /dev/null +++ b/Swift/QtUI/QtPlainChatView.cpp @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2013-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtPlainChatView.h> + +#include <QDialog> +#include <QFileDialog> +#include <QInputDialog> +#include <QLabel> +#include <QMenu> +#include <QProgressBar> +#include <QPushButton> +#include <QScrollBar> +#include <QTextEdit> +#include <QVBoxLayout> + +#include <Swiften/Base/FileSize.h> + +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.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() { +} + +static QString chatMessageToString(const ChatWindow::ChatMessage& message) { + QString result; + for (auto&& part : message.getParts()) { + std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + std::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart; + std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart; + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart; + + if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); + text.replace("\n","<br/>"); + result += text; + continue; + } + if ((uriPart = std::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) { + QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); + result += "<a href='" + uri + "' >" + uri + "</a>"; + continue; + } + if ((emoticonPart = std::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(part))) { + result += P2QSTRING(emoticonPart->alternativeText); + continue; + } + if ((highlightPart = std::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, std::shared_ptr<SecurityLabel> label, const std::string& /*avatarPath*/, const boost::posix_time::ptime& time) { + 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, std::shared_ptr<SecurityLabel> label, const std::string& /*avatarPath*/, const boost::posix_time::ptime& time) { + 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; +} + +std::string QtPlainChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/) +{ + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></p>"; + log_->append(text); + return ""; +} + +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) +{ + 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) +{ + 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::replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& /*id*/, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/) { + QString text = "<p><i>"; + text += chatMessageToString(message); + text += "</i></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, const std::string& /*avatarPath*/, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description) +{ + const std::string ftId = "ft" + std::to_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 descriptionMessage = description.isEmpty() ? "" : (" \"" + Q2PSTRING(description) + "\""); + const std::string message = std::string() + "Confirm file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>" + descriptionMessage; + 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 descriptionMessage = description.empty() ? "" : (" \"" + description + "\""); + const std::string message = std::string() + "Incoming file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>" + descriptionMessage; + 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; + + const std::string message = transfer->message_; + + 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, 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(std::make_shared<JoinMUCUIEvent>(action->jid_.toString(), action->password_, boost::optional<std::string>(), 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_(nullptr), 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::Initialisation: { + status = "Preparing to send <i>"+ filename + "</i>..."; + FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId); + parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject())); + layout_->addWidget(cancel); + break; + } + 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..d6ba14b --- /dev/null +++ b/Swift/QtUI/QtPlainChatView.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2013-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +#include <boost/date_time/posix_time/posix_time.hpp> + +#include <QTextEdit> +#include <QWidget> + +#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*/, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/); + /** 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*/, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/); + + virtual std::string 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*/); + virtual void replaceWithAction(const ChatWindow::ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/); + virtual void replaceLastMessage(const ChatWindow::ChatMessage& /*message*/, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/); + virtual void replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/); + virtual void setAckState(const std::string& /*id*/, ChatWindow::AckState /*state*/); + + virtual std::string addFileTransfer(const std::string& /*senderName*/, const std::string& /*avatarPath*/, bool /*senderIsSelf*/, const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/, const std::string& /*description*/); + 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, std::shared_ptr<SecurityLabel> > lastMessageLabel_; + int idGenerator_; + + }; +} diff --git a/Swift/QtUI/QtProfileWindow.cpp b/Swift/QtUI/QtProfileWindow.cpp index b1cdd19..10326bb 100644 --- a/Swift/QtUI/QtProfileWindow.cpp +++ b/Swift/QtUI/QtProfileWindow.cpp @@ -1,7 +1,7 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ /* @@ -10,108 +10,158 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtProfileWindow.h" -#include "ui_QtProfileWindow.h" +#include <Swift/QtUI/QtProfileWindow.h> #include <QCloseEvent> #include <QMovie> #include <QShortcut> #include <QTextDocument> +#include <QTimer> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/ui_QtProfileWindow.h> namespace Swift { QtProfileWindow::QtProfileWindow() : - QWidget(), - ui(new Ui::QtProfileWindow) { - ui->setupUi(this); - new QShortcut(QKeySequence::Close, this, SLOT(close())); - ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); - connect(ui->savePushButton, SIGNAL(clicked()), SLOT(handleSave())); - setEditable(false); - setAttribute(Qt::WA_DeleteOnClose); + QWidget(), + ui(new Ui::QtProfileWindow) { + ui->setupUi(this); + + ui->statusLabel->setText(tr("Retrieving profile information for this user.")); + ui->statusLabel->setVisible(false); + + ui->emptyLabel->setText(tr("No profile information is available for this user.")); + ui->emptyLabel->setVisible(false); + + 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); + setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); + + adjustSizeTimer.setSingleShot(true); + connect(&adjustSizeTimer, SIGNAL(timeout()), SLOT(handleAdjustSizeTimeout())); } QtProfileWindow::~QtProfileWindow() { - delete ui; + delete ui; } void QtProfileWindow::setJID(const JID& jid) { - this->jid = jid; - updateTitle(); + this->jid = jid; + updateTitle(); } void QtProfileWindow::setVCard(VCard::ref vcard) { - ui->vcard->setVCard(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); + } + updateWindowSize(); } void QtProfileWindow::setEnabled(bool b) { - ui->vcard->setEnabled(b); - ui->savePushButton->setEnabled(b); + ui->vcard->setEnabled(b); + ui->savePushButton->setEnabled(b); } void QtProfileWindow::setEditable(bool b) { - if (b) { - ui->savePushButton->show(); - ui->vcard->setEditable(true); - } else { - ui->savePushButton->hide(); - ui->vcard->setEditable(false); - } - updateTitle(); + ui->throbberLabel->setVisible(b); + ui->savePushButton->setVisible(b); + ui->vcard->setEditable(b); + updateTitle(); + updateWindowSize(); } void QtProfileWindow::setProcessing(bool processing) { - if (processing) { - ui->throbberLabel->movie()->start(); - ui->throbberLabel->show(); - } - else { - ui->throbberLabel->hide(); - ui->throbberLabel->movie()->stop(); - } + if (processing) { + ui->throbberLabel->movie()->start(); + ui->throbberLabel->show(); + ui->statusLabel->setVisible(true); + ui->vcard->setVisible(false); + } + else { + ui->throbberLabel->hide(); + ui->throbberLabel->movie()->stop(); + ui->statusLabel->setVisible(false); + ui->vcard->setVisible(true); + } } 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(""); - } + if (!error.empty()) { + ui->errorLabel->setText("<font color='red'>" + QtUtilities::htmlEscape(P2QSTRING(error)) + "</font>"); + } + else { + ui->errorLabel->setText(""); + } + ui->errorLabel->setVisible(!error.empty()); } void QtProfileWindow::show() { - QWidget::show(); - QWidget::activateWindow(); + QWidget::showNormal(); + QWidget::activateWindow(); + QWidget::raise(); } void QtProfileWindow::hide() { - QWidget::hide(); + QWidget::hide(); +} + +QSize QtProfileWindow::sizeHint() const { + return QWidget::sizeHint(); } void QtProfileWindow::updateTitle() { - QString jidString; - if (jid.isValid()) { - jidString = QString(" ( %1 )").arg(P2QSTRING(jid.toString())); - } + QString jidString; + if (jid.isValid()) { + jidString = QString(" ( %1 )").arg(P2QSTRING(jid.toString())); + } + + if (ui->vcard->isEditable()) { + setWindowTitle(tr("Edit Profile") + jidString); + } else { + setWindowTitle(tr("Show Profile") + jidString); + } +} - if (ui->vcard->isEditable()) { - setWindowTitle(tr("Edit Profile") + jidString); - } else { - setWindowTitle(tr("Show Profile") + jidString); - } +void QtProfileWindow::updateWindowSize() { + // Delay resizing to the end of the event loop, because Qt calculates the correct layout asynchronously. + // Qt will post LayoutRequests for widgets on the event loop on show and widgets will recaluclate their + // layout as they process these events. + // We use the complete and correct size hint from the freshly calculated layout by delaying execution of + // the resize code to the end of Qt's event loop. + if (!adjustSizeTimer.isActive()) { + adjustSizeTimer.start(0); + } } void QtProfileWindow::closeEvent(QCloseEvent* event) { - event->accept(); - onWindowAboutToBeClosed(jid); + event->accept(); + onWindowAboutToBeClosed(jid); } void QtProfileWindow::handleSave() { - onVCardChangeRequest(ui->vcard->getVCard()); + onVCardChangeRequest(ui->vcard->getVCard()); +} + +void QtProfileWindow::handleAdjustSizeTimeout() { + // Force recaluclation of all layout geometry in children widgets. + // This is required on Windows to have the correct size even on first show. + QList<QWidget *> children = findChildren<QWidget*>(); + for (auto child : children) { + child->updateGeometry(); + } + + updateGeometry(); + adjustSize(); } } diff --git a/Swift/QtUI/QtProfileWindow.h b/Swift/QtUI/QtProfileWindow.h index a2af63a..7315807 100644 --- a/Swift/QtUI/QtProfileWindow.h +++ b/Swift/QtUI/QtProfileWindow.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ /* @@ -12,46 +12,52 @@ #pragma once +#include <QTimer> +#include <QWidget> + #include <Swiften/JID/JID.h> #include <Swift/Controllers/UIInterfaces/ProfileWindow.h> -#include <QWidget> - namespace Ui { - class QtProfileWindow; + class QtProfileWindow; } namespace Swift { class QtProfileWindow : public QWidget, public ProfileWindow { - Q_OBJECT + Q_OBJECT + + public: + QtProfileWindow(); + virtual ~QtProfileWindow(); - public: - QtProfileWindow(); - virtual ~QtProfileWindow(); + virtual void setJID(const JID& jid); + virtual void setVCard(VCard::ref vcard); - virtual void setJID(const JID& jid); - virtual void setVCard(VCard::ref vcard); + virtual void setEnabled(bool b); + virtual void setProcessing(bool processing); + virtual void setError(const std::string& error); + virtual void setEditable(bool b); - virtual void setEnabled(bool b); - virtual void setProcessing(bool processing); - virtual void setError(const std::string& error); - virtual void setEditable(bool b); + virtual void show(); + virtual void hide(); - virtual void show(); - virtual void hide(); + virtual QSize sizeHint() const; - private: - void updateTitle(); - virtual void closeEvent(QCloseEvent* event); + private: + void updateTitle(); + void updateWindowSize(); + virtual void closeEvent(QCloseEvent* event); - private slots: - void handleSave(); + private slots: + void handleSave(); + void handleAdjustSizeTimeout(); - private: - Ui::QtProfileWindow* ui; - JID jid; + private: + Ui::QtProfileWindow* ui; + JID jid; + QTimer adjustSizeTimer; }; } diff --git a/Swift/QtUI/QtProfileWindow.ui b/Swift/QtUI/QtProfileWindow.ui index 68a36da..ed2986d 100644 --- a/Swift/QtUI/QtProfileWindow.ui +++ b/Swift/QtUI/QtProfileWindow.ui @@ -13,7 +13,7 @@ <property name="windowTitle"> <string>Edit Profile</string> </property> - <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0"> + <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0"> <property name="margin"> <number>0</number> </property> @@ -21,6 +21,39 @@ <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> @@ -33,7 +66,7 @@ <property name="sizeHint" stdset="0"> <size> <width>40</width> - <height>20</height> + <height>0</height> </size> </property> </spacer> @@ -46,19 +79,6 @@ </widget> </item> <item> - <widget class="QLabel" name="throbberLabel"> - <property name="text"> - <string/> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <property name="textInteractionFlags"> - <set>Qt::NoTextInteraction</set> - </property> - </widget> - </item> - <item> <widget class="QPushButton" name="savePushButton"> <property name="text"> <string>Save</string> diff --git a/Swift/QtUI/QtRecentEmojisGrid.cpp b/Swift/QtUI/QtRecentEmojisGrid.cpp new file mode 100644 index 0000000..54cca76 --- /dev/null +++ b/Swift/QtUI/QtRecentEmojisGrid.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtRecentEmojisGrid.h> + +#include <algorithm> + +#include <QSettings> +#include <QVector> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/QtEmojiCell.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + const int QtRecentEmojisGrid::MAX_RECENTS = 50; + + QtRecentEmojisGrid::QtRecentEmojisGrid(QSettings* settings) : QtEmojisGrid(), settings_(settings) { + loadSettings(); + connect(this, SIGNAL(onEmojiSelected(QString)), this, SLOT(handleEmojiClicked(QString))); + refresh(); + } + + QtRecentEmojisGrid::~QtRecentEmojisGrid() { + writeSettings(); + } + + void QtRecentEmojisGrid::handleEmojiClicked(QString emoji) { + recents_.erase(std::remove(recents_.begin(), recents_.end(), emoji), recents_.end()); + + if (recents_.size() > MAX_RECENTS) { + recents_.resize(MAX_RECENTS - 1); + } + + recents_.push_front(emoji); + refresh(); + } + + void QtRecentEmojisGrid::refresh() { + QtEmojisGrid::setEmojis(recents_); + } + + void QtRecentEmojisGrid::loadSettings() { + QByteArray readData = settings_->value("recentEmojis").toByteArray(); + QDataStream readStream(&readData, QIODevice::ReadOnly); + readStream >> recents_; + } + + void QtRecentEmojisGrid::writeSettings() { + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << recents_; + settings_->setValue("recentEmojis", data); + } +} diff --git a/Swift/QtUI/QtRecentEmojisGrid.h b/Swift/QtUI/QtRecentEmojisGrid.h new file mode 100644 index 0000000..6201b26 --- /dev/null +++ b/Swift/QtUI/QtRecentEmojisGrid.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <memory> + +#include <QGridLayout> +#include <QSettings> +#include <QSize> +#include <QString> +#include <QVector> + +#include <SwifTools/EmojiMapper.h> + +#include <Swift/QtUI/QtEmojisGrid.h> + +namespace Swift { + class QtRecentEmojisGrid : public QtEmojisGrid { + Q_OBJECT + public: + explicit QtRecentEmojisGrid(QSettings* settings); + ~QtRecentEmojisGrid(); + + public slots: + void handleEmojiClicked(QString emoji); + + private: + void refresh(); + void loadSettings(); + void writeSettings(); + + private: + static const int MAX_RECENTS; + + private: + QVector<QString> recents_; + QSettings* settings_; + }; +} diff --git a/Swift/QtUI/QtResourceHelper.cpp b/Swift/QtUI/QtResourceHelper.cpp new file mode 100644 index 0000000..a802b19 --- /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 44459d5..b405d10 100644 --- a/Swift/QtUI/QtRosterHeader.cpp +++ b/Swift/QtUI/QtRosterHeader.cpp @@ -1,112 +1,139 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtRosterHeader.h" +#include <Swift/QtUI/QtRosterHeader.h> -#include <QHBoxLayout> -#include <QVBoxLayout> +#include <QBitmap> #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 <qdebug.h> -#include "QtStatusWidget.h" -#include <Swift/QtUI/QtElidingLabel.h> #include <Swift/QtUI/QtClickableLabel.h> +#include <Swift/QtUI/QtElidingLabel.h> #include <Swift/QtUI/QtNameWidget.h> -#include "QtScaledAvatarCache.h" +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtStatusWidget.h> +#include <Swift/QtUI/Roster/RosterTooltip.h> namespace Swift { -QtRosterHeader::QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent) : QWidget(parent) { - QHBoxLayout* topLayout = new QHBoxLayout(); - topLayout->setSpacing(3); - topLayout->setContentsMargins(4,4,4,4); - setLayout(topLayout); - setMinimumHeight(50); - setMaximumHeight(50); - - avatarLabel_ = new QtClickableLabel(this); - avatarLabel_->setMinimumSize(avatarSize_, avatarSize_); - avatarLabel_->setMaximumSize(avatarSize_, avatarSize_); - avatarLabel_->setAlignment(Qt::AlignCenter); - setAvatar(":/icons/avatar.png"); - avatarLabel_->setScaledContents(false); - topLayout->addWidget(avatarLabel_); - connect(avatarLabel_, SIGNAL(clicked()), this, SIGNAL(onEditProfileRequest())); - - QVBoxLayout* rightLayout = new QVBoxLayout(); - rightLayout->setSpacing(4); - rightLayout->setContentsMargins(4,0,0,0); - 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())); - 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(statusCache, this); - connect(statusWidget_, SIGNAL(onChangeStatusRequest(StatusShow::Type, const QString&)), this, SLOT(handleChangeStatusRequest(StatusShow::Type, const QString&))); - rightLayout->addWidget(statusWidget_); - - show(); +QtRosterHeader::QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent) : QWidget(parent), statusEdit_(nullptr) { + QHBoxLayout* topLayout = new QHBoxLayout(); + topLayout->setSpacing(3); + topLayout->setContentsMargins(4,4,4,4); + setLayout(topLayout); + setMinimumHeight(50); + setMaximumHeight(50); + + avatarLabel_ = new QtClickableLabel(this); + avatarLabel_->setMinimumSize(avatarSize_, avatarSize_); + avatarLabel_->setMaximumSize(avatarSize_, avatarSize_); + avatarLabel_->setAlignment(Qt::AlignCenter); + setAvatar(":/icons/avatar.png"); + avatarLabel_->setScaledContents(false); + topLayout->addWidget(avatarLabel_); + connect(avatarLabel_, SIGNAL(clicked()), this, SIGNAL(onEditProfileRequest())); + + QVBoxLayout* rightLayout = new QVBoxLayout(); + rightLayout->setSpacing(4); + rightLayout->setContentsMargins(4,0,0,0); + 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())); + 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(statusCache, this); + connect(statusWidget_, SIGNAL(onChangeStatusRequest(StatusShow::Type, const QString&)), this, SLOT(handleChangeStatusRequest(StatusShow::Type, const QString&))); + rightLayout->addWidget(statusWidget_); + + show(); } void QtRosterHeader::handleChangeStatusRequest(StatusShow::Type type, const QString& text) { - emit onChangeStatusRequest(type, text); + emit onChangeStatusRequest(type, text); } void QtRosterHeader::setStatusText(const QString& statusMessage) { - statusWidget_->setStatusText(statusMessage); + statusWidget_->setStatusText(statusMessage); } void QtRosterHeader::setStatusType(StatusShow::Type type) { - statusWidget_->setStatusType(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); + } } void QtRosterHeader::setConnecting() { - statusWidget_->setConnecting(); + statusWidget_->setConnecting(); } void QtRosterHeader::setStreamEncryptionStatus(bool tlsInPlace) { - securityInfoButton_->setVisible(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); - QPixmap avatar; - if (QFileInfo(scaledAvatarPath).exists()) { - avatar.load(scaledAvatarPath); - } - else { - avatar = QPixmap(":/icons/avatar.png").scaled(avatarSize_, avatarSize_, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - avatarLabel_->setPixmap(avatar); + QString scaledAvatarPath = QtScaledAvatarCache(avatarSize_).getScaledAvatarPath(path); + QPixmap avatar; + if (QFileInfo(scaledAvatarPath).exists()) { + avatar.load(scaledAvatarPath); + } + else { + avatar = QPixmap(":/icons/avatar.svg").scaled(avatarSize_, avatarSize_, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + avatarLabel_->setPixmap(avatar); } void QtRosterHeader::setNick(const QString& nick) { - nameWidget_->setNick(nick); + nameWidget_->setNick(nick); +} + +void QtRosterHeader::setContactRosterItem(std::shared_ptr<ContactRosterItem> contact) { + contact_ = contact; } void QtRosterHeader::setJID(const QString& jid) { - nameWidget_->setJID(jid); + nameWidget_->setJID(jid); } diff --git a/Swift/QtUI/QtRosterHeader.h b/Swift/QtUI/QtRosterHeader.h index ad19178..8370eb5 100644 --- a/Swift/QtUI/QtRosterHeader.h +++ b/Swift/QtUI/QtRosterHeader.h @@ -1,58 +1,66 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWidget> +#include <string> + #include <QLabel> #include <QPixmap> #include <QSize> #include <QToolButton> +#include <QWidget> -#include <string> -#include "Swiften/Elements/StatusShow.h" +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Elements/VCard.h> -#include "QtTextEdit.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> + +#include <Swift/QtUI/QtTextEdit.h> class QHBoxLayout; namespace Swift { - class QtClickableLabel; - class QtStatusWidget; - class QtNameWidget; - class SettingsProvider; - class StatusCache; - - class QtRosterHeader : public QWidget { - Q_OBJECT - public: - QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent = NULL); - void setAvatar(const QString& path); - - void setJID(const QString& jid); - void setNick(const QString& nick); - - void setStatusText(const QString& statusMessage); - void setStatusType(StatusShow::Type type); - void setConnecting(); - void setStreamEncryptionStatus(bool tlsInPlace); - signals: - void onChangeStatusRequest(StatusShow::Type showType, const QString &statusMessage); - void onEditProfileRequest(); - void onShowCertificateInfo(); - - private slots: - void handleChangeStatusRequest(StatusShow::Type type, const QString &statusMessage); - private: - QString name_; - QtClickableLabel* avatarLabel_; - QtNameWidget* nameWidget_; - QtTextEdit* statusEdit_; - QtStatusWidget* statusWidget_; - QToolButton* securityInfoButton_; - static const int avatarSize_; - }; + class QtClickableLabel; + class QtStatusWidget; + class QtNameWidget; + class SettingsProvider; + class StatusCache; + + class QtRosterHeader : public QWidget { + Q_OBJECT + public: + QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent = nullptr); + void setAvatar(const QString& path); + + void setJID(const QString& jid); + void setNick(const QString& nick); + void setContactRosterItem(std::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: + void handleChangeStatusRequest(StatusShow::Type type, const QString &statusMessage); + private: + QString name_; + QtClickableLabel* avatarLabel_; + QtNameWidget* nameWidget_; + QtTextEdit* statusEdit_; + QtStatusWidget* statusWidget_; + QToolButton* securityInfoButton_; + static const int avatarSize_; + std::shared_ptr<ContactRosterItem> contact_; + }; } diff --git a/Swift/QtUI/QtScaledAvatarCache.cpp b/Swift/QtUI/QtScaledAvatarCache.cpp index 46ec2fc..e3a28d6 100644 --- a/Swift/QtUI/QtScaledAvatarCache.cpp +++ b/Swift/QtUI/QtScaledAvatarCache.cpp @@ -1,61 +1,88 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtScaledAvatarCache.h" +#include <Swift/QtUI/QtScaledAvatarCache.h> -#include <QFileInfo> +#include <QByteArray> #include <QDir> -#include <QPixmap> +#include <QFileInfo> #include <QImage> #include <QImageReader> #include <QPainter> -#include <QByteArray> +#include <QPixmap> #include <Swiften/Base/Log.h> + #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { +namespace { + // This number needs to be incremented whenever the avatar scaling procedure changes. + const int QT_SCALED_AVATAR_CACHE_VERSION = 1; +} + QtScaledAvatarCache::QtScaledAvatarCache(int size) : size(size) { } +static QPixmap cropToBiggestCenteredSquare(const QPixmap& input) { + QPixmap squareCropped; + if (input.width() != input.height()) { + QRect centeredSquare; + if (input.width() > input.height()) { + int x = (input.width() - input.height()) / 2; + centeredSquare = QRect(x, 0, input.height(), input.height()); + } + else { + int y = (input.height() - input.width()) / 2; + centeredSquare = QRect(0, y, input.width(), input.width()); + } + squareCropped = input.copy(centeredSquare); + } + else { + squareCropped = input; + } + return squareCropped; +} + QString QtScaledAvatarCache::getScaledAvatarPath(const QString& path) { - QFileInfo avatarFile(path); - if (avatarFile.exists()) { - if (!avatarFile.dir().exists(QString::number(size))) { - if (!avatarFile.dir().mkdir(QString::number(size))) { - return path; - } - } - QDir targetDir(avatarFile.dir().absoluteFilePath(QString::number(size))); - QString targetFile = targetDir.absoluteFilePath(avatarFile.baseName()); - if (!QFileInfo(targetFile).exists()) { - QPixmap avatarPixmap; - if (avatarPixmap.load(path)) { - QPixmap maskedAvatar(avatarPixmap.size()); - maskedAvatar.fill(QColor(0, 0, 0, 0)); - QPainter maskPainter(&maskedAvatar); - maskPainter.setBrush(Qt::black); - maskPainter.drawRoundedRect(maskedAvatar.rect(), 25.0, 25.0, Qt::RelativeSize); - maskPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); - maskPainter.drawPixmap(0, 0, avatarPixmap); - maskPainter.end(); - - if (!maskedAvatar.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(targetFile, "PNG")) { - return path; - } - } else { - SWIFT_LOG(debug) << "Failed to load " << Q2PSTRING(path) << std::endl; - } - } - return targetFile; - } - else { - return path; - } + QFileInfo avatarFile(path); + if (avatarFile.exists() && !avatarFile.absolutePath().startsWith(":/")) { + QString cacheSubPath = QString("ScaledAvatarCacheV%1/%2").arg(QString::number(QT_SCALED_AVATAR_CACHE_VERSION), QString::number(size)); + if (!avatarFile.dir().mkpath(cacheSubPath)) { + SWIFT_LOG(error) << "avatarFile.dir(): " << Q2PSTRING(avatarFile.dir().absolutePath()); + SWIFT_LOG(error) << "Failed creating cache folder: " << Q2PSTRING(cacheSubPath); + return path; + } + QDir targetDir(avatarFile.dir().absoluteFilePath(cacheSubPath)); + QString targetFile = targetDir.absoluteFilePath(avatarFile.baseName()); + if (!QFileInfo(targetFile).exists()) { + QPixmap avatarPixmap; + if (avatarPixmap.load(path)) { + QPixmap squaredAvatarPixmap = cropToBiggestCenteredSquare(avatarPixmap); + QPixmap maskedAvatar(squaredAvatarPixmap.size()); + maskedAvatar.fill(QColor(0, 0, 0, 0)); + QPainter maskPainter(&maskedAvatar); + maskPainter.setBrush(Qt::black); + maskPainter.drawRoundedRect(maskedAvatar.rect(), 25.0, 25.0, Qt::RelativeSize); + maskPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); + maskPainter.drawPixmap(0, 0, squaredAvatarPixmap); + maskPainter.end(); + if (!maskedAvatar.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation).save(targetFile, "PNG")) { + return path; + } + } else { + SWIFT_LOG(warning) << "Failed to load " << Q2PSTRING(path); + } + } + return targetFile; + } + else { + return path; + } } diff --git a/Swift/QtUI/QtScaledAvatarCache.h b/Swift/QtUI/QtScaledAvatarCache.h index cc3ac07..748fb40 100644 --- a/Swift/QtUI/QtScaledAvatarCache.h +++ b/Swift/QtUI/QtScaledAvatarCache.h @@ -1,22 +1,23 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <string> + #include <QString> namespace Swift { - class QtScaledAvatarCache { - public: - QtScaledAvatarCache(int size); + class QtScaledAvatarCache { + public: + QtScaledAvatarCache(int size); - QString getScaledAvatarPath(const QString& path); + QString getScaledAvatarPath(const QString& path); - private: - int size; - }; + private: + int size; + }; } diff --git a/Swift/QtUI/QtSettingsProvider.cpp b/Swift/QtUI/QtSettingsProvider.cpp index a98cdf3..42ac22d 100644 --- a/Swift/QtUI/QtSettingsProvider.cpp +++ b/Swift/QtUI/QtSettingsProvider.cpp @@ -1,13 +1,13 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <QtSettingsProvider.h> +#include <Swift/QtUI/QtSettingsProvider.h> -#include <QStringList> #include <QFile> +#include <QStringList> namespace Swift { @@ -19,105 +19,105 @@ QtSettingsProvider::~QtSettingsProvider() { } bool QtSettingsProvider::hasSetting(const std::string& key) { - return !settings_.value(key.c_str()).isNull(); + return !settings_.value(key.c_str()).isNull(); } std::string QtSettingsProvider::getSetting(const Setting<std::string>& setting) { - QVariant variant = settings_.value(setting.getKey().c_str()); - return variant.isNull() ? setting.getDefaultValue() : std::string(variant.toString().toUtf8()); + QVariant variant = settings_.value(setting.getKey().c_str()); + return variant.isNull() ? setting.getDefaultValue() : std::string(variant.toString().toUtf8()); } void QtSettingsProvider::storeSetting(const Setting<std::string>& setting, const std::string& settingValue) { - bool changed = false; - if (getSetting(setting) != settingValue) { - changed = true; - } - settings_.setValue(setting.getKey().c_str(), settingValue.c_str()); - if (changed) { - onSettingChanged(setting.getKey()); - } - updatePermissions(); + bool changed = false; + if (getSetting(setting) != settingValue) { + changed = true; + } + settings_.setValue(setting.getKey().c_str(), settingValue.c_str()); + if (changed) { + onSettingChanged(setting.getKey()); + } + updatePermissions(); } bool QtSettingsProvider::getSetting(const Setting<bool>& setting) { - QVariant variant = settings_.value(setting.getKey().c_str()); - return variant.isNull() ? setting.getDefaultValue() : variant.toBool(); + QVariant variant = settings_.value(setting.getKey().c_str()); + return variant.isNull() ? setting.getDefaultValue() : variant.toBool(); } void QtSettingsProvider::storeSetting(const Setting<bool>& setting, const bool& settingValue) { - bool changed = false; - if (getSetting(setting) != settingValue) { - changed = true; - } - settings_.setValue(setting.getKey().c_str(), settingValue); - if (changed) { - onSettingChanged(setting.getKey()); - } - updatePermissions(); + bool changed = false; + if (getSetting(setting) != settingValue) { + changed = true; + } + settings_.setValue(setting.getKey().c_str(), settingValue); + if (changed) { + onSettingChanged(setting.getKey()); + } + updatePermissions(); } int QtSettingsProvider::getSetting(const Setting<int>& setting) { - QVariant variant = settings_.value(setting.getKey().c_str()); - return variant.isNull() ? setting.getDefaultValue() : variant.toInt(); + QVariant variant = settings_.value(setting.getKey().c_str()); + return variant.isNull() ? setting.getDefaultValue() : variant.toInt(); } void QtSettingsProvider::storeSetting(const Setting<int>& setting, const int& settingValue) { - bool changed = false; - if (getSetting(setting) != settingValue) { - changed = true; - } - settings_.setValue(setting.getKey().c_str(), settingValue); - if (changed) { - onSettingChanged(setting.getKey()); - } - updatePermissions(); + bool changed = false; + if (getSetting(setting) != settingValue) { + changed = true; + } + settings_.setValue(setting.getKey().c_str(), settingValue); + if (changed) { + onSettingChanged(setting.getKey()); + } + updatePermissions(); } std::vector<std::string> QtSettingsProvider::getAvailableProfiles() { - std::vector<std::string> profiles; - QVariant profilesVariant = settings_.value("profileList"); - foreach(QString profileQString, profilesVariant.toStringList()) { - profiles.push_back(std::string(profileQString.toUtf8())); - } - return profiles; + std::vector<std::string> profiles; + QVariant profilesVariant = settings_.value("profileList"); + for (const auto& profileQString : profilesVariant.toStringList()) { + profiles.push_back(std::string(profileQString.toUtf8())); + } + return profiles; } void QtSettingsProvider::createProfile(const std::string& profile) { - QStringList stringList = settings_.value("profileList").toStringList(); - stringList.append(profile.c_str()); - settings_.setValue("profileList", stringList); - updatePermissions(); + QStringList stringList = settings_.value("profileList").toStringList(); + stringList.append(profile.c_str()); + settings_.setValue("profileList", stringList); + updatePermissions(); } void QtSettingsProvider::removeProfile(const std::string& profile) { - QString profileStart(QString(profile.c_str()) + ":"); - foreach (QString key, settings_.allKeys()) { - if (key.startsWith(profileStart)) { - settings_.remove(key); - } - } - QStringList stringList = settings_.value("profileList").toStringList(); - stringList.removeAll(profile.c_str()); - settings_.setValue("profileList", stringList); - updatePermissions(); + QString profileStart(QString(profile.c_str()) + ":"); + for (auto&& key : settings_.allKeys()) { + if (key.startsWith(profileStart)) { + settings_.remove(key); + } + } + QStringList stringList = settings_.value("profileList").toStringList(); + stringList.removeAll(profile.c_str()); + settings_.setValue("profileList", stringList); + updatePermissions(); } QSettings* QtSettingsProvider::getQSettings() { - return &settings_; + return &settings_; } void QtSettingsProvider::updatePermissions() { #if !defined(Q_OS_WIN) && !defined(Q_OS_MAC) - QFile file(settings_.fileName()); - if (file.exists()) { - file.setPermissions(QFile::ReadOwner|QFile::WriteOwner); - } + QFile file(settings_.fileName()); + if (file.exists()) { + file.setPermissions(QFile::ReadOwner|QFile::WriteOwner); + } #endif } bool QtSettingsProvider::getIsSettingFinal(const std::string& /*settingPath*/) { - return false; + return false; } } diff --git a/Swift/QtUI/QtSettingsProvider.h b/Swift/QtUI/QtSettingsProvider.h index e85bfb5..21e9211 100644 --- a/Swift/QtUI/QtSettingsProvider.h +++ b/Swift/QtUI/QtSettingsProvider.h @@ -1,40 +1,40 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <Swift/Controllers/Settings/SettingsProvider.h> - #include <QSettings> +#include <Swift/Controllers/Settings/SettingsProvider.h> + namespace Swift { class QtSettingsProvider : public SettingsProvider { - public: - QtSettingsProvider(); - virtual ~QtSettingsProvider(); - virtual std::string getSetting(const Setting<std::string>& setting); - virtual void storeSetting(const Setting<std::string>& setting, const std::string& value); - virtual bool getSetting(const Setting<bool>& setting); - virtual void storeSetting(const Setting<bool>& setting, const bool& value); - virtual int getSetting(const Setting<int>& setting); - virtual void storeSetting(const Setting<int>& setting, const int& value); - virtual std::vector<std::string> getAvailableProfiles(); - virtual void createProfile(const std::string& profile); - virtual void removeProfile(const std::string& profile); - virtual bool hasSetting(const std::string& key); - QSettings* getQSettings(); - protected: - virtual bool getIsSettingFinal(const std::string& settingPath); - - private: - void updatePermissions(); - - private: - QSettings settings_; + public: + QtSettingsProvider(); + virtual ~QtSettingsProvider(); + virtual std::string getSetting(const Setting<std::string>& setting); + virtual void storeSetting(const Setting<std::string>& setting, const std::string& value); + virtual bool getSetting(const Setting<bool>& setting); + virtual void storeSetting(const Setting<bool>& setting, const bool& value); + virtual int getSetting(const Setting<int>& setting); + virtual void storeSetting(const Setting<int>& setting, const int& value); + virtual std::vector<std::string> getAvailableProfiles(); + virtual void createProfile(const std::string& profile); + virtual void removeProfile(const std::string& profile); + virtual bool hasSetting(const std::string& key); + QSettings* getQSettings(); + protected: + virtual bool getIsSettingFinal(const std::string& settingPath); + + private: + void updatePermissions(); + + private: + QSettings settings_; }; } diff --git a/Swift/QtUI/QtSingleWindow.cpp b/Swift/QtUI/QtSingleWindow.cpp index c970296..af7e552 100644 --- a/Swift/QtUI/QtSingleWindow.cpp +++ b/Swift/QtUI/QtSingleWindow.cpp @@ -1,15 +1,21 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtSingleWindow.h> +#include <QPushButton> +#include <QVBoxLayout> + +#include <Swiften/Base/Platform.h> -#include <Swiften/Base/foreach.h> -#include <Swift/QtUI/QtSettingsProvider.h> #include <Swift/QtUI/QtChatTabs.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Swift/QtUI/ServerList/QtServerListView.h> +#include <Swift/QtUI/ServerList/ServerListModel.h> namespace Swift { @@ -17,65 +23,104 @@ 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(); + settings_ = settings; + auto geometryVariant = settings_->getQSettings()->value(SINGLE_WINDOW_GEOMETRY); + if (geometryVariant.isValid()) { + restoreGeometry(geometryVariant.toByteArray()); + } + connect(this, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(/*int, int*/))); + setChildrenCollapsible(false); + + auto left = new QWidget(this); + serverList_ = new QtServerListView(); + serverListModel_ = new ServerListModel(); + serverList_->setModel(serverListModel_); + serverListModel_->setModelData(&accountData_); + accountData_.onDataChanged.connect(boost::bind(&ServerListModel::handleDataChanged, serverListModel_)); + auto addButton = new QPushButton("+", left); + QVBoxLayout* leftLayout = new QVBoxLayout(); + leftLayout->addWidget(serverList_); + leftLayout->addWidget(addButton); + left->setLayout(leftLayout); + QSplitter::addWidget(left); + loginWindows_ = new QStackedWidget(this); + QSplitter::addWidget(loginWindows_); + tabs_ = new QStackedWidget(this); + QSplitter::addWidget(tabs_); + restoreSplitters(); + setStretchFactor(0, 0); + setStretchFactor(1, 0); + setStretchFactor(2, 1); + connect(serverList_, &QtServerListView::clicked, this, &QtSingleWindow::handleListItemClicked); + connect(addButton, &QPushButton::clicked, this, &QtSingleWindow::wantsToAddAccount); +#ifdef SWIFTEN_PLATFORM_MACOSX + setHandleWidth(0); +#endif } 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); + 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::handleSplitterMoved() { + QList<QVariant> variantValues; + QList<int> intValues = sizes(); + for (const auto& 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); + auto splitsVariant = settings_->getQSettings()->value(SINGLE_WINDOW_SPLITS); + if (splitsVariant.isValid()) { + auto variantValues = splitsVariant.toList(); + QList<int> intValues; + for (auto&& value : variantValues) { + intValues.append(value.toInt()); + } + setSizes(intValues); + } + else { + handleSplitterMoved(); + } } -void QtSingleWindow::insertAtFront(QWidget* widget) { - insertWidget(0, widget); - restoreSplitters(); +void QtSingleWindow::handleGeometryChanged() { + settings_->getQSettings()->setValue(SINGLE_WINDOW_GEOMETRY, saveGeometry()); } -void QtSingleWindow::handleGeometryChanged() { - settings_->getQSettings()->setValue(SINGLE_WINDOW_GEOMETRY, saveGeometry()); +void QtSingleWindow::resizeEvent(QResizeEvent* event) { + handleGeometryChanged(); + QSplitter::resizeEvent(event); +} +void QtSingleWindow::moveEvent(QMoveEvent*) { + handleGeometryChanged(); } -void QtSingleWindow::resizeEvent(QResizeEvent*) { - handleGeometryChanged(); +void QtSingleWindow::addAccount(QtLoginWindow* loginWindow, QtChatTabs* tabs) { + loginWindows_->addWidget(loginWindow); + tabs_->addWidget(tabs); + std::string account = QString("Account %1").arg(loginWindows_->count()).toStdString(); + accountData_.addAccount(account); + emit serverListModel_->layoutChanged(); } -void QtSingleWindow::moveEvent(QMoveEvent*) { - handleGeometryChanged(); +void QtSingleWindow::handleListItemClicked(const QModelIndex& item) { + auto currentTabs = tabs_->widget(tabs_->currentIndex()); + disconnect(currentTabs, SIGNAL(onTitleChanged(const QString&)), this, SLOT(handleTabsTitleChanged(const QString&))); + loginWindows_->setCurrentIndex(item.row()); + tabs_->setCurrentIndex(item.row()); + currentTabs = tabs_->widget(tabs_->currentIndex()); + connect(currentTabs, SIGNAL(onTitleChanged(const QString&)), this, SLOT(handleTabsTitleChanged(const QString&))); + //TODO change the title of the window. + handleTabsTitleChanged(QString("Swift")); + } } diff --git a/Swift/QtUI/QtSingleWindow.h b/Swift/QtUI/QtSingleWindow.h index b55b3c9..a707cd3 100644 --- a/Swift/QtUI/QtSingleWindow.h +++ b/Swift/QtUI/QtSingleWindow.h @@ -1,37 +1,54 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <QListWidget> #include <QSplitter> +#include <QStackedWidget> + +#include <Swift/QtUI/ServerList/ServerListModel.h> 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_; - }; + class QtChatTabs; + class QtLoginWindow; + class QtSettingsProvider; + class QtServerListView; + class ServerListModel; + + class QtSingleWindow : public QSplitter { + Q_OBJECT + public: + QtSingleWindow(QtSettingsProvider* settings); + virtual ~QtSingleWindow(); + void addAccount(QtLoginWindow* widget, QtChatTabs* tabs); + + signals: + void wantsToAddAccount(); + + protected: + void resizeEvent(QResizeEvent*); + void moveEvent(QMoveEvent*); + private slots: + void handleSplitterMoved(); + void handleTabsTitleChanged(const QString& title); + void handleListItemClicked(const QModelIndex&); + private: + void handleGeometryChanged(); + void restoreSplitters(); + + private: + + QtSettingsProvider* settings_; + SwiftAccountData accountData_; + QtServerListView* serverList_; + ServerListModel* serverListModel_; + QStackedWidget* loginWindows_; + QStackedWidget* tabs_; + }; } diff --git a/Swift/QtUI/QtSoundPlayer.cpp b/Swift/QtUI/QtSoundPlayer.cpp index 3f3782d..22f544d 100644 --- a/Swift/QtUI/QtSoundPlayer.cpp +++ b/Swift/QtUI/QtSoundPlayer.cpp @@ -1,43 +1,46 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtSoundPlayer.h" +#include <Swift/QtUI/QtSoundPlayer.h> -#include <QSound> #include <iostream> -#include <SwifTools/Application/ApplicationPathProvider.h> -#include <QtSwiftUtil.h> +#include <QSound> + #include <Swiften/Base/Path.h> +#include <SwifTools/Application/ApplicationPathProvider.h> + +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { - + QtSoundPlayer::QtSoundPlayer(ApplicationPathProvider* applicationPathProvider) : applicationPathProvider(applicationPathProvider) { } void QtSoundPlayer::playSound(SoundEffect sound, const std::string& soundResource) { - switch (sound) { - case MessageReceived: - playSound(soundResource.empty() ? "/sounds/message-received.wav" : soundResource); - break; - } + switch (sound) { + case MessageReceived: + playSound(soundResource.empty() ? "/sounds/message-received.wav" : soundResource); + break; + } } void QtSoundPlayer::playSound(const std::string& soundResource) { - boost::filesystem::path resourcePath = applicationPathProvider->getResourcePath(soundResource); - if (boost::filesystem::exists(resourcePath)) { - QSound::play(P2QSTRING(pathToString(resourcePath))); - } - else if (boost::filesystem::exists(soundResource)) { - QSound::play(P2QSTRING(soundResource)); - } - else { - std::cerr << "Unable to find sound: " << soundResource << std::endl; - } + boost::filesystem::path resourcePath = applicationPathProvider->getResourcePath(soundResource); + if (boost::filesystem::exists(resourcePath)) { + QSound::play(P2QSTRING(pathToString(resourcePath))); + } + else if (boost::filesystem::exists(soundResource)) { + QSound::play(P2QSTRING(soundResource)); + } + else { + std::cerr << "Unable to find sound: " << soundResource << std::endl; + } } } diff --git a/Swift/QtUI/QtSoundPlayer.h b/Swift/QtUI/QtSoundPlayer.h index f8da392..8a0b8fe 100644 --- a/Swift/QtUI/QtSoundPlayer.h +++ b/Swift/QtUI/QtSoundPlayer.h @@ -1,30 +1,30 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/Controllers/SoundPlayer.h" - #include <QObject> +#include <Swift/Controllers/SoundPlayer.h> + namespace Swift { - class ApplicationPathProvider; - + class ApplicationPathProvider; + - class QtSoundPlayer : public QObject, public SoundPlayer { - Q_OBJECT - public: - QtSoundPlayer(ApplicationPathProvider* applicationPathProvider); + class QtSoundPlayer : public QObject, public SoundPlayer { + Q_OBJECT + public: + QtSoundPlayer(ApplicationPathProvider* applicationPathProvider); - void playSound(SoundEffect sound, const std::string& soundResource); + void playSound(SoundEffect sound, const std::string& soundResource); - private: - void playSound(const std::string& soundResource); + private: + void playSound(const std::string& soundResource); - private: - ApplicationPathProvider* applicationPathProvider; - }; + private: + ApplicationPathProvider* applicationPathProvider; + }; } diff --git a/Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp new file mode 100644 index 0000000..03af455 --- /dev/null +++ b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtSoundSelectionStyledItemDelegate.h> + +#include <QApplication> +#include <QComboBox> +#include <QEvent> +#include <QFileDialog> +#include <QMenu> +#include <QMouseEvent> +#include <QPainter> +#include <QStyle> +#include <QStyleOptionComboBox> + +#include <Swiften/Base/Log.h> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtSoundSelectionStyledItemDelegate::QtSoundSelectionStyledItemDelegate(QObject* parent) : QStyledItemDelegate(parent) { + +} + +void QtSoundSelectionStyledItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + // draw item selected background + painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base()); + + auto editRoleString = index.data(Qt::EditRole).toString(); + + // draw combo box + QStyleOptionComboBox opt; + opt.rect = option.rect; + opt.rect.setHeight(opt.rect.height() + 2); + opt.state = QStyle::State_Active | QStyle::State_Enabled; + if (editRoleString.isEmpty()) { + opt.currentText = tr("No sound"); + } + else if (editRoleString == "defaultSound") { + opt.currentText = tr("Default sound"); + } + else { + opt.currentText = editRoleString; + } + + painter->save(); + QFont smallFont; + smallFont.setPointSize(smallFont.pointSize() - 3); + painter->setFont(smallFont); + + QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &opt, painter); + QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &opt, painter); + painter->restore(); +} + +bool QtSoundSelectionStyledItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) { + if (event->type() == QEvent::MouseButtonRelease) { + auto mouseEvent = dynamic_cast<QMouseEvent*>(event); + assert(mouseEvent); + auto editRoleString = index.data(Qt::EditRole).toString(); + + auto popUpMenu = new QMenu(); + + auto noSound = popUpMenu->addAction(tr("No sound")); + auto defaultSound = popUpMenu->addAction(tr("Default sound")); + QAction* customSoundFile = nullptr; + QAction* selectedAction = nullptr; + if (editRoleString.isEmpty()) { + selectedAction = noSound; + } + else if (editRoleString == "defaultSound") { + selectedAction = defaultSound; + } + else { + customSoundFile = popUpMenu->addAction(editRoleString); + selectedAction = customSoundFile; + } + if (selectedAction) { + selectedAction->setCheckable(true); + selectedAction->setChecked(true); + } + + selectedAction = popUpMenu->exec(mouseEvent->globalPos(), selectedAction); + + if (selectedAction == defaultSound) { + model->setData(index, "defaultSound", Qt::EditRole); + } + else if (selectedAction == noSound) { + model->setData(index, "", Qt::EditRole); + } + + delete popUpMenu; + } + return true; +} + +}; diff --git a/Swift/QtUI/QtSoundSelectionStyledItemDelegate.h b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.h new file mode 100644 index 0000000..f03cacc --- /dev/null +++ b/Swift/QtUI/QtSoundSelectionStyledItemDelegate.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QStyledItemDelegate> + +namespace Swift { + +class QtSoundSelectionStyledItemDelegate : public QStyledItemDelegate { + Q_OBJECT + public: + QtSoundSelectionStyledItemDelegate(QObject* parent = nullptr); + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const; + + protected: + virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); +}; + +} diff --git a/Swift/QtUI/QtSpellCheckHighlighter.cpp b/Swift/QtUI/QtSpellCheckHighlighter.cpp new file mode 100644 index 0000000..cb467e2 --- /dev/null +++ b/Swift/QtUI/QtSpellCheckHighlighter.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtSpellCheckHighlighter.h> + +#include <SwifTools/SpellChecker.h> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { + +QtSpellCheckHighlighter::QtSpellCheckHighlighter(QTextDocument* parent, SpellChecker* spellChecker) : QSyntaxHighlighter(parent), checker_(spellChecker) { + +} + +QtSpellCheckHighlighter::~QtSpellCheckHighlighter() { + +} + +void QtSpellCheckHighlighter::highlightBlock(const QString& text) { + misspelledPositions_.clear(); + std::string fragment = Q2PSTRING(text); + checker_->checkFragment(fragment, misspelledPositions_); + + QTextCharFormat spellingErrorFormat; + spellingErrorFormat.setUnderlineColor(QColor(Qt::red)); + spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + + for (auto&& position : misspelledPositions_) { + setFormat(boost::get<0>(position), boost::get<1>(position) - boost::get<0>(position), spellingErrorFormat); + }; +} + +PositionPairList QtSpellCheckHighlighter::getMisspelledPositions() const { + return misspelledPositions_; +} + +} diff --git a/Swift/QtUI/QtSpellCheckHighlighter.h b/Swift/QtUI/QtSpellCheckHighlighter.h new file mode 100644 index 0000000..5519a1d --- /dev/null +++ b/Swift/QtUI/QtSpellCheckHighlighter.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QSyntaxHighlighter> + +#include <SwifTools/SpellParser.h> + +class QString; +class QTextDocument; + +namespace Swift { + +class SpellChecker; + +class QtSpellCheckHighlighter : public QSyntaxHighlighter { + Q_OBJECT + +public: + QtSpellCheckHighlighter(QTextDocument* parent, SpellChecker* spellChecker); + virtual ~QtSpellCheckHighlighter(); + + PositionPairList getMisspelledPositions() const; + +protected: + virtual void highlightBlock(const QString& text); + +private: + SpellChecker* checker_; + PositionPairList misspelledPositions_; +}; + + +} diff --git a/Swift/QtUI/QtSpellCheckerWindow.cpp b/Swift/QtUI/QtSpellCheckerWindow.cpp index db2b1e7..23b0963 100644 --- a/Swift/QtUI/QtSpellCheckerWindow.cpp +++ b/Swift/QtUI/QtSpellCheckerWindow.cpp @@ -4,112 +4,95 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "Swift/QtUI/QtSpellCheckerWindow.h" +/* + * Copyright (c) 2016-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swift/Controllers/Settings/SettingsProvider.h> -#include <Swift/Controllers/SettingConstants.h> -#include <Swift/QtUI/QtUISettingConstants.h> -#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSpellCheckerWindow.h> #include <QCoreApplication> -#include <QFileDialog> #include <QDir> +#include <QFileDialog> #include <QStringList> #include <QTimer> +#include <Swiften/Base/Log.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUISettingConstants.h> + namespace Swift { QtSpellCheckerWindow::QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent) : QDialog(parent) { - settings_ = settings; - ui_.setupUi(this); + settings_ = settings; + ui_.setupUi(this); #ifdef HAVE_HUNSPELL - ui_.hunspellOptions->show(); + ui_.hunspellOptions->show(); #else - ui_.hunspellOptions->hide(); - QTimer::singleShot(0, this, SLOT(shrinkWindow())); + 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(); + 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())); + setFromSettings(); } void QtSpellCheckerWindow::shrinkWindow() { - resize(0,0); + 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)); + ui_.spellChecker->setChecked(settings_->getSetting(QtUISettingConstants::SPELL_CHECKER)); + setEnabled(settings_->getSetting(QtUISettingConstants::SPELL_CHECKER)); } -void QtSpellCheckerWindow::handleChecker(bool state) { - setEnabled(state); +void QtSpellCheckerWindow::setSupportedLanguages(const std::vector<std::string>& languages) { + languageItems_.clear(); + ui_.languageView->clear(); + for (const auto& shortLang : languages) { + auto locale = QLocale(P2QSTRING(shortLang)); + auto label = QString("%1 ( %2 )").arg(locale.nativeLanguageName(), locale.nativeCountryName()); + + QListWidgetItem* item = new QListWidgetItem(label); + item->setData(Qt::UserRole, P2QSTRING(shortLang)); + languageItems_[shortLang] = item; + ui_.languageView->addItem(item); + } } -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::setActiveLanguage(const std::string& language) { + SWIFT_LOG_ASSERT(languageItems_.find(language) != languageItems_.end(), warning) << "Language '" << language << "' is not available."; + if (languageItems_.find(language) != languageItems_.end()) { + languageItems_[language]->setSelected(true); + } } -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::setAutomaticallyIdentifiesLanguage(bool isAutomaticallyIdentifying) { + ui_.languageView->setHidden(isAutomaticallyIdentifying); } -void QtSpellCheckerWindow::handleCancel() { - this->done(0); +void QtSpellCheckerWindow::handleChecker(bool state) { + setEnabled(state); } -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::setEnabled(bool state) { + ui_.language->setEnabled(state); } -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::handleApply() { + settings_->storeSetting(QtUISettingConstants::SPELL_CHECKER, ui_.spellChecker->isChecked()); + QList<QListWidgetItem* > selectedLanguage = ui_.languageView->selectedItems(); + if (!selectedLanguage.empty()) { + settings_->storeSetting(QtUISettingConstants::SPELL_CHECKER_LANGUAGE, Q2PSTRING(selectedLanguage.first()->data(Qt::UserRole).toString())); + } + this->done(0); } -void QtSpellCheckerWindow::showFiles(const QStringList& files) { - ui_.languageView->clear(); - for (int i = 0; i < files.size(); ++i) { - ui_.languageView->insertItem(i, files[i]); - } +void QtSpellCheckerWindow::handleCancel() { + this->done(0); } } diff --git a/Swift/QtUI/QtSpellCheckerWindow.h b/Swift/QtUI/QtSpellCheckerWindow.h index 7b63318..86ebd40 100644 --- a/Swift/QtUI/QtSpellCheckerWindow.h +++ b/Swift/QtUI/QtSpellCheckerWindow.h @@ -4,31 +4,49 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once -#include "ui_QtSpellCheckerWindow.h" +#include <string> +#include <unordered_map> +#include <vector> #include <QDialog> +#include <Swift/QtUI/ui_QtSpellCheckerWindow.h> + +class QListWidgetItem; + 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_; - }; + class SettingsProvider; + class QtSpellCheckerWindow : public QDialog, protected Ui::QtSpellCheckerWindow { + Q_OBJECT + public: + QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent = nullptr); + + void setSupportedLanguages(const std::vector<std::string>& languages); + void setActiveLanguage(const std::string& language); + void setAutomaticallyIdentifiesLanguage(bool isAutomaticallyIdentifying); + + public slots: + void handleChecker(bool state); + void handleCancel(); + void handleApply(); + + private slots: + void shrinkWindow(); + + private: + void setEnabled(bool state); + void setFromSettings(); + + SettingsProvider* settings_; + Ui::QtSpellCheckerWindow ui_; + std::unordered_map<std::string, QListWidgetItem*> languageItems_; + }; } diff --git a/Swift/QtUI/QtSpellCheckerWindow.ui b/Swift/QtUI/QtSpellCheckerWindow.ui index b7f5161..dcb70fa 100644 --- a/Swift/QtUI/QtSpellCheckerWindow.ui +++ b/Swift/QtUI/QtSpellCheckerWindow.ui @@ -25,45 +25,6 @@ <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"> @@ -73,7 +34,11 @@ </widget> </item> <item> - <widget class="QListWidget" name="languageView"/> + <widget class="QListWidget" name="languageView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> </item> </layout> </item> diff --git a/Swift/QtUI/QtStatusWidget.cpp b/Swift/QtUI/QtStatusWidget.cpp index 8cc366a..5e2ba5f 100644 --- a/Swift/QtUI/QtStatusWidget.cpp +++ b/Swift/QtUI/QtStatusWidget.cpp @@ -1,299 +1,298 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtStatusWidget.h" +#include <Swift/QtUI/QtStatusWidget.h> #include <algorithm> -#include <boost/lambda/lambda.hpp> -#include <boost/lambda/bind.hpp> +#include <QApplication> #include <QBoxLayout> #include <QComboBox> -#include <QLabel> -#include <QFrame> -#include <QPoint> -#include <QStackedWidget> -#include <QApplication> #include <QDesktopWidget> -#include <qdebug.h> +#include <QFrame> +#include <QLabel> #include <QListWidget> #include <QListWidgetItem> #include <QMovie> +#include <QPoint> +#include <QStackedWidget> + +#include <qdebug.h> -#include "Swift/QtUI/QtElidingLabel.h" -#include "Swift/QtUI/QtLineEdit.h" -#include "Swift/QtUI/QtSwiftUtil.h" -#include <Swift/Controllers/StatusUtil.h> #include <Swift/Controllers/StatusCache.h> +#include <Swift/Controllers/StatusUtil.h> -namespace lambda = boost::lambda; +#include <Swift/QtUI/QtElidingLabel.h> +#include <Swift/QtUI/QtLineEdit.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtStatusWidget::QtStatusWidget(StatusCache* statusCache, QWidget *parent) : QWidget(parent), statusCache_(statusCache), editCursor_(Qt::IBeamCursor), viewCursor_(Qt::PointingHandCursor) { - allTypes_.push_back(StatusShow::Online); - allTypes_.push_back(StatusShow::FFC); - allTypes_.push_back(StatusShow::Away); - allTypes_.push_back(StatusShow::XA); - allTypes_.push_back(StatusShow::DND); - allTypes_.push_back(StatusShow::None); - - isClicking_ = false; - connecting_ = false; - setMaximumHeight(24); - - connectingMovie_ = new QMovie(":/icons/connecting.mng"); - - QHBoxLayout* mainLayout = new QHBoxLayout(this); - mainLayout->setSpacing(0); - mainLayout->setContentsMargins(0,0,0,0); - - stack_ = new QStackedWidget(this); - stack_->setLineWidth(2); - stack_->setFrameShape(QFrame::StyledPanel); - mainLayout->addWidget(stack_); - - QWidget* page1 = new QWidget(this); - stack_->addWidget(page1); - QHBoxLayout* page1Layout = new QHBoxLayout(page1); - page1Layout->setSpacing(0); - page1Layout->setContentsMargins(0,0,0,0); - page1->setCursor(viewCursor_); - - statusIcon_ = new QLabel(this); - statusIcon_->setMinimumSize(16, 16); - statusIcon_->setMaximumSize(16, 16); - page1Layout->addWidget(statusIcon_); - - statusTextLabel_ = new QtElidingLabel(this); - QFont font = statusTextLabel_->font(); - font.setItalic(true); - statusTextLabel_->setFont(font); - page1Layout->addWidget(statusTextLabel_); - - icons_[StatusShow::Online] = QIcon(":/icons/online.png"); - icons_[StatusShow::Away] = QIcon(":/icons/away.png"); - icons_[StatusShow::DND] = QIcon(":/icons/dnd.png"); - icons_[StatusShow::None] = QIcon(":/icons/offline.png"); - - setStatusType(StatusShow::None); - - QWidget* page2 = new QWidget(this); - QHBoxLayout* page2Layout = new QHBoxLayout(page2); - page2Layout->setSpacing(0); - page2Layout->setContentsMargins(0,0,0,0); - stack_->addWidget(page2); - - statusEdit_ = new QtLineEdit(this); - page2Layout->addWidget(statusEdit_); - connect(statusEdit_, SIGNAL(returnPressed()), this, SLOT(handleEditComplete())); - connect(statusEdit_, SIGNAL(escapePressed()), this, SLOT(handleEditCancelled())); - connect(statusEdit_, SIGNAL(textChanged(const QString&)), this, SLOT(generateList())); - - setStatusText(""); - - - menu_ = new QListWidget(); - menu_->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint ); - menu_->setAlternatingRowColors(true); - menu_->setFocusProxy(statusEdit_); - menu_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - menu_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - QSizePolicy policy(menu_->sizePolicy()); - policy.setVerticalPolicy(QSizePolicy::Expanding); - menu_->setSizePolicy(policy); - - - connect(menu_, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(handleItemClicked(QListWidgetItem*))); - - viewMode(); + allTypes_.push_back(StatusShow::Online); + allTypes_.push_back(StatusShow::FFC); + allTypes_.push_back(StatusShow::Away); + allTypes_.push_back(StatusShow::XA); + allTypes_.push_back(StatusShow::DND); + allTypes_.push_back(StatusShow::None); + + isClicking_ = false; + connecting_ = false; + setMaximumHeight(24); + + connectingMovie_ = new QMovie(":/icons/connecting.mng"); + + QHBoxLayout* mainLayout = new QHBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + + stack_ = new QStackedWidget(this); + stack_->setLineWidth(2); + stack_->setFrameShape(QFrame::StyledPanel); + mainLayout->addWidget(stack_); + + QWidget* page1 = new QWidget(this); + stack_->addWidget(page1); + QHBoxLayout* page1Layout = new QHBoxLayout(page1); + page1Layout->setSpacing(0); + page1Layout->setContentsMargins(0,0,0,0); + page1->setCursor(viewCursor_); + + statusIcon_ = new QLabel(this); + statusIcon_->setMinimumSize(16, 16); + statusIcon_->setMaximumSize(16, 16); + page1Layout->addWidget(statusIcon_); + + statusTextLabel_ = new QtElidingLabel(this); + QFont font = statusTextLabel_->font(); + font.setItalic(true); + statusTextLabel_->setFont(font); + page1Layout->addWidget(statusTextLabel_); + + icons_[StatusShow::Online] = QIcon(":/icons/online.png"); + icons_[StatusShow::Away] = QIcon(":/icons/away.png"); + icons_[StatusShow::DND] = QIcon(":/icons/dnd.png"); + icons_[StatusShow::None] = QIcon(":/icons/offline.png"); + + setStatusType(StatusShow::None); + + QWidget* page2 = new QWidget(this); + QHBoxLayout* page2Layout = new QHBoxLayout(page2); + page2Layout->setSpacing(0); + page2Layout->setContentsMargins(0,0,0,0); + stack_->addWidget(page2); + + statusEdit_ = new QtLineEdit(this); + page2Layout->addWidget(statusEdit_); + connect(statusEdit_, SIGNAL(returnPressed()), this, SLOT(handleEditComplete())); + connect(statusEdit_, SIGNAL(escapePressed()), this, SLOT(handleEditCancelled())); + connect(statusEdit_, SIGNAL(textChanged(const QString&)), this, SLOT(generateList())); + + setStatusText(""); + + + menu_ = new QListWidget(); + menu_->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint ); + menu_->setAlternatingRowColors(true); + menu_->setFocusProxy(statusEdit_); + menu_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + menu_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QSizePolicy policy(menu_->sizePolicy()); + policy.setVerticalPolicy(QSizePolicy::Expanding); + menu_->setSizePolicy(policy); + + + connect(menu_, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(handleItemClicked(QListWidgetItem*))); + + viewMode(); } QtStatusWidget::~QtStatusWidget() { - delete menu_; - delete connectingMovie_; + delete menu_; + delete connectingMovie_; } void QtStatusWidget::handleApplicationFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { - QWidget* now = qApp->focusWidget(); - if (!editing_ || stack_->currentIndex() == 0) { - return; - } - if (!now || (now != menu_ && now != statusEdit_ && !now->isAncestorOf(statusEdit_) && !now->isAncestorOf(menu_) && !statusEdit_->isAncestorOf(now) && !menu_->isAncestorOf(now))) { - handleEditCancelled(); - } - + QWidget* now = qApp->focusWidget(); + if (!editing_ || stack_->currentIndex() == 0) { + return; + } + if (!now || (now != menu_ && now != statusEdit_ && !now->isAncestorOf(statusEdit_) && !now->isAncestorOf(menu_) && !statusEdit_->isAncestorOf(now) && !menu_->isAncestorOf(now))) { + handleEditCancelled(); + } + } void QtStatusWidget::mousePressEvent(QMouseEvent*) { - if (stack_->currentIndex() == 0) { - handleClicked(); - } + if (stack_->currentIndex() == 0) { + handleClicked(); + } } void QtStatusWidget::generateList() { - if (!editing_) { - return; - } - QString text = statusEdit_->text(); - newStatusText_ = text; - menu_->clear(); - foreach (StatusShow::Type type, icons_.keys()) { - QListWidgetItem* item = new QListWidgetItem(text == "" ? getNoMessage() : text, menu_); - item->setIcon(icons_[type]); - item->setToolTip(P2QSTRING(statusShowTypeToFriendlyName(type)) + ": " + item->text()); - item->setStatusTip(item->toolTip()); - item->setData(Qt::UserRole, QVariant(type)); - } - std::vector<StatusCache::PreviousStatus> previousStatuses = statusCache_->getMatches(Q2PSTRING(text), 8); - foreach (StatusCache::PreviousStatus savedStatus, previousStatuses) { - if (savedStatus.first.empty() || std::find_if(allTypes_.begin(), allTypes_.end(), - savedStatus.second == lambda::_1 && savedStatus.first == lambda::bind(&statusShowTypeToFriendlyName, lambda::_1)) != allTypes_.end()) { - continue; - } - QListWidgetItem* item = new QListWidgetItem(P2QSTRING(savedStatus.first), menu_); - item->setIcon(icons_[savedStatus.second]); - item->setToolTip(item->text()); - item->setStatusTip(item->toolTip()); - item->setData(Qt::UserRole, QVariant(savedStatus.second)); - } - foreach (StatusShow::Type type, icons_.keys()) { - if (Q2PSTRING(text) == statusShowTypeToFriendlyName(type)) { - continue; - } - QListWidgetItem* item = new QListWidgetItem(P2QSTRING(statusShowTypeToFriendlyName(type)), menu_); - item->setIcon(icons_[type]); - item->setToolTip(item->text()); - item->setStatusTip(item->toolTip()); - item->setData(Qt::UserRole, QVariant(type)); - } - resizeMenu(); + if (!editing_) { + return; + } + QString text = statusEdit_->text(); + newStatusText_ = text; + menu_->clear(); + for (const auto& type : icons_.keys()) { + QListWidgetItem* item = new QListWidgetItem(text == "" ? getNoMessage() : text, menu_); + item->setIcon(icons_[type]); + item->setToolTip(P2QSTRING(statusShowTypeToFriendlyName(type)) + ": " + item->text()); + item->setStatusTip(item->toolTip()); + item->setData(Qt::UserRole, QVariant(type)); + } + std::vector<StatusCache::PreviousStatus> previousStatuses = statusCache_->getMatches(Q2PSTRING(text), 8); + for (const auto& savedStatus : previousStatuses) { + if (savedStatus.first.empty() || std::find_if(allTypes_.begin(), allTypes_.end(), [&](StatusShow::Type type) { + return (savedStatus.second == type) && (savedStatus.first == statusShowTypeToFriendlyName(type)); + }) != 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)); + } + for (const auto& type : icons_.keys()) { + if (Q2PSTRING(text) == statusShowTypeToFriendlyName(type)) { + continue; + } + QListWidgetItem* item = new QListWidgetItem(P2QSTRING(statusShowTypeToFriendlyName(type)), menu_); + item->setIcon(icons_[type]); + item->setToolTip(item->text()); + item->setStatusTip(item->toolTip()); + item->setData(Qt::UserRole, QVariant(type)); + } + resizeMenu(); } void QtStatusWidget::resizeMenu() { - int height = menu_->sizeHintForRow(0) * menu_->count(); - int marginLeft; - int marginTop; - int marginRight; - int marginBottom; - menu_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom); - height += marginTop + marginBottom; - - menu_->setGeometry(menu_->x(), menu_->y(), menu_->width(), height); + int height = menu_->sizeHintForRow(0) * menu_->count(); + int marginLeft; + int marginTop; + int marginRight; + int marginBottom; + menu_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom); + height += marginTop + marginBottom; + + menu_->setGeometry(menu_->x(), menu_->y(), menu_->width(), height); } void QtStatusWidget::handleClicked() { - editing_ = true; - 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 = 200; - int height = 80; - - int screenWidth = geometry.x() + geometry.width(); - if (x + width > screenWidth) { - x = screenWidth - width; - } - //foreach (StatusShow::Type type, allTypes_) { - // if (statusEdit_->text() == P2QSTRING(statusShowTypeToFriendlyName(type))) { - statusEdit_->setText(""); - // } - //} - generateList(); - - height = menu_->sizeHintForRow(0) * menu_->count(); - int marginLeft; - int marginTop; - int marginRight; - int marginBottom; - menu_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom); - height += marginTop + marginBottom; - width += marginLeft + marginRight; - - menu_->setGeometry(x, y, width, height); - menu_->move(x, y); - menu_->setMaximumWidth(width); - menu_->show(); - activateWindow(); - statusEdit_->selectAll(); - stack_->setCurrentIndex(1); - statusEdit_->setFocus(); - connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*)), Qt::QueuedConnection); + editing_ = true; + 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 = 200; + int height = 80; + + int screenWidth = geometry.x() + geometry.width(); + if (x + width > screenWidth) { + x = screenWidth - width; + } + //for (StatusShow::Type type : allTypes_) { + // if (statusEdit_->text() == P2QSTRING(statusShowTypeToFriendlyName(type))) { + statusEdit_->setText(""); + // } + //} + generateList(); + + height = menu_->sizeHintForRow(0) * menu_->count(); + int marginLeft; + int marginTop; + int marginRight; + int marginBottom; + menu_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom); + height += marginTop + marginBottom; + width += marginLeft + marginRight; + + menu_->setGeometry(x, y, width, height); + menu_->move(x, y); + menu_->setMaximumWidth(width); + menu_->show(); + activateWindow(); + statusEdit_->selectAll(); + stack_->setCurrentIndex(1); + statusEdit_->setFocus(); + connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*)), Qt::QueuedConnection); } void QtStatusWidget::viewMode() { - disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*))); - editing_ = false; - menu_->hide(); - stack_->setCurrentIndex(0); + disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*))); + editing_ = false; + menu_->hide(); + stack_->setCurrentIndex(0); } void QtStatusWidget::handleEditComplete() { - editing_ = false; - statusText_ = newStatusText_; - viewMode(); - emit onChangeStatusRequest(selectedStatusType_, statusText_); - statusCache_->addRecent(Q2PSTRING(statusText_), selectedStatusType_); + editing_ = false; + statusText_ = newStatusText_; + viewMode(); + emit onChangeStatusRequest(selectedStatusType_, statusText_); + statusCache_->addRecent(Q2PSTRING(statusText_), selectedStatusType_); } void QtStatusWidget::handleEditCancelled() { - editing_ = false; - setStatusText(statusText_); - viewMode(); + editing_ = false; + setStatusText(statusText_); + viewMode(); } StatusShow::Type QtStatusWidget::getSelectedStatusShow() { - return selectedStatusType_; + return selectedStatusType_; } void QtStatusWidget::handleItemClicked(QListWidgetItem* item) { - editing_ = false; - selectedStatusType_ = static_cast<StatusShow::Type>(item->data(Qt::UserRole).toInt()); - QString message = item->data(Qt::DisplayRole).toString(); - newStatusText_ = message == getNoMessage() ? "" : message; - statusEdit_->setText(newStatusText_); - handleEditComplete(); + editing_ = false; + selectedStatusType_ = static_cast<StatusShow::Type>(item->data(Qt::UserRole).toInt()); + QString message = item->data(Qt::DisplayRole).toString(); + newStatusText_ = message == getNoMessage() ? "" : message; + statusEdit_->setText(newStatusText_); + handleEditComplete(); } void QtStatusWidget::setNewToolTip() { - if (connecting_) { - statusTextLabel_->setToolTip(tr("Connecting")); - } else { - statusTextLabel_->setToolTip(P2QSTRING(statusShowTypeToFriendlyName(selectedStatusType_)) + ": " + statusTextLabel_->text()); - } + if (connecting_) { + statusTextLabel_->setToolTip(tr("Connecting")); + } else { + statusTextLabel_->setToolTip(P2QSTRING(statusShowTypeToFriendlyName(selectedStatusType_)) + ": " + statusTextLabel_->text()); + } } void QtStatusWidget::setStatusText(const QString& text) { - connectingMovie_->stop(); - statusText_ = text; - statusEdit_->setText(text); - QString escapedText(text.isEmpty() ? getNoMessage() : text); - statusTextLabel_->setText(escapedText); - setNewToolTip(); + connectingMovie_->stop(); + statusText_ = text; + statusEdit_->setText(text); + QString escapedText(text.isEmpty() ? getNoMessage() : text); + statusTextLabel_->setText(escapedText); + setNewToolTip(); } void QtStatusWidget::setConnecting() { - connecting_ = true; - statusIcon_->setMovie(connectingMovie_); - connectingMovie_->start(); - setNewToolTip(); + connecting_ = true; + statusIcon_->setMovie(connectingMovie_); + connectingMovie_->start(); + setNewToolTip(); } void QtStatusWidget::setStatusType(StatusShow::Type type) { - connecting_ = false; - selectedStatusType_ = icons_.contains(type) ? type : StatusShow::Online; - statusIcon_->setPixmap(icons_[selectedStatusType_].pixmap(16, 16)); - setNewToolTip(); + connecting_ = false; + selectedStatusType_ = icons_.contains(type) ? type : StatusShow::Online; + statusIcon_->setPixmap(icons_[selectedStatusType_].pixmap(16, 16)); + setNewToolTip(); } QString QtStatusWidget::getNoMessage() { - return QString(tr("(No message)")); + return QString(tr("(No message)")); } } diff --git a/Swift/QtUI/QtStatusWidget.h b/Swift/QtUI/QtStatusWidget.h index 87e8d4a..f346868 100644 --- a/Swift/QtUI/QtStatusWidget.h +++ b/Swift/QtUI/QtStatusWidget.h @@ -1,16 +1,16 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swiften/Elements/StatusShow.h" - -#include <QWidget> -#include <QMap> #include <QIcon> +#include <QMap> +#include <QWidget> + +#include <Swiften/Elements/StatusShow.h> class QComboBox; class QLabel; @@ -20,55 +20,55 @@ class QListWidgetItem; class QMovie; namespace Swift { - class QtLineEdit; - class QtElidingLabel; - class StatusCache; + class QtLineEdit; + class QtElidingLabel; + class StatusCache; - class QtStatusWidget : public QWidget { - Q_OBJECT - public: - QtStatusWidget(StatusCache* statusCache, QWidget *parent); - ~QtStatusWidget(); - StatusShow::Type getSelectedStatusShow(); - void setStatusType(StatusShow::Type type); - void setConnecting(); - signals: - void onChangeStatusRequest(StatusShow::Type showType, const QString& text); - public slots: - void setStatusText(const QString& text); - private slots: - void generateList(); - void handleClicked(); - void handleEditComplete(); - void handleEditCancelled(); - void handleApplicationFocusChanged(QWidget* old, QWidget* now); - protected slots: - virtual void mousePressEvent(QMouseEvent* event); - void handleItemClicked(QListWidgetItem* item); - static QString getNoMessage(); - private: - void resizeMenu(); - void viewMode(); - void setNewToolTip(); - //QComboBox *types_; - StatusCache* statusCache_; - QStackedWidget* stack_; - QLabel* statusIcon_; - QtElidingLabel* statusTextLabel_; - QtLineEdit* statusEdit_; - QString statusText_; - QString newStatusText_; - QMap<StatusShow::Type, QIcon> icons_; - StatusShow::Type selectedStatusType_; - bool isClicking_; - QListWidget* menu_; - QCursor editCursor_; - QCursor viewCursor_; - bool editing_; - QMovie* connectingMovie_; - bool connecting_; - static const QString NO_MESSAGE; - std::vector<StatusShow::Type> allTypes_; - }; + class QtStatusWidget : public QWidget { + Q_OBJECT + public: + QtStatusWidget(StatusCache* statusCache, QWidget *parent); + ~QtStatusWidget(); + StatusShow::Type getSelectedStatusShow(); + void setStatusType(StatusShow::Type type); + void setConnecting(); + signals: + void onChangeStatusRequest(StatusShow::Type showType, const QString& text); + public slots: + void setStatusText(const QString& text); + private slots: + void generateList(); + void handleClicked(); + void handleEditComplete(); + void handleEditCancelled(); + void handleApplicationFocusChanged(QWidget* old, QWidget* now); + protected slots: + virtual void mousePressEvent(QMouseEvent* event); + void handleItemClicked(QListWidgetItem* item); + static QString getNoMessage(); + private: + void resizeMenu(); + void viewMode(); + void setNewToolTip(); + //QComboBox *types_; + StatusCache* statusCache_; + QStackedWidget* stack_; + QLabel* statusIcon_; + QtElidingLabel* statusTextLabel_; + QtLineEdit* statusEdit_; + QString statusText_; + QString newStatusText_; + QMap<StatusShow::Type, QIcon> icons_; + StatusShow::Type selectedStatusType_; + bool isClicking_; + QListWidget* menu_; + QCursor editCursor_; + QCursor viewCursor_; + bool editing_; + QMovie* connectingMovie_; + bool connecting_; + static const QString NO_MESSAGE; + std::vector<StatusShow::Type> allTypes_; + }; } diff --git a/Swift/QtUI/QtStrings.h b/Swift/QtUI/QtStrings.h index 3317dbf..84bc7f9 100644 --- a/Swift/QtUI/QtStrings.h +++ b/Swift/QtUI/QtStrings.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ /* @@ -16,7 +16,7 @@ QT_TRANSLATE_NOOP("CloseButton", "Close Tab"); -QT_TRANSLATE_NOOP3("QApplication", "QT_LAYOUT_DIRECTION", "Translate this to LTR for left-to-right or RTL for right-to-left languages"); +QT_TRANSLATE_NOOP3("QGuiApplication", "QT_LAYOUT_DIRECTION", "Translate this to LTR for left-to-right or RTL for right-to-left languages"); QT_TRANSLATE_NOOP("QLineEdit", "Select All"); QT_TRANSLATE_NOOP("QLineEdit", "&Undo"); @@ -26,6 +26,14 @@ QT_TRANSLATE_NOOP("QLineEdit", "&Copy"); QT_TRANSLATE_NOOP("QLineEdit", "&Paste"); QT_TRANSLATE_NOOP("QLineEdit", "Delete"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "Select All"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "&Undo"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "&Redo"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "Cu&t"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "&Copy"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "&Paste"); +QT_TRANSLATE_NOOP("QWidgetTextControl", "Delete"); + QT_TRANSLATE_NOOP("QScrollBar", "Scroll here"); QT_TRANSLATE_NOOP("QScrollBar", "Top"); QT_TRANSLATE_NOOP("QScrollBar", "Bottom"); @@ -76,6 +84,10 @@ QT_TRANSLATE_NOOP("QDialogButtonBox", "Cancel"); QT_TRANSLATE_NOOP("QMessageBox", "Show Details..."); QT_TRANSLATE_NOOP("QMessageBox", "Hide Details..."); +QT_TRANSLATE_NOOP("QPlatformTheme", "OK"); +QT_TRANSLATE_NOOP("QPlatformTheme", "Cancel"); +QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults"); + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Services"); QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide %1"); QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU", "Hide Others"); diff --git a/Swift/QtUI/QtSubscriptionRequestWindow.cpp b/Swift/QtUI/QtSubscriptionRequestWindow.cpp index d22cbd0..c8c4178 100644 --- a/Swift/QtUI/QtSubscriptionRequestWindow.cpp +++ b/Swift/QtUI/QtSubscriptionRequestWindow.cpp @@ -1,84 +1,90 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/QtSubscriptionRequestWindow.h" +#include <Swift/QtUI/QtSubscriptionRequestWindow.h> -#include <QPushButton> #include <QHBoxLayout> -#include <QVBoxLayout> #include <QLabel> +#include <QPushButton> +#include <QVBoxLayout> -#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()); - QVBoxLayout* layout = new QVBoxLayout(); - QLabel* label = new QLabel(text, this); - layout->addWidget(label); +QtSubscriptionRequestWindow::QtSubscriptionRequestWindow(std::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent) : QDialog(parent), event_(event) { + 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()) { - QLabel* doneLabel = new QLabel(tr("You have already replied to this request")); - QPushButton* okButton = new QPushButton(tr("OK"), this); - connect(okButton, SIGNAL(clicked()), this, SLOT(handleDefer())); - layout->addWidget(doneLabel); - layout->addWidget(okButton); - } else { - QPushButton* yesButton = new QPushButton(tr("Yes"), this); - yesButton->setDefault(true); - connect(yesButton, SIGNAL(clicked()), this, SLOT(handleYes())); - QPushButton* noButton = new QPushButton(tr("No"), this); - connect(noButton, SIGNAL(clicked()), this, SLOT(handleNo())); - QPushButton* deferButton = new QPushButton(tr("Defer"), this); - deferButton->setShortcut(QKeySequence(Qt::Key_Escape)); - connect(deferButton, SIGNAL(clicked()), this, SLOT(handleDefer())); + if (event_->getConcluded()) { + QLabel* doneLabel = new QLabel(tr("You have already replied to this request")); + QPushButton* okButton = new QPushButton(tr("OK"), this); + connect(okButton, SIGNAL(clicked()), this, SLOT(handleDefer())); + layout->addWidget(doneLabel); + layout->addWidget(okButton); + } else { + QPushButton* yesButton = new QPushButton(tr("Accept"), this); + yesButton->setDefault(true); + connect(yesButton, SIGNAL(clicked()), this, SLOT(handleYes())); + QPushButton* noButton = new QPushButton(tr("Reject"), this); + connect(noButton, SIGNAL(clicked()), this, SLOT(handleNo())); + QPushButton* deferButton = new QPushButton(tr("Defer"), this); + deferButton->setShortcut(QKeySequence(Qt::Key_Escape)); + connect(deferButton, SIGNAL(clicked()), this, SLOT(handleDefer())); - QHBoxLayout* buttonLayout = new QHBoxLayout(); - buttonLayout->addWidget(yesButton); - buttonLayout->addWidget(noButton); - buttonLayout->addWidget(deferButton); - - layout->addLayout(buttonLayout); - } + QHBoxLayout* buttonLayout = new QHBoxLayout(); + buttonLayout->addWidget(yesButton); + 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); + } - setLayout(layout); + setLayout(layout); } void QtSubscriptionRequestWindow::handleYes() { - event_->accept(); - delete this; + event_->accept(); + delete this; } void QtSubscriptionRequestWindow::handleNo() { - event_->decline(); - delete this; + event_->decline(); + delete this; } void QtSubscriptionRequestWindow::handleDefer() { - event_->defer(); - delete this; + event_->defer(); + delete this; } QtSubscriptionRequestWindow::~QtSubscriptionRequestWindow() { - windows_.removeOne(this); + windows_.removeOne(this); } -QtSubscriptionRequestWindow* QtSubscriptionRequestWindow::getWindow(boost::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent) { - foreach (QtSubscriptionRequestWindow* window, windows_) { - if (window->getEvent() == event) { - return window; - } - } - QtSubscriptionRequestWindow* window = new QtSubscriptionRequestWindow(event, parent); - windows_.append(window); - return window; +QtSubscriptionRequestWindow* QtSubscriptionRequestWindow::getWindow(std::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent) { + for (auto window : windows_) { + if (window->getEvent() == event) { + return window; + } + } + QtSubscriptionRequestWindow* window = new QtSubscriptionRequestWindow(event, parent); + windows_.append(window); + return window; } -boost::shared_ptr<SubscriptionRequestEvent> QtSubscriptionRequestWindow::getEvent() { - return event_; +std::shared_ptr<SubscriptionRequestEvent> QtSubscriptionRequestWindow::getEvent() { + return event_; } QList<QtSubscriptionRequestWindow*> QtSubscriptionRequestWindow::windows_; diff --git a/Swift/QtUI/QtSubscriptionRequestWindow.h b/Swift/QtUI/QtSubscriptionRequestWindow.h index 7392aac..3f1e816 100644 --- a/Swift/QtUI/QtSubscriptionRequestWindow.h +++ b/Swift/QtUI/QtSubscriptionRequestWindow.h @@ -1,34 +1,34 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QDialog> +#include <memory> -#include <boost/shared_ptr.hpp> +#include <QDialog> -#include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" +#include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> namespace Swift { - class QtSubscriptionRequestWindow : public QDialog { - Q_OBJECT - public: - static QtSubscriptionRequestWindow* getWindow(boost::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent = 0); - ~QtSubscriptionRequestWindow(); - boost::shared_ptr<SubscriptionRequestEvent> getEvent(); - private slots: - void handleYes(); - void handleNo(); - void handleDefer(); - private: - QtSubscriptionRequestWindow(boost::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent = 0); - static QList<QtSubscriptionRequestWindow*> windows_; - boost::shared_ptr<SubscriptionRequestEvent> event_; - /*QPushButton* yesButton_; - QPushButton* noButton_; - QPushButton* deferButton_;*/ - }; + class QtSubscriptionRequestWindow : public QDialog { + Q_OBJECT + public: + static QtSubscriptionRequestWindow* getWindow(std::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent = nullptr); + ~QtSubscriptionRequestWindow(); + std::shared_ptr<SubscriptionRequestEvent> getEvent(); + private slots: + void handleYes(); + void handleNo(); + void handleDefer(); + private: + QtSubscriptionRequestWindow(std::shared_ptr<SubscriptionRequestEvent> event, QWidget* parent = nullptr); + static QList<QtSubscriptionRequestWindow*> windows_; + std::shared_ptr<SubscriptionRequestEvent> event_; + /*QPushButton* yesButton_; + QPushButton* noButton_; + QPushButton* deferButton_;*/ + }; } diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 183f64d..73fd733 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -1,50 +1,58 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtSwift.h> -#include <string> #include <map> +#include <string> #include <boost/bind.hpp> -#include <QFile> -#include <QMessageBox> #include <QApplication> +#include <QFile> +#include <QFontDatabase> #include <QMap> +#include <QMessageBox> #include <qdebug.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/Path.h> +#include <Swiften/Base/Paths.h> #include <Swiften/Base/Platform.h> -#include <Swiften/Elements/Presence.h> +#include <Swiften/Base/String.h> #include <Swiften/Client/Client.h> -#include <Swiften/Base/Paths.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/StringCodecs/Base64.h> +#include <Swiften/TLS/TLSContextFactory.h> #include <SwifTools/Application/PlatformApplicationPathProvider.h> #include <SwifTools/AutoUpdater/AutoUpdater.h> #include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h> +#include <SwifTools/EmojiMapper.h> -#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> -#include <Swift/Controllers/Storages/FileStoragesFactory.h> -#include <Swift/Controllers/Settings/XMLSettingsProvider.h> -#include <Swift/Controllers/Settings/SettingsProviderHierachy.h> -#include <Swift/Controllers/MainController.h> #include <Swift/Controllers/ApplicationInfo.h> #include <Swift/Controllers/BuildVersion.h> +#include <Swift/Controllers/AccountController.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/SettingsProviderHierachy.h> +#include <Swift/Controllers/Settings/XMLSettingsProvider.h> #include <Swift/Controllers/StatusCache.h> +#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> +#include <Swift/Controllers/Storages/FileStoragesFactory.h> -#include <Swift/QtUI/QtLoginWindow.h> #include <Swift/QtUI/QtChatTabs.h> -#include <Swift/QtUI/QtSystemTray.h> +#include <Swift/QtUI/QtChatWindowFactory.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtSingleWindow.h> #include <Swift/QtUI/QtSoundPlayer.h> #include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSystemTray.h> #include <Swift/QtUI/QtUIFactory.h> -#include <Swift/QtUI/QtChatWindowFactory.h> -#include <Swift/QtUI/QtSingleWindow.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/SwiftUpdateFeeds.h> #if defined(SWIFTEN_PLATFORM_WINDOWS) #include <Swift/QtUI/WindowsNotifier.h> @@ -52,6 +60,8 @@ #include <SwifTools/Notifier/GrowlNotifier.h> #elif defined(SWIFTEN_PLATFORM_LINUX) #include <Swift/QtUI/FreeDesktopNotifier.h> +#elif defined(SWIFTEN_PLATFORM_MACOSX) +#include <SwifTools/Notifier/NotificationCenterNotifier.h> #else #include <SwifTools/Notifier/NullNotifier.h> #endif @@ -70,198 +80,403 @@ #include <Swift/QtUI/QtDBUSURIHandler.h> #endif -namespace Swift{ - #if defined(SWIFTEN_PLATFORM_MACOSX) -//#define SWIFT_APPCAST_URL "http://swift.im/appcast/swift-mac-dev.xml" -#else -//#define SWIFT_APPCAST_URL "" +#include <Swift/QtUI/CocoaUIHelpers.h> #endif +namespace Swift{ + po::options_description QtSwift::getOptionsDescription() { - po::options_description result("Options"); - result.add_options() - ("debug", "Turn on debug logging") - ("help", "Show this help message") - ("version", "Show version information") - ("netbook-mode", "Use netbook mode display (unsupported)") - ("no-tabs", "Don't manage chat windows in tabs (unsupported)") - ("latency-debug", "Use latency debugging (unsupported)") - ("multi-account", po::value<int>()->default_value(1), "Number of accounts to open windows for (unsupported)") - ("start-minimized", "Don't show the login/roster window at startup") + po::options_description result("Options"); + result.add_options() + ("debug", "Turn on debug logging") + ("help", "Show this help message") + ("version", "Show version information") + ("no-tabs", "Don't manage chat windows in tabs (unsupported)") + ("latency-debug", "Use latency debugging (unsupported)") + ("enable-jid-adhocs", "Enable AdHoc commands to custom JIDs.") #if QT_VERSION >= 0x040800 - ("language", po::value<std::string>(), "Use a specific language, instead of the system-wide one") + ("language", po::value<std::string>(), "Use a specific language, instead of the system-wide one") #endif - ; - return result; + ("logfile", po::value<std::string>()->implicit_value(""), "Save all logging information to a file") + ("enable-future", "Enable future features (unsupported). This will persist across restarts") + ("disable-future", "Disable future features. This will persist across restarts") + ; + return result; } XMLSettingsProvider* QtSwift::loadSettingsFile(const QString& fileName) { - QFile configFile(fileName); - if (configFile.exists() && configFile.open(QIODevice::ReadOnly)) { - QString xmlString; - while (!configFile.atEnd()) { - QByteArray line = configFile.readLine(); - xmlString += line + "\n"; - } - return new XMLSettingsProvider(Q2PSTRING(xmlString)); - } - return new XMLSettingsProvider(""); + QFile configFile(fileName); + if (configFile.exists() && configFile.open(QIODevice::ReadOnly)) { + QString xmlString; + while (!configFile.atEnd()) { + QByteArray line = configFile.readLine(); + xmlString += line + "\n"; + } + return new XMLSettingsProvider(Q2PSTRING(xmlString)); + } + return new XMLSettingsProvider(""); } 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); - } - } - } + 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); - QCoreApplication::setOrganizationDomain(SWIFT_ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(buildVersion); - - qtSettings_ = new QtSettingsProvider(); - 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 { - numberOfAccounts = options["multi-account"].as<int>(); - } catch (...) { - /* This seems to fail on a Mac when the .app is launched directly (the usual path).*/ - numberOfAccounts = 1; - } - - if (options.count("debug")) { - Log::setLogLevel(Swift::Log::debug); - } - - 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(), networkFactories_.getCryptoProvider()); - certificateStorageFactory_ = new CertificateFileStorageFactory(applicationPathProvider_->getDataDir(), tlsFactories_.getCertificateFactory(), networkFactories_.getCryptoProvider()); - chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, ""); - soundPlayer_ = new QtSoundPlayer(applicationPathProvider_); - - // Ugly, because the dock depends on the tray, but the temporary - // multi-account hack creates one tray per account. - QtSystemTray* systemTray = new QtSystemTray(); - systemTrays_.push_back(systemTray); +const std::string& QtSwift::updateChannelToFeed(const std::string& channel) { + static const std::string invalidChannel; + if (channel == UpdateFeeds::StableChannel) { + return UpdateFeeds::StableAppcastFeed; + } + else if (channel == UpdateFeeds::TestingChannel) { + return UpdateFeeds::TestingAppcastFeed; + } + else if (channel == UpdateFeeds::DevelopmentChannel) { + return UpdateFeeds::DevelopmentAppcastFeed; + } + else { + return invalidChannel; + } +} + +QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMainThreadCaller_), autoUpdater_(nullptr), idleDetector_(&idleQuerier_, networkFactories_.getTimerFactory(), 1000) { + QCoreApplication::setApplicationName(SWIFT_APPLICATION_NAME); + QCoreApplication::setOrganizationName(SWIFT_ORGANIZATION_NAME); + QCoreApplication::setOrganizationDomain(SWIFT_ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(buildVersion); + + qtSettings_ = new QtSettingsProvider(); + xmlSettings_ = loadSettingsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "system-settings.xml"))); + settingsHierarchy_ = new SettingsProviderHierachy(); + settingsHierarchy_->addProviderToTopOfStack(xmlSettings_); + settingsHierarchy_->addProviderToTopOfStack(qtSettings_); + + networkFactories_.getTLSContextFactory()->setDisconnectOnCardRemoval(settingsHierarchy_->getSetting(SettingConstants::DISCONNECT_ON_CARD_REMOVAL)); + + loadEmoticonsFile(":/emoticons/emoticons.txt", emoticons_); + loadEmoticonsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "emoticons.txt")), emoticons_); + + splitter_ = new QtSingleWindow(qtSettings_); + connect(splitter_, SIGNAL(wantsToAddAccount()), this, SLOT(handleWantsToAddAccount())); + + if (options.count("debug")) { + Log::setLogLevel(Swift::Log::debug); + } + + if (options.count("enable-future")) { + settingsHierarchy_->storeSetting(SettingConstants::FUTURE, true); + } + + if (options.count("disable-future")) { + settingsHierarchy_->storeSetting(SettingConstants::FUTURE, false); + } + + if (options.count("logfile")) { + try { + std::string fileName = options["logfile"].as<std::string>(); + Log::setLogFile(fileName); + } + catch (...) { + SWIFT_LOG(error) << "Error while retrieving the specified log file name from the command line"; + } + } + //TODO this old option can be purged + useDelayForLatency_ = options.count("latency-debug") > 0; + + // Load fonts + std::vector<std::string> fontNames = { + "themes/Default/Lato2OFL/Lato-Black.ttf", + "themes/Default/Lato2OFL/Lato-BlackItalic.ttf", + "themes/Default/Lato2OFL/Lato-Bold.ttf", + "themes/Default/Lato2OFL/Lato-BoldItalic.ttf", + "themes/Default/Lato2OFL/Lato-Hairline.ttf", + "themes/Default/Lato2OFL/Lato-HairlineItalic.ttf", + "themes/Default/Lato2OFL/Lato-Heavy.ttf", + "themes/Default/Lato2OFL/Lato-HeavyItalic.ttf", + "themes/Default/Lato2OFL/Lato-Italic.ttf", + "themes/Default/Lato2OFL/Lato-Light.ttf", + "themes/Default/Lato2OFL/Lato-LightItalic.ttf", + "themes/Default/Lato2OFL/Lato-Medium.ttf", + "themes/Default/Lato2OFL/Lato-MediumItalic.ttf", + "themes/Default/Lato2OFL/Lato-Regular.ttf", + "themes/Default/Lato2OFL/Lato-Semibold.ttf", + "themes/Default/Lato2OFL/Lato-SemiboldItalic.ttf", + "themes/Default/Lato2OFL/Lato-Thin.ttf", + "themes/Default/Lato2OFL/Lato-ThinItalic.ttf" + }; + + for (auto&& fontName : fontNames) { + std::string fontPath = std::string(":/") + fontName; + int error = QFontDatabase::addApplicationFont(P2QSTRING(fontPath)); + SWIFT_LOG_ASSERT(error != -1, error) << "Failed to load font " << fontPath; + } + +#ifdef SWIFTEN_PLATFORM_LINUX + std::string fontPath = std::string(":/themes/Default/Noto/NotoColorEmoji.ttf"); + int error = QFontDatabase::addApplicationFont(P2QSTRING(fontPath)); + SWIFT_LOG_ASSERT(error != -1, error) << "Failed to load font " << fontPath; + QFont::insertSubstitution(QApplication::font().family(),"NotoColorEmoji"); +#endif +#ifdef SWIFTEN_PLATFORM_WINDOWS + QFont::insertSubstitution(QApplication::font().family(), "Segoe UI Emoji"); +#endif + enableAdHocCommandOnJID_ = options.count("enable-jid-adhocs") > 0; + applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME); + storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir(), networkFactories_.getCryptoProvider()); + certificateStorageFactory_ = new CertificateFileStorageFactory(applicationPathProvider_->getDataDir(), tlsFactories_.getCertificateFactory(), networkFactories_.getCryptoProvider()); + soundPlayer_ = new QtSoundPlayer(applicationPathProvider_); + + // Ugly, because the dock depends on the tray, but the temporary + // multi-account hack creates one tray per account. + QtSystemTray* systemTray = new QtSystemTray(); + systemTrays_.push_back(systemTray); #if defined(HAVE_GROWL) - notifier_ = new GrowlNotifier(SWIFT_APPLICATION_NAME); + notifier_ = new GrowlNotifier(SWIFT_APPLICATION_NAME); #elif defined(SWIFTEN_PLATFORM_WINDOWS) - notifier_ = new WindowsNotifier(SWIFT_APPLICATION_NAME, applicationPathProvider_->getResourcePath("/images/logo-icon-32.png"), systemTray->getQSystemTrayIcon()); + notifier_ = new WindowsNotifier(SWIFT_APPLICATION_NAME, applicationPathProvider_->getResourcePath("/images/logo-icon-32.png"), systemTray->getQSystemTrayIcon()); #elif defined(SWIFTEN_PLATFORM_LINUX) - notifier_ = new FreeDesktopNotifier(SWIFT_APPLICATION_NAME); + notifier_ = new FreeDesktopNotifier(SWIFT_APPLICATION_NAME); +#elif defined(SWIFTEN_PLATFORM_MACOSX) + notifier_ = new NotificationCenterNotifier(); #else - notifier_ = new NullNotifier(); + notifier_ = new NullNotifier(); #endif #if defined(SWIFTEN_PLATFORM_MACOSX) - dock_ = new MacOSXDock(&cocoaApplication_); + dock_ = new MacOSXDock(&cocoaApplication_); #else - dock_ = new NullDock(); + dock_ = new NullDock(); #endif #if defined(SWIFTEN_PLATFORM_MACOSX) - uriHandler_ = new QtURIHandler(); + uriHandler_ = new QtURIHandler(); #elif defined(SWIFTEN_PLATFORM_WIN32) - uriHandler_ = new NullURIHandler(); + uriHandler_ = new NullURIHandler(); #else - uriHandler_ = new QtDBUSURIHandler(); + uriHandler_ = new QtDBUSURIHandler(); #endif - statusCache_ = new StatusCache(applicationPathProvider_); - - if (splitter_) { - splitter_->show(); - } - - for (int i = 0; i < numberOfAccounts; i++) { - if (i > 0) { - // Don't add the first tray (see note above) - systemTrays_.push_back(new QtSystemTray()); - } - QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), statusCache_, startMinimized, !emoticons.empty()); - uiFactories_.push_back(uiFactory); - MainController* mainController = new MainController( - &clientMainThreadCaller_, - &networkFactories_, - uiFactory, - settingsHierachy_, - systemTrays_[i], - soundPlayer_, - storagesFactory_, - certificateStorageFactory_, - dock_, - notifier_, - uriHandler_, - &idleDetector_, - emoticons, - options.count("latency-debug") > 0); - mainControllers_.push_back(mainController); - } - - - // PlatformAutoUpdaterFactory autoUpdaterFactory; - // if (autoUpdaterFactory.isSupported()) { - // autoUpdater_ = autoUpdaterFactory.createAutoUpdater(SWIFT_APPCAST_URL); - // autoUpdater_->checkForUpdates(); - // } + statusCache_ = new StatusCache(applicationPathProvider_); + + splitter_->show(); + + PlatformAutoUpdaterFactory autoUpdaterFactory; + if (autoUpdaterFactory.isSupported() && settingsHierarchy_->getSetting(QtUISettingConstants::ENABLE_SOFTWARE_UPDATES) + && !settingsHierarchy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL).empty()) { + autoUpdater_ = autoUpdaterFactory.createAutoUpdater(updateChannelToFeed(settingsHierarchy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL))); + autoUpdater_->checkForUpdates(); + autoUpdater_->onUpdateStateChanged.connect(boost::bind(&QtSwift::handleAutoUpdaterStateChanged, this, _1)); + + settingsHierarchy_->onSettingChanged.connect([&](const std::string& path) { + if (path == QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL.getKey()) { + autoUpdater_->setAppcastFeed(updateChannelToFeed(settingsHierarchy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL))); + autoUpdater_->checkForUpdates(); + } + }); + } + migrateLastLoginAccount(); + restoreAccounts(); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(handleAboutToQuit())); } QtSwift::~QtSwift() { - delete autoUpdater_; - foreach (QtUIFactory* factory, uiFactories_) { - delete factory; - } - foreach (MainController* controller, mainControllers_) { - delete controller; - } - delete notifier_; - foreach (QtSystemTray* tray, systemTrays_) { - delete tray; - } - delete tabs_; - delete splitter_; - delete settingsHierachy_; - delete qtSettings_; - delete xmlSettings_; - delete statusCache_; - delete uriHandler_; - delete dock_; - delete soundPlayer_; - delete chatWindowFactory_; - delete certificateStorageFactory_; - delete storagesFactory_; + delete autoUpdater_; + for (auto* factory : uiFactories_) { + delete factory; + } + for (auto* controller : accountControllers_) { + delete controller; + } + delete notifier_; + for (auto* tray : systemTrays_) { + delete tray; + } + delete splitter_; + delete settingsHierarchy_; + delete qtSettings_; + delete xmlSettings_; + delete statusCache_; + delete uriHandler_; + delete dock_; + delete soundPlayer_; + delete certificateStorageFactory_; + delete storagesFactory_; + delete applicationPathProvider_; +} + +void QtSwift::handleAboutToQuit() { +#if defined(SWIFTEN_PLATFORM_MACOSX) + // This is required so Sparkle knows about the application shutting down + // and can update the application in background. + CocoaUIHelpers::sendCocoaApplicationWillTerminateNotification(); +#endif } +void QtSwift::handleAutoUpdaterStateChanged(AutoUpdater::State updatedState) { + switch (updatedState) { + case AutoUpdater::State::NotCheckedForUpdatesYet: + break; + case AutoUpdater::State::CheckingForUpdate: + break; + case AutoUpdater::State::DownloadingUpdate: + break; + case AutoUpdater::State::ErrorCheckingForUpdate: + break; + case AutoUpdater::State::NoUpdateAvailable: + break; + case AutoUpdater::State::RestartToInstallUpdate: + notifier_->showMessage(Notifier::SystemMessage, Q2PSTRING(tr("Swift Update Available")), Q2PSTRING(tr("Restart Swift to update to the new Swift version.")), "", [](){}); + } +} + +void QtSwift::handleWantsToAddAccount() { + auto loginWindow = addAccount(); + if (!settingsHierarchy_->getSetting(SettingConstants::FORGET_PASSWORDS)) { + for (const auto& profile : settingsHierarchy_->getAvailableProfiles()) { + ProfileSettingsProvider profileSettings(profile, settingsHierarchy_); + if (profileSettings.getIntSetting("enabled", 0)) { + // No point showing accounts that're already logged in + continue; + } + const auto& password = profileSettings.getStringSetting("pass"); + const auto& certificate = profileSettings.getStringSetting("certificate"); + const auto& jid = profileSettings.getStringSetting("jid"); + const auto& clientOptions = parseClientOptions(profileSettings.getStringSetting("options")); + loginWindow->addAvailableAccount(jid, password, certificate, clientOptions); + } + } +} + +void QtSwift::restoreAccounts() { + if (!settingsHierarchy_->getSetting(SettingConstants::FORGET_PASSWORDS)) { + for (const auto& profile : settingsHierarchy_->getAvailableProfiles()) { + ProfileSettingsProvider profileSettings(profile, settingsHierarchy_); + if (!profileSettings.getIntSetting("enabled", 0)) { + continue; + } + const auto& jid = profileSettings.getStringSetting("jid"); + const auto& password = profileSettings.getStringSetting("pass"); + const auto& certificate = profileSettings.getStringSetting("certificate"); + const auto& clientOptions = parseClientOptions(profileSettings.getStringSetting("options")); + auto loginWindow = addAccount(); + loginWindow->addAvailableAccount(jid, password, certificate, clientOptions); + loginWindow->loginClicked(); + } + } +} + +void QtSwift::migrateLastLoginAccount() { + const SettingsProvider::Setting<bool> loginAutomatically = SettingsProvider::Setting<bool>("loginAutomatically", false); + if (settingsHierarchy_->getSetting(loginAutomatically)) { + auto selectedLoginJID = settingsHierarchy_->getSetting(SettingsProvider::Setting<std::string>("lastLoginJID", "")); + for (const auto& profile : settingsHierarchy_->getAvailableProfiles()) { + ProfileSettingsProvider profileSettings(profile, settingsHierarchy_); + if (profileSettings.getStringSetting("jid") == selectedLoginJID) { + profileSettings.storeInt("enabled", 1); + break; + } + } + settingsHierarchy_->storeSetting(loginAutomatically, false); + } +} + +QtLoginWindow* QtSwift::addAccount() { + if (uiFactories_.size() > 0) { + // Don't add the first tray (see note above) + systemTrays_.push_back(new QtSystemTray()); + } + auto tabs = new QtChatTabs(settingsHierarchy_, true); + QtUIFactory* uiFactory = new QtUIFactory(settingsHierarchy_, qtSettings_, tabs, splitter_, systemTrays_[systemTrays_.size() - 1], networkFactories_.getTimerFactory(), statusCache_, autoUpdater_, emoticons_, enableAdHocCommandOnJID_); + uiFactories_.push_back(uiFactory); + AccountController* accountController = new AccountController( + &clientMainThreadCaller_, + &networkFactories_, + uiFactory, + settingsHierarchy_, + systemTrays_[systemTrays_.size() - 1], + soundPlayer_, + storagesFactory_, + certificateStorageFactory_, + dock_, + notifier_, + uriHandler_, + &idleDetector_, + emoticons_, + useDelayForLatency_); + accountControllers_.push_back(accountController); + + //FIXME - accountController has already created the window, so we can pass null here and get the old one + auto loginWindow = uiFactory->createLoginWindow(nullptr); + + return dynamic_cast<QtLoginWindow*>(loginWindow); +} + +//FIXME: Switch all this to boost::serialise + +#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 QtSwift::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) + PARSE_BOOL(tlsOptions.schannelTLS1_0Workaround, false) + PARSE_BOOL(singleSignOn, false) + + return result; +} + + } diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 1ea8886..811b6e4 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -1,94 +1,118 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/program_options/variables_map.hpp> +#include <map> +#include <string> + #include <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> -#include <Swiften/TLS/PlatformTLSFactories.h> +#include <Swiften/Base/Platform.h> +#include <Swiften/Client/ClientOptions.h> +#include <Swiften/EventLoop/Qt/QtEventLoop.h> #include <Swiften/Network/BoostNetworkFactories.h> -#include <string> -#include "Swiften/Base/Platform.h" -#include "Swiften/EventLoop/Qt/QtEventLoop.h" -#include "QtSettingsProvider.h" +#include <Swiften/TLS/PlatformTLSFactories.h> + +#include <SwifTools/AutoUpdater/AutoUpdater.h> +#include <SwifTools/Idle/ActualIdleDetector.h> +#include <SwifTools/Idle/PlatformIdleQuerier.h> + +#include <Swift/QtUI/QtSettingsProvider.h> + #if defined(SWIFTEN_PLATFORM_MACOSX) -#include "SwifTools/Application/CocoaApplication.h" -#include "CocoaApplicationActivateHelper.h" +#include <SwifTools/Application/CocoaApplication.h> +#include <CocoaApplicationActivateHelper.h> #endif #if defined(SWIFTEN_PLATFORM_WINDOWS) -#include "WindowsNotifier.h" +#include <WindowsNotifier.h> #endif -#include "SwifTools/Idle/PlatformIdleQuerier.h" -#include "SwifTools/Idle/ActualIdleDetector.h" namespace po = boost::program_options; class QSplitter; namespace Swift { - class QtUIFactory; - class CertificateStorageFactory; - class Dock; - class Notifier; - class StoragesFactory; - class AutoUpdater; - class ApplicationPathProvider; - class AvatarStorage; - class CapsStorage; - class MainController; - class QtSystemTray; - class QtChatTabs; - class QtChatWindowFactory; - class QtSoundPlayer; - class QtMUCSearchWindowFactory; - class QtUserSearchWindowFactory; - class EventLoop; - class URIHandler; - class SettingsProviderHierachy; - class XMLSettingsProvider; - class StatusCache; - class QtSingleWindow; - - class QtSwift : public QObject { - Q_OBJECT - public: - QtSwift(const po::variables_map& options); - static po::options_description getOptionsDescription(); - ~QtSwift(); - private: - XMLSettingsProvider* loadSettingsFile(const QString& fileName); - void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons); - private: - QtEventLoop clientMainThreadCaller_; - PlatformTLSFactories tlsFactories_; - BoostNetworkFactories networkFactories_; - QtChatWindowFactory* chatWindowFactory_; - std::vector<MainController*> mainControllers_; - std::vector<QtSystemTray*> systemTrays_; - std::vector<QtUIFactory*> uiFactories_; - QtSettingsProvider* qtSettings_; - XMLSettingsProvider* xmlSettings_; - SettingsProviderHierachy* settingsHierachy_; - QtSingleWindow* splitter_; - QtSoundPlayer* soundPlayer_; - Dock* dock_; - URIHandler* uriHandler_; - QtChatTabs* tabs_; - ApplicationPathProvider* applicationPathProvider_; - StoragesFactory* storagesFactory_; - CertificateStorageFactory* certificateStorageFactory_; - AutoUpdater* autoUpdater_; - Notifier* notifier_; - StatusCache* statusCache_; - PlatformIdleQuerier idleQuerier_; - ActualIdleDetector idleDetector_; + class ApplicationPathProvider; + class AvatarStorage; + class CapsStorage; + class CertificateStorageFactory; + class Dock; + class EventLoop; + class AccountController; + class Notifier; + class QtChatTabs; + class QtChatWindowFactory; + class QtLoginWindow; + class QtMUCSearchWindowFactory; + class QtSingleWindow; + class QtSoundPlayer; + class QtSystemTray; + class QtUIFactory; + class QtUserSearchWindowFactory; + class SettingsProviderHierachy; + class StatusCache; + class StoragesFactory; + class URIHandler; + class XMLSettingsProvider; + + class QtSwift : public QObject { + Q_OBJECT + public: + QtSwift(const po::variables_map& options); + static po::options_description getOptionsDescription(); + ~QtSwift(); + + private slots: + void handleAboutToQuit(); + void handleAutoUpdaterStateChanged(AutoUpdater::State updatedState); + void handleWantsToAddAccount(); + + private: + XMLSettingsProvider* loadSettingsFile(const QString& fileName); + void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons); + static const std::string& updateChannelToFeed(const std::string& channel); + QtLoginWindow* addAccount(); + ClientOptions parseClientOptions(const std::string& optionString); + void restoreAccounts(); + /** + * Upgrades the config from pre-multi-account to post-multi-account format (added in 5.0). + */ + void migrateLastLoginAccount(); + + private: + QtEventLoop clientMainThreadCaller_; + PlatformTLSFactories tlsFactories_; + BoostNetworkFactories networkFactories_; + QtChatWindowFactory* chatWindowFactory_; + std::vector<AccountController*> accountControllers_; + std::vector<QtSystemTray*> systemTrays_; + std::vector<QtUIFactory*> uiFactories_; + QtSettingsProvider* qtSettings_; + XMLSettingsProvider* xmlSettings_; + SettingsProviderHierachy* settingsHierarchy_; + QtSingleWindow* splitter_; + QtSoundPlayer* soundPlayer_; + Dock* dock_; + URIHandler* uriHandler_; + ApplicationPathProvider* applicationPathProvider_; + StoragesFactory* storagesFactory_; + CertificateStorageFactory* certificateStorageFactory_; + AutoUpdater* autoUpdater_ = nullptr; + Notifier* notifier_; + StatusCache* statusCache_; + PlatformIdleQuerier idleQuerier_; + ActualIdleDetector idleDetector_; + std::map<std::string, std::string> emoticons_; + bool enableAdHocCommandOnJID_ = false; + bool useDelayForLatency_; #if defined(SWIFTEN_PLATFORM_MACOSX) - CocoaApplication cocoaApplication_; - CocoaApplicationActivateHelper cocoaApplicationActivateHelper_; + CocoaApplication cocoaApplication_; + CocoaApplicationActivateHelper cocoaApplicationActivateHelper_; #endif - }; + }; } diff --git a/Swift/QtUI/QtSwiftUtil.h b/Swift/QtUI/QtSwiftUtil.h index c903af1..25e52c6 100644 --- a/Swift/QtUI/QtSwiftUtil.h +++ b/Swift/QtUI/QtSwiftUtil.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once diff --git a/Swift/QtUI/QtSystemTray.cpp b/Swift/QtUI/QtSystemTray.cpp index 456a56f..d9bfbc3 100644 --- a/Swift/QtUI/QtSystemTray.cpp +++ b/Swift/QtUI/QtSystemTray.cpp @@ -1,12 +1,12 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma GCC diagnostic ignored "-Wredundant-decls" -#include "Swift/QtUI/QtSystemTray.h" +#include <Swift/QtUI/QtSystemTray.h> #include <QtDebug> #if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) @@ -19,72 +19,72 @@ #include <QAction> namespace Swift { -QtSystemTray::QtSystemTray() : QObject(), 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))); +QtSystemTray::QtSystemTray() : QObject(), statusType_(StatusShow::None), trayMenu_(nullptr), 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_); - } + 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(); + trayIcon_->show(); } QtSystemTray::~QtSystemTray() { - delete trayMenu_; - delete trayIcon_; + delete trayMenu_; + delete trayIcon_; } void QtSystemTray::setUnreadMessages(bool some) { - unreadMessages_ = some; - updateStatusIcon(); + unreadMessages_ = some; + updateStatusIcon(); } void QtSystemTray::handleThrobberFrameChanged(int /*frameNumber*/) { - trayIcon_->setIcon(QIcon(throbberMovie_.currentPixmap())); + trayIcon_->setIcon(QIcon(throbberMovie_.currentPixmap())); } void QtSystemTray::setConnecting() { - connecting_ = true; - updateStatusIcon(); + connecting_ = true; + updateStatusIcon(); } void QtSystemTray::handleIconActivated(QSystemTrayIcon::ActivationReason reason) { - if (reason == QSystemTrayIcon::Trigger) { - emit clicked(); - } + if (reason == QSystemTrayIcon::Trigger) { + emit clicked(); + } } void QtSystemTray::setStatusType(StatusShow::Type type) { - connecting_ = false; - statusType_ = type; - updateStatusIcon(); + connecting_ = false; + statusType_ = type; + updateStatusIcon(); } void QtSystemTray::updateStatusIcon() { - throbberMovie_.stop(); - if (unreadMessages_) { - trayIcon_->setIcon(newMessageIcon_); - } else if (connecting_) { - throbberMovie_.start(); - } else { - switch (statusType_) { - case StatusShow::Online : trayIcon_->setIcon(onlineIcon_);break; - case StatusShow::FFC : trayIcon_->setIcon(onlineIcon_);break; - case StatusShow::Away : trayIcon_->setIcon(awayIcon_);break; - case StatusShow::XA : trayIcon_->setIcon(awayIcon_);break; - case StatusShow::DND : trayIcon_->setIcon(dndIcon_);break; - case StatusShow::None : trayIcon_->setIcon(offlineIcon_);break; - } - } + throbberMovie_.stop(); + if (unreadMessages_) { + trayIcon_->setIcon(newMessageIcon_); + } else if (connecting_) { + throbberMovie_.start(); + } else { + switch (statusType_) { + case StatusShow::Online : trayIcon_->setIcon(onlineIcon_);break; + case StatusShow::FFC : trayIcon_->setIcon(onlineIcon_);break; + case StatusShow::Away : trayIcon_->setIcon(awayIcon_);break; + case StatusShow::XA : trayIcon_->setIcon(awayIcon_);break; + case StatusShow::DND : trayIcon_->setIcon(dndIcon_);break; + case StatusShow::None : trayIcon_->setIcon(offlineIcon_);break; + } + } } } diff --git a/Swift/QtUI/QtSystemTray.h b/Swift/QtUI/QtSystemTray.h index 8691e19..ba2c5cb 100644 --- a/Swift/QtUI/QtSystemTray.h +++ b/Swift/QtUI/QtSystemTray.h @@ -1,50 +1,50 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/Controllers/SystemTray.h" - -#include <QSystemTrayIcon> #include <QMovie> +#include <QSystemTrayIcon> + +#include <Swift/Controllers/SystemTray.h> class QIcon; class QMenu; namespace Swift { - class QtSystemTray : public QObject, public SystemTray { - Q_OBJECT - public: - QtSystemTray(); - ~QtSystemTray(); - void setUnreadMessages(bool some); - void setStatusType(StatusShow::Type type); - void setConnecting(); - QSystemTrayIcon* getQSystemTrayIcon() { - return trayIcon_; - } + class QtSystemTray : public QObject, public SystemTray { + Q_OBJECT + public: + QtSystemTray(); + ~QtSystemTray(); + void setUnreadMessages(bool some); + void setStatusType(StatusShow::Type type); + void setConnecting(); + QSystemTrayIcon* getQSystemTrayIcon() { + return trayIcon_; + } - signals: - void clicked(); - private slots: - void handleIconActivated(QSystemTrayIcon::ActivationReason reason); - void handleThrobberFrameChanged(int); - private: - void updateStatusIcon(); - StatusShow::Type statusType_; - QSystemTrayIcon* trayIcon_; - QMenu* trayMenu_; - QIcon onlineIcon_; - QIcon awayIcon_; - QIcon dndIcon_; - QIcon offlineIcon_; - QIcon newMessageIcon_; - QMovie throbberMovie_; - bool unreadMessages_; - bool connecting_; - }; + signals: + void clicked(); + private slots: + void handleIconActivated(QSystemTrayIcon::ActivationReason reason); + void handleThrobberFrameChanged(int); + private: + void updateStatusIcon(); + StatusShow::Type statusType_; + QSystemTrayIcon* trayIcon_; + QMenu* trayMenu_; + QIcon onlineIcon_; + QIcon awayIcon_; + QIcon dndIcon_; + QIcon offlineIcon_; + QIcon newMessageIcon_; + QMovie throbberMovie_; + bool unreadMessages_; + bool connecting_; + }; } diff --git a/Swift/QtUI/QtTabWidget.cpp b/Swift/QtUI/QtTabWidget.cpp index 614439a..99ef6ee 100644 --- a/Swift/QtUI/QtTabWidget.cpp +++ b/Swift/QtUI/QtTabWidget.cpp @@ -1,10 +1,16 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtTabWidget.h" +#include <Swift/QtUI/QtTabWidget.h> + +#include <QLabel> +#include <QPainter> +#include <QPoint> + +#include <Swift/QtUI/Trellis/QtDNDTabBar.h> namespace Swift { @@ -17,7 +23,44 @@ QtTabWidget::~QtTabWidget() { } QTabBar* QtTabWidget::tabBar() { - return QTabWidget::tabBar(); + return QTabWidget::tabBar(); +} + +void QtTabWidget::setTabBar(QTabBar* tabBar) { + QTabWidget::setTabBar(tabBar); + if (dynamic_cast<QtDNDTabBar*>(tabBar)) { + setAcceptDrops(true); + } + else { + setAcceptDrops(false); + } +} + +void QtTabWidget::dragEnterEvent(QDragEnterEvent* event) { + QtDNDTabBar* dndTabBar = dynamic_cast<QtDNDTabBar*>(tabBar()); + if (dndTabBar) { + dndTabBar->dragEnterEvent(event); + } +} + +void QtTabWidget::dropEvent(QDropEvent* event) { + QtDNDTabBar* dndTabBar = dynamic_cast<QtDNDTabBar*>(tabBar()); + if (dndTabBar) { + dndTabBar->dropEvent(event); + } +} + +void QtTabWidget::paintEvent(QPaintEvent * event) { + QTabWidget::paintEvent(event); + if (count() == 0) { + QLabel label; + label.setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + label.setGeometry(QRect(QPoint(0,0), size()) - QMargins(10,10,10,10)); + label.setWordWrap(true); + label.setText(tr("This empty cell is a placeholder for chat windows. You can move existing chats to this cell by dragging the tab over here. You can change the number of cells via the 'Change layout' dialog under the 'View' menu.")); + QPainter painter(this); + painter.drawPixmap(label.geometry().topLeft(), label.grab()); + } } } diff --git a/Swift/QtUI/QtTabWidget.h b/Swift/QtUI/QtTabWidget.h index e4a44ce..f19721d 100644 --- a/Swift/QtUI/QtTabWidget.h +++ b/Swift/QtUI/QtTabWidget.h @@ -1,18 +1,26 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once + #include <QTabWidget> namespace Swift { - class QtTabWidget : public QTabWidget { - Q_OBJECT - public: - QtTabWidget(QWidget* parent); - ~QtTabWidget(); - QTabBar* tabBar(); - }; + class QtTabWidget : public QTabWidget { + Q_OBJECT + public: + QtTabWidget(QWidget* parent); + virtual ~QtTabWidget(); + + QTabBar* tabBar(); + void setTabBar(QTabBar* tabBar); + + protected: + virtual void dragEnterEvent(QDragEnterEvent* event); + virtual void dropEvent(QDropEvent* event); + virtual void paintEvent(QPaintEvent* event); + }; } diff --git a/Swift/QtUI/QtTabbable.cpp b/Swift/QtUI/QtTabbable.cpp index 84a5100..df2cbfe 100644 --- a/Swift/QtUI/QtTabbable.cpp +++ b/Swift/QtUI/QtTabbable.cpp @@ -1,60 +1,73 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtTabbable.h" +#include <Swift/QtUI/QtTabbable.h> #include <QApplication> +#include <QKeyEvent> +#include <QShortcut> -#include "QtChatTabs.h" +#include <Swiften/Base/Platform.h> + +#include <Swift/QtUI/QtChatTabs.h> +#include <Swift/QtUI/QtUtilities.h> namespace Swift { +QtTabbable::QtTabbable() : QWidget() { + +} + QtTabbable::~QtTabbable() { - emit windowClosing(); + } bool QtTabbable::isWidgetSelected() { - /*isActiveWindow() shouldn't be necessary, but I don't trust it as far as I can throw it*/ - if (!isActiveWindow()) { - return false; - } - QtChatTabs* parent = qobject_cast<QtChatTabs*>(window()); - return parent ? parent->getCurrentTab() == this : isAncestorOf(QApplication::focusWidget()); + /*isActiveWindow() shouldn't be necessary, but I don't trust it as far as I can throw it*/ + if (!isActiveWindow()) { + return false; + } + QtChatTabs* parent = qobject_cast<QtChatTabs*>(window()); + return parent ? parent->getCurrentTab() == this : isAncestorOf(QApplication::focusWidget()); } -void QtTabbable::keyPressEvent(QKeyEvent *event) { - handleKeyPressEvent(event); +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(). + if (keyEvent->modifiers().testFlag(QtUtilities::ctrlHardwareKeyModifier) && keyEvent->key() == Qt::Key_Tab) { + // Only handle KeyRelease event as Linux also generate KeyPress event in case of CTRL+TAB being pressed + // in the roster of a MUC. + if (keyEvent->type() == QEvent::KeyRelease) { + emit requestNextTab(); + } + return true; + } +#ifdef SWIFTEN_PLATFORM_LINUX + else if (keyEvent->modifiers().testFlag(QtUtilities::ctrlHardwareKeyModifier) && keyEvent->key() == Qt::Key_Backtab && keyEvent->type() != QEvent::KeyRelease) { +#else + else if (keyEvent->modifiers().testFlag(QtUtilities::ctrlHardwareKeyModifier) && keyEvent->key() == Qt::Key_Backtab) { +#endif +#ifdef SWIFTEN_PLATFORM_WINDOWS + // Windows emits both the KeyPress and KeyRelease events. + if (keyEvent->type() == QEvent::KeyPress) { +#else + if (keyEvent->type() != QEvent::ShortcutOverride) { +#endif + emit requestPreviousTab(); + } + return true; + } + } + return QWidget::event(event); } -void QtTabbable::handleKeyPressEvent(QKeyEvent *event) { - event->ignore(); - int key = event->key(); - Qt::KeyboardModifiers modifiers = event->modifiers(); - if (key == Qt::Key_W && modifiers == Qt::ControlModifier) { - close(); - event->accept(); - } else if ( - (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) -// || (key == Qt::Key_Left && modifiers == (Qt::ControlModifier & Qt::ShiftModifier)) - ) { - emit requestPreviousTab(); - event->accept(); - } else if ( - (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) -// || (key == Qt::Key_Right && modifiers == (Qt::ControlModifier & Qt::ShiftModifier) - || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) - ) { - emit requestNextTab(); - event->accept(); - } else if ( - (key == Qt::Key_A && modifiers == Qt::AltModifier) - ) { - emit requestActiveTab(); - event->accept(); - } +void QtTabbable::closeEvent(QCloseEvent* event) { + emit windowClosing(); + event->accept(); } } diff --git a/Swift/QtUI/QtTabbable.h b/Swift/QtUI/QtTabbable.h index 0b67b19..63c60f4 100644 --- a/Swift/QtUI/QtTabbable.h +++ b/Swift/QtUI/QtTabbable.h @@ -1,40 +1,42 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QKeyEvent> -#include <QWidget> +#include <string> +#include <QWidget> namespace Swift { - class QtTabbable : public QWidget { - Q_OBJECT - public: - enum AlertType {NoActivity, WaitingActivity, ImpendingActivity}; - ~QtTabbable(); - bool isWidgetSelected(); - virtual AlertType getWidgetAlertState() {return NoActivity;} - virtual int getCount() {return 0;} - protected: - QtTabbable() : QWidget() {} - void keyPressEvent(QKeyEvent* event); + class QtTabbable : public QWidget { + Q_OBJECT + public: + enum AlertType {NoActivity, WaitingActivity, ImpendingActivity}; + virtual ~QtTabbable(); + + bool isWidgetSelected(); + virtual AlertType getWidgetAlertState() {return NoActivity;} + virtual size_t getCount() {return 0;} + virtual std::string getID() const = 0; + virtual void setEmphasiseFocus(bool /*emphasise*/) {} - protected slots: - void handleKeyPressEvent(QKeyEvent* event); + protected: + QtTabbable(); + virtual bool event(QEvent* event); + virtual void closeEvent(QCloseEvent* event); - signals: - void titleUpdated(); - void countUpdated(); - void windowClosing(); - void windowOpening(); - void wantsToActivate(); - void requestPreviousTab(); - void requestNextTab(); - void requestActiveTab(); - void requestFlash(); - }; + signals: + void titleUpdated(); + void countUpdated(); + void windowClosing(); + void windowOpening(); + void wantsToActivate(); + void requestPreviousTab(); + void requestNextTab(); + void requestActiveTab(); + void requestFlash(); + }; } diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 2c4677e..b3c57a7 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -1,235 +1,283 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <boost/tuple/tuple.hpp> +#include <Swift/QtUI/QtTextEdit.h> + #include <boost/algorithm/string.hpp> #include <boost/bind.hpp> +#include <boost/tuple/tuple.hpp> -#include <Swiften/Base/foreach.h> +#include <QApplication> +#include <QKeyEvent> +#include <QKeySequence> +#include <QMenu> +#include <QTextDocument> +#include <QMimeData> + +#include <Swiften/Base/Log.h> -#include <SwifTools/SpellCheckerFactory.h> #include <SwifTools/SpellChecker.h> +#include <SwifTools/SpellCheckerFactory.h> -#include <Swift/QtUI/QtTextEdit.h> -#include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtSpellCheckerWindow.h> -#include <Swift/Controllers/SettingConstants.h> - -#include <QApplication> -#include <QFontMetrics> -#include <QKeyEvent> -#include <QDebug> -#include <QMenu> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtUtilities.h> namespace Swift { -QtTextEdit::QtTextEdit(SettingsProvider* settings, QWidget* parent) : QTextEdit(parent) { - connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); - checker_ = NULL; - settings_ = settings; +QtTextEdit::QtTextEdit(SettingsProvider* settings, QWidget* parent) : QTextEdit(parent), checker_(nullptr), highlighter_(nullptr) { + connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); + settings_ = settings; #ifdef HAVE_SPELLCHECKER - setUpSpellChecker(); + setUpSpellChecker(); #endif - handleTextChanged(); + handleTextChanged(); + QTextOption textOption = document()->defaultTextOption(); + textOption.setWrapMode(QTextOption::WordWrap); + document()->setDefaultTextOption(textOption); } QtTextEdit::~QtTextEdit() { - delete checker_; + delete checker_; } void QtTextEdit::keyPressEvent(QKeyEvent* event) { - int key = event->key(); - Qt::KeyboardModifiers modifiers = event->modifiers(); - if ((key == Qt::Key_Enter || key == Qt::Key_Return) - && (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { - emit returnPressed(); - } - else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier) - || (key == Qt::Key_C && modifiers == Qt::ControlModifier && textCursor().selectedText().isEmpty()) - || (key == Qt::Key_W && modifiers == Qt::ControlModifier) - || (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) - || (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) - || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) - || (key == Qt::Key_A && modifiers == Qt::AltModifier) - || (key == Qt::Key_Tab) - ) { - emit unhandledKeyPressEvent(event); - } - else if ((key == Qt::Key_Up) - || (key == Qt::Key_Down)) { - emit unhandledKeyPressEvent(event); - QTextEdit::keyPressEvent(event); - } - else { - QTextEdit::keyPressEvent(event); -#ifdef HAVE_SPELLCHECKER - if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { - underlineMisspells(); - } + int key = event->key(); + Qt::KeyboardModifiers modifiers = event->modifiers(); + if ((key == Qt::Key_Enter || key == Qt::Key_Return) + && (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { + emit returnPressed(); + } + else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier) + || (key == Qt::Key_C && modifiers == Qt::ControlModifier && textCursor().selectedText().isEmpty()) + || (key == Qt::Key_W && modifiers == Qt::ControlModifier) + || (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) + || (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) + || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) + || (key == Qt::Key_A && modifiers == Qt::AltModifier) + || (key == Qt::Key_Tab) +#ifdef SWIFTEN_PLATFORM_MACOSX + || (key == Qt::Key_Minus && (modifiers & Qt::ControlModifier)) + || (key == Qt::Key_Equal && (modifiers & Qt::ControlModifier)) #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); - } + || (event->matches(QKeySequence::ZoomIn)) + || (event->matches(QKeySequence::ZoomOut)) + ) { + emit unhandledKeyPressEvent(event); + } + else if ((key == Qt::Key_Up) + || (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); + } +} + +void QtTextEdit::setEmphasiseFocus(bool emphasise) { + emphasiseFocus_ = emphasise; + updateStyleSheet(); +} + +void QtTextEdit::setCorrectionHighlight(bool correctionHighlight) { + correctionHighlight_ = correctionHighlight; + updateStyleSheet(); +} + +void QtTextEdit::updateStyleSheet() { + QString newStyleSheet; + + if (correctionHighlight_) { + newStyleSheet += "background: rgb(255, 255, 153); color: black;"; + } + + if (emphasiseFocus_) { + if (hasFocus()) { + newStyleSheet += "border: 2px solid palette(highlight);"; + } + } + + setStyleSheet(newStyleSheet); + handleTextChanged(); +} + +void QtTextEdit::focusInEvent(QFocusEvent* event) { + receivedFocus(); + QTextEdit::focusInEvent(event); + updateStyleSheet(); +} + +void QtTextEdit::focusOutEvent(QFocusEvent* event) { + lostFocus(); + QTextEdit::focusOutEvent(event); + updateStyleSheet(); } void QtTextEdit::handleTextChanged() { - QSize previous(maximumSize()); - setMaximumSize(QSize(maximumWidth(), sizeHint().height())); - if (previous != maximumSize()) { - updateGeometry(); - } + QSize previous(maximumSize()); + setMaximumSize(QSize(maximumWidth(), sizeHint().height())); + if (previous != maximumSize()) { + updateGeometry(); + } } -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); +void QtTextEdit::replaceMisspelledWord(const QString& word, size_t cursorPosition) { + QTextCursor cursor = textCursor(); + auto wordPosition = getWordFromCursor(cursorPosition); + if (wordPosition) { + 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); +boost::optional<PositionPair> QtTextEdit::getWordFromCursor(size_t cursorPosition) { + PositionPairList misspelledPositions = highlighter_->getMisspelledPositions(); + for (auto& misspelledPosition : misspelledPositions) { + if (cursorPosition >= boost::get<0>(misspelledPosition) && cursorPosition <= boost::get<1>(misspelledPosition)) { + return misspelledPosition; + } + } + return boost::optional<PositionPair>(boost::make_tuple(-1,-1)); } QSize QtTextEdit::sizeHint() const { - QFontMetrics inputMetrics(currentFont()); - QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999); - QRect boundingRect = inputMetrics.boundingRect(horizontalBounds, Qt::TextWordWrap, toPlainText() + "A"); - int left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - int height = boundingRect.height() + top + bottom + inputMetrics.height(); - return QSize(width(), height); - //int numberOfLines = 1; - //int lineHeight = inputMetrics.lineSpacing(); - //return QSize(QTextEdit::sizeHint().width(), lineHeight * numberOfLines); + QSize hint = document()->size().toSize(); + QMargins margins = contentsMargins(); + return hint + QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); } void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) { - QMenu* menu = createStandardContextMenu(); - QTextCursor cursor = cursorForPosition(event->pos()); + 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()); - } - } + 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 (auto& replaceWordAction : replaceWordActions_) { + if (replaceWordAction == result) { + replaceMisspelledWord(replaceWordAction->text(), cursor.position()); + } + } #else - menu->exec(event->globalPos()); + menu->exec(event->globalPos()); #endif - delete menu; + delete menu; +} + +void QtTextEdit::dropEvent(QDropEvent* event) { + if (event->mimeData()->hasUrls()) { + itemDropped(event); + } + else { + QTextEdit::dropEvent(event); + } } 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()); + replaceWordActions_.clear(); + if (checker_ && highlighter_) { + QAction* insertPoint = menu->actions().first(); + QTextCursor cursor = cursorForPosition(event->pos()); + auto wordPosition = getWordFromCursor(cursor.position()); + if (!wordPosition) { + // 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 (auto& word : wordList) { + QAction* wordAction = new QAction(word.c_str(), menu); + menu->insertAction(insertPoint, wordAction); + replaceWordActions_.push_back(wordAction); + } + } + menu->insertAction(insertPoint, menu->addSeparator()); + } } #ifdef HAVE_SPELLCHECKER -void QtTextEdit::setUpSpellChecker() -{ - SpellCheckerFactory* checkerFactory = new SpellCheckerFactory(); - delete checker_; - if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { - std::string dictPath = settings_->getSetting(SettingConstants::DICT_PATH); - std::string dictFile = settings_->getSetting(SettingConstants::DICT_FILE); - checker_ = checkerFactory->createSpellChecker(dictPath + dictFile); - delete checkerFactory; - } - else { - checker_ = NULL; - } +void QtTextEdit::setUpSpellChecker() { + delete highlighter_; + highlighter_ = nullptr; + delete checker_; + checker_ = nullptr; + if (settings_->getSetting(QtUISettingConstants::SPELL_CHECKER)) { + checker_ = SpellCheckerFactory().createSpellChecker(); + if (checker_) { + if (!checker_->isAutomaticallyDetectingLanguage()) { + checker_->setActiveLanguage(settings_->getSetting(QtUISettingConstants::SPELL_CHECKER_LANGUAGE)); + } + highlighter_ = new QtSpellCheckHighlighter(document(), checker_); + } + else { + // Spellchecking is not working, as we did not get a valid checker from the factory. Disable spellchecking. + SWIFT_LOG(warning) << "Spellchecking is currently misconfigured in Swift (e.g. missing dictionary or broken dictionary file). Disable spellchecking."; + settings_->storeSetting(QtUISettingConstants::SPELL_CHECKER, false); + } + + } } #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(); - } + if (!spellCheckerWindow_) { + spellCheckerWindow_ = new QtSpellCheckerWindow(settings_); + settings_->onSettingChanged.connect(boost::bind(&QtTextEdit::handleSettingChanged, this, _1)); + spellCheckerWindow_->show(); + } + else { + spellCheckerWindow_->show(); + spellCheckerWindow_->raise(); + spellCheckerWindow_->activateWindow(); + } + if (checker_) { + spellCheckerWindow_->setAutomaticallyIdentifiesLanguage(checker_->isAutomaticallyDetectingLanguage()); + if (!checker_->isAutomaticallyDetectingLanguage()) { + spellCheckerWindow_->setSupportedLanguages(checker_->supportedLanguages()); + spellCheckerWindow_->setActiveLanguage(checker_->activeLanguage()); + } + } } void QtTextEdit::handleSettingChanged(const std::string& settings) { - if (settings == SettingConstants::SPELL_CHECKER.getKey() - || settings == SettingConstants::DICT_PATH.getKey() - || settings == SettingConstants::DICT_FILE.getKey()) { + if (settings == QtUISettingConstants::SPELL_CHECKER.getKey() || + settings == QtUISettingConstants::SPELL_CHECKER_LANGUAGE.getKey()) { #ifdef HAVE_SPELLCHECKER - setUpSpellChecker(); - underlineMisspells(); + setUpSpellChecker(); + if (highlighter_) { + highlighter_->rehighlight(); + } #endif - } + } } } diff --git a/Swift/QtUI/QtTextEdit.h b/Swift/QtUI/QtTextEdit.h index a8df4d3..178f258 100644 --- a/Swift/QtUI/QtTextEdit.h +++ b/Swift/QtUI/QtTextEdit.h @@ -1,50 +1,72 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <SwifTools/SpellParser.h> +#include <boost/optional.hpp> + +#include <QPointer> +#include <QTextEdit> #include <Swift/Controllers/Settings/SettingsProvider.h> -#include <Swift/Controllers/SettingConstants.h> -#include <QTextEdit> -#include <QPointer> +#include <SwifTools/SpellParser.h> + +#include <Swift/QtUI/QtSpellCheckHighlighter.h> namespace Swift { - class SpellChecker; - class QtSpellCheckerWindow; - class QtTextEdit : public QTextEdit { - Q_OBJECT - public: - 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); - }; + class SpellChecker; + class QtSpellCheckerWindow; + + class QtTextEdit : public QTextEdit { + Q_OBJECT + public: + QtTextEdit(SettingsProvider* settings, QWidget* parent = nullptr); + virtual ~QtTextEdit(); + virtual QSize sizeHint() const; + + void setEmphasiseFocus(bool emphasise); + void setCorrectionHighlight(bool coorectionHighlight); + + signals: + void wordCorrected(QString& word); + void returnPressed(); + void unhandledKeyPressEvent(QKeyEvent* event); + void receivedFocus(); + void lostFocus(); + void itemDropped(QDropEvent* event); + + public slots: + void handleSettingChanged(const std::string& settings); + + protected: + virtual void keyPressEvent(QKeyEvent* event); + virtual void focusInEvent(QFocusEvent* event); + virtual void focusOutEvent(QFocusEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void dropEvent(QDropEvent* event); + + private slots: + void handleTextChanged(); + + private: + void addSuggestions(QMenu* menu, QContextMenuEvent* event); + void replaceMisspelledWord(const QString& word, size_t cursorPosition); + void setUpSpellChecker(); + void spellCheckerSettingsWindow(); + boost::optional<PositionPair> getWordFromCursor(size_t cursorPosition); + void updateStyleSheet(); + + private: + SpellChecker* checker_; + QtSpellCheckHighlighter* highlighter_; + std::vector<QAction*> replaceWordActions_; + SettingsProvider* settings_; + QPointer<QtSpellCheckerWindow> spellCheckerWindow_; + bool emphasiseFocus_ = false; + bool correctionHighlight_ = false; + }; } diff --git a/Swift/QtUI/QtTranslator.h b/Swift/QtUI/QtTranslator.h index a2129f0..92abf77 100644 --- a/Swift/QtUI/QtTranslator.h +++ b/Swift/QtUI/QtTranslator.h @@ -1,25 +1,37 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QCoreApplication> +#include <QDateTime> #include <Swift/Controllers/Translator.h> +#include <Swift/QtUI/QtSwiftUtil.h> + class QtTranslator : public Swift::Translator { - public: - QtTranslator() { - } + public: + QtTranslator() { + } - virtual std::string translate(const std::string& text, const std::string& context) { + 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()); + return std::string(QCoreApplication::translate(context.c_str(), text.c_str(), nullptr).toUtf8()); #else - return std::string(QCoreApplication::translate(context.c_str(), text.c_str(), 0, QCoreApplication::UnicodeUTF8).toUtf8()); + return std::string(QCoreApplication::translate(context.c_str(), text.c_str(), 0, QCoreApplication::UnicodeUTF8).toUtf8()); #endif - } + } + + virtual std::string ptimeToHumanReadableString(const boost::posix_time::ptime& time) { + return Q2PSTRING(QDateTime::fromTime_t(posixTimeToTimeT(time)).toString(Qt::SystemLocaleLongDate)); + } + + private: + static std::time_t posixTimeToTimeT(boost::posix_time::ptime pt) { + return std::time_t((pt - boost::posix_time::ptime(boost::gregorian::date(1970,1,1))).total_seconds()); + } }; diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index e5db22d..49f55dd 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -1,179 +1,189 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtUIFactory.h> +#include <algorithm> + #include <QSplitter> -#include <Swift/QtUI/QtXMLConsoleWidget.h> +#include <Swiften/Base/Log.h> +#include <Swiften/Whiteboard/WhiteboardSession.h> + +#include <Swift/Controllers/Settings/SettingsProviderHierachy.h> + +#include <Swift/QtUI/MUCSearch/QtMUCSearchWindow.h> +#include <Swift/QtUI/QtAdHocCommandWindow.h> +#include <Swift/QtUI/QtBlockListEditorWindow.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/QtFdpFormSubmitWindow.h> #include <Swift/QtUI/QtFileTransferListWidget.h> -#include <Swift/QtUI/QtHighlightEditorWidget.h> -#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h> -#include <Swift/Controllers/Settings/SettingsProviderHierachy.h> -#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtHighlightNotificationConfigDialog.h> #include <Swift/QtUI/QtHistoryWindow.h> -#include <Swiften/Whiteboard/WhiteboardSession.h> +#include <Swift/QtUI/QtJoinMUCWindow.h> +#include <Swift/QtUI/QtLoginWindow.h> +#include <Swift/QtUI/QtMainWindow.h> +#include <Swift/QtUI/QtProfileWindow.h> +#include <Swift/QtUI/QtSettingsProvider.h> #include <Swift/QtUI/QtSingleWindow.h> -#include <Swift/QtUI/QtBlockListEditorWindow.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSystemTray.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtXMLConsoleWidget.h> +#include <Swift/QtUI/UserSearch/QtUserSearchWindow.h> +#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h> namespace Swift { -QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) { - chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); - historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); +QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, TimerFactory* timerFactory, StatusCache* statusCache, AutoUpdater* autoUpdater, std::map<std::string, std::string>& emoticons, bool enableAdHocCommandOnJID) : settings_(settings), qtOnlySettings_(qtOnlySettings), tabs_(tabs), netbookSplitter_(netbookSplitter), systemTray_(systemTray), timerFactory_(timerFactory), lastMainWindow_(nullptr), loginWindow_(nullptr), statusCache_(statusCache), autoUpdater_(autoUpdater), emoticons_(emoticons), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) { + emoticonsExist_ = !emoticons_.empty(); + chatWindowFactory_ = new QtChatWindowFactory(netbookSplitter_, settings, qtOnlySettings, tabs_, ":/themes/Default/", emoticons_); + chatFontSize_ = settings_->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); + historyFontSize_ = settings_->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); +} + +QtUIFactory::~QtUIFactory() { + SWIFT_LOG(debug) << "Entering QtUIFactory destructor. chatWindows size:" << chatWindows_.size(); + for (auto chat : chatWindows_) { + SWIFT_LOG_ASSERT(chat.isNull(), debug) << "QtUIFactory has active chat windows and has not been reset properly"; + } + delete chatWindowFactory_; } XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { - QtXMLConsoleWidget* widget = new QtXMLConsoleWidget(); - tabs->addTab(widget); - if (!tabs->isVisible()) { - tabs->show(); - } - widget->show(); - return widget; + QtXMLConsoleWidget* widget = new QtXMLConsoleWidget(); + tabs_->addTab(widget); + showTabs(); + widget->show(); + return widget; } HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) { - QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream); - tabs->addTab(window); - if (!tabs->isVisible()) { - tabs->show(); - } + QtHistoryWindow* window = new QtHistoryWindow(settings_, uiEventStream); + tabs_->addTab(window); + showTabs(); + connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int))); - connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int))); - - window->handleFontResized(historyFontSize_); - window->show(); - return window; + window->handleFontResized(historyFontSize_); + window->show(); + return window; } void QtUIFactory::handleHistoryWindowFontResized(int size) { - historyFontSize_ = size; - settings->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size); + historyFontSize_ = size; + settings_->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size); } FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { - QtFileTransferListWidget* widget = new QtFileTransferListWidget(); - tabs->addTab(widget); - if (!tabs->isVisible()) { - tabs->show(); - } - widget->show(); - return widget; + QtFileTransferListWidget* widget = new QtFileTransferListWidget(); + tabs_->addTab(widget); + showTabs(); + widget->show(); + return widget; } -MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { - lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_); - return lastMainWindow; +MainWindow* QtUIFactory::createMainWindow(Chattables& chattables, UIEventStream* eventStream) { + lastMainWindow_ = new QtMainWindow(chattables, settings_, eventStream, loginWindow_->getMenus(), statusCache_, emoticonsExist_, enableAdHocCommandOnJID_); + tabs_->setViewMenu(lastMainWindow_->getMenus()[0]); + return lastMainWindow_; } LoginWindow* QtUIFactory::createLoginWindow(UIEventStream* eventStream) { - loginWindow = new QtLoginWindow(eventStream, settings, timerFactory_); - if (netbookSplitter) { - netbookSplitter->insertAtFront(loginWindow); - } - connect(systemTray, SIGNAL(clicked()), loginWindow, SLOT(toggleBringToFront())); - -#ifndef SWIFT_MOBILE - QVariant loginWindowGeometryVariant = qtOnlySettings->getQSettings()->value("loginWindowGeometry"); - if (loginWindowGeometryVariant.isValid()) { - loginWindow->restoreGeometry(loginWindowGeometryVariant.toByteArray()); - } - connect(loginWindow, SIGNAL(geometryChanged()), this, SLOT(handleLoginWindowGeometryChanged())); - if (startMinimized) loginWindow->hide(); -#endif - return loginWindow; -} - -void QtUIFactory::handleLoginWindowGeometryChanged() { - qtOnlySettings->getQSettings()->setValue("loginWindowGeometry", loginWindow->saveGeometry()); + if (loginWindow_) { + return loginWindow_; + } + loginWindow_ = new QtLoginWindow(eventStream, settings_, timerFactory_, autoUpdater_); + netbookSplitter_->addAccount(loginWindow_, tabs_); + connect(systemTray_, SIGNAL(clicked()), loginWindow_, SLOT(toggleBringToFront())); + return loginWindow_; } EventWindow* QtUIFactory::createEventWindow() { - return lastMainWindow->getEventWindow(); + return lastMainWindow_->getEventWindow(); } ChatListWindow* QtUIFactory::createChatListWindow(UIEventStream*) { - return lastMainWindow->getChatListWindow(); + return lastMainWindow_->getChatListWindow(); } MUCSearchWindow* QtUIFactory::createMUCSearchWindow() { - return new QtMUCSearchWindow(); + return new QtMUCSearchWindow(); } ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eventStream) { - QtChatWindow* window = dynamic_cast<QtChatWindow*>(chatWindowFactory->createChatWindow(contact, eventStream)); - chatWindows.push_back(window); - std::vector<QPointer<QtChatWindow> > deletions; - foreach (QPointer<QtChatWindow> existingWindow, chatWindows) { - if (existingWindow.isNull()) { - deletions.push_back(existingWindow); - } else { - connect(window, SIGNAL(fontResized(int)), existingWindow, SLOT(handleFontResized(int))); - connect(existingWindow, SIGNAL(fontResized(int)), window, SLOT(handleFontResized(int))); - } - } - foreach (QPointer<QtChatWindow> deletedWindow, deletions) { - chatWindows.erase(std::remove(chatWindows.begin(), chatWindows.end(), deletedWindow), chatWindows.end()); - } - connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int))); - window->handleFontResized(chatFontSize); - return window; + QtChatWindow* window = dynamic_cast<QtChatWindow*>(chatWindowFactory_->createChatWindow(contact, eventStream)); + + // remove already closed and thereby deleted chat windows + chatWindows_.erase(std::remove_if(chatWindows_.begin(), chatWindows_.end(), + [](QPointer<QtChatWindow>& window) { + return window.isNull(); + }), chatWindows_.end()); + + chatWindows_.push_back(window); + + connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int))); + window->handleFontResized(chatFontSize_); + return window; } void QtUIFactory::handleChatWindowFontResized(int size) { - chatFontSize = size; - settings->storeSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE, size); + chatFontSize_ = size; + settings_->storeSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE, size); + + // resize font in other chat windows + for (auto&& existingWindow : chatWindows_) { + if (!existingWindow.isNull()) { + existingWindow->handleFontResized(size); + } + } } UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) { - return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings); + return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings_); } JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) { - return new QtJoinMUCWindow(uiEventStream); + return new QtJoinMUCWindow(uiEventStream); } ProfileWindow* QtUIFactory::createProfileWindow() { - return new QtProfileWindow(); + return new QtProfileWindow(); } ContactEditWindow* QtUIFactory::createContactEditWindow() { - return new QtContactEditWindow(); + return new QtContactEditWindow(); } -WhiteboardWindow* QtUIFactory::createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession) { - return new QtWhiteboardWindow(whiteboardSession); +WhiteboardWindow* QtUIFactory::createWhiteboardWindow(std::shared_ptr<WhiteboardSession> whiteboardSession) { + return new QtWhiteboardWindow(whiteboardSession); } -HighlightEditorWidget* QtUIFactory::createHighlightEditorWidget() { - return new QtHighlightEditorWidget(); +HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() { + return new QtHighlightNotificationConfigDialog(qtOnlySettings_); } BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { - return new QtBlockListEditorWindow(); + return new QtBlockListEditorWindow(); +} + +AdHocCommandWindow* QtUIFactory::createAdHocCommandWindow(std::shared_ptr<OutgoingAdHocCommandSession> command) { + return new QtAdHocCommandWindow(command); +} + +std::unique_ptr<FdpFormSubmitWindow> QtUIFactory::createFdpFormSubmitWindow() { + return std::make_unique<QtFdpFormSubmitWindow>(); } -void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) { - new QtAdHocCommandWindow(command); +void QtUIFactory::showTabs() { + if (!tabs_->isVisible()) { + tabs_->show(); + } } } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 662c78e..04836fe 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -1,11 +1,13 @@ /* - * Copyright (c) 2010-2012 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <vector> + #include <QObject> #include <QPointer> @@ -14,64 +16,71 @@ class QSplitter; namespace Swift { - class QtSettingsProvider; - class SettingsProviderHierachy; - class QtChatTabs; - class QtSystemTray; - class QtLoginWindow; - class QtMainWindow; - class QtChatTheme; - class QtChatWindowFactory; - class QtChatWindow; - class TimerFactory; - class historyWindow_; - class WhiteboardSession; - class StatusCache; - class QtSingleWindow; + class AutoUpdater; + class Chattables; + class FdpFormSubmitWindow; + class QtChatTabs; + class QtChatTheme; + class QtChatWindow; + class QtChatWindowFactory; + class QtLoginWindow; + class QtMainWindow; + class QtSettingsProvider; + class QtSingleWindow; + class QtSystemTray; + class SettingsProviderHierachy; + class StatusCache; + class TimerFactory; + class WhiteboardSession; - class QtUIFactory : public QObject, public UIFactory { - Q_OBJECT - public: - QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist); + class QtUIFactory : public QObject, public UIFactory { + Q_OBJECT + public: + QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, TimerFactory* timerFactory, StatusCache* statusCache, AutoUpdater* autoUpdater, std::map<std::string, std::string>& emoticons, bool enableAdHocCommandOnJID); + ~QtUIFactory(); + virtual XMLConsoleWidget* createXMLConsoleWidget(); + virtual HistoryWindow* createHistoryWindow(UIEventStream*); + virtual MainWindow* createMainWindow(Chattables& chattables, UIEventStream* eventStream); + virtual LoginWindow* createLoginWindow(UIEventStream* eventStream); + virtual EventWindow* createEventWindow(); + virtual ChatListWindow* createChatListWindow(UIEventStream*); + virtual MUCSearchWindow* createMUCSearchWindow(); + virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); + virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups); + virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream); + virtual ProfileWindow* createProfileWindow(); + virtual ContactEditWindow* createContactEditWindow(); + virtual FileTransferListWidget* createFileTransferListWidget(); + virtual WhiteboardWindow* createWhiteboardWindow(std::shared_ptr<WhiteboardSession> whiteboardSession); + virtual HighlightEditorWindow* createHighlightEditorWindow(); + virtual BlockListEditorWidget* createBlockListEditorWidget(); + virtual AdHocCommandWindow* createAdHocCommandWindow(std::shared_ptr<OutgoingAdHocCommandSession> command); + virtual std::unique_ptr<FdpFormSubmitWindow> createFdpFormSubmitWindow(); - virtual XMLConsoleWidget* createXMLConsoleWidget(); - virtual HistoryWindow* createHistoryWindow(UIEventStream*); - virtual MainWindow* createMainWindow(UIEventStream* eventStream); - virtual LoginWindow* createLoginWindow(UIEventStream* eventStream); - virtual EventWindow* createEventWindow(); - virtual ChatListWindow* createChatListWindow(UIEventStream*); - virtual MUCSearchWindow* createMUCSearchWindow(); - virtual ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); - virtual UserSearchWindow* createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups); - virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream); - virtual ProfileWindow* createProfileWindow(); - virtual ContactEditWindow* createContactEditWindow(); - virtual FileTransferListWidget* createFileTransferListWidget(); - virtual WhiteboardWindow* createWhiteboardWindow(boost::shared_ptr<WhiteboardSession> whiteboardSession); - virtual HighlightEditorWidget* createHighlightEditorWidget(); - virtual BlockListEditorWidget* createBlockListEditorWidget(); - virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); + private slots: + void handleChatWindowFontResized(int); + void handleHistoryWindowFontResized(int); - private slots: - void handleLoginWindowGeometryChanged(); - void handleChatWindowFontResized(int); - void handleHistoryWindowFontResized(int); + private: + void showTabs(); - private: - SettingsProviderHierachy* settings; - QtSettingsProvider* qtOnlySettings; - QtChatTabs* tabs; - QtSingleWindow* netbookSplitter; - QtSystemTray* systemTray; - QtChatWindowFactory* chatWindowFactory; - TimerFactory* timerFactory_; - QtMainWindow* lastMainWindow; - QtLoginWindow* loginWindow; - StatusCache* statusCache; - std::vector<QPointer<QtChatWindow> > chatWindows; - bool startMinimized; - int chatFontSize; - int historyFontSize_; - bool emoticonsExist_; - }; + private: + SettingsProviderHierachy* settings_; + QtSettingsProvider* qtOnlySettings_; + QtChatTabs* tabs_; + QtSingleWindow* netbookSplitter_; + QtSystemTray* systemTray_; + QtChatWindowFactory* chatWindowFactory_; + TimerFactory* timerFactory_; + QtMainWindow* lastMainWindow_; + QtLoginWindow* loginWindow_; + StatusCache* statusCache_; + AutoUpdater* autoUpdater_; + std::vector<QPointer<QtChatWindow> > chatWindows_; + int chatFontSize_; + int historyFontSize_; + bool emoticonsExist_; + std::map<std::string, std::string>& emoticons_; + bool enableAdHocCommandOnJID_; + }; } diff --git a/Swift/QtUI/QtUISettingConstants.cpp b/Swift/QtUI/QtUISettingConstants.cpp index 68001d7..c7a7a16 100644 --- a/Swift/QtUI/QtUISettingConstants.cpp +++ b/Swift/QtUI/QtUISettingConstants.cpp @@ -1,18 +1,29 @@ /* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/SwiftUpdateFeeds.h> + namespace Swift { const SettingsProvider::Setting<bool> QtUISettingConstants::COMPACT_ROSTER("compactRoster", false); const SettingsProvider::Setting<std::string> QtUISettingConstants::CLICKTHROUGH_BANNER("clickthroughBanner", ""); const SettingsProvider::Setting<int> QtUISettingConstants::CURRENT_ROSTER_TAB("currentRosterTab", 0); 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::CHATWINDOW_FONT_SIZE("chatWindowFontSize_V3", 3); 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); +const SettingsProvider::Setting<bool> QtUISettingConstants::SPELL_CHECKER("spellChecker", false); +const SettingsProvider::Setting<std::string> QtUISettingConstants::SPELL_CHECKER_LANGUAGE("spellCheckerLanguage", "en_US"); +const SettingsProvider::Setting<std::string> QtUISettingConstants::TRELLIS_GRID_SIZE("trellisGridSize", ""); +const SettingsProvider::Setting<std::string> QtUISettingConstants::TRELLIS_GRID_POSITIONS("trellisGridPositions", ""); +const SettingsProvider::Setting<bool> QtUISettingConstants::ENABLE_SOFTWARE_UPDATES("enableSoftwareUpdates", true); +const SettingsProvider::Setting<std::string> QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL("softwareUpdateChannel", UpdateFeeds::StableChannel); + } diff --git a/Swift/QtUI/QtUISettingConstants.h b/Swift/QtUI/QtUISettingConstants.h index 8ac835f..a569587 100644 --- a/Swift/QtUI/QtUISettingConstants.h +++ b/Swift/QtUI/QtUISettingConstants.h @@ -1,22 +1,53 @@ /* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once +#include <string> + #include <Swift/Controllers/Settings/SettingsProvider.h> namespace Swift { - class QtUISettingConstants { - public: - static const SettingsProvider::Setting<bool> COMPACT_ROSTER; - static const SettingsProvider::Setting<std::string> CLICKTHROUGH_BANNER; - static const SettingsProvider::Setting<int> CURRENT_ROSTER_TAB; - 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; - }; + class QtUISettingConstants { + public: + static const SettingsProvider::Setting<bool> COMPACT_ROSTER; + static const SettingsProvider::Setting<std::string> CLICKTHROUGH_BANNER; + static const SettingsProvider::Setting<int> CURRENT_ROSTER_TAB; + 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; + static const SettingsProvider::Setting<bool> SPELL_CHECKER; + static const SettingsProvider::Setting<std::string> SPELL_CHECKER_LANGUAGE; + /** + * The #TRELLIS_GRID_SIZE setting specifies the dimensions of the grid used for the trellis + * layout. + * + * Its value is a Qt serialized representation. + */ + static const SettingsProvider::Setting<std::string> TRELLIS_GRID_SIZE; + /** + * The #TRELLIS_GRID_POSITIONS setting specifies where conversations to contacts or rooms go + * in the trellis grid. + * + * Its value is a Qt serialized representation. + */ + static const SettingsProvider::Setting<std::string> TRELLIS_GRID_POSITIONS; + /** + * The #ENABLE_SOFTWARE_UPDATES setting specifies, whether Swift + * should automatically check for software updates in regular + * intervals and install them automatically. + */ + static const SettingsProvider::Setting<bool> ENABLE_SOFTWARE_UPDATES; + /** + * The #SOFTWARE_UPDATE_CHANNEL setting defines what update channel + * Swift uses to check for, and receive, updates. + */ + static const SettingsProvider::Setting<std::string> SOFTWARE_UPDATE_CHANNEL; + }; } diff --git a/Swift/QtUI/QtURIHandler.cpp b/Swift/QtUI/QtURIHandler.cpp index 197f001..24bd328 100644 --- a/Swift/QtUI/QtURIHandler.cpp +++ b/Swift/QtUI/QtURIHandler.cpp @@ -1,30 +1,30 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtURIHandler.h" +#include <Swift/QtUI/QtURIHandler.h> #include <QCoreApplication> #include <QFileOpenEvent> #include <QUrl> -#include "QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> using namespace Swift; QtURIHandler::QtURIHandler() { - qApp->installEventFilter(this); + qApp->installEventFilter(this); } bool QtURIHandler::eventFilter(QObject*, QEvent* event) { - if (event->type() == QEvent::FileOpen) { - QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event); - if (fileOpenEvent->url().scheme() == "xmpp") { - onURI(Q2PSTRING(fileOpenEvent->url().toString())); - return true; - } - } - return false; + if (event->type() == QEvent::FileOpen) { + QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event); + if (fileOpenEvent->url().scheme() == "xmpp") { + onURI(Q2PSTRING(fileOpenEvent->url().toString())); + return true; + } + } + return false; } diff --git a/Swift/QtUI/QtURIHandler.h b/Swift/QtUI/QtURIHandler.h index a02114a..309e55b 100644 --- a/Swift/QtUI/QtURIHandler.h +++ b/Swift/QtUI/QtURIHandler.h @@ -1,22 +1,23 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QObject> + #include <SwifTools/URIHandler/URIHandler.h> class QUrl; namespace Swift { - class QtURIHandler : public QObject, public URIHandler { - public: - QtURIHandler(); + class QtURIHandler : public QObject, public URIHandler { + public: + QtURIHandler(); - private: - bool eventFilter(QObject* obj, QEvent* event); - }; + private: + bool eventFilter(QObject* obj, QEvent* event); + }; } diff --git a/Swift/QtUI/QtURLValidator.cpp b/Swift/QtUI/QtURLValidator.cpp index 4d56b98..8017710 100644 --- a/Swift/QtUI/QtURLValidator.cpp +++ b/Swift/QtUI/QtURLValidator.cpp @@ -1,12 +1,13 @@ /* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/QtURLValidator.h> #include <Swiften/Base/URL.h> + #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -15,10 +16,10 @@ 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; + 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 index fa17253..887828a 100644 --- a/Swift/QtUI/QtURLValidator.h +++ b/Swift/QtUI/QtURLValidator.h @@ -1,17 +1,17 @@ /* - * Copyright (c) 2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2012 Isode Limited. + * All rights reserved. + * See the COPYING file 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; - }; + class QtURLValidator : public QValidator { + Q_OBJECT + public: + QtURLValidator(QObject* parent); + virtual QValidator::State validate(QString& input, int& pos) const; + }; } diff --git a/Swift/QtUI/QtUpdateFeedSelectionDialog.cpp b/Swift/QtUI/QtUpdateFeedSelectionDialog.cpp new file mode 100644 index 0000000..1f4058a --- /dev/null +++ b/Swift/QtUI/QtUpdateFeedSelectionDialog.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtUpdateFeedSelectionDialog.h> + +#include <QComboBox> + +#include <Swift/Controllers/Settings/SettingsProvider.h> + +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/SwiftUpdateFeeds.h> + +namespace Swift { + +QtUpdateFeedSelectionDialog::QtUpdateFeedSelectionDialog(SettingsProvider* settingsProvider) : QDialog(), settings_(settingsProvider) { + ui.setupUi(this); + + connect(ui.currentChannelComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [&] (int newIndex) { + setDescriptionForIndex(newIndex); + }); + + auto updateChannel = settings_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL); + if (updateChannel == UpdateFeeds::StableChannel) { + ui.currentChannelComboBox->setCurrentIndex(0); + } + else if (updateChannel == UpdateFeeds::TestingChannel) { + ui.currentChannelComboBox->setCurrentIndex(1); + } + else if (updateChannel == UpdateFeeds::DevelopmentChannel) { + ui.currentChannelComboBox->setCurrentIndex(2); + } + + connect(this, &QDialog::accepted, [&]() { + auto newUpdateChannel = std::string(""); + switch (ui.currentChannelComboBox->currentIndex()) { + case 0: + newUpdateChannel = UpdateFeeds::StableChannel; + break; + case 1: + newUpdateChannel = UpdateFeeds::TestingChannel; + break; + case 2: + newUpdateChannel = UpdateFeeds::DevelopmentChannel; + break; + } + settings_->storeSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL, newUpdateChannel); + }); + + setAttribute(Qt::WA_DeleteOnClose); +} + +void QtUpdateFeedSelectionDialog::setDescriptionForIndex(int index) { + switch (index) { + case 0: + ui.stableDescriptionLabel->show(); + ui.testingDescriptionLabel->hide(); + ui.developmentDescriptionLabel->hide(); + break; + case 1: + ui.stableDescriptionLabel->hide(); + ui.testingDescriptionLabel->show(); + ui.developmentDescriptionLabel->hide(); + break; + case 2: + ui.stableDescriptionLabel->hide(); + ui.testingDescriptionLabel->hide(); + ui.developmentDescriptionLabel->show(); + break; + default: + ui.stableDescriptionLabel->hide(); + ui.testingDescriptionLabel->hide(); + ui.developmentDescriptionLabel->hide(); + break; + } + setFixedSize(sizeHint()); +} + + + +} diff --git a/Swift/QtUI/QtUpdateFeedSelectionDialog.h b/Swift/QtUI/QtUpdateFeedSelectionDialog.h new file mode 100644 index 0000000..80b986f --- /dev/null +++ b/Swift/QtUI/QtUpdateFeedSelectionDialog.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QDialog> + +#include <Swift/QtUI/ui_QtUpdateFeedSelectionDialog.h> + +namespace Swift { + +class SettingsProvider; + +class QtUpdateFeedSelectionDialog : public QDialog { + Q_OBJECT + public: + QtUpdateFeedSelectionDialog(SettingsProvider* settingsProvider); + + private: + void setDescriptionForIndex(int index); + + private: + Ui::QtUpdateFeedSelectionDialog ui; + SettingsProvider* settings_ = nullptr; +}; + +} diff --git a/Swift/QtUI/QtUpdateFeedSelectionDialog.ui b/Swift/QtUI/QtUpdateFeedSelectionDialog.ui new file mode 100644 index 0000000..2eac1f1 --- /dev/null +++ b/Swift/QtUI/QtUpdateFeedSelectionDialog.ui @@ -0,0 +1,146 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtUpdateFeedSelectionDialog</class> + <widget class="QDialog" name="QtUpdateFeedSelectionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>335</width> + <height>158</height> + </rect> + </property> + <property name="windowTitle"> + <string>Select Update Channel</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QComboBox" name="currentChannelComboBox"> + <property name="currentText"> + <string/> + </property> + <property name="currentIndex"> + <number>-1</number> + </property> + <item> + <property name="text"> + <string>Stable Channel</string> + </property> + <property name="icon"> + <iconset theme=":/icons/delivery-success.svg"/> + </property> + </item> + <item> + <property name="text"> + <string>Testing Channel</string> + </property> + <property name="icon"> + <iconset theme=":/icons/delivery-warning.svg"/> + </property> + </item> + <item> + <property name="text"> + <string>Development Channel</string> + </property> + <property name="icon"> + <iconset theme=":/icons/delivery-warning.svg"/> + </property> + </item> + </widget> + </item> + <item> + <widget class="QLabel" name="stableDescriptionLabel"> + <property name="text"> + <string>This release channel includes our stable releases. They went throught internal QA testing and had previous RC releases to find critical bugs.</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="alignment"> + <set>Qt::AlignJustify|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="testingDescriptionLabel"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>This release channel includes our stable releases, beta releases and release candidates. They should be free from obvious bugs and are released for wider testing to find more obscure bugs.</string> + </property> + <property name="alignment"> + <set>Qt::AlignJustify|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="developmentDescriptionLabel"> + <property name="text"> + <string>This release channel includes our stable releases, beta releases, release candidates and development releases. The development releases are not thoroughly tested and might contain bugs.</string> + </property> + <property name="alignment"> + <set>Qt::AlignJustify|Qt::AlignTop</set> + </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>accepted()</signal> + <receiver>QtUpdateFeedSelectionDialog</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>QtUpdateFeedSelectionDialog</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/QtUtilities.cpp b/Swift/QtUI/QtUtilities.cpp index e9aa4a4..6eb0b04 100644 --- a/Swift/QtUI/QtUtilities.cpp +++ b/Swift/QtUI/QtUtilities.cpp @@ -1,42 +1,54 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtUtilities.h" +#include <Swift/QtUI/QtUtilities.h> -#include <QTextDocument> -#include <QWidget> +#include <QtGui> #if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) #include <QX11Info> #include <X11/Xlib.h> #include <X11/Xutil.h> #endif +#include <QTextDocument> +#include <QWidget> +#include <QDateTime> -#include "Swift/Controllers/ApplicationInfo.h" +#include <Swift/Controllers/ApplicationInfo.h> namespace QtUtilities { + void setX11Resource(QWidget* widget, const QString& c) { -#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(); - hint.res_class = res_class; - XSetClassHint(widget->x11Info().display(), widget->winId(), &hint); +#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) && QT_VERSION < 0x050000 + char res_class[] = SWIFT_APPLICATION_NAME; + XClassHint hint; + 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); #else - (void) widget; - (void) c; + (void) widget; + (void) c; #endif } QString htmlEscape(const QString& s) { #if QT_VERSION >= 0x050000 - return s.toHtmlEscaped(); + return s.toHtmlEscaped(); #else - return Qt::escape(s); + return Qt::escape(s); #endif } +long long secondsToNextMidnight(const QDateTime& currentTime) { + auto secondsToMidnight = 0ll; + auto nextMidnight = currentTime.addDays(1); + nextMidnight.setTime(QTime(0,0)); + secondsToMidnight = currentTime.secsTo(nextMidnight); + return secondsToMidnight; +} + } diff --git a/Swift/QtUI/QtUtilities.h b/Swift/QtUI/QtUtilities.h index 40c16bc..c6f4311 100644 --- a/Swift/QtUI/QtUtilities.h +++ b/Swift/QtUI/QtUtilities.h @@ -1,15 +1,31 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once class QWidget; class QString; +class QDateTime; + +#include <QKeyEvent> namespace QtUtilities { - void setX11Resource(QWidget* widget, const QString& c); - QString htmlEscape(const QString& s); + 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 + + /** + * @brief secondsToNextMidnight calculates the seconds until next midnight. + * @param currentTime This is the current time, which SHOULD have a Qt::TimeSpec + * of Qt::LocalTime to correctly handle DST changes in the current locale. + * @return + */ + long long secondsToNextMidnight(const QDateTime& currentTime); } diff --git a/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp b/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp index ebd62bc..da4f22f 100644 --- a/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp +++ b/Swift/QtUI/QtVCardWidget/QtCloseButton.cpp @@ -4,7 +4,13 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtCloseButton.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtCloseButton.h> #include <QMouseEvent> #include <QPainter> @@ -18,28 +24,28 @@ 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)); + return QSize(style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, nullptr), style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, nullptr)); } bool QtCloseButton::event(QEvent *e) { - if (e->type() == QEvent::Enter || e->type() == QEvent::Leave) { - update(); - } - return QAbstractButton::event(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); + 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 index cb92e12..0c6e192 100644 --- a/Swift/QtUI/QtVCardWidget/QtCloseButton.h +++ b/Swift/QtUI/QtVCardWidget/QtCloseButton.h @@ -4,21 +4,27 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file 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; + class QtCloseButton : public QAbstractButton { + Q_OBJECT + public: + explicit QtCloseButton(QWidget *parent = nullptr); + virtual QSize sizeHint() const; - protected: - virtual bool event(QEvent *e); - virtual void paintEvent(QPaintEvent* ); - }; + protected: + virtual bool event(QEvent *e); + virtual void paintEvent(QPaintEvent* ); + }; } diff --git a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp index e7e3175..079f77d 100644 --- a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp +++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp @@ -4,52 +4,62 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtRemovableItemDelegate.h" -#include <Swiften/Base/Platform.h> +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> + #include <QEvent> #include <QPainter> +#include <Swiften/Base/Platform.h> + namespace Swift { QtRemovableItemDelegate::QtRemovableItemDelegate(const QStyle* style) : style(style) { } -void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const { - QStyleOption opt; - opt.state = option.state; - opt.state |= QStyle::State_AutoRaise; - if (option.state.testFlag(QStyle::State_MouseOver)) { - opt.state |= QStyle::State_Raised; - } - opt.rect = option.rect; - painter->save(); - painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base()); +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); + // workaround for Qt not painting relative to the cell we're in, on OS X + int height = style->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, nullptr); + painter->translate(option.rect.x(), option.rect.y() + (option.rect.height() - height)/2); #endif - style->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, painter); - painter->restore(); + style->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, painter); + } + painter->restore(); } QWidget* QtRemovableItemDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { - return NULL; + return nullptr; } bool QtRemovableItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { - if (event->type() == QEvent::MouseButtonRelease) { - model->removeRow(index.row()); - return true; - } else { - return QItemDelegate::editorEvent(event, model, option, index); - } + 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), style->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, 0)); - return size; + QSize size(style->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, nullptr) + 2, style->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, nullptr) + 2); + return size; } } diff --git a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h index 75137e1..ea9e0b2 100644 --- a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h +++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h @@ -11,18 +11,18 @@ namespace Swift { class QtRemovableItemDelegate : public QItemDelegate { - public: - QtRemovableItemDelegate(const QStyle* style); + 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; + 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); + protected: + virtual bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); - private: - const QStyle* style; + private: + const QStyle* style; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp index 4f1d3ab..9eef970 100644 --- a/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp +++ b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.cpp @@ -4,48 +4,55 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtResizableLineEdit.h" +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + + +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> namespace Swift { QtResizableLineEdit::QtResizableLineEdit(QWidget* parent) : - QLineEdit(parent) { - connect(this, SIGNAL(textChanged(QString)), SLOT(textChanged(QString))); - setMinimumWidth(30); + QLineEdit(parent), editable(false) { + connect(this, SIGNAL(textChanged(QString)), SLOT(textChanged(QString))); + setMinimumWidth(30); } QtResizableLineEdit::~QtResizableLineEdit() { } bool QtResizableLineEdit::isEditable() const { - return editable; + return editable; } void QtResizableLineEdit::setEditable(const bool editable) { - this->editable = editable; - if (editable) { - setReadOnly(false); - } else { - setReadOnly(true); - } + this->editable = editable; + if (editable) { + setReadOnly(false); + } else { + setReadOnly(true); + } } QSize QtResizableLineEdit::sizeHint() const { - int horizontalMargin = 10; - int verticalMargin = 6; - QSize textDimensions; + int horizontalMargin = 10; + int verticalMargin = 6; + QSize textDimensions; #if QT_VERSION >= 0x040700 - textDimensions = fontMetrics().boundingRect(text().isEmpty() ? placeholderText() : text()).size(); + textDimensions = fontMetrics().boundingRect(text().isEmpty() ? placeholderText() : text()).size(); #else - textDimensions = fontMetrics().boundingRect(text().isEmpty() ? QString(" ") : text()).size(); + textDimensions = fontMetrics().boundingRect(text().isEmpty() ? QString(" ") : text()).size(); #endif - textDimensions.setWidth(textDimensions.width() + horizontalMargin); - textDimensions.setHeight(textDimensions.height() + verticalMargin); - return textDimensions; + textDimensions.setWidth(textDimensions.width() + horizontalMargin); + textDimensions.setHeight(textDimensions.height() + verticalMargin); + return textDimensions; } void QtResizableLineEdit::textChanged(QString) { - updateGeometry(); + updateGeometry(); } } diff --git a/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h index 9022d38..eab5b2b 100644 --- a/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h +++ b/Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h @@ -4,30 +4,36 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QLineEdit> namespace Swift { - class QtResizableLineEdit : public QLineEdit { - Q_OBJECT - Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + class QtResizableLineEdit : public QLineEdit { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) - public: - explicit QtResizableLineEdit(QWidget* parent = 0); - ~QtResizableLineEdit(); + public: + explicit QtResizableLineEdit(QWidget* parent = nullptr); + ~QtResizableLineEdit(); - bool isEditable() const; - void setEditable(const bool); + bool isEditable() const; + void setEditable(const bool); - virtual QSize sizeHint() const; + virtual QSize sizeHint() const; - private slots: - void textChanged(QString); + private slots: + void textChanged(QString); - private: - bool editable; - }; + private: + bool editable; + }; } diff --git a/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp b/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp index bade009..02ceb0a 100644 --- a/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp +++ b/Swift/QtUI/QtVCardWidget/QtTagComboBox.cpp @@ -4,7 +4,13 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtTagComboBox.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtTagComboBox.h> #include <QAbstractItemView> #include <QtGui> @@ -12,14 +18,14 @@ 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; + setEditable(false); + displayModel = new QStandardItemModel(this); + displayItem = new QStandardItem(); + displayItem->setText(""); + displayModel->insertRow(0, displayItem); + editMenu = new QMenu(this); + this->setModel(displayModel); + editable = true; } QtTagComboBox::~QtTagComboBox() { @@ -27,40 +33,40 @@ QtTagComboBox::~QtTagComboBox() { } bool QtTagComboBox::isEditable() const { - return editable; + return editable; } void QtTagComboBox::setEditable(const bool editable) { - this->editable = 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); + 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; - } - } + QList<QAction*> tagActions = editMenu->actions(); + for (auto 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; + QList<QAction*> tagActions = editMenu->actions(); + for (auto action : tagActions) { + if (action->data() == id) { + return action->isChecked(); + } + } + return false; } void QtTagComboBox::showPopup() { @@ -72,31 +78,31 @@ 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); + 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); + QList<QAction*> tagActions = editMenu->actions(); + QString text = ""; + for (auto 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 index 37a60af..e9dbbdd 100644 --- a/Swift/QtUI/QtVCardWidget/QtTagComboBox.h +++ b/Swift/QtUI/QtVCardWidget/QtTagComboBox.h @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QComboBox> @@ -14,33 +20,33 @@ namespace Swift { class QtTagComboBox : public QComboBox { - Q_OBJECT - Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) - public: - explicit QtTagComboBox(QWidget* parent = 0); - ~QtTagComboBox(); + public: + explicit QtTagComboBox(QWidget* parent = nullptr); + ~QtTagComboBox(); - bool isEditable() const; - void setEditable(const bool); + 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; + 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 void showPopup(); + virtual void hidePopup(); - virtual bool event(QEvent* event); + virtual bool event(QEvent* event); - private: - void updateDisplayItem(); + private: + void updateDisplayItem(); - private: - bool editable; - QStandardItemModel* displayModel; - QStandardItem* displayItem; - QMenu* editMenu; + private: + bool editable; + QStandardItemModel* displayModel; + QStandardItem* displayItem; + QMenu* editMenu; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp index d9bb4fe..596006a 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.cpp @@ -1,165 +1,182 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardAddressField.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardAddressField.h> #include <QGridLayout> +#include <Swift/QtUI/QtElidingLabel.h> #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtVCardAddressField::QtVCardAddressField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Address")) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Address")), streetLineEdit(nullptr), poboxLineEdit(nullptr), addressextLineEdit(nullptr), cityLineEdit(nullptr), pocodeLineEdit(nullptr), regionLineEdit(nullptr), countryLineEdit(nullptr), textFieldGridLayout(nullptr), textFieldGridLayoutItem(nullptr), deliveryTypeLabel(nullptr), domesticRadioButton(nullptr), internationalRadioButton(nullptr), buttonGroup(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardAddressField::~QtVCardAddressField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + disconnect(this, SLOT(handleEditibleChanged(bool))); } void QtVCardAddressField::setupContentWidgets() { - textFieldGridLayout = new QGridLayout(); + textFieldGridLayout = new QGridLayout(); - streetLineEdit = new QtResizableLineEdit(this); - textFieldGridLayout->addWidget(streetLineEdit, 0, 0, Qt::AlignVCenter); + streetLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(streetLineEdit, 0, 0, Qt::AlignVCenter); - poboxLineEdit = new QtResizableLineEdit(this); - textFieldGridLayout->addWidget(poboxLineEdit, 0, 1, Qt::AlignVCenter); + poboxLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(poboxLineEdit, 0, 1, Qt::AlignVCenter); - addressextLineEdit = new QtResizableLineEdit(this); - textFieldGridLayout->addWidget(addressextLineEdit, 1, 0, Qt::AlignVCenter); + addressextLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(addressextLineEdit, 1, 0, Qt::AlignVCenter); - cityLineEdit = new QtResizableLineEdit(this); - textFieldGridLayout->addWidget(cityLineEdit, 2, 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); + pocodeLineEdit = new QtResizableLineEdit(this); + textFieldGridLayout->addWidget(pocodeLineEdit, 2, 1, Qt::AlignVCenter); - regionLineEdit = new QtResizableLineEdit(this); - textFieldGridLayout->addWidget(regionLineEdit, 3, 0, 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); + 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")); + 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); + deliveryTypeLabel = new QtElidingLabel(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); + 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); + 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); + buttonGroup = new QButtonGroup(this); + buttonGroup->addButton(domesticRadioButton); + buttonGroup->addButton(internationalRadioButton); - setTabOrder(internationalRadioButton, getTagComboBox()); - getTagComboBox()->addTag("postal", tr("Postal")); - getTagComboBox()->addTag("parcel", tr("Parcel")); + setTabOrder(internationalRadioButton, getTagComboBox()); + getTagComboBox()->addTag("postal", tr("Postal")); + getTagComboBox()->addTag("parcel", tr("Parcel")); - QtVCardHomeWork::setTagComboBox(getTagComboBox()); + QtVCardHomeWork::setTagComboBox(getTagComboBox()); - textFields << streetLineEdit << poboxLineEdit << addressextLineEdit << cityLineEdit << pocodeLineEdit << regionLineEdit << countryLineEdit; - childWidgets << deliveryTypeLabel << domesticRadioButton << internationalRadioButton; + 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); + for (auto 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(); + 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)); + 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; + 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) { - if (streetLineEdit) streetLineEdit->setEditable(isEditable); - if (poboxLineEdit) poboxLineEdit->setEditable(isEditable); - if (addressextLineEdit) addressextLineEdit->setEditable(isEditable); - if (cityLineEdit) cityLineEdit->setEditable(isEditable); - if (pocodeLineEdit) pocodeLineEdit->setEditable(isEditable); - if (regionLineEdit) regionLineEdit->setEditable(isEditable); - if (countryLineEdit) countryLineEdit->setEditable(isEditable); - - if (deliveryTypeLabel) { - deliveryTypeLabel->setText(buttonGroup->checkedButton() == 0 ? "" : buttonGroup->checkedButton()->text()); - deliveryTypeLabel->setVisible(!isEditable); - } - if (domesticRadioButton) domesticRadioButton->setVisible(isEditable); - if (internationalRadioButton) 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; }"); - } - } + 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() == nullptr ? "" : buttonGroup->checkedButton()->text()); + deliveryTypeLabel->setVisible(!isEditable); + + domesticRadioButton->setVisible(isEditable); + internationalRadioButton->setVisible(isEditable); + + for (auto 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 index 5a1256a..aeebbff 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardAddressField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressField.h @@ -4,57 +4,65 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#pragma once +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swiften/Elements/VCard.h> +#pragma once #include <QButtonGroup> #include <QRadioButton> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" -#include "QtVCardHomeWork.h" +#include <Swiften/Elements/VCard.h> + +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h> namespace Swift { +class QtElidingLabel; + 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; + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Address"), UNLIMITED_INSTANCES, QtVCardAddressField) + + QtVCardAddressField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, 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; + + QtElidingLabel* deliveryTypeLabel; + QRadioButton* domesticRadioButton; + QRadioButton* internationalRadioButton; + QButtonGroup* buttonGroup; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp index 20f48b9..e4a75cd 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.cpp @@ -1,97 +1,107 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardAddressLabelField.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h> -#include <QGridLayout> #include <boost/algorithm/string.hpp> +#include <QGridLayout> + +#include <Swift/QtUI/QtElidingLabel.h> #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtVCardAddressLabelField::QtVCardAddressLabelField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Address Label")) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Address Label")), addressLabelPlainTextEdit(nullptr), deliveryTypeLabel(nullptr), domesticRadioButton(nullptr), internationalRadioButton(nullptr), buttonGroup(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardAddressLabelField::~QtVCardAddressLabelField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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); + 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); + deliveryTypeLabel = new QtElidingLabel(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); + 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); + 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); + buttonGroup = new QButtonGroup(this); + buttonGroup->addButton(domesticRadioButton); + buttonGroup->addButton(internationalRadioButton); - setTabOrder(internationalRadioButton, getTagComboBox()); - getTagComboBox()->addTag("postal", tr("Postal")); - getTagComboBox()->addTag("parcel", tr("Parcel")); + setTabOrder(internationalRadioButton, getTagComboBox()); + getTagComboBox()->addTag("postal", tr("Postal")); + getTagComboBox()->addTag("parcel", tr("Parcel")); - QtVCardHomeWork::setTagComboBox(getTagComboBox()); - deliveryTypeLabel->hide(); - childWidgets << addressLabelPlainTextEdit << deliveryTypeLabel << domesticRadioButton << internationalRadioButton; + QtVCardHomeWork::setTagComboBox(getTagComboBox()); + deliveryTypeLabel->hide(); + childWidgets << addressLabelPlainTextEdit << deliveryTypeLabel << domesticRadioButton << internationalRadioButton; } bool QtVCardAddressLabelField::isEmpty() const { - return addressLabelPlainTextEdit->toPlainText().isEmpty(); + 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)); + 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; + 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) { - if (addressLabelPlainTextEdit) { - addressLabelPlainTextEdit->setReadOnly(!isEditable); - addressLabelPlainTextEdit->setStyleSheet(isEditable ? "" : "QPlainTextEdit { background: transparent; }"); - } - - if (deliveryTypeLabel) { - deliveryTypeLabel->setText(buttonGroup->checkedButton() == 0 ? "" : buttonGroup->checkedButton()->text()); - deliveryTypeLabel->setVisible(!isEditable); - } - if (domesticRadioButton) domesticRadioButton->setVisible(isEditable); - if (internationalRadioButton) internationalRadioButton->setVisible(isEditable); + assert(addressLabelPlainTextEdit); + assert(deliveryTypeLabel); + assert(domesticRadioButton); + assert(internationalRadioButton); + + addressLabelPlainTextEdit->setReadOnly(!isEditable); + addressLabelPlainTextEdit->setStyleSheet(isEditable ? "" : "QPlainTextEdit { background: transparent; }"); + + deliveryTypeLabel->setText(buttonGroup->checkedButton() == nullptr ? "" : 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 index 0e097d9..ceca88a 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardAddressLabelField.h @@ -4,46 +4,55 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QButtonGroup> #include <QPlainTextEdit> #include <QRadioButton> + #include <Swiften/Elements/VCard.h> -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" -#include "QtVCardHomeWork.h" +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h> namespace Swift { +class QtElidingLabel; + class QtVCardAddressLabelField : public QtVCardGeneralField, public QtVCardHomeWork { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("AddressLabel", UNLIMITED_INSTANCES, QtVCardAddressLabelField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Address Label"), UNLIMITED_INSTANCES, QtVCardAddressLabelField) - QtVCardAddressLabelField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardAddressLabelField(); + QtVCardAddressLabelField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardAddressLabelField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setAddressLabel(const VCard::AddressLabel& addressLabel); - VCard::AddressLabel getAddressLabel() const; + void setAddressLabel(const VCard::AddressLabel& addressLabel); + VCard::AddressLabel getAddressLabel() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QPlainTextEdit* addressLabelPlainTextEdit; + private: + QPlainTextEdit* addressLabelPlainTextEdit; - QLabel* deliveryTypeLabel; - QRadioButton* domesticRadioButton; - QRadioButton* internationalRadioButton; - QButtonGroup* buttonGroup; + QtElidingLabel* deliveryTypeLabel; + QRadioButton* domesticRadioButton; + QRadioButton* internationalRadioButton; + QButtonGroup* buttonGroup; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp index 2afc2f6..1111295 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.cpp @@ -1,61 +1,68 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardBirthdayField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h> + +#include <boost/algorithm/string.hpp> #include <QGridLayout> #include <QHBoxLayout> -#include <boost/algorithm/string.hpp> #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtVCardBirthdayField::QtVCardBirthdayField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Birthday"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Birthday"), false, false), birthdayLabel(nullptr), birthdayDateEdit(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardBirthdayField::~QtVCardBirthdayField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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); + 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); + QHBoxLayout* birthdayLayout = new QHBoxLayout(); + birthdayLayout->addWidget(birthdayLabel); + birthdayLayout->addWidget(birthdayDateEdit); - getGridLayout()->addLayout(birthdayLayout, getGridLayout()->rowCount()-1, 2, Qt::AlignVCenter); + getGridLayout()->addLayout(birthdayLayout, getGridLayout()->rowCount()-1, 2, Qt::AlignVCenter); - getTagComboBox()->hide(); - birthdayLabel->hide(); - childWidgets << birthdayLabel << birthdayDateEdit; + getTagComboBox()->hide(); + birthdayLabel->hide(); + childWidgets << birthdayLabel << birthdayDateEdit; } bool QtVCardBirthdayField::isEmpty() const { - return false; + return false; } void QtVCardBirthdayField::setBirthday(const boost::posix_time::ptime& birthday) { - birthdayDateEdit->setDate(B2QDATE(birthday).date()); + birthdayDateEdit->setDate(B2QDATE(birthday).date()); } boost::posix_time::ptime QtVCardBirthdayField::getBirthday() const { - return boost::posix_time::from_time_t(QDateTime(birthdayDateEdit->date()).toTime_t()); + 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); + 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 index 4be6e27..0383b6c 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardBirthdayField.h @@ -4,40 +4,47 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QDateEdit> + #include <Swiften/Elements/VCard.h> -#include "QtCloseButton.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#include <Swift/QtUI/QtVCardWidget/QtCloseButton.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> namespace Swift { class QtVCardBirthdayField : public QtVCardGeneralField { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("Birthday", 1, QtVCardBirthdayField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Birthday"), 1, QtVCardBirthdayField) - QtVCardBirthdayField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardBirthdayField(); + QtVCardBirthdayField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardBirthdayField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setBirthday(const boost::posix_time::ptime& addressLabel); - boost::posix_time::ptime getBirthday() const; + void setBirthday(const boost::posix_time::ptime& addressLabel); + boost::posix_time::ptime getBirthday() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QLabel* birthdayLabel; - QDateEdit* birthdayDateEdit; + private: + QLabel* birthdayLabel; + QDateEdit* birthdayDateEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp index f907d78..2a8e1c9 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.cpp @@ -1,12 +1,19 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardDescriptionField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h> #include <boost/algorithm/string.hpp> + #include <QFontMetrics> #include <QGridLayout> @@ -15,50 +22,50 @@ namespace Swift { QtVCardDescriptionField::QtVCardDescriptionField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Description"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Description"), false, false), descriptionPlainTextEdit(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardDescriptionField::~QtVCardDescriptionField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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; + 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(); + return descriptionPlainTextEdit->toPlainText().isEmpty(); } void QtVCardDescriptionField::setDescription(const std::string& description) { - descriptionPlainTextEdit->setPlainText(P2QSTRING(description)); + descriptionPlainTextEdit->setPlainText(P2QSTRING(description)); } std::string QtVCardDescriptionField::getDescription() const { - return Q2PSTRING(descriptionPlainTextEdit->toPlainText()); + return Q2PSTRING(descriptionPlainTextEdit->toPlainText()); } void QtVCardDescriptionField::handleEditibleChanged(bool isEditable) { - if (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; }"); - } + 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 index 3b1b3d9..c06dd97 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardDescriptionField.h @@ -4,39 +4,45 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#pragma once +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swiften/Elements/VCard.h> +#pragma once #include <QPlainTextEdit> -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#include <Swiften/Elements/VCard.h> + +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> namespace Swift { class QtVCardDescriptionField : public QtVCardGeneralField { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("Description", 1, QtVCardDescriptionField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Description"), 1, QtVCardDescriptionField) - QtVCardDescriptionField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardDescriptionField(); + QtVCardDescriptionField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardDescriptionField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setDescription(const std::string& description); - std::string getDescription() const; + void setDescription(const std::string& description); + std::string getDescription() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QPlainTextEdit* descriptionPlainTextEdit; + private: + QPlainTextEdit* descriptionPlainTextEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h b/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h index 168c01b..fa42c49 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h @@ -4,49 +4,55 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once +#include <typeinfo> + #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 FieldInfo : public QtVCardFieldInfo { \ + public: \ + virtual ~FieldInfo() { \ + } \ + \ + virtual QString getMenuName() const { \ + return 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) != nullptr; \ + } \ + }; 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; - }; + 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 index 5b3ef87..9bb6a35 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.cpp @@ -1,20 +1,29 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardGeneralField.h" +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> + +#include <cassert> #include <QHBoxLayout> -#include <Swiften/Base/Log.h> +#include <QToolTip> + +#include <Swift/QtUI/QtElidingLabel.h> namespace Swift { QtVCardGeneralField::QtVCardGeneralField(QWidget* parent, QGridLayout* layout, bool editable, int row, QString label, bool preferrable, bool taggable) : - QWidget(parent), preferrable(preferrable), taggable(taggable), layout(layout), row(row), preferredCheckBox(0), label(0), labelText(label), - tagComboBox(0), closeButton(0) { - setEditable(editable); + QWidget(parent), editable(editable), preferrable(preferrable), starVisible(false), taggable(taggable), layout(layout), row(row), preferredCheckBox(nullptr), label(nullptr), labelText(label), + tagComboBox(nullptr), tagLabel(nullptr), closeButton(nullptr) { } QtVCardGeneralField::~QtVCardGeneralField() { @@ -22,94 +31,126 @@ QtVCardGeneralField::~QtVCardGeneralField() { } void QtVCardGeneralField::initialize() { - if (preferrable) { - preferredCheckBox = new QCheckBox(this); - preferredCheckBox->setStyleSheet( - "QCheckBox::indicator { width: 18px; height: 18px; }" - "QCheckBox::indicator:checked { image: url(:/icons/star-checked.png); }" - "QCheckBox::indicator:unchecked { image: url(:/icons/star-unchecked); }" - ); - layout->addWidget(preferredCheckBox, row, 0, Qt::AlignVCenter); - childWidgets << preferredCheckBox; - } - label = new QLabel(this); - label->setText(labelText); - layout->addWidget(label, row, 1, Qt::AlignVCenter | Qt::AlignRight); - - tagLabel = new QLabel(this); - tagLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); - - tagComboBox = new QtTagComboBox(this); - closeButton = new QtCloseButton(this); - connect(closeButton, SIGNAL(clicked()), SLOT(handleCloseButtonClicked())); - - QHBoxLayout* tagLayout = new QHBoxLayout(); - tagLayout->addWidget(tagLabel); - tagLayout->addWidget(tagComboBox); - - setupContentWidgets(); - layout->addLayout(tagLayout, row, 4, Qt::AlignTop); - layout->addWidget(closeButton, row, 5, Qt::AlignCenter); - closeButton->resize(12, 12); - tagLabel->hide(); - - childWidgets << label << tagComboBox << tagLabel << closeButton; + 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.png); }" + ); + 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 QtElidingLabel(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; + return editable; } void QtVCardGeneralField::setEditable(bool editable) { - this->editable = editable; - if (tagComboBox) { - if (taggable) { - tagLabel->setText(tagComboBox->itemText(0)); - tagComboBox->setVisible(editable); - tagLabel->setVisible(!editable); - } else { - tagLabel->hide(); - tagComboBox->hide(); - } - } - if (closeButton) closeButton->setVisible(editable); - if (preferredCheckBox) { - if (editable) { - preferredCheckBox->show(); - } else if (!preferredCheckBox->isChecked()) { - preferredCheckBox->hide(); - } - preferredCheckBox->setEnabled(editable); - } - editableChanged(this->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); + if (preferredCheckBox) preferredCheckBox->setChecked(preferred); + updatePreferredStarVisibility(); } bool QtVCardGeneralField::getPreferred() const { - return preferredCheckBox ? preferredCheckBox->isChecked() : false; + return preferredCheckBox ? preferredCheckBox->isChecked() : false; } void QtVCardGeneralField::customCleanup() { } QtTagComboBox* QtVCardGeneralField::getTagComboBox() const { - return tagComboBox; + return tagComboBox; } QGridLayout* QtVCardGeneralField::getGridLayout() const { - return layout; + return layout; } void QtVCardGeneralField::handleCloseButtonClicked() { - customCleanup(); - foreach(QWidget* widget, childWidgets) { - widget->hide(); - layout->removeWidget(widget); - } - deleteField(this); + customCleanup(); + for (auto 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 index 4afe692..48ecf9f 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QCheckBox> @@ -11,67 +17,77 @@ #include <QLabel> #include <QWidget> -#include "QtCloseButton.h" -#include "QtTagComboBox.h" +#include <Swift/QtUI/QtVCardWidget/QtCloseButton.h> +#include <Swift/QtUI/QtVCardWidget/QtTagComboBox.h> namespace Swift { +class QtElidingLabel; + /* - * covers features like: - * - preffered (star ceckbox) - * - combo check boxh - * - label - * - remove button + * covers features like: + * - preffered (star ceckbox) + * - combo check box + * - label + * - remove button */ class QtVCardGeneralField : public QWidget { - Q_OBJECT - Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged) - Q_PROPERTY(bool empty READ isEmpty) - - public: - explicit QtVCardGeneralField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false, int row = 0, QString label = QString(), - bool preferrable = true, bool taggable = true); - virtual ~QtVCardGeneralField(); - - void initialize(); - - virtual bool isEditable() const; - virtual void setEditable(bool); - - virtual bool isEmpty() const = 0; - - void setPreferred(const bool preferred); - bool getPreferred() const; - - protected: - virtual void setupContentWidgets() = 0; - virtual void customCleanup(); - - QtTagComboBox* getTagComboBox() const; - QGridLayout* getGridLayout() const; - - signals: - void editableChanged(bool); - void deleteField(QtVCardGeneralField*); - - public slots: - void handleCloseButtonClicked(); - - protected: - QList<QWidget*> childWidgets; - - private: - bool editable; - bool preferrable; - bool taggable; - QGridLayout* layout; - int row; - QCheckBox* preferredCheckBox; - QLabel* label; - QString labelText; - QtTagComboBox* tagComboBox; - QLabel* tagLabel; - QtCloseButton* closeButton; + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged) + Q_PROPERTY(bool empty READ isEmpty) + + public: + explicit QtVCardGeneralField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, 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; + QtElidingLabel* tagLabel; + QtCloseButton* closeButton; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp index 3119a80..7f204ca 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.cpp @@ -4,36 +4,42 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardHomeWork.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h> namespace Swift { -QtVCardHomeWork::QtVCardHomeWork() : tagComboBox(0) { +QtVCardHomeWork::QtVCardHomeWork() : tagComboBox(nullptr) { } QtVCardHomeWork::~QtVCardHomeWork() { } void QtVCardHomeWork::setTagComboBox(QtTagComboBox* tagBox) { - tagComboBox = tagBox; - tagComboBox->addTag("home", QObject::tr("Home")); - tagComboBox->addTag("work", QObject::tr("Work")); + tagComboBox = tagBox; + tagComboBox->addTag("home", QObject::tr("Home")); + tagComboBox->addTag("work", QObject::tr("Work")); } void QtVCardHomeWork::setHome(const bool home) { - tagComboBox->setTag("home", home); + tagComboBox->setTag("home", home); } bool QtVCardHomeWork::getHome() const { - return tagComboBox->isTagSet("home"); + return tagComboBox->isTagSet("home"); } void QtVCardHomeWork::setWork(const bool work) { - tagComboBox->setTag("work", work); + tagComboBox->setTag("work", work); } bool QtVCardHomeWork::getWork() const { - return tagComboBox->isTagSet("work"); + return tagComboBox->isTagSet("work"); } } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h index 768d984..dd1452a 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h @@ -4,28 +4,34 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QObject> -#include "QtTagComboBox.h" +#include <Swift/QtUI/QtVCardWidget/QtTagComboBox.h> namespace Swift { class QtVCardHomeWork { - public: - QtVCardHomeWork(); - virtual ~QtVCardHomeWork(); + public: + QtVCardHomeWork(); + virtual ~QtVCardHomeWork(); - void setTagComboBox(QtTagComboBox* tagBox); + void setTagComboBox(QtTagComboBox* tagBox); - void setHome(const bool home); - bool getHome() const; - void setWork(const bool work); - bool getWork() const; + void setHome(const bool home); + bool getHome() const; + void setWork(const bool work); + bool getWork() const; - private: - QtTagComboBox* tagComboBox; + private: + QtTagComboBox* tagComboBox; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp index b946fc4..9deb7ba 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.cpp @@ -1,14 +1,21 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardInternetEMailField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h> #include <QGridLayout> #include <QHBoxLayout> #include <QTextDocument> + #include <Swiften/Base/Log.h> #include <Swift/QtUI/QtSwiftUtil.h> @@ -17,65 +24,66 @@ namespace Swift { QtVCardInternetEMailField::QtVCardInternetEMailField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("E-Mail")) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("E-Mail")), emailLineEdit(nullptr), emailLabel(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardInternetEMailField::~QtVCardInternetEMailField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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); + 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")); + 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; + 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(); + 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)); + 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; + 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) { - if (isEditable) { - if (emailLineEdit) emailLineEdit->show(); - if (emailLabel) emailLabel->hide(); - } else { - if (emailLineEdit) emailLineEdit->hide(); - if (emailLabel) { - emailLabel->setText(QString("<a href=\"mailto:%1\">%1</a>").arg(QtUtilities::htmlEscape(emailLineEdit->text()))); - emailLabel->show(); - } - } + 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 index 3f8a27f..07f4f8d 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardInternetEMailField.h @@ -4,40 +4,46 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/VCard.h> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" -#include "QtVCardHomeWork.h" +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h> namespace Swift { class QtVCardInternetEMailField : public QtVCardGeneralField, public QtVCardHomeWork { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("E-Mail", UNLIMITED_INSTANCES, QtVCardInternetEMailField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("E-Mail"), UNLIMITED_INSTANCES, QtVCardInternetEMailField) - QtVCardInternetEMailField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardInternetEMailField(); + QtVCardInternetEMailField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardInternetEMailField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setInternetEMailAddress(const VCard::EMailAddress& address); - VCard::EMailAddress getInternetEMailAddress() const; + void setInternetEMailAddress(const VCard::EMailAddress& address); + VCard::EMailAddress getInternetEMailAddress() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QtResizableLineEdit* emailLineEdit; - QLabel* emailLabel; + private: + QtResizableLineEdit* emailLineEdit; + QLabel* emailLabel; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp index ecb5533..f93ea06 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.cpp @@ -1,14 +1,21 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardJIDField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardJIDField.h> + +#include <boost/algorithm/string.hpp> #include <QGridLayout> #include <QTextDocument> -#include <boost/algorithm/string.hpp> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtUtilities.h> @@ -16,57 +23,58 @@ namespace Swift { QtVCardJIDField::QtVCardJIDField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("JID"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("JID"), false, false), jidLabel(nullptr), jidLineEdit(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardJIDField::~QtVCardJIDField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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); + 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")); + 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); + 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(); + jidLabel->hide(); + getTagComboBox()->hide(); - childWidgets << jidLabel << jidLineEdit; + childWidgets << jidLabel << jidLineEdit; } bool QtVCardJIDField::isEmpty() const { - return jidLineEdit->text().isEmpty(); + return jidLineEdit->text().isEmpty(); } void QtVCardJIDField::setJID(const JID& jid) { - std::string jidStr = jid.toBare().toString(); - jidLineEdit->setText(P2QSTRING(jidStr)); + std::string jidStr = jid.toBare().toString(); + jidLineEdit->setText(P2QSTRING(jidStr)); } JID QtVCardJIDField::getJID() const { - return JID(Q2PSTRING(jidLineEdit->text())); + return JID(Q2PSTRING(jidLineEdit->text())); } void QtVCardJIDField::handleEditibleChanged(bool isEditable) { - if (isEditable) { - if (jidLineEdit) jidLineEdit->show(); - if (jidLabel) jidLabel->hide(); - } else { - if (jidLineEdit) jidLineEdit->hide(); - if (jidLabel) { - jidLabel->setText(QString("<a href=\"xmpp:%1\">%1</a>").arg(QtUtilities::htmlEscape(jidLineEdit->text()))); - jidLabel->show(); - } - } + 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 index 016bcf8..f2df9f9 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardJIDField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardJIDField.h @@ -4,39 +4,45 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/VCard.h> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> namespace Swift { class QtVCardJIDField : public QtVCardGeneralField { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("JID", UNLIMITED_INSTANCES, QtVCardJIDField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("JID"), UNLIMITED_INSTANCES, QtVCardJIDField) - QtVCardJIDField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardJIDField(); + QtVCardJIDField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardJIDField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setJID(const JID& jid); - JID getJID() const; + void setJID(const JID& jid); + JID getJID() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QLabel* jidLabel; - QtResizableLineEdit* jidLineEdit; + private: + QLabel* jidLabel; + QtResizableLineEdit* jidLineEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp index 9ecc8e0..5162c73 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.cpp @@ -1,135 +1,151 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardOrganizationField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h> + +#include <boost/algorithm/string.hpp> #include <QGridLayout> #include <QHBoxLayout> #include <QHeaderView> -#include <boost/algorithm/string.hpp> #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtVCardOrganizationField::QtVCardOrganizationField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Organisation"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Organization"), false, false), organizationLabel(nullptr), organizationLineEdit(nullptr), unitsTreeWidget(nullptr), itemDelegate(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardOrganizationField::~QtVCardOrganizationField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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); + 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); + getGridLayout()->addLayout(organizationLayout, getGridLayout()->rowCount()-1, 2, 1, 2, Qt::AlignVCenter); - itemDelegate = new QtRemovableItemDelegate(style()); + itemDelegate = new QtRemovableItemDelegate(style()); - unitsTreeWidget = new QTreeWidget(this); - unitsTreeWidget->setColumnCount(2); - unitsTreeWidget->header()->setStretchLastSection(false); - int closeIconWidth = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0); - unitsTreeWidget->header()->resizeSection(1, closeIconWidth); + 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); + unitsTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); #else - unitsTreeWidget->header()->setResizeMode(0, QHeaderView::Stretch); + 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); + 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); + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + item->setFlags(item->flags() | Qt::ItemIsEditable); + unitsTreeWidget->addTopLevelItem(item); - getTagComboBox()->hide(); - organizationLabel->hide(); - childWidgets << organizationLabel << organizationLineEdit << unitsTreeWidget; + getTagComboBox()->hide(); + organizationLabel->hide(); + childWidgets << organizationLabel << organizationLineEdit << unitsTreeWidget; } bool QtVCardOrganizationField::isEmpty() const { - return organizationLineEdit->text().isEmpty() && unitsTreeWidget->model()->rowCount() != 0; + 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); + organizationLineEdit->setText(P2QSTRING(organization.name)); + unitsTreeWidget->clear(); + for (const auto& 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; + 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) { - if (organizationLineEdit) { - 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); - } - } - if (unitsTreeWidget) unitsTreeWidget->setVisible(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) { - 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); - } - getTagComboBox()->hide(); + guaranteeEmptyRow(); +} + +void QtVCardOrganizationField::handleRowsRemoved(const QModelIndex&, int, int) { + guaranteeEmptyRow(); +} + +void QtVCardOrganizationField::guaranteeEmptyRow() { + bool hasEmptyRow = false; + QList<QTreeWidgetItem*> rows = unitsTreeWidget->findItems("", Qt::MatchFixedString); + for (auto 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 index 917e22a..23e89c0 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardOrganizationField.h @@ -4,47 +4,57 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#pragma once +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swiften/Elements/VCard.h> +#pragma once #include <QTreeWidget> -#include "QtRemovableItemDelegate.h" -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#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 + Q_OBJECT + + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Organization"), UNLIMITED_INSTANCES, QtVCardOrganizationField) - public: - GENERIC_QT_VCARD_FIELD_INFO("Organization", UNLIMITED_INSTANCES, QtVCardOrganizationField) + QtVCardOrganizationField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardOrganizationField(); - QtVCardOrganizationField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardOrganizationField(); + virtual bool isEmpty() const; - virtual bool isEmpty() const; + void setOrganization(const VCard::Organization& organization); + VCard::Organization getOrganization() const; - void setOrganization(const VCard::Organization& organization); - VCard::Organization getOrganization() const; + protected: + virtual void setupContentWidgets(); - protected: - virtual void setupContentWidgets(); + public slots: + void handleEditibleChanged(bool isEditable); - public slots: - void handleEditibleChanged(bool isEditable); + private slots: + void handleItemChanged(QTreeWidgetItem*, int); + void handleRowsRemoved(const QModelIndex&, int, int); - private slots: - void handleItemChanged(QTreeWidgetItem*, int); + private: + void guaranteeEmptyRow(); - private: - QLabel* organizationLabel; - QtResizableLineEdit* organizationLineEdit; - QTreeWidget* unitsTreeWidget; - QtRemovableItemDelegate* itemDelegate; + private: + QLabel* organizationLabel; + QtResizableLineEdit* organizationLineEdit; + QTreeWidget* unitsTreeWidget; + QtRemovableItemDelegate* itemDelegate; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp index 3ddc86b..eef6728 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.cpp @@ -1,176 +1,152 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardPhotoAndNameFields.h" -#include "ui_QtVCardPhotoAndNameFields.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h> #include <QMenu> +#include <Swift/QtUI/QtVCardWidget/ui_QtVCardPhotoAndNameFields.h> + 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(); + 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->labelFN->hide(); + ui->labelNICKNAME->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")); + 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 - addFieldMenu = new QMenu("Name", this); - - actionSignalMapper = new QSignalMapper(this); - - connect(actionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(showField(const QString &))); - prepareAddFieldMenu(); + setEditable(false); } QtVCardPhotoAndNameFields::~QtVCardPhotoAndNameFields() { - delete ui; - delete actionSignalMapper; + delete ui; } bool QtVCardPhotoAndNameFields::isEditable() const { - return editable; + return editable; } void QtVCardPhotoAndNameFields::setEditable(bool editable) { - this->editable = 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->avatarWidget->setEditable(editable); + ui->lineEditFN->setVisible(editable); + ui->labelFN->setVisible(!editable); - ui->lineEditNICKNAME->setVisible(editable ? true : !ui->lineEditNICKNAME->text().isEmpty()); - ui->lineEditNICKNAME->setEditable(editable); - ui->lineEditNICKNAME->setStyleSheet(editable ? "" : "QLineEdit {border: none; background-color: transparent;}"); + ui->lineEditNICKNAME->setVisible(editable); + ui->labelNICKNAME->setVisible(!editable); - // 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); - ui->labelFULLNAME->setText( ui->lineEditPREFIX->text() + " " + ui->lineEditGIVEN->text() + " " + - ui->lineEditMIDDLE->text() + " " + ui->lineEditFAMILY->text() + " " + ui->lineEditSUFFIX->text()); - - prepareAddFieldMenu(); -} + // 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); -QMenu* QtVCardPhotoAndNameFields::getAddFieldMenu() const { - return addFieldMenu; + QStringList fullname; + fullname << ui->lineEditPREFIX->text() << ui->lineEditGIVEN->text() << ui->lineEditMIDDLE->text(); + fullname << ui->lineEditFAMILY->text() << ui->lineEditSUFFIX->text(); + for (auto& i : fullname) { + i = i.trimmed(); + } + ui->labelFULLNAME->setText((fullname.filter(QRegExp(".+"))).join(" ")); } void QtVCardPhotoAndNameFields::setAvatar(const ByteArray &data, const std::string &type) { - ui->avatarWidget->setAvatar(data, type); + ui->avatarWidget->setAvatar(data, type); } ByteArray QtVCardPhotoAndNameFields::getAvatarData() const { - return ui->avatarWidget->getAvatarData(); + return ui->avatarWidget->getAvatarData(); } std::string QtVCardPhotoAndNameFields::getAvatarType() const { - return ui->avatarWidget->getAvatarType(); + return ui->avatarWidget->getAvatarType(); } -void QtVCardPhotoAndNameFields::setFormattedName(const QString formattedName) { - ui->lineEditFN->setText(formattedName); +void QtVCardPhotoAndNameFields::setFormattedName(const QString& formattedName) { + ui->lineEditFN->setText(formattedName); + ui->labelFN->setText(formattedName); } QString QtVCardPhotoAndNameFields::getFormattedName() const { - return ui->lineEditFN->text(); + return ui->lineEditFN->text(); } -void QtVCardPhotoAndNameFields::setNickname(const QString nickname) { - ui->lineEditNICKNAME->setText(nickname); +void QtVCardPhotoAndNameFields::setNickname(const QString& nickname) { + ui->lineEditNICKNAME->setText(nickname); + ui->labelNICKNAME->setText(nickname); } QString QtVCardPhotoAndNameFields::getNickname() const { - return ui->lineEditNICKNAME->text(); + return ui->lineEditNICKNAME->text(); } -void QtVCardPhotoAndNameFields::setPrefix(const QString prefix) { - ui->lineEditPREFIX->setText(prefix); +void QtVCardPhotoAndNameFields::setPrefix(const QString& prefix) { + ui->lineEditPREFIX->setText(prefix); } QString QtVCardPhotoAndNameFields::getPrefix() const { - return ui->lineEditPREFIX->text(); + return ui->lineEditPREFIX->text(); } -void QtVCardPhotoAndNameFields::setGivenName(const QString givenName) { - ui->lineEditGIVEN->setText(givenName); +void QtVCardPhotoAndNameFields::setGivenName(const QString& givenName) { + ui->lineEditGIVEN->setText(givenName); } QString QtVCardPhotoAndNameFields::getGivenName() const { - return ui->lineEditGIVEN->text(); + return ui->lineEditGIVEN->text(); } -void QtVCardPhotoAndNameFields::setMiddleName(const QString middleName) { - ui->lineEditMIDDLE->setText(middleName); +void QtVCardPhotoAndNameFields::setMiddleName(const QString& middleName) { + ui->lineEditMIDDLE->setText(middleName); } QString QtVCardPhotoAndNameFields::getMiddleName() const { - return ui->lineEditMIDDLE->text(); + return ui->lineEditMIDDLE->text(); } -void QtVCardPhotoAndNameFields::setFamilyName(const QString familyName) { - ui->lineEditFAMILY->setText(familyName); +void QtVCardPhotoAndNameFields::setFamilyName(const QString& familyName) { + ui->lineEditFAMILY->setText(familyName); } QString QtVCardPhotoAndNameFields::getFamilyName() const { - return ui->lineEditFAMILY->text(); + return ui->lineEditFAMILY->text(); } -void QtVCardPhotoAndNameFields::setSuffix(const QString suffix) { - ui->lineEditSUFFIX->setText(suffix); +void QtVCardPhotoAndNameFields::setSuffix(const QString& suffix) { + ui->lineEditSUFFIX->setText(suffix); } QString QtVCardPhotoAndNameFields::getSuffix() const { - return ui->lineEditSUFFIX->text(); -} - -void QtVCardPhotoAndNameFields::prepareAddFieldMenu() { - foreach(QAction* action, addFieldMenu->actions()) { - actionSignalMapper->removeMappings(action); - } - - addFieldMenu->clear(); - foreach(QObject* obj, children()) { - QLineEdit* lineEdit = 0; - if (!(lineEdit = dynamic_cast<QLineEdit*>(obj))) continue; - if (lineEdit->isHidden()) { -#if QT_VERSION >= 0x040700 - QAction* action = addFieldMenu->addAction(QString("Add ") + lineEdit->placeholderText(), actionSignalMapper, SLOT(map())); -#else - QAction* action = addFieldMenu->addAction(QString("Add ") + lineEdit->toolTip(), actionSignalMapper, SLOT(map())); -#endif - actionSignalMapper->setMapping(action, lineEdit->objectName()); - } - } -} - -void QtVCardPhotoAndNameFields::showField(const QString& widgetName) { - QLineEdit* lineEditToShow = findChild<QLineEdit*>(widgetName); - if (lineEditToShow) lineEditToShow->show(); - - prepareAddFieldMenu(); + return ui->lineEditSUFFIX->text(); } } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h index f279701..ca6a1f3 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h @@ -4,70 +4,65 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QMenu> -#include <QSignalMapper> #include <QWidget> + #include <Swiften/Base/ByteArray.h> namespace Ui { - class QtVCardPhotoAndNameFields; + 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); - - QMenu* getAddFieldMenu() const; - - void setAvatar(const ByteArray& data, const std::string& type); - ByteArray getAvatarData() const; - std::string getAvatarType() const; + class QtVCardPhotoAndNameFields : public QWidget { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) - void setFormattedName(const QString formattedName); - QString getFormattedName() const; + public: + explicit QtVCardPhotoAndNameFields(QWidget* parent = nullptr); + ~QtVCardPhotoAndNameFields(); - void setNickname(const QString nickname); - QString getNickname() const; + bool isEditable() const; + void setEditable(bool); - void setPrefix(const QString prefix); - QString getPrefix() const; + void setAvatar(const ByteArray& data, const std::string& type); + ByteArray getAvatarData() const; + std::string getAvatarType() const; - void setGivenName(const QString givenName); - QString getGivenName() const; + void setFormattedName(const QString& formattedName); + QString getFormattedName() const; - void setMiddleName(const QString middleName); - QString getMiddleName() const; + void setNickname(const QString& nickname); + QString getNickname() const; - void setFamilyName(const QString familyName); - QString getFamilyName() const; + void setPrefix(const QString& prefix); + QString getPrefix() const; - void setSuffix(const QString suffix); - QString getSuffix() const; + void setGivenName(const QString& givenName); + QString getGivenName() const; - public slots: - void showField(const QString& widgetName); + void setMiddleName(const QString& middleName); + QString getMiddleName() const; - private: - void prepareAddFieldMenu(); + void setFamilyName(const QString& familyName); + QString getFamilyName() const; - private: - Ui::QtVCardPhotoAndNameFields* ui; - bool editable; + void setSuffix(const QString& suffix); + QString getSuffix() const; - QMenu* addFieldMenu; - QSignalMapper* actionSignalMapper; - }; + private: + Ui::QtVCardPhotoAndNameFields* ui; + bool editable; + }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui index 04da2bc..97acba0 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui +++ b/Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.ui @@ -9,8 +9,8 @@ <rect> <x>0</x> <y>0</y> - <width>522</width> - <height>81</height> + <width>207</width> + <height>168</height> </rect> </property> <property name="sizePolicy"> @@ -26,15 +26,24 @@ <property name="sizeConstraint"> <enum>QLayout::SetMinimumSize</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> <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"> @@ -52,35 +61,66 @@ </widget> </item> <item row="0" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QHBoxLayout" name="horizontalLayoutFN"> + <property name="spacing"> + <number>0</number> + </property> <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> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="leftMargin"> + <number>0</number> </property> - <property name="toolTip"> - <string>Formatted Name</string> - </property> - </widget> + <item> + <widget class="Swift::QtElidingLabel" name="labelFN"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>18</pointsize> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <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> + </layout> </item> <item> <spacer name="horizontalSpacer_2"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>0</width> <height>20</height> </size> </property> @@ -89,25 +129,57 @@ </layout> </item> <item row="1" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout_3"> + <layout class="QHBoxLayout" name="horizontalLayoutNICKNAME" stretch="0,0"> + <property name="spacing"> + <number>0</number> + </property> <item> - <widget class="Swift::QtResizableLineEdit" name="lineEditNICKNAME"> - <property name="toolTip"> - <string>Nickname</string> - </property> - <property name="frame"> - <bool>true</bool> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>0</number> </property> - </widget> + <item> + <widget class="Swift::QtElidingLabel" name="labelNICKNAME"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="Swift::QtResizableLineEdit" name="lineEditNICKNAME"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Nickname</string> + </property> + <property name="frame"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> </item> <item> <spacer name="horizontalSpacer_3"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> + <width>0</width> <height>20</height> </size> </property> @@ -116,111 +188,137 @@ </layout> </item> <item row="2" column="1"> - <layout class="QHBoxLayout" name="horizontalLayout"> + <layout class="QHBoxLayout" name="horizontalLayoutFULLNAME_1"> <property name="spacing"> - <number>2</number> + <number>0</number> </property> <property name="sizeConstraint"> - <enum>QLayout::SetMinimumSize</enum> + <enum>QLayout::SetDefaultConstraint</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> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <property name="leftMargin"> + <number>0</number> </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> + <widget class="Swift::QtElidingLabel" name="labelFULLNAME"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string/> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutFULLNAME_2"> + <property name="spacing"> + <number>5</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <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> + </layout> + </item> + </layout> </item> <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> <property name="sizeHint" stdset="0"> <size> <width>40</width> @@ -245,7 +343,21 @@ <header>Swift/QtUI/QtAvatarWidget.h</header> <container>1</container> </customwidget> + <customwidget> + <class>Swift::QtElidingLabel</class> + <extends>QLabel</extends> + <header>Swift/QtUI/QtElidingLabel.h</header> + </customwidget> </customwidgets> + <tabstops> + <tabstop>lineEditFN</tabstop> + <tabstop>lineEditNICKNAME</tabstop> + <tabstop>lineEditPREFIX</tabstop> + <tabstop>lineEditGIVEN</tabstop> + <tabstop>lineEditMIDDLE</tabstop> + <tabstop>lineEditFAMILY</tabstop> + <tabstop>lineEditSUFFIX</tabstop> + </tabstops> <resources/> <connections/> </ui> diff --git a/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp index 8af4e64..e9e29ad 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.cpp @@ -1,50 +1,57 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardRoleField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardRoleField.h> -#include <QGridLayout> #include <boost/algorithm/string.hpp> +#include <QGridLayout> + #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtVCardRoleField::QtVCardRoleField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Role"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Role"), false, false), roleLineEdit(nullptr) { + 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; + 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(); + return roleLineEdit->text().isEmpty(); } void QtVCardRoleField::setRole(const std::string& role) { - roleLineEdit->setText(P2QSTRING(role)); + roleLineEdit->setText(P2QSTRING(role)); } std::string QtVCardRoleField::getRole() const { - return Q2PSTRING(roleLineEdit->text()); + return Q2PSTRING(roleLineEdit->text()); } void QtVCardRoleField::handleEditibleChanged(bool isEditable) { - if (roleLineEdit) { - roleLineEdit->setEditable(isEditable); - roleLineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); - } + 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 index 3c819ed..a507fef 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardRoleField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardRoleField.h @@ -4,38 +4,44 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/VCard.h> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> namespace Swift { class QtVCardRoleField : public QtVCardGeneralField { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("Role", UNLIMITED_INSTANCES, QtVCardRoleField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Role"), UNLIMITED_INSTANCES, QtVCardRoleField) - QtVCardRoleField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardRoleField(); + QtVCardRoleField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardRoleField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setRole(const std::string& role); - std::string getRole() const; + void setRole(const std::string& role); + std::string getRole() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QtResizableLineEdit* roleLineEdit; + private: + QtResizableLineEdit* roleLineEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp index ee93c01..9421016 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.cpp @@ -1,10 +1,16 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardTelephoneField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h> #include <QGridLayout> @@ -13,88 +19,88 @@ namespace Swift { QtVCardTelephoneField::QtVCardTelephoneField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Telephone")) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Telephone")), telephoneLineEdit(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardTelephoneField::~QtVCardTelephoneField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + disconnect(this, SLOT(handleEditibleChanged(bool))); } void QtVCardTelephoneField::setupContentWidgets() { - telephoneLineEdit = new QtResizableLineEdit(this); + telephoneLineEdit = new QtResizableLineEdit(this); #if QT_VERSION >= 0x040700 - telephoneLineEdit->setPlaceholderText(tr("0118 999 881 999 119 7253")); + 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; + 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(); + 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); + 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; + 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) { - if (telephoneLineEdit) { - telephoneLineEdit->setEditable(isEditable); - telephoneLineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); - } + 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 index b433e3c..13bb232 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardTelephoneField.h @@ -4,39 +4,45 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/VCard.h> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" -#include "QtVCardHomeWork.h" +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardHomeWork.h> namespace Swift { class QtVCardTelephoneField : public QtVCardGeneralField, public QtVCardHomeWork { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("Telephone", UNLIMITED_INSTANCES, QtVCardTelephoneField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Telephone"), UNLIMITED_INSTANCES, QtVCardTelephoneField) - QtVCardTelephoneField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardTelephoneField(); + QtVCardTelephoneField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardTelephoneField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setTelephone(const VCard::Telephone& telephone); - VCard::Telephone getTelephone() const; + void setTelephone(const VCard::Telephone& telephone); + VCard::Telephone getTelephone() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QtResizableLineEdit* telephoneLineEdit; + private: + QtResizableLineEdit* telephoneLineEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp index aac4e31..14c3813 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.cpp @@ -1,51 +1,58 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardTitleField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardTitleField.h> -#include <QGridLayout> #include <boost/algorithm/string.hpp> +#include <QGridLayout> + #include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtVCardTitleField::QtVCardTitleField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Title"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("Title"), false, false), titleLineEdit(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardTitleField::~QtVCardTitleField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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; + 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(); + return titleLineEdit->text().isEmpty(); } void QtVCardTitleField::setTitle(const std::string& title) { - titleLineEdit->setText(P2QSTRING(title)); + titleLineEdit->setText(P2QSTRING(title)); } std::string QtVCardTitleField::getTitle() const { - return Q2PSTRING(titleLineEdit->text()); + return Q2PSTRING(titleLineEdit->text()); } void QtVCardTitleField::handleEditibleChanged(bool isEditable) { - if (titleLineEdit) { - titleLineEdit->setEditable(isEditable); - titleLineEdit->setStyleSheet(isEditable ? "" : "QLineEdit { border: none; background: transparent; }"); - } + 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 index 28dc603..0ea3772 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardTitleField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardTitleField.h @@ -4,38 +4,44 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/VCard.h> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> namespace Swift { class QtVCardTitleField : public QtVCardGeneralField { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("Title", UNLIMITED_INSTANCES, QtVCardTitleField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("Title"), UNLIMITED_INSTANCES, QtVCardTitleField) - QtVCardTitleField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardTitleField(); + QtVCardTitleField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardTitleField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setTitle(const std::string& title); - std::string getTitle() const; + void setTitle(const std::string& title); + std::string getTitle() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QtResizableLineEdit* titleLineEdit; + private: + QtResizableLineEdit* titleLineEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp b/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp index 35cc4ce..153b897 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardURLField.cpp @@ -1,70 +1,77 @@ /* - * Copyright (c) 2012 Tobias Markmann + * Copyright (c) 2012-2014 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardURLField.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardURLField.h> + +#include <boost/algorithm/string.hpp> #include <QGridLayout> #include <QHBoxLayout> #include <QTextDocument> -#include <boost/algorithm/string.hpp> #include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtUtilities.h> - namespace Swift { QtVCardURLField::QtVCardURLField(QWidget* parent, QGridLayout *layout, bool editable) : - QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("URL"), false, false) { - connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); + QtVCardGeneralField(parent, layout, editable, layout->rowCount(), tr("URL"), false, false), urlLabel(nullptr), urlLineEdit(nullptr) { + connect(this, SIGNAL(editableChanged(bool)), SLOT(handleEditibleChanged(bool))); } QtVCardURLField::~QtVCardURLField() { - disconnect(this, SLOT(handleEditibleChanged(bool))); + 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; + 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(); + return urlLineEdit->text().isEmpty(); } void QtVCardURLField::setURL(const std::string& url) { - urlLineEdit->setText(P2QSTRING(url)); + urlLineEdit->setText(P2QSTRING(url)); } std::string QtVCardURLField::getURL() const { - return Q2PSTRING(urlLineEdit->text()); + return Q2PSTRING(urlLineEdit->text()); } void QtVCardURLField::handleEditibleChanged(bool isEditable) { - if (isEditable) { - if (urlLineEdit) urlLineEdit->show(); - if (urlLabel) urlLabel->hide(); - } else { - if (urlLineEdit) urlLineEdit->hide(); - if (urlLabel) { - urlLabel->setText(QString("<a href=\"%1\">%1</a>").arg(QtUtilities::htmlEscape(urlLineEdit->text()))); - urlLabel->show(); - } - } + 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 index 2c011d8..3830a7b 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardURLField.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardURLField.h @@ -4,39 +4,45 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/VCard.h> -#include "QtResizableLineEdit.h" -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" +#include <Swift/QtUI/QtVCardWidget/QtResizableLineEdit.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> namespace Swift { class QtVCardURLField : public QtVCardGeneralField { - Q_OBJECT + Q_OBJECT - public: - GENERIC_QT_VCARD_FIELD_INFO("URL", UNLIMITED_INSTANCES, QtVCardURLField) + public: + GENERIC_QT_VCARD_FIELD_INFO(tr("URL"), UNLIMITED_INSTANCES, QtVCardURLField) - QtVCardURLField(QWidget* parent = 0, QGridLayout* layout = 0, bool editable = false); - virtual ~QtVCardURLField(); + QtVCardURLField(QWidget* parent = nullptr, QGridLayout* layout = nullptr, bool editable = false); + virtual ~QtVCardURLField(); - virtual bool isEmpty() const; + virtual bool isEmpty() const; - void setURL(const std::string& url); - std::string getURL() const; + void setURL(const std::string& url); + std::string getURL() const; - protected: - virtual void setupContentWidgets(); + protected: + virtual void setupContentWidgets(); - public slots: - void handleEditibleChanged(bool isEditable); + public slots: + void handleEditibleChanged(bool isEditable); - private: - QLabel* urlLabel; - QtResizableLineEdit* urlLineEdit; + private: + QLabel* urlLabel; + QtResizableLineEdit* urlLineEdit; }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp index 1c80fa4..1cd5505 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp @@ -4,365 +4,423 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtVCardWidget.h" -#include "ui_QtVCardWidget.h" +/* + * Copyright (c) 2014-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/QtVCardWidget/QtVCardWidget.h> -#include <QDebug> #include <QLineEdit> #include <QMenu> -#include "QtVCardAddressField.h" -#include "QtVCardAddressLabelField.h" -#include "QtVCardBirthdayField.h" -#include "QtVCardDescriptionField.h" -#include "QtVCardGeneralField.h" -#include "QtVCardInternetEMailField.h" -#include "QtVCardJIDField.h" -#include "QtVCardOrganizationField.h" -#include "QtVCardRoleField.h" -#include "QtVCardTelephoneField.h" -#include "QtVCardTitleField.h" -#include "QtVCardURLField.h" - #include <Swift/QtUI/QtSwiftUtil.h> - -#include <Swiften/Base/Log.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/QtVCardWidget/ui_QtVCardWidget.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); - - menu->addMenu(ui->photoAndName->getAddFieldMenu()); - ui->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); + 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, std::make_shared<QtVCardInternetEMailField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardTelephoneField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardAddressField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardAddressLabelField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardBirthdayField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardJIDField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardDescriptionField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardRoleField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardTitleField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardOrganizationField::FieldInfo>()); + addFieldType(menu, std::make_shared<QtVCardURLField::FieldInfo>()); + + setEditable(false); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); } QtVCardWidget::~QtVCardWidget() { - delete ui; + delete ui; +} + +QSize QtVCardWidget::sizeHint() const { + QSize newSizeHint = ui->photoAndName->sizeHint(); + + // use mininmal size that does not require scrolling + QSize fieldsWidgetSize = ui->scrollArea->widget()->minimumSize(); + fieldsWidgetSize.setWidth(ui->scrollArea->widget()->sizeHint().width()); + + newSizeHint += QSize(0, ui->line->height()); + + newSizeHint = QSize(std::max(newSizeHint.width(), fieldsWidgetSize.width()), newSizeHint.height() + fieldsWidgetSize.height()); + + // add layout margin + newSizeHint += QSize(layout()->contentsMargins().left() + layout()->contentsMargins().right(), layout()->contentsMargins().top() + layout()->contentsMargins().bottom()); + + // the spaceing before and after the line between the profile header and its fields + newSizeHint += QSize(0, layout()->spacing() * 2); + + return newSizeHint; } bool QtVCardWidget::isEditable() const { - return editable; + return editable; } void QtVCardWidget::setEditable(bool editable) { - this->editable = editable; - - ui->photoAndName->setProperty("editable", QVariant(editable)); + this->editable = editable; - foreach(QtVCardGeneralField* field, fields) { - field->setEditable(editable); - } + ui->photoAndName->setProperty("editable", QVariant(editable)); - if (editable) { - ui->toolButton->show(); - //if ((findChild<QtVCardBirthdayField*>() == 0)) { - //} - } else { - ui->toolButton->hide(); - } + for (auto field : fields) { + field->setEditable(editable); + } + toolButton->setVisible(editable); - editableChanged(editable); + editableChanged(editable); } void QtVCardWidget::setVCard(VCard::ref vcard) { - SWIFT_LOG(debug) << std::endl; - 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); - } - - setEditable(editable); + clearFields(); + this->vcard = std::make_shared<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()); + + for (const auto& address : vcard->getEMailAddresses()) { + if (address.isInternet) { + QtVCardInternetEMailField* internetEmailField = new QtVCardInternetEMailField(this, ui->cardFields); + internetEmailField->initialize(); + internetEmailField->setInternetEMailAddress(address); + appendField(internetEmailField); + } + } + + for (const auto& telephone : vcard->getTelephones()) { + QtVCardTelephoneField* telField = new QtVCardTelephoneField(this, ui->cardFields); + telField->initialize(); + telField->setTelephone(telephone); + appendField(telField); + } + + for (const auto& address : vcard->getAddresses()) { + QtVCardAddressField* addressField = new QtVCardAddressField(this, ui->cardFields); + addressField->initialize(); + addressField->setAddress(address); + appendField(addressField); + } + + for (const auto& 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); + } + + for (const auto& 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); + } + + for (const auto& org : vcard->getOrganizations()) { + QtVCardOrganizationField* orgField = new QtVCardOrganizationField(this, ui->cardFields); + orgField->initialize(); + orgField->setOrganization(org); + appendField(orgField); + } + + for (const auto& role : vcard->getRoles()) { + QtVCardRoleField* roleField = new QtVCardRoleField(this, ui->cardFields); + roleField->initialize(); + roleField->setRole(role); + appendField(roleField); + } + + for (const auto& title : vcard->getTitles()) { + QtVCardTitleField* titleField = new QtVCardTitleField(this, ui->cardFields); + titleField->initialize(); + titleField->setTitle(title); + appendField(titleField); + } + + for (const auto& url : vcard->getURLs()) { + QtVCardURLField* urlField = new QtVCardURLField(this, ui->cardFields); + urlField->initialize(); + urlField->setURL(url); + appendField(urlField); + } + + relayoutToolButton(); + setEditable(editable); } VCard::ref QtVCardWidget::getVCard() { - SWIFT_LOG(debug) << std::endl; - 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; + 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 = nullptr; + QtVCardDescriptionField* descriptionField = nullptr; + + for (auto 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); - } + QAction* action = nullptr; + if ((action = dynamic_cast<QAction*>(sender()))) { + std::shared_ptr<QtVCardFieldInfo> fieldInfo = actionFieldInfo[action]; + QWidget* newField = fieldInfo->createFieldInstance(this, ui->cardFields, true); + QtVCardGeneralField* newGeneralField = dynamic_cast<QtVCardGeneralField*>(newField); + if (newGeneralField) { + newGeneralField->initialize(); + appendField(newGeneralField); + relayoutToolButton(); + } + } } void QtVCardWidget::removeField(QtVCardGeneralField *field) { - fields.remove(field); - delete field; + int sameFields = 0; + QtVCardGeneralField* fieldToChange = nullptr; + for (auto 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) { - QAction* action = new QAction(tr("Add ") + fieldType->getMenuName(), this); - actionFieldInfo[action] = fieldType; - connect(action, SIGNAL(triggered()), this, SLOT(addField())); - menu->addAction(action); +void QtVCardWidget::addFieldType(QMenu* menu, std::shared_ptr<QtVCardFieldInfo> fieldType) { + if (!fieldType->getMenuName().isEmpty()) { + QAction* action = new QAction(tr("Add %1").arg(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; +int QtVCardWidget::fieldTypeInstances(std::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()); - } - delete child->widget(); - delete child; - } - } +static void layoutDeleteChildren(QLayout *layout) { + while(layout->count() > 0) { + QLayoutItem* child; + if ((child = layout->takeAt(0)) != nullptr) { + 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(); + for (auto field : fields) { + delete field; + } + fields.clear(); - assert(ui->cardFields->count() >= 0); - layoutDeleteChildren(ui->cardFields); + 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); - } + std::vector<QtVCardGeneralField*> items_to_remove; + for (auto field : fields) { + if (field->property("empty").isValid() && field->property("empty").toBool()) { + ui->cardFields->removeWidget(field); + items_to_remove.push_back(field); + delete field; + } + } + + for (auto field : items_to_remove) { + fields.remove(field); + } } void QtVCardWidget::appendField(QtVCardGeneralField *field) { - connect(field, SIGNAL(deleteField(QtVCardGeneralField*)), SLOT(removeField(QtVCardGeneralField*))); - fields.push_back(field); + connect(field, SIGNAL(deleteField(QtVCardGeneralField*)), SLOT(removeField(QtVCardGeneralField*))); + + QtVCardGeneralField* fieldToChange = nullptr; + for (auto 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 index 07f528d..9aae158 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardWidget.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.h @@ -4,57 +4,71 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once +#include <list> +#include <memory> + +#include <QToolButton> #include <QWidget> + #include <Swiften/Elements/VCard.h> -#include <boost/smart_ptr/make_shared.hpp> -#include "QtVCardFieldInfo.h" -#include "QtVCardGeneralField.h" -#include "QtVCardPhotoAndNameFields.h" +#include <Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardGeneralField.h> +#include <Swift/QtUI/QtVCardWidget/QtVCardPhotoAndNameFields.h> namespace Ui { - class QtVCardWidget; + 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); - - private: - VCard::ref vcard; - Ui::QtVCardWidget* ui; - bool editable; - QMenu* menu; - std::list<QtVCardGeneralField*> fields; - std::map<QAction*, boost::shared_ptr<QtVCardFieldInfo> > actionFieldInfo; - }; + class QtVCardWidget : public QWidget { + Q_OBJECT + Q_PROPERTY(bool editable READ isEditable WRITE setEditable) + + public : + explicit QtVCardWidget(QWidget* parent = nullptr); + ~QtVCardWidget(); + + bool isEditable() const; + void setEditable(bool); + + void setVCard(VCard::ref vcard); + VCard::ref getVCard(); + + virtual QSize sizeHint() const; + + signals: + void editableChanged(bool editable); + + private slots: + void addField(); + void removeField(QtVCardGeneralField* field); + + private: + void addFieldType(QMenu*, std::shared_ptr<QtVCardFieldInfo>); + int fieldTypeInstances(std::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*, std::shared_ptr<QtVCardFieldInfo> > actionFieldInfo; + }; } diff --git a/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui b/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui index eae1006..3a2997b 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.ui @@ -6,120 +6,121 @@ <rect> <x>0</x> <y>0</y> - <width>535</width> - <height>126</height> + <width>239</width> + <height>102</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> - <layout class="QGridLayout" name="gridLayout" rowstretch="1,0" columnstretch="1,0"> - <property name="margin"> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,1"> + <property name="spacing"> + <number>2</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 row="0" column="0" colspan="2"> - <layout class="QVBoxLayout" name="card" stretch="0,0,1"> - <property name="spacing"> - <number>2</number> + <item> + <widget class="Swift::QtVCardPhotoAndNameFields" name="photoAndName" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</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> + </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>229</width> + <height>77</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinAndMaxSize</enum> </property> - <property name="frameShadow"> - <enum>QFrame::Plain</enum> + <property name="leftMargin"> + <number>0</number> </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> + <property name="topMargin"> + <number>0</number> </property> - <property name="widgetResizable"> - <bool>true</bool> + <property name="rightMargin"> + <number>0</number> </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + <property name="bottomMargin"> + <number>0</number> </property> - <widget class="QWidget" name="scrollAreaWidgetContents"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>523</width> - <height>76</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> + <item> + <layout class="QGridLayout" name="cardFields"/> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> </property> - <property name="margin"> - <number>0</number> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> </property> - <item> - <layout class="QGridLayout" name="cardFields"> - </layout> - </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> - <item row="1" column="1"> - <widget class="QToolButton" name="toolButton"> - <property name="text"> - <string>Add Field</string> - </property> - <property name="popupMode"> - <enum>QToolButton::InstantPopup</enum> - </property> - <property name="autoRaise"> - <bool>false</bool> - </property> - <property name="arrowType"> - <enum>Qt::NoArrow</enum> - </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </widget> </item> </layout> diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp index bc57de4..75a23f8 100644 --- a/Swift/QtUI/QtWebKitChatView.cpp +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -1,40 +1,45 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtWebKitChatView.h" +#include <Swift/QtUI/QtWebKitChatView.h> -#include <boost/format.hpp> - -#include <QtDebug> +#include <QApplication> +#include <QDesktopServices> +#include <QDesktopWidget> #include <QEventLoop> #include <QFile> -#include <QDesktopServices> -#include <QVBoxLayout> -#include <QWebFrame> +#include <QFileDevice> +#include <QFileDialog> +#include <QFileInfo> +#include <QInputDialog> #include <QKeyEvent> +#include <QMessageBox> #include <QStackedWidget> #include <QTimer> -#include <QMessageBox> -#include <QApplication> -#include <QInputDialog> -#include <QFileDialog> +#include <QVBoxLayout> +#include <QWebFrame> +#include <QWebSettings> +#include <QtDebug> +#include <Swiften/Base/FileSize.h> #include <Swiften/Base/Log.h> #include <Swiften/StringCodecs/Base64.h> -#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include <Swift/QtUI/QtWebView.h> +#include <Swift/QtUI/MessageSnippet.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/QtWebView.h> #include <Swift/QtUI/SystemMessageSnippet.h> namespace Swift { @@ -46,903 +51,985 @@ const QString QtWebKitChatView::ButtonFileTransferCancel = QString("filetransfer const QString QtWebKitChatView::ButtonFileTransferSetDescription = QString("filetransfer-setdescription"); const QString QtWebKitChatView::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest"); const QString QtWebKitChatView::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest"); +const QString QtWebKitChatView::ButtonFileTransferOpenFile = QString("filetransfer-openfile"); const QString QtWebKitChatView::ButtonMUCInvite = QString("mucinvite"); +const QString QtWebKitChatView::ButtonResendMessage = QString("resend-message"); +const QString QtWebKitChatView::ButtonResendPopup = QString("popup-resend"); + + +namespace { + const double minimalFontScaling = 0.7; +} -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())); +QtWebKitChatView::QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, SettingsProvider* settings, bool disableAutoScroll /*= false*/) : QtChatView(parent), window_(window), eventStream_(eventStream), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll), previousMessageKind_(PreviosuMessageWasNone), previousMessageWasSelf_(false), showEmoticons_(false), insertingLastLine_(false), idCounter_(0), settings_(settings) { + 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); + /* 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_); + mainLayout->addWidget(webView_); #endif #ifdef SWIFT_EXPERIMENTAL_FT - setAcceptDrops(true); + 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(); + 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())); - 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))); + 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))); + connect(jsBridge, SIGNAL(verticalScrollBarPositionChanged(double)), this, SLOT(handleVerticalScrollBarPositionChanged(double))); } QtWebKitChatView::~QtWebKitChatView() { - delete jsBridge; + 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(); - } + 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) { + resetView(); + logCleared(); + resizeFont(fontSizeSteps_); + } } 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(); + webView_->keyPressEvent(event); +} + +void QtWebKitChatView::addMessageBottom(std::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(std::shared_ptr<ChatSnippet> /* snippet */) { + // TODO: Implement this in a sensible manner later. + SWIFT_LOG(error) << "Not yet implemented!"; +} + +void QtWebKitChatView::addToDOM(std::shared_ptr<ChatSnippet> snippet) { + //qDebug() << snippet->getContent(); + rememberScrolledToBottom(); + + QWebElement insertElement = webPage_->mainFrame()->findFirstElement("#insert"); + assert(!insertElement.isNull()); + insertElement.prependOutside(snippet->getContent()); + + //qDebug() << "-----------------"; + //qDebug() << webPage_->mainFrame()->toHtml(); } void QtWebKitChatView::addLastSeenLine() { - /* if the line is added we should break the snippet */ - insertingLastLine_ = true; - if (lineSeparator_.isNull()) { - lineSeparator_ = newInsertPoint_.clone(); - lineSeparator_.setInnerXml(QString("<hr/>")); - newInsertPoint_.prependOutside(lineSeparator_); - } - else { - QWebElement lineSeparatorC = lineSeparator_.clone(); - lineSeparatorC.removeFromDocument(); - } - newInsertPoint_.prependOutside(lineSeparator_); -} - -void QtWebKitChatView::replaceLastMessage(const QString& newMessage) { - assert(viewReady_); - rememberScrolledToBottom(); - assert(!lastElement_.isNull()); - QWebElement replace = lastElement_.findFirst("span.swift_message"); - assert(!replace.isNull()); - QString old = lastElement_.toOuterXml(); - replace.setInnerXml(ChatSnippet::escape(newMessage)); + // Remove a potentially existing unread bar. + QWebElement existingUnreadBar = webPage_->mainFrame()->findFirstElement("div.unread"); + if (!existingUnreadBar.isNull()) { + existingUnreadBar.removeFromDocument(); + } + + QWebElement insertElement = webPage_->mainFrame()->findFirstElement("#insert"); + insertElement.prependOutside(theme_->getUnread()); +} + +void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const ChatWindow::TimestampBehaviour timestampBehaviour) { + rememberScrolledToBottom(); + QWebElement insertElement = webPage_->mainFrame()->findFirstElement("#insert"); + assert(!insertElement.isNull()); + + QWebElement lastMessageElement = insertElement.previousSibling(); + QWebElement messageChild = lastMessageElement.findFirst("span.swift_message"); + assert(!messageChild.isNull()); + messageChild.setInnerXml(ChatSnippet::escape(newMessage)); + if (timestampBehaviour == ChatWindow::UpdateTimestamp) { + QWebElement timeChild = lastMessageElement.findFirst("span.swift_time"); + assert(!timeChild.isNull()); + timeChild.setInnerXml(ChatSnippet::timeToEscapedString(QDateTime::currentDateTime())); + } } void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) { - rememberScrolledToBottom(); - replaceLastMessage(newMessage); - QWebElement replace = lastElement_.findFirst("span.swift_time"); - assert(!replace.isNull()); - replace.setInnerXml(ChatSnippet::escape(note)); + 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(); + return lastElement_.toPlainText(); } void QtWebKitChatView::addToJSEnvironment(const QString& name, QObject* obj) { - webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, 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."; - } + 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"); - } - } + 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); - } + 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); + 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); + 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"); + 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); + if (webPage_) { + 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. */ + 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 (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(); - } + if (isAtBottom_ && !disableAutoScroll_) { + scrollToBottom(); + } } void QtWebKitChatView::handleLinkClicked(const QUrl& url) { - QDesktopServices::openUrl(url); + QDesktopServices::openUrl(url); } void QtWebKitChatView::handleViewLoadFinished(bool ok) { - Q_ASSERT(ok); - viewReady_ = true; + Q_ASSERT(ok); + viewReady_ = true; } void QtWebKitChatView::increaseFontSize(int numSteps) { - //qDebug() << "Increasing"; - fontSizeSteps_ += numSteps; - emit fontResized(fontSizeSteps_); + //qDebug() << "Increasing"; + fontSizeSteps_ += numSteps; + emit fontResized(fontSizeSteps_); } void QtWebKitChatView::decreaseFontSize() { - fontSizeSteps_--; - if (fontSizeSteps_ < 0) { - fontSizeSteps_ = 0; - } - emit fontResized(fontSizeSteps_); + 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); + fontSizeSteps_ = fontSizeSteps; + double size = minimalFontScaling + 0.1 * fontSizeSteps_; + QString sizeString(QString().setNum(size, 'g', 3) + "em"); + + // Set the font size in the <style id="text-resize-style"> element in the theme <head> element. + QWebElement resizableTextStyle = document_.findFirst("style#text-resize-style"); + assert(!resizableTextStyle.isNull()); + resizableTextStyle.setInnerXml(QString("span.swift_resizable { font-size: %1;}").arg(sizeString)); + webView_->setFontSizeIsMinimal(size == minimalFontScaling); } 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); + 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_) { + syncLoop.processEvents(QEventLoop::AllEvents, 50); + } + document_ = webPage_->mainFrame()->documentElement(); + + scrollToBottom(); + + connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); + + // Hooking up to scroll bar update, because Qt does not provide a way to retrieve accurate scroll bar updates from C++ directly. + QWebElement body = document_.findFirst("body"); + assert(!body.isNull()); + body.setAttribute("onscroll", "chatwindow.verticalScrollBarPositionChanged(document.body.scrollTop / (document.body.scrollHeight - window.innerHeight))"); + + // Adjust web view default 96 DPI setting to screen DPI. + // For more information see https://webkit.org/blog/57/css-units/ + webView_->setZoomFactor(QApplication::desktop()->screen()->logicalDpiX() / 96.0); + + body.setStyleProperty("font-size", QString("%1pt").arg(QApplication::font().pointSize())); + +#ifdef Q_OS_WIN + // Fix for Swift-162 + // Changing the font weight for windows in order for the fonts to display better. + // In the future, when we enable dpiawareness on windows, this can be reverted. + body.setStyleProperty("font-weight", QString("500")); +#endif // Q_OS_WIN + } 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(); + 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) + "%"); + rememberScrolledToBottom(); + QWebElement ftElement = findElementWithID(document_, "div", id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!"; + 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) + " %"); + 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); + rememberScrolledToBottom(); + QWebElement ftElement = findElementWithID(document_, "div", id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id); + return; + } + + QString newInnerHTML = ""; + if (state == ChatWindow::Initialisation) { + QWebElement filenameSizeDescriptionElement = ftElement.parent().firstChild(); + QString description = QtUtilities::htmlEscape(descriptions_[id]); + if (!description.isEmpty()) { + filenameSizeDescriptionElement.prependOutside(QString(" \"%1\"").arg(description)); + } + newInnerHTML = tr("Preparing to transfer.") + "<br/>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id); + } + else if (state == ChatWindow::WaitingForAccept) { + newInnerHTML = tr("Waiting for other side to accept the transfer.") + "<br/>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id); + } + else 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." ) + " " + buildChatWindowButton(tr("Open file"), ButtonFileTransferOpenFile, id, filePaths_[id]); + filePaths_.erase(id); + } + 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); + 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")); - } + 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(); - } +void QtWebKitChatView::askDesktopToOpenFile(const QString& filename) { + QFileInfo fileInfo(filename); + if (fileInfo.exists() && fileInfo.isFile()) { + QDesktopServices::openUrl(QUrl::fromLocalFile(filename)); + } } int QtWebKitChatView::getSnippetPositionByDate(const QDate& date) { - QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate)); + QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate)); - return message.geometry().top(); + 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_); + // TODO: Implement or refactor later. + SWIFT_LOG(error) << "Not yet implemented!"; } - 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)); + const ChatWindow::ChatMessage& message, + const std::string& senderName, + bool senderIsSelf, + std::shared_ptr<SecurityLabel> label, + const std::string& avatarPath, + const boost::posix_time::ptime& time) { + return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, message.getHighlightActionSender(), ChatSnippet::getDirection(message)); +} + +QString QtWebKitChatView::getHighlightSpanStart(const std::string& text, const std::string& background) { + QString ecsapeColor; + QString escapeBackground; + if (!text.empty()) { + ecsapeColor = QString("color: %1").arg(QtUtilities::htmlEscape(P2QSTRING(text))); + } + if (!background.empty()) { + escapeBackground = QString("background: %1").arg(QtUtilities::htmlEscape(P2QSTRING(background))); + } + return QString("<span style=\"%1; %2;\">").arg(ecsapeColor).arg(escapeBackground); +} + +QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { + return getHighlightSpanStart(highlight.getFrontColor().get_value_or(""), highlight.getBackColor().get_value_or("")); } QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) { - QString result; - foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) { - boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; - boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart; - boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart; - boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart; - - if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { - QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); - text.replace("\n","<br/>"); - result += text; - continue; - } - if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) { - QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); - result += "<a href='" + uri + "' >" + uri + "</a>"; - continue; - } - if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(part))) { - QString textStyle = showEmoticons_ ? "style='display:none'" : ""; - QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; - result += "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + P2QSTRING(emoticonPart->imagePath) + "'/></span><span class='swift_emoticon_text' " + textStyle + ">" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + "</span>"; - continue; - } - if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) { - //FIXME: Maybe do something here. Anything, really. - continue; - } - - } - return result; + QString result; + for (const auto& part : message.getParts()) { + std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; + std::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart; + std::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart; + std::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart; + + if ((textPart = std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) { + QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text)); + text.replace("\n","<br/>"); + result += text; + continue; + } + if ((uriPart = std::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) { + QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target)); + result += "<a href='" + uri + "' >" + uri + "</a>"; + continue; + } + if ((emoticonPart = std::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 = std::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) { + QString spanStart = getHighlightSpanStart(highlightPart->action.getFrontColor().get_value_or(""), highlightPart->action.getBackColor().get_value_or("")); + result += spanStart + QtUtilities::htmlEscape(P2QSTRING(highlightPart->text)) + "</span>"; + continue; + } + + } + return result; } +std::string QtWebKitChatView::addMessage( + const QString& message, + const std::string& senderName, + bool senderIsSelf, + std::shared_ptr<SecurityLabel> label, + const std::string& avatarPath, + const QString& style, + const boost::posix_time::ptime& time, + const HighlightAction& highlight, + ChatSnippet::Direction direction) { -QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) { - QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor())); - QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground())); - if (color.isEmpty()) { - color = "black"; - } - if (background.isEmpty()) { - background = "yellow"; - } + QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); - return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background); + std::string messageMarkingValue = ""; + + QString htmlString; + if (label) { + messageMarkingValue = label->getDisplayMarking(); + 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(messageMarkingValue))); + } + + QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; + QString styleSpanEnd = style == "" ? "" : "</span>"; + + bool highlightWholeMessage = highlight.getFrontColor() || highlight.getBackColor(); + QString highlightSpanStart = highlightWholeMessage ? getHighlightSpanStart(highlight) : ""; + QString highlightSpanEnd = highlightWholeMessage ? "</span>" : ""; + htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ; + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf, label); + + QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.svg" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); + std::string id = "id" + std::to_string(idCounter_++); + addMessageBottom(std::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction)); + + previousMessageWasSelf_ = senderIsSelf; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasMessage; + previousMessageDisplayMarking_ = messageMarkingValue; + return id; } -std::string QtWebKitChatView::addMessage( - const QString& message, - const std::string& senderName, - bool senderIsSelf, - boost::shared_ptr<SecurityLabel> label, - const std::string& avatarPath, - const QString& style, - const boost::posix_time::ptime& time, - const HighlightAction& highlight, - ChatSnippet::Direction direction) { - - QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); - - QString htmlString; - if (label) { - htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor()))); - htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking()))); - } - - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : ""; - QString highlightSpanEnd = highlight.highlightText() ? "</span>" : ""; - htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ; - - bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf); - - QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); - std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); - addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction)); - - previousMessageWasSelf_ = senderIsSelf; - previousSenderName_ = P2QSTRING(senderName); - previousMessageKind_ = PreviousMessageWasMessage; - return id; -} - -std::string QtWebKitChatView::addAction(const ChatWindow::ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) { - return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message)); -} - -// FIXME: Move this to a different file -std::string formatSize(const boost::uintmax_t bytes) { - static const char *siPrefix[] = {"k", "M", "G", "T", "P", "E", "Z", "Y", NULL}; - int power = 0; - double engBytes = bytes; - while (engBytes >= 1000) { - ++power; - engBytes = engBytes / 1000.0; - } - return str( boost::format("%.1lf %sB") % engBytes % (power > 0 ? siPrefix[power-1] : "") ); +std::string QtWebKitChatView::addAction(const ChatWindow::ChatMessage& message, const std::string &senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { + return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, message.getHighlightActionSender(), ChatSnippet::getDirection(message)); } static QString encodeButtonArgument(const QString& str) { - return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str))))); + return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str))))); } static QString decodeButtonArgument(const QString& str) { - return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(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); + 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; +} + +QString QtWebKitChatView::buildChatWindowPopupImageButton(const QString& title, const QString& path, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) { + return QString("<div class=\"popup\" onclick='chatwindow.buttonClicked(\"%3\", \"%5\", \"6\", \"7\", \"8\", \"9\")' \"><img src='%1' title='%2'/><span class=\"popuptext\" id=\"resendPopup\" ><div class=\"resendButton\" onclick='chatwindow.buttonClicked(\"%4\", \"%5\", \"6\", \"7\", \"8\", \"9\")'>Resend</div></span></div>").arg(path).arg(title).arg(QtWebKitChatView::ButtonResendPopup).arg(QtWebKitChatView::ButtonResendMessage).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5)); +} + +void QtWebKitChatView::resizeEvent(QResizeEvent* event) { + // This code ensures that if the user is scrolled all to the bottom of a chat view, + // the view stays scrolled to the bottom if the view is resized or if the message + // input widget becomes multi line. + if (isAtBottom_) { + scrollToBottom(); + } + QWidget::resizeEvent(event); +} + +std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, const std::string& avatarPath, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description) { + SWIFT_LOG(debug) << "addFileTransfer"; + QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); + + QString actionText; + QString htmlString; + QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes)); + QString sanitizedFileName = QtUtilities::htmlEscape(P2QSTRING(filename)); + QString sanitizedDescription = QtUtilities::htmlEscape(P2QSTRING(description)); + if (senderIsSelf) { + // outgoing + filePaths_[ft_id] = sanitizedFileName; + actionText = tr("Send file: %1 (%2)").arg(sanitizedFileName).arg(formattedFileSize); + htmlString = actionText + " <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: %1 (%2)").arg(sanitizedFileName).arg(formattedFileSize); + if (!sanitizedDescription.isEmpty()) { + actionText += QString(" \"%1\"").arg(sanitizedDescription); + } + htmlString = actionText + " <br/>" + + "<div id='" + ft_id + "'>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) + + buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) + + "</div>"; + } + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf); + QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); + QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.svg" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); + std::string id = "ftmessage" + std::to_string(idCounter_++); + addMessageBottom(std::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::universal_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); + setFileTransferProgress(P2QSTRING(id), percentageDone); } void QtWebKitChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) { - setFileTransferStatus(P2QSTRING(id), state, P2QSTRING(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); + 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.svg"; + std::string id = "wbmessage" + std::to_string(idCounter_++); + addMessageBottom(std::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(contact), B2QDATE(boost::posix_time::second_clock::universal_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); +} + +static bool isFilePathWritable(const QString& path) { + QFileInfo fileInfo = QFileInfo(path); + if (fileInfo.exists()) { + return fileInfo.isWritable(); + } + else { + bool writable = false; + QFile writeTestFile(path); + if (writeTestFile.open(QIODevice::ReadWrite)) { + writeTestFile.write("test"); + if (writeTestFile.error() == QFileDevice::NoError) { + writable = true; + } + } + writeTestFile.close(); + writeTestFile.remove(); + return writable; + } +} + +void QtWebKitChatView::setFileTransferWarning(QString id, QString warningText) { + QWebElement ftElement = findElementWithID(document_, "div", id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id); + return; + } + + removeFileTransferWarning(id); + ftElement.appendInside(QString("<div class='ft_warning' style='color: red;'><br/>%1</div>").arg(QtUtilities::htmlEscape(warningText))); +} + +void QtWebKitChatView::removeFileTransferWarning(QString id) { + QWebElement ftElement = findElementWithID(document_, "div", id); + if (ftElement.isNull()) { + SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id); + return; + } + + QWebElement warningElement = ftElement.findFirst(".ft_warning"); + if (!warningElement.isNull()) { + warningElement.removeFromDocument(); + } } -void QtWebKitChatView::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) { - setWhiteboardSessionStatus(P2QSTRING(id), state); +void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) { + QString arg1 = decodeButtonArgument(encodedArgument1); + QString arg2 = decodeButtonArgument(encodedArgument2); + QString arg3 = decodeButtonArgument(encodedArgument3); + QString arg4 = decodeButtonArgument(encodedArgument4); + QString arg5 = decodeButtonArgument(encodedArgument5); + + if (id.startsWith(ButtonFileTransferCancel)) { + QString ft_id = arg1; + window_->onFileTransferCancel(Q2PSTRING(ft_id)); + } + else if (id.startsWith(ButtonFileTransferSetDescription)) { + QString ft_id = arg1; + bool ok = false; + QString text = QInputDialog::getText(this, tr("File transfer description"), + tr("Description:"), QLineEdit::Normal, "", &ok); + if (ok) { + descriptions_[ft_id] = text; + } + } + else if (id.startsWith(ButtonFileTransferSendRequest)) { + QString ft_id = arg1; + QString text = descriptions_.find(ft_id) == descriptions_.end() ? QString() : descriptions_[ft_id]; + window_->onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text)); + } + else if (id.startsWith(ButtonFileTransferAcceptRequest)) { + QString ft_id = arg1; + QString filename = arg2; + + QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename); + if (!path.isEmpty() && isFilePathWritable(path)) { + filePaths_[ft_id] = path; + window_->onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path)); + removeFileTransferWarning(ft_id); + } + else { + setFileTransferWarning(ft_id, tr("The chosen save location is not writable! Click the 'Accept' button to select a different save location.")); + } + } + else if (id.startsWith(ButtonFileTransferOpenFile)) { + QString ft_id = arg1; + QString filename = arg2; + askDesktopToOpenFile(filename); + } + 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(std::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, isImpromptu.contains("true"), isContinuation.contains("true"))); + setMUCInvitationJoined(elementID); + } + else if (id.startsWith(ButtonResendPopup)) { + QString chatID = arg1; + QWebElement message = document_.findFirst("#" + arg1); + if (!message.isNull()) { + QWebElement popuptext = message.findFirst("span#resendPopup.popuptext"); + if (!popuptext.isNull()) { + popuptext.toggleClass("show"); + } + } + } + else if (id.startsWith(ButtonResendMessage)) { + QString chatID = arg1; + window_->resendMessage(Q2PSTRING(chatID)); + } + else { + SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )"; + } +} + +void QtWebKitChatView::handleVerticalScrollBarPositionChanged(double position) { + rememberScrolledToBottom(); + if (position == 0) { + emit scrollReachedTop(); + } + else if (position == 1) { + emit scrollReachedBottom(); + } } +void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessage) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } + QString errorMessageHTML(chatMessageToHTML(errorMessage)); + std::string id = "id" + std::to_string(idCounter_++); + addMessageBottom(std::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, P2QSTRING(id), ChatSnippet::getDirection(errorMessage))); - -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; - } + previousMessageWasSelf_ = false; + previousMessageKind_ = PreviousMessageWasSystem; } -void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessage) { - if (window_->isWidgetSelected()) { - window_->onAllMessagesRead(); - } +std::string QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) { + 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))); + QString messageHTML = chatMessageToHTML(message); + std::string id = "id" + std::to_string(idCounter_++); + addMessageBottom(std::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, P2QSTRING(id), getActualDirection(message, direction))); - previousMessageWasSelf_ = false; - previousMessageKind_ = PreviousMessageWasSystem; + previousMessageKind_ = PreviousMessageWasSystem; + return id; } -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))); +void QtWebKitChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) { + replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", message.getHighlightActionSender()); +} - previousMessageKind_ = PreviousMessageWasSystem; +void QtWebKitChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) { + replaceMessage(chatMessageToHTML(message), id, time, "", message.getHighlightActionSender()); } -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::replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, ChatWindow::TimestampBehaviour timestampBehavior) { + replaceSystemMessage(chatMessageToHTML(message), P2QSTRING(id), timestampBehavior); } -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::replaceSystemMessage(const QString& newMessage, const QString& id, const ChatWindow::TimestampBehaviour timestampBehaviour) { + 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)); + + if (timestampBehaviour == ChatWindow::UpdateTimestamp) { + QWebElement replace = message.findFirst("span.swift_time"); + assert(!replace.isNull()); + replace.setInnerXml(ChatSnippet::timeToEscapedString(QDateTime::currentDateTime())); + } + } + else { + qWarning() << "Trying to replace element with id " << id << " but it's not there."; + } } 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(); - } + if (!id.empty()) { + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } - QString messageHTML(message); + QString messageHTML(message); - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : ""; - QString highlightSpanEnd = highlight.highlightText() ? "</span>" : ""; - messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd; + QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; + QString styleSpanEnd = style == "" ? "" : "</span>"; + QString highlightSpanStart = (highlight.getFrontColor() || highlight.getBackColor()) ? getHighlightSpanStart(highlight) : ""; + QString highlightSpanEnd = (highlight.getFrontColor() || highlight.getBackColor()) ? "</span>" : ""; + messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd; - replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time)); - } - else { - std::cerr << "Trying to replace a message with no id"; - } + 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(); - } + if (window_->isWidgetSelected()) { + window_->onAllMessagesRead(); + } - QString messageHTML = chatMessageToHTML(message); - addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction))); + QString messageHTML = chatMessageToHTML(message); + std::string id = "id" + std::to_string(idCounter_++); + addMessageBottom(std::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, P2QSTRING(id), getActualDirection(message, direction))); - previousMessageKind_ = PreviousMessageWasPresence; + previousMessageKind_ = PreviousMessageWasPresence; } -void QtWebKitChatView::replaceLastMessage(const ChatWindow::ChatMessage& message) { - replaceLastMessage(chatMessageToHTML(message)); +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(); - } + 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 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>"; + 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); + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false); - QString qAvatarPath = "qrc:/icons/avatar.png"; + QString qAvatarPath = "qrc:/icons/avatar.svg"; - 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; + addMessageBottom(std::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::universal_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); + 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 = buildChatWindowPopupImageButton(tr("This message may not have been sent. Click to resend."), "qrc:/icons/error.png", P2QSTRING(id)); + break; + } + setAckXML(P2QSTRING(id), xml); } void QtWebKitChatView::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { - QString xml; - switch (state) { - case ChatWindow::ReceiptReceived: - xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>"; - break; - case ChatWindow::ReceiptRequested: - xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>"; - break; - } - setReceiptXML(P2QSTRING(id), xml); -} - -bool QtWebKitChatView::appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) { - bool result = previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName))); - if (insertingLastLine_) { - insertingLastLine_ = false; - return false; - } - return result; + QString xml; + switch (state) { + case ChatWindow::ReceiptReceived: + xml = "<img src='qrc:/icons/delivery-success.svg' title='" + tr("The receipt for this message has been received.") + "'/>"; + break; + case ChatWindow::ReceiptRequested: + xml = "<img src='qrc:/icons/delivery-warning.svg' 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/delivery-failure.svg' 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, const std::shared_ptr<SecurityLabel>& label /*=nullptr*/) { + bool result = previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName))); + if (insertingLastLine_) { + insertingLastLine_ = false; + return false; + } + if (settings_->getSetting(SettingConstants::MUC_MARKING_ELISION)) { + if (label && label->getDisplayMarking() != previousMessageDisplayMarking_) { + if (label->getDisplayMarking() != "") { + 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); - } + 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 index 6bdaf96..f0b4459 100644 --- a/Swift/QtUI/QtWebKitChatView.h +++ b/Swift/QtUI/QtWebKitChatView.h @@ -1,19 +1,17 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QString> -#include <QWidget> +#include <memory> + #include <QList> +#include <QString> #include <QWebElement> - -#include <boost/shared_ptr.hpp> - -#include <Swiften/Base/Override.h> +#include <QWidget> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> @@ -25,163 +23,177 @@ class QUrl; class QDate; namespace Swift { - class QtWebView; - class QtChatTheme; - class QtChatWindowJSBridge; - class UIEventStream; - class QtChatWindow; - class QtWebKitChatView : public QtChatView { - Q_OBJECT - - public: - static const QString ButtonWhiteboardSessionCancel; - static const QString ButtonWhiteboardSessionAcceptRequest; - static const QString ButtonWhiteboardShowWindow; - static const QString ButtonFileTransferCancel; - static const QString ButtonFileTransferSetDescription; - static const QString ButtonFileTransferSendRequest; - static const QString ButtonFileTransferAcceptRequest; - static const QString ButtonMUCInvite; - public: - QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false); - ~QtWebKitChatView(); - - /** Add message to window. - * @return id of added message (for acks). - */ - virtual std::string addMessage(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; - /** Adds action to window. - * @return id of added message (for acks); - */ - virtual std::string addAction(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; - - virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE; - virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE; - - virtual void addErrorMessage(const ChatWindow::ChatMessage& message) SWIFTEN_OVERRIDE; - virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; - virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE; - void replaceLastMessage(const ChatWindow::ChatMessage& message); - void setAckState(const std::string& id, ChatWindow::AckState state); - - virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) SWIFTEN_OVERRIDE; - virtual void setFileTransferProgress(std::string, const int percentageDone) SWIFTEN_OVERRIDE; - virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") SWIFTEN_OVERRIDE; - virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) SWIFTEN_OVERRIDE; - virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) SWIFTEN_OVERRIDE; - virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) SWIFTEN_OVERRIDE; - virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) SWIFTEN_OVERRIDE; - - virtual void showEmoticons(bool show) SWIFTEN_OVERRIDE; - void addMessageTop(boost::shared_ptr<ChatSnippet> snippet); - void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet); - - int getSnippetPositionByDate(const QDate& date); // FIXME : This probably shouldn't have been public - void addLastSeenLine(); - - private: // previously public, now private - void replaceLastMessage(const QString& newMessage); - void replaceLastMessage(const QString& newMessage, const QString& note); - void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time); - void rememberScrolledToBottom(); - void setAckXML(const QString& id, const QString& xml); - void setReceiptXML(const QString& id, const QString& xml); - void displayReceiptInfo(const QString& id, bool showIt); - - QString getLastSentMessage(); - void addToJSEnvironment(const QString&, QObject*); - void setFileTransferProgress(QString id, const int percentageDone); - void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); - void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state); - void setMUCInvitationJoined(QString id); - - signals: - void gotFocus(); - void fontResized(int); - void logCleared(); - void scrollRequested(int pos); - void scrollReachedTop(); - void scrollReachedBottom(); - - public slots: - void copySelectionToClipboard(); - void handleLinkClicked(const QUrl&); - void resetView(); - void resetTopInsertPoint(); - void increaseFontSize(int numSteps = 1); - void decreaseFontSize(); - void resizeFont(int fontSizeSteps); - void scrollToBottom(); - void handleKeyPressEvent(QKeyEvent* event); - - private slots: - void handleViewLoadFinished(bool); - void handleFrameSizeChanged(); - void handleClearRequested(); - void handleScrollRequested(int dx, int dy, const QRect& rectToScroll); - void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); - - private: - enum PreviousMessageKind { - PreviosuMessageWasNone, - PreviousMessageWasMessage, - PreviousMessageWasSystem, - PreviousMessageWasPresence, - PreviousMessageWasFileTransfer, - PreviousMessageWasMUCInvite - }; - std::string addMessage( - const QString& message, - const std::string& senderName, - bool senderIsSelf, - boost::shared_ptr<SecurityLabel> label, - const std::string& avatarPath, - const QString& style, - const boost::posix_time::ptime& time, - const HighlightAction& highlight, - ChatSnippet::Direction direction); - void replaceMessage( - const QString& message, - const std::string& id, - const boost::posix_time::ptime& time, - const QString& style, - const HighlightAction& highlight); - bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf); - static ChatSnippet::Direction getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction); - QString chatMessageToHTML(const ChatWindow::ChatMessage& message); - QString getHighlightSpanStart(const HighlightAction& highlight); - static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString()); - - private: - void headerEncode(); - void messageEncode(); - void addToDOM(boost::shared_ptr<ChatSnippet> snippet); - QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); - - QtChatWindow* window_; - UIEventStream* eventStream_; - bool viewReady_; - bool isAtBottom_; - bool topMessageAdded_; - int scrollBarMaximum_; - QtWebView* webView_; - QWebPage* webPage_; - int fontSizeSteps_; - QtChatTheme* theme_; - QWebElement newInsertPoint_; - QWebElement topInsertPoint_; - QWebElement lineSeparator_; - QWebElement lastElement_; - QWebElement firstElement_; - QWebElement document_; - bool disableAutoScroll_; - QtChatWindowJSBridge* jsBridge; - PreviousMessageKind previousMessageKind_; - bool previousMessageWasSelf_; - bool showEmoticons_; - bool insertingLastLine_; - int idCounter_; - QString previousSenderName_; - std::map<QString, QString> descriptions_; - }; + class QtWebView; + class QtChatTheme; + class QtChatWindowJSBridge; + class UIEventStream; + class QtChatWindow; + class SettingsProvider; + 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 ButtonFileTransferOpenFile; + static const QString ButtonMUCInvite; + static const QString ButtonResendMessage; + static const QString ButtonResendPopup; + public: + QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, SettingsProvider* settings, bool disableAutoScroll = false); + ~QtWebKitChatView() override; + + /** 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, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) 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, std::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) override; + + virtual std::string addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) override; + virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) override; + + virtual void addErrorMessage(const ChatWindow::ChatMessage& message) override; + virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) override; + virtual void replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, ChatWindow::TimestampBehaviour timestampBehaviour) override; + virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) override; + virtual void replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour timestampBehaviour) override; + virtual void setAckState(const std::string& id, ChatWindow::AckState state) override; + + virtual std::string addFileTransfer(const std::string& senderName, const std::string& avatarPath, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description) override; + virtual void setFileTransferProgress(std::string, const int percentageDone) override; + virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") 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) override; + virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) override; + virtual void setWhiteboardSessionStatus(const std::string& id, const ChatWindow::WhiteboardSessionState state) override; + virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) override; + + virtual void showEmoticons(bool show) override; + void addMessageTop(std::shared_ptr<ChatSnippet> snippet); + void addMessageBottom(std::shared_ptr<ChatSnippet> snippet); + + int getSnippetPositionByDate(const QDate& date); // FIXME : This probably shouldn't have been public + virtual void addLastSeenLine() override; + + 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 replaceSystemMessage(const QString& newMessage, const QString&id, const ChatWindow::TimestampBehaviour timestampBehaviour); + 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 setFileTransferWarning(QString id, QString warningText); + void removeFileTransferWarning(QString id); + void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state); + void setMUCInvitationJoined(QString id); + void askDesktopToOpenFile(const QString& filename); + + 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(); + virtual void resizeFont(int fontSizeSteps) override; + virtual void scrollToBottom() override; + virtual void handleKeyPressEvent(QKeyEvent* event) override; + + private slots: + void handleViewLoadFinished(bool); + void handleFrameSizeChanged(); + void handleClearRequested(); + void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5); + void handleVerticalScrollBarPositionChanged(double position); + + private: + enum PreviousMessageKind { + PreviosuMessageWasNone, + PreviousMessageWasMessage, + PreviousMessageWasSystem, + PreviousMessageWasPresence, + PreviousMessageWasFileTransfer, + PreviousMessageWasMUCInvite + }; + std::string addMessage( + const QString& message, + const std::string& senderName, + bool senderIsSelf, + std::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, const std::shared_ptr<SecurityLabel>& label = nullptr); + 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()); + static QString buildChatWindowPopupImageButton(const QString& title, const QString& path, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString()); + + protected: + void resizeEvent(QResizeEvent* event) override; + + private: + void headerEncode(); + void messageEncode(); + void addToDOM(std::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 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_; + std::map<QString, QString> filePaths_; + std::string previousMessageDisplayMarking_; + SettingsProvider* settings_ = nullptr; + }; } diff --git a/Swift/QtUI/QtWebView.cpp b/Swift/QtUI/QtWebView.cpp index 33fa817..24636ed 100644 --- a/Swift/QtUI/QtWebView.cpp +++ b/Swift/QtUI/QtWebView.cpp @@ -1,43 +1,54 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtWebView.h" +#include <Swift/QtUI/QtWebView.h> -#include <QKeyEvent> #include <QFocusEvent> -#include <boost/numeric/conversion/cast.hpp> +#include <QKeyEvent> +#include <QKeySequence> #include <QMenu> + #include <Swiften/Base/Log.h> namespace Swift { QtWebView::QtWebView(QWidget* parent) : QWebView(parent), fontSizeIsMinimal(false) { - setRenderHint(QPainter::SmoothPixmapTransform); - filteredActions.push_back(QWebPage::CopyLinkToClipboard); - filteredActions.push_back(QWebPage::CopyImageToClipboard); - filteredActions.push_back(QWebPage::Copy); - if (Log::getLogLevel() == Log::debug) { - filteredActions.push_back(QWebPage::InspectElement); - } + setRenderHint(QPainter::SmoothPixmapTransform); + filteredActions.push_back(QWebPage::CopyLinkToClipboard); + filteredActions.push_back(QWebPage::CopyImageToClipboard); + filteredActions.push_back(QWebPage::Copy); + if (Log::getLogLevel() == Log::debug) { + filteredActions.push_back(QWebPage::InspectElement); + } } void QtWebView::keyPressEvent(QKeyEvent* event) { - Qt::KeyboardModifiers modifiers = event->modifiers(); - int key = event->key(); - if (modifiers == Qt::ShiftModifier && (key == Qt::Key_PageUp || key == Qt::Key_PageDown)) { - modifiers = Qt::NoModifier; - } - QKeyEvent* translatedEvent = new QKeyEvent(QEvent::KeyPress, - key, - modifiers, - event->text(), - event->isAutoRepeat(), - boost::numeric_cast<unsigned short>(event->count())); - QWebView::keyPressEvent(translatedEvent); - delete translatedEvent; + Qt::KeyboardModifiers modifiers = event->modifiers(); + int key = event->key(); + if (event->matches(QKeySequence::ZoomIn) || (key == Qt::Key_Equal && (modifiers & Qt::ControlModifier))) { + event->accept(); + emit fontGrowRequested(); + return; + } + if (event->matches(QKeySequence::ZoomOut) || (key == Qt::Key_Minus && (modifiers & Qt::ControlModifier))) { + event->accept(); + emit fontShrinkRequested(); + return; + } + if (modifiers == Qt::ShiftModifier && (key == Qt::Key_PageUp || key == Qt::Key_PageDown)) { + modifiers = Qt::NoModifier; + } + QKeyEvent* translatedEvent = new QKeyEvent(QEvent::KeyPress, + key, + modifiers, + event->text(), + event->isAutoRepeat(), + event->count()); + QWebView::keyPressEvent(translatedEvent); + delete translatedEvent; } void QtWebView::dragEnterEvent(QDragEnterEvent*) { @@ -45,43 +56,43 @@ void QtWebView::dragEnterEvent(QDragEnterEvent*) { } void QtWebView::setFontSizeIsMinimal(bool minimum) { - fontSizeIsMinimal = minimum; + fontSizeIsMinimal = minimum; } void QtWebView::contextMenuEvent(QContextMenuEvent* ev) { - // Filter out the relevant actions from the standard actions - - QMenu* menu = page()->createStandardContextMenu(); - QList<QAction*> actions(menu->actions()); - for (int i = 0; i < actions.size(); ++i) { - QAction* action = actions.at(i); - bool removeAction = true; - for(size_t j = 0; j < filteredActions.size(); ++j) { - if (action == pageAction(filteredActions[j])) { - removeAction = false; - break; - } - } - if (removeAction) { - menu->removeAction(action); - } - } + // Filter out the relevant actions from the standard actions + + QMenu* menu = page()->createStandardContextMenu(); + QList<QAction*> actions(menu->actions()); + for (auto action : actions) { + bool removeAction = true; + for(auto& filteredAction : filteredActions) { + if (action == pageAction(filteredAction)) { + removeAction = false; + break; + } + } + if (removeAction) { + menu->removeAction(action); + } + } - // Add our own custom actions - menu->addAction(tr("Clear"), this, SIGNAL(clearRequested())); - menu->addAction(tr("Increase font size"), this, SIGNAL(fontGrowRequested())); - QAction* shrink = new QAction(tr("Decrease font size"), this); - shrink->setEnabled(!fontSizeIsMinimal); - connect(shrink, SIGNAL(triggered()), this, SIGNAL(fontShrinkRequested())); - menu->addAction(shrink); + // Add our own custom actions + menu->addAction(tr("Clear"), this, SIGNAL(clearRequested())); + menu->addAction(tr("Increase font size"), this, SIGNAL(fontGrowRequested()), QKeySequence(QKeySequence::ZoomIn)); + QAction* shrink = new QAction(tr("Decrease font size"), this); + shrink->setShortcut(QKeySequence(QKeySequence::ZoomOut)); + shrink->setEnabled(!fontSizeIsMinimal); + connect(shrink, SIGNAL(triggered()), this, SIGNAL(fontShrinkRequested())); + menu->addAction(shrink); - menu->exec(ev->globalPos()); - delete menu; + menu->exec(ev->globalPos()); + delete menu; } void QtWebView::focusInEvent(QFocusEvent* event) { - QWebView::focusInEvent(event); - emit gotFocus(); + QWebView::focusInEvent(event); + emit gotFocus(); } } diff --git a/Swift/QtUI/QtWebView.h b/Swift/QtUI/QtWebView.h index 202037f..985a0db 100644 --- a/Swift/QtUI/QtWebView.h +++ b/Swift/QtUI/QtWebView.h @@ -1,36 +1,37 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWebView> #include <vector> +#include <QWebView> + namespace Swift { - class QtWebView : public QWebView { - Q_OBJECT - public: - QtWebView(QWidget* parent); - void keyPressEvent(QKeyEvent* event); - void dragEnterEvent(QDragEnterEvent *event); - void contextMenuEvent(QContextMenuEvent* ev); - void setFontSizeIsMinimal(bool minimum); - - signals: - void gotFocus(); - void clearRequested(); - void fontGrowRequested(); - void fontShrinkRequested(); - - protected: - void focusInEvent(QFocusEvent* event); - - private: - std::vector<QWebPage::WebAction> filteredActions; - bool fontSizeIsMinimal; - }; + class QtWebView : public QWebView { + Q_OBJECT + public: + QtWebView(QWidget* parent); + void keyPressEvent(QKeyEvent* event); + void dragEnterEvent(QDragEnterEvent *event); + void contextMenuEvent(QContextMenuEvent* ev); + void setFontSizeIsMinimal(bool minimum); + + signals: + void gotFocus(); + void clearRequested(); + void fontGrowRequested(); + void fontShrinkRequested(); + + protected: + void focusInEvent(QFocusEvent* event); + + private: + std::vector<QWebPage::WebAction> filteredActions; + bool fontSizeIsMinimal; + }; } diff --git a/Swift/QtUI/QtWin32NotifierWindow.h b/Swift/QtUI/QtWin32NotifierWindow.h index cd43cf2..bf55706 100644 --- a/Swift/QtUI/QtWin32NotifierWindow.h +++ b/Swift/QtUI/QtWin32NotifierWindow.h @@ -1,29 +1,29 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QWidget> -#include "SwifTools/Notifier/Win32NotifierWindow.h" +#include <SwifTools/Notifier/Win32NotifierWindow.h> namespace Swift { - class QtWin32NotifierWindow : public QWidget, public Win32NotifierWindow { - public: - QtWin32NotifierWindow(QWidget* parent = NULL) { - setVisible(false); - } + class QtWin32NotifierWindow : public QWidget, public Win32NotifierWindow { + public: + QtWin32NotifierWindow(QWidget* parent = NULL) { + setVisible(false); + } - bool winEvent (MSG* message, long* result ) { - onMessageReceived(message); - return false; - } + bool winEvent (MSG* message, long* result ) { + onMessageReceived(message); + return false; + } - virtual HWND getID() const { - return (HWND) winId(); - } - }; + virtual HWND getID() const { + return (HWND) winId(); + } + }; } diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp index 284f3c3..a7f9702 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.cpp +++ b/Swift/QtUI/QtXMLConsoleWidget.cpp @@ -1,109 +1,120 @@ /* - * Copyright (c) 2010-2013 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "QtXMLConsoleWidget.h" +#include <Swift/QtUI/QtXMLConsoleWidget.h> +#include <string> + +#include <QCheckBox> #include <QCloseEvent> -#include <QTextEdit> -#include <QVBoxLayout> #include <QPushButton> #include <QScrollBar> -#include <QCheckBox> +#include <QTextEdit> +#include <QVBoxLayout> -#include "QtSwiftUtil.h" -#include <string> +#include <Swiften/Base/format.h> + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { QtXMLConsoleWidget::QtXMLConsoleWidget() { - setWindowTitle(tr("Console")); + setWindowTitle(tr("Console")); - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setSpacing(0); - layout->setContentsMargins(0,0,0,0); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(0,0,0,0); - textEdit = new QTextEdit(this); - textEdit->setReadOnly(true); - layout->addWidget(textEdit); + textEdit = new QTextEdit(this); + textEdit->setReadOnly(true); + layout->addWidget(textEdit); - QWidget* bottom = new QWidget(this); - layout->addWidget(bottom); - bottom->setAutoFillBackground(true); + QWidget* bottom = new QWidget(this); + layout->addWidget(bottom); + bottom->setAutoFillBackground(true); - QHBoxLayout* buttonLayout = new QHBoxLayout(bottom); - buttonLayout->setContentsMargins(10,0,20,0); - buttonLayout->setSpacing(0); + QHBoxLayout* buttonLayout = new QHBoxLayout(bottom); + buttonLayout->setContentsMargins(10,0,20,0); + buttonLayout->setSpacing(0); - enabled = new QCheckBox(tr("Trace input/output"), bottom); - enabled->setChecked(true); - buttonLayout->addWidget(enabled); + enabled = new QCheckBox(tr("Trace input/output"), bottom); + enabled->setChecked(true); + buttonLayout->addWidget(enabled); - buttonLayout->addStretch(); + buttonLayout->addStretch(); - QPushButton* clearButton = new QPushButton(tr("Clear"), bottom); - connect(clearButton, SIGNAL(clicked()), textEdit, SLOT(clear())); - buttonLayout->addWidget(clearButton); + QPushButton* clearButton = new QPushButton(tr("Clear"), bottom); + connect(clearButton, SIGNAL(clicked()), textEdit, SLOT(clear())); + buttonLayout->addWidget(clearButton); - setWindowTitle(tr("Debug Console")); - emit titleUpdated(); + setWindowTitle(tr("Debug Console")); + emit titleUpdated(); } QtXMLConsoleWidget::~QtXMLConsoleWidget() { } void QtXMLConsoleWidget::showEvent(QShowEvent* event) { - emit windowOpening(); - emit titleUpdated(); /* This just needs to be somewhere after construction */ - QWidget::showEvent(event); + emit windowOpening(); + emit titleUpdated(); /* This just needs to be somewhere after construction */ + QWidget::showEvent(event); } void QtXMLConsoleWidget::show() { - QWidget::show(); - emit windowOpening(); + QWidget::show(); + emit windowOpening(); } void QtXMLConsoleWidget::activate() { - emit wantsToActivate(); + emit wantsToActivate(); } void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) { - emit windowClosing(); - event->accept(); + emit windowClosing(); + event->accept(); } 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)); +} + +std::string QtXMLConsoleWidget::getID() const { + return "QtXMLConsoleWidget"; } void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QColor& color) { - if (enabled->isChecked()) { - QScrollBar* scrollBar = textEdit->verticalScrollBar(); - bool scrollToBottom = (!scrollBar || scrollBar->value() == scrollBar->maximum()); - - QTextCursor cursor(textEdit->document()); - cursor.beginEditBlock(); - cursor.movePosition(QTextCursor::End); - QTextCharFormat format; - format.setForeground(QBrush(color)); - cursor.mergeCharFormat(format); - cursor.insertText(P2QSTRING(data)); - cursor.endEditBlock(); - - // 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()); - } - } + if (enabled->isChecked()) { + QScrollBar* scrollBar = textEdit->verticalScrollBar(); + bool scrollToBottom = (!scrollBar || scrollBar->value() == scrollBar->maximum()); + + QTextCursor cursor(textEdit->document()); + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::End); + QTextCharFormat format; + format.setForeground(QBrush(color)); + cursor.mergeCharFormat(format); + cursor.insertText(P2QSTRING(data)); + cursor.endEditBlock(); + + // 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/QtXMLConsoleWidget.h b/Swift/QtUI/QtXMLConsoleWidget.h index 912ad90..ef10e63 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.h +++ b/Swift/QtUI/QtXMLConsoleWidget.h @@ -1,40 +1,43 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/Controllers/UIInterfaces/XMLConsoleWidget.h" -#include "QtTabbable.h" +#include <Swift/Controllers/UIInterfaces/XMLConsoleWidget.h> + +#include <Swift/QtUI/QtTabbable.h> class QTextEdit; class QCheckBox; class QColor; namespace Swift { - class QtXMLConsoleWidget : public QtTabbable, public XMLConsoleWidget { - Q_OBJECT + class QtXMLConsoleWidget : public QtTabbable, public XMLConsoleWidget { + Q_OBJECT + + public: + QtXMLConsoleWidget(); + ~QtXMLConsoleWidget(); - public: - QtXMLConsoleWidget(); - ~QtXMLConsoleWidget(); + void show(); + void activate(); - void show(); - void activate(); + virtual void handleDataRead(const SafeByteArray& data); + virtual void handleDataWritten(const SafeByteArray& data); - virtual void handleDataRead(const SafeByteArray& data); - virtual void handleDataWritten(const SafeByteArray& data); + virtual std::string getID() const; - private: - virtual void closeEvent(QCloseEvent* event); - virtual void showEvent(QShowEvent* event); + private: + virtual void closeEvent(QCloseEvent* event); + virtual void showEvent(QShowEvent* event); - void appendTextIfEnabled(const std::string& data, const QColor& color); + void appendTextIfEnabled(const std::string& data, const QColor& color); - private: - QTextEdit* textEdit; - QCheckBox* enabled; - }; + private: + QTextEdit* textEdit; + QCheckBox* enabled; + }; } diff --git a/Swift/QtUI/Roster/DelegateCommons.cpp b/Swift/QtUI/Roster/DelegateCommons.cpp index 776d90c..e4a2f46 100644 --- a/Swift/QtUI/Roster/DelegateCommons.cpp +++ b/Swift/QtUI/Roster/DelegateCommons.cpp @@ -1,108 +1,111 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "DelegateCommons.h" +#include <Swift/QtUI/Roster/DelegateCommons.h> -#include <QtScaledAvatarCache.h> +#include <QColor> #include <QFileInfo> -namespace Swift { +#include <Swift/QtUI/QtScaledAvatarCache.h> +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); + 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, bool isIdle, int unreadCount, bool compact) const { - painter->save(); - QRect fullRegion(option.rect); - if ( option.state & QStyle::State_Selected ) { - painter->fillRect(fullRegion, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } else { - painter->setPen(QPen(nameColor)); - } - - QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin)); - - QRect idleIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth*2, fullRegion.height() - verticalMargin)); - int calculatedAvatarSize = presenceIconRegion.height(); - //This overlaps the presenceIcon, so must be painted first - QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize)); - - QPixmap avatarPixmap; - if (!compact && !avatarPath.isEmpty()) { - QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath); - if (QFileInfo(scaledAvatarPath).exists()) { - avatarPixmap.load(scaledAvatarPath); - } - } - if (!compact && avatarPixmap.isNull()) { - avatarPixmap = QPixmap(":/icons/avatar.png").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - - if (!compact) { - painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap); - } - - //Paint the presence icon over the top of the avatar - presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); - - if (isIdle) { - idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); - } - - QFontMetrics nameMetrics(nameFont); - painter->setFont(nameFont); - int extraFontWidth = nameMetrics.width("H"); - int leftOffset = (compact ? presenceIconRegion : avatarRegion).right() + horizontalMargin * 2 + extraFontWidth / 2; - QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0/*-leftOffset*/, 0)); - - int nameHeight = nameMetrics.height() + verticalMargin; - QRect nameRegion(textRegion.adjusted(0, verticalMargin, 0, 0)); - - DelegateCommons::drawElidedText(painter, nameRegion, name); - - if (!compact) { - painter->setFont(detailFont); - painter->setPen(QPen(QColor(160,160,160))); - - QRect statusTextRegion(textRegion.adjusted(0, nameHeight, 0, 0)); - DelegateCommons::drawElidedText(painter, statusTextRegion, statusText); - } - - if (unreadCount > 0) { - QRect unreadRect(fullRegion.right() - unreadCountSize - horizontalMargin, fullRegion.top() + (fullRegion.height() - unreadCountSize) / 2, unreadCountSize, unreadCountSize); - QPen pen(QColor("black")); - pen.setWidth(1); - painter->setRenderHint(QPainter::Antialiasing, true); - painter->setPen(pen); - painter->setBrush(QBrush(QColor("red"), Qt::SolidPattern)); - //painter->setBackgroundMode(Qt::OpaqueMode); - painter->drawEllipse(unreadRect); - painter->setBackgroundMode(Qt::TransparentMode); - painter->setPen(QColor("white")); - drawElidedText(painter, unreadRect, QString("%1").arg(unreadCount), Qt::AlignCenter); - } - - painter->restore(); + painter->save(); + QRect fullRegion(option.rect); + if ( option.state & QStyle::State_Selected ) { + painter->fillRect(fullRegion, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } else { + painter->setPen(QPen(nameColor)); + } + auto secondLineColor = painter->pen().color(); + secondLineColor.setAlphaF(0.7); + + QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin)); + + QRect idleIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth*2, fullRegion.height() - verticalMargin)); + int calculatedAvatarSize = presenceIconRegion.height(); + //This overlaps the presenceIcon, so must be painted first + QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize)); + + QPixmap avatarPixmap; + if (!compact && !avatarPath.isEmpty()) { + QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath); + if (QFileInfo(scaledAvatarPath).exists()) { + avatarPixmap.load(scaledAvatarPath); + } + } + if (!compact && avatarPixmap.isNull()) { + avatarPixmap = QPixmap(":/icons/avatar.svg").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + + if (!compact) { + painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap); + } + + //Paint the presence icon over the top of the avatar + presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + + if (isIdle) { + idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + } + + QFontMetrics nameMetrics(nameFont); + painter->setFont(nameFont); + int extraFontWidth = nameMetrics.width("H"); + int leftOffset = (compact ? presenceIconRegion : avatarRegion).right() + horizontalMargin * 2 + extraFontWidth / 2; + QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0/*-leftOffset*/, 0)); + + int nameHeight = nameMetrics.height() + verticalMargin; + QRect nameRegion(textRegion.adjusted(0, verticalMargin, 0, 0)); + + DelegateCommons::drawElidedText(painter, nameRegion, name); + + if (!compact) { + painter->setFont(detailFont); + painter->setPen(QPen(secondLineColor)); + + QRect statusTextRegion(textRegion.adjusted(0, nameHeight, 0, 0)); + DelegateCommons::drawElidedText(painter, statusTextRegion, statusText); + } + + if (unreadCount > 0) { + QRect unreadRect(fullRegion.right() - unreadCountSize - horizontalMargin, fullRegion.top() + (fullRegion.height() - unreadCountSize) / 2, unreadCountSize, unreadCountSize); + QPen pen(QColor("black")); + pen.setWidth(1); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(pen); + painter->setBrush(QBrush(QColor("red"), Qt::SolidPattern)); + //painter->setBackgroundMode(Qt::OpaqueMode); + painter->drawEllipse(unreadRect); + painter->setBackgroundMode(Qt::TransparentMode); + painter->setPen(QColor("white")); + drawElidedText(painter, unreadRect, QString("%1").arg(unreadCount), Qt::AlignCenter); + } + + painter->restore(); } QSize DelegateCommons::contactSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/, bool compact ) const { - int heightByAvatar = (compact ? presenceIconHeight : avatarSize) + verticalMargin * 2; - QFontMetrics nameMetrics(nameFont); - QFontMetrics statusMetrics(detailFont); - int sizeByText = 2 * verticalMargin + nameMetrics.height() + (compact ? 0 : statusMetrics.height()); - //Doesn't work, yay! FIXME: why? - //QSize size = (option.state & QStyle::State_Selected) ? QSize(150, 80) : QSize(150, avatarSize_ + margin_ * 2); - //qDebug() << "Returning size" << size; - return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar); + int heightByAvatar = (compact ? presenceIconHeight : avatarSize) + verticalMargin * 2; + QFontMetrics nameMetrics(nameFont); + QFontMetrics statusMetrics(detailFont); + int sizeByText = 2 * verticalMargin + nameMetrics.height() + (compact ? 0 : statusMetrics.height()); + //Doesn't work, yay! FIXME: why? + //QSize size = (option.state & QStyle::State_Selected) ? QSize(150, 80) : QSize(150, avatarSize_ + margin_ * 2); + //qDebug() << "Returning size" << size; + return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar); } const int DelegateCommons::horizontalMargin = 2; diff --git a/Swift/QtUI/Roster/DelegateCommons.h b/Swift/QtUI/Roster/DelegateCommons.h index 0684410..74b08f2 100644 --- a/Swift/QtUI/Roster/DelegateCommons.h +++ b/Swift/QtUI/Roster/DelegateCommons.h @@ -1,43 +1,43 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QApplication> #include <QFont> +#include <QIcon> #include <QPainter> #include <QRect> #include <QString> -#include <QIcon> #include <QStyleOptionViewItem> namespace Swift { - class DelegateCommons { - public: - DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()), idleIcon(QIcon(":/icons/zzz.png")) { - detailFontSizeDrop = nameFont.pointSize() >= 10 ? 2 : 0; - detailFont.setStyle(QFont::StyleItalic); - detailFont.setPointSize(nameFont.pointSize() - detailFontSizeDrop); - } + class DelegateCommons { + public: + DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()), idleIcon(QIcon(":/icons/zzz.png")) { + detailFontSizeDrop = nameFont.pointSize() >= 10 ? 2 : 0; + detailFont.setStyle(QFont::StyleItalic); + detailFont.setPointSize(nameFont.pointSize() - detailFontSizeDrop); + } - static void drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags = Qt::AlignTop); + static void drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags = Qt::AlignTop); - QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index, bool compact) const; - void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const; + 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, bool isIdle, int unreadCount, bool compact) const; - int detailFontSizeDrop; - QFont nameFont; - QFont detailFont; - static const int horizontalMargin; - static const int verticalMargin; - static const int farLeftMargin; - static const int avatarSize; - static const int presenceIconHeight; - static const int presenceIconWidth; - static const int unreadCountSize; - QIcon idleIcon; - }; + int detailFontSizeDrop; + QFont nameFont; + QFont detailFont; + static const int horizontalMargin; + static const int verticalMargin; + static const int farLeftMargin; + static const int avatarSize; + static const int presenceIconHeight; + static const int presenceIconWidth; + static const int unreadCountSize; + QIcon idleIcon; + }; } diff --git a/Swift/QtUI/Roster/GroupItemDelegate.cpp b/Swift/QtUI/Roster/GroupItemDelegate.cpp index ffe0a98..0356aa0 100644 --- a/Swift/QtUI/Roster/GroupItemDelegate.cpp +++ b/Swift/QtUI/Roster/GroupItemDelegate.cpp @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "GroupItemDelegate.h" +#include <Swift/QtUI/Roster/GroupItemDelegate.h> #include <QPainter> #include <QPen> @@ -13,100 +13,100 @@ namespace Swift { GroupItemDelegate::GroupItemDelegate() : groupFont_(QApplication::font()) { - groupFont_.setPointSize(common_.nameFont.pointSize() - common_.detailFontSizeDrop); - groupFont_.setWeight(QFont::Bold); + groupFont_.setPointSize(common_.nameFont.pointSize() - common_.detailFontSizeDrop); + groupFont_.setWeight(QFont::Bold); } QSize GroupItemDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { - QFontMetrics groupMetrics(groupFont_); - return QSize(150, groupMetrics.height() + common_.verticalMargin + 2); + QFontMetrics groupMetrics(groupFont_); + return QSize(150, groupMetrics.height() + common_.verticalMargin + 2); } void GroupItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QString& name, int rowCount, bool expanded) const { - painter->save(); - painter->setPen(QPen(QColor(189, 189, 189))); - //FIXME: It looks like Qt is passing us a rectangle that's too small - //This deliberately draws outside the lines, and we need to find a better solution. - int correctionAmount = groupCornerRadius_ > 0 ? 0 : 1; - QRect region(QPoint(option.rect.left() - correctionAmount, option.rect.top()), QSize(option.rect.width() + correctionAmount, option.rect.height() - common_.verticalMargin)); - QLinearGradient fillGradient(region.topLeft(), region.bottomLeft()); - fillGradient.setColorAt(0, QColor(244, 244, 244)); - fillGradient.setColorAt(0.1, QColor(231, 231, 231)); - fillGradient.setColorAt(1, QColor(209, 209, 209)); - - QBrush backgroundBrush = QBrush(fillGradient); - QPainterPath fillPath; - fillPath.addRoundedRect(region, groupCornerRadius_, groupCornerRadius_); - QPainterPath linePath; - linePath.addRoundedRect(region, groupCornerRadius_, groupCornerRadius_); - painter->fillPath(fillPath, backgroundBrush); - painter->drawPath(linePath); - - int triangleHorizontalOffset = 1; - int triangleWidth = 9; - int triangleHeight = 5; - paintExpansionTriangle(painter, region.adjusted(common_.horizontalMargin + triangleHorizontalOffset + 1, 0, 0, 0), triangleWidth, triangleHeight, expanded); - - int textLeftOffset = 3 * common_.horizontalMargin + 1 + triangleWidth + triangleHorizontalOffset; - QFontMetrics fontMetrics(groupFont_); - int textTopOffset = (region.height() - fontMetrics.height()) / 2; - painter->setFont(groupFont_); - int contactCountWidth = 0; - QRect textRect = region.adjusted(textLeftOffset, textTopOffset, -1 * textLeftOffset, -1 * textTopOffset); - - if (!expanded) { - QFontMetrics groupMetrics(groupFont_); - int contactCount = rowCount; - QString countString = QString("%1").arg(contactCount); - contactCountWidth = groupMetrics.width(countString) + 2 * common_.horizontalMargin; - int offsetAmount = textRect.width() - contactCountWidth + common_.horizontalMargin; - QRect countRect = textRect.adjusted(offsetAmount, 0, 0/*-1 * offsetAmount*/, 0); - paintShadowText(painter, countRect, countString); - } - QRect nameTextRect = expanded ? textRect : textRect.adjusted(0, 0, -contactCountWidth, 0); - QString elidedName = fontMetrics.elidedText(name, Qt::ElideRight, nameTextRect.width(), Qt::TextShowMnemonic); - paintShadowText(painter, nameTextRect, elidedName); - painter->restore(); + painter->save(); + painter->setPen(QPen(QColor(189, 189, 189))); + //FIXME: It looks like Qt is passing us a rectangle that's too small + //This deliberately draws outside the lines, and we need to find a better solution. + int correctionAmount = groupCornerRadius_ > 0 ? 0 : 1; + QRect region(QPoint(option.rect.left() - correctionAmount, option.rect.top()), QSize(option.rect.width() + correctionAmount, option.rect.height() - common_.verticalMargin)); + QLinearGradient fillGradient(region.topLeft(), region.bottomLeft()); + fillGradient.setColorAt(0, QColor(244, 244, 244)); + fillGradient.setColorAt(0.1, QColor(231, 231, 231)); + fillGradient.setColorAt(1, QColor(209, 209, 209)); + + QBrush backgroundBrush = QBrush(fillGradient); + QPainterPath fillPath; + fillPath.addRoundedRect(region, groupCornerRadius_, groupCornerRadius_); + QPainterPath linePath; + linePath.addRoundedRect(region, groupCornerRadius_, groupCornerRadius_); + painter->fillPath(fillPath, backgroundBrush); + painter->drawPath(linePath); + + int triangleHorizontalOffset = 1; + int triangleWidth = 9; + int triangleHeight = 5; + paintExpansionTriangle(painter, region.adjusted(common_.horizontalMargin + triangleHorizontalOffset + 1, 0, 0, 0), triangleWidth, triangleHeight, expanded); + + int textLeftOffset = 3 * common_.horizontalMargin + 1 + triangleWidth + triangleHorizontalOffset; + QFontMetrics fontMetrics(groupFont_); + int textTopOffset = (region.height() - fontMetrics.height()) / 2; + painter->setFont(groupFont_); + int contactCountWidth = 0; + QRect textRect = region.adjusted(textLeftOffset, textTopOffset, -1 * textLeftOffset, -1 * textTopOffset); + + if (!expanded) { + QFontMetrics groupMetrics(groupFont_); + int contactCount = rowCount; + QString countString = QString("%1").arg(contactCount); + contactCountWidth = groupMetrics.width(countString) + 2 * common_.horizontalMargin; + int offsetAmount = textRect.width() - contactCountWidth + common_.horizontalMargin; + QRect countRect = textRect.adjusted(offsetAmount, 0, 0/*-1 * offsetAmount*/, 0); + paintShadowText(painter, countRect, countString); + } + QRect nameTextRect = expanded ? textRect : textRect.adjusted(0, 0, -contactCountWidth, 0); + QString elidedName = fontMetrics.elidedText(name, Qt::ElideRight, nameTextRect.width(), Qt::TextShowMnemonic); + paintShadowText(painter, nameTextRect, elidedName); + painter->restore(); } void GroupItemDelegate::paintExpansionTriangle(QPainter* painter, const QRect& region, int width, int height, bool expanded) const { - // height is the height of the downward pointing triangle - QPolygonF triangle; - if (expanded) { - QPointF triangleTopLeft(region.left(), region.top() + region.height() / 2 - height / 2); - triangle << triangleTopLeft; - triangle << triangleTopLeft + QPointF(width, 0); - triangle << triangleTopLeft + QPointF(width / 2, height); - // The expanded triangle should be a little lower, because its pointy shape makes it feel - // as if it's too high. - triangle.translate(QPointF(0,1)); - } - else { - QPointF triangleTopLeft(region.left() + ((width - height) / 2), region.top() + region.height() / 2 - width / 2); - triangle << triangleTopLeft; - triangle << triangleTopLeft + QPointF(height, width / 2); - triangle << triangleTopLeft + QPointF(0, width); - } - //qDebug() << "Painting triangle: " << triangle; - - QPolygonF triangleShadow(triangle); - triangleShadow.translate(QPointF(0, -1)); - - QPainterPath trianglePath; - QPainterPath triangleShadowPath; - QBrush triangleBrush(QColor(110, 110, 110)); - QBrush triangleShadowBrush(QColor(47, 47, 47)); - trianglePath.addPolygon(triangle); - triangleShadowPath.addPolygon(triangleShadow); - painter->fillPath(triangleShadowPath, triangleShadowBrush); - painter->fillPath(trianglePath, triangleBrush); + // height is the height of the downward pointing triangle + QPolygonF triangle; + if (expanded) { + QPointF triangleTopLeft(region.left(), region.top() + region.height() / 2 - height / 2); + triangle << triangleTopLeft; + triangle << triangleTopLeft + QPointF(width, 0); + triangle << triangleTopLeft + QPointF(width / 2, height); + // The expanded triangle should be a little lower, because its pointy shape makes it feel + // as if it's too high. + triangle.translate(QPointF(0,1)); + } + else { + QPointF triangleTopLeft(region.left() + ((width - height) / 2), region.top() + region.height() / 2 - width / 2); + triangle << triangleTopLeft; + triangle << triangleTopLeft + QPointF(height, width / 2); + triangle << triangleTopLeft + QPointF(0, width); + } + //qDebug() << "Painting triangle: " << triangle; + + QPolygonF triangleShadow(triangle); + triangleShadow.translate(QPointF(0, -1)); + + QPainterPath trianglePath; + QPainterPath triangleShadowPath; + QBrush triangleBrush(QColor(110, 110, 110)); + QBrush triangleShadowBrush(QColor(47, 47, 47)); + trianglePath.addPolygon(triangle); + triangleShadowPath.addPolygon(triangleShadow); + painter->fillPath(triangleShadowPath, triangleShadowBrush); + painter->fillPath(trianglePath, triangleBrush); } void GroupItemDelegate::paintShadowText(QPainter* painter, const QRect& region, const QString& text) const { - painter->setPen(QPen(QColor(254, 254, 254))); - painter->drawText(region.adjusted(0, 1, 0, 0), Qt::AlignTop, text); - painter->setPen(QPen(QColor(115, 115, 115))); - painter->drawText(region, Qt::AlignTop, text); + painter->setPen(QPen(QColor(254, 254, 254))); + painter->drawText(region.adjusted(0, 1, 0, 0), Qt::AlignTop, text); + painter->setPen(QPen(QColor(115, 115, 115))); + painter->drawText(region, Qt::AlignTop, text); } const int GroupItemDelegate::groupCornerRadius_ = 0; diff --git a/Swift/QtUI/Roster/GroupItemDelegate.h b/Swift/QtUI/Roster/GroupItemDelegate.h index 5d8ba0f..f989ed0 100644 --- a/Swift/QtUI/Roster/GroupItemDelegate.h +++ b/Swift/QtUI/Roster/GroupItemDelegate.h @@ -1,29 +1,29 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QStyledItemDelegate> #include <QColor> #include <QFont> +#include <QStyledItemDelegate> -#include "DelegateCommons.h" +#include <Swift/QtUI/Roster/DelegateCommons.h> namespace Swift { - class QtTreeWidgetItem; - class GroupItemDelegate { - public: - GroupItemDelegate(); - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QString& name, int rowCount, bool expanded) const; - private: - void paintShadowText(QPainter* painter, const QRect& region, const QString& text) const; - void paintExpansionTriangle(QPainter* painter, const QRect& region, int width, int height, bool expanded) const; - QFont groupFont_; - static const int groupCornerRadius_; - DelegateCommons common_; - }; + class QtTreeWidgetItem; + class GroupItemDelegate { + public: + GroupItemDelegate(); + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QString& name, int rowCount, bool expanded) const; + private: + void paintShadowText(QPainter* painter, const QRect& region, const QString& text) const; + void paintExpansionTriangle(QPainter* painter, const QRect& region, int width, int height, bool expanded) const; + QFont groupFont_; + static const int groupCornerRadius_; + DelegateCommons common_; + }; } diff --git a/Swift/QtUI/Roster/QtFilterWidget.cpp b/Swift/QtUI/Roster/QtFilterWidget.cpp new file mode 100644 index 0000000..c017d29 --- /dev/null +++ b/Swift/QtUI/Roster/QtFilterWidget.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2014-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/Roster/QtFilterWidget.h> + +#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> + +namespace Swift { + +QtFilterWidget::QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, UIEventStream* eventStream, QBoxLayout* layout) : QWidget(parent), treeView_(treeView), eventStream_(eventStream), fuzzyRosterFilter_(nullptr), 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() { + filterLineEdit_->removeEventFilter(this); + if (treeView_) { + treeView_->removeEventFilter(this); + if (treeView_->getRoster()) { + treeView_->getRoster()->onFilterAdded.disconnect(boost::bind(&QtFilterWidget::handleFilterAdded, this, _1)); + treeView_->getRoster()->onFilterRemoved.disconnect(boost::bind(&QtFilterWidget::handleFilterRemoved, this, _1)); + } + } +} + +bool QtFilterWidget::eventFilter(QObject*, QEvent* event) { + if (!treeView_) { + return false; + } + + 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_) + || (keyEvent->key() == Qt::Key_Menu)) { + 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(std::make_shared<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(); + for (auto 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)); + for (auto filter : filters_) { + treeView_->getRoster()->addFilter(filter); + } + filters_.clear(); +} + +void QtFilterWidget::updateRosterFilters() { + if (!treeView_) { + return; + } + + if (fuzzyRosterFilter_) { + if (filterLineEdit_->text().isEmpty()) { + // remove currently installed search filter and put old filters back + treeView_->getRoster()->removeFilter(fuzzyRosterFilter_); + delete fuzzyRosterFilter_; + fuzzyRosterFilter_ = nullptr; + 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 (!treeView_) { + return; + } + + if (fuzzyRosterFilter_) { + treeView_->getRoster()->removeFilter(fuzzyRosterFilter_); + delete fuzzyRosterFilter_; + fuzzyRosterFilter_ = nullptr; + } + 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..85f607e --- /dev/null +++ b/Swift/QtUI/Roster/QtFilterWidget.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2016-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <vector> + +#include <QBoxLayout> +#include <QPointer> +#include <QWidget> + +#include <Swift/Controllers/Roster/FuzzyRosterFilter.h> +#include <Swift/Controllers/Roster/RosterFilter.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 = nullptr); + 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_; + QPointer<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 12dc1e4..a12863d 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.cpp +++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp @@ -1,25 +1,26 @@ /* - * Copyright (c) 2011-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Roster/QtOccupantListWidget.h" +#include <Swift/QtUI/Roster/QtOccupantListWidget.h> -#include <QContextMenuEvent> -#include <QMenu> #include <QAction> +#include <QContextMenuEvent> #include <QInputDialog> +#include <QMenu> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "QtSwiftUtil.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) { } @@ -28,47 +29,47 @@ QtOccupantListWidget::~QtOccupantListWidget() { } void QtOccupantListWidget::setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions) { - availableOccupantActions_ = actions; + availableOccupantActions_ = actions; } void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) { - QModelIndex index = indexAt(event->pos()); - if (!index.isValid()) { - return; - } + QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) { + return; + } - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - if (contact) { - onSomethingSelectedChanged(contact); - QMenu contextMenu; - if (availableOccupantActions_.empty()) { - QAction* noAction = contextMenu.addAction(tr("No actions for this user")); - noAction->setEnabled(false); - contextMenu.exec(event->globalPos()); - } - else { - std::map<QAction*, ChatWindow::OccupantAction> actions; - foreach (ChatWindow::OccupantAction availableAction, availableOccupantActions_) { - QString text = "Error: missing string"; - switch (availableAction) { - case ChatWindow::Kick: text = tr("Kick user"); break; - case ChatWindow::Ban: text = tr("Kick and ban user"); break; - case ChatWindow::MakeModerator: text = tr("Make moderator"); break; - case ChatWindow::MakeParticipant: text = tr("Make participant"); break; - case ChatWindow::MakeVisitor: text = tr("Remove voice"); break; - case ChatWindow::AddContact: text = tr("Add to contacts"); break; - case ChatWindow::ShowProfile: text = tr("Show profile"); break; - } - QAction* action = contextMenu.addAction(text); - actions[action] = availableAction; - } - QAction* result = contextMenu.exec(event->globalPos()); - if (result) { - onOccupantActionSelected(actions[result], contact); - } - } - } + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact) { + onSomethingSelectedChanged(contact); + QMenu contextMenu; + if (availableOccupantActions_.empty()) { + QAction* noAction = contextMenu.addAction(tr("No actions for this user")); + noAction->setEnabled(false); + contextMenu.exec(event->globalPos()); + } + else { + std::map<QAction*, ChatWindow::OccupantAction> actions; + for (const auto& availableAction : availableOccupantActions_) { + QString text = "Error: missing string"; + switch (availableAction) { + case ChatWindow::Kick: text = tr("Kick user"); break; + case ChatWindow::Ban: text = tr("Kick and ban user"); break; + case ChatWindow::MakeModerator: text = tr("Make moderator"); break; + case ChatWindow::MakeParticipant: text = tr("Make participant"); break; + case ChatWindow::MakeVisitor: text = tr("Remove voice"); break; + case ChatWindow::AddContact: text = tr("Add to contacts"); break; + case ChatWindow::ShowProfile: text = tr("Show profile"); break; + } + QAction* action = contextMenu.addAction(text); + actions[action] = availableAction; + } + QAction* result = contextMenu.exec(event->globalPos()); + if (result) { + onOccupantActionSelected(actions[result], contact); + } + } + } } } diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.h b/Swift/QtUI/Roster/QtOccupantListWidget.h index 729115a..0f80943 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.h +++ b/Swift/QtUI/Roster/QtOccupantListWidget.h @@ -1,30 +1,31 @@ /* - * Copyright (c) 2011-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/QtUI/Roster/QtTreeWidget.h" +#include <boost/signals2.hpp> -#include "Swiften/Base/boost_bsignals.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + +#include <Swift/QtUI/Roster/QtTreeWidget.h> namespace Swift { class SettingsProvider; class QtOccupantListWidget : public QtTreeWidget { - Q_OBJECT - public: - QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); - virtual ~QtOccupantListWidget(); - void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions); - boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; - protected: - void contextMenuEvent(QContextMenuEvent* event); - private: - std::vector<ChatWindow::OccupantAction> availableOccupantActions_; + Q_OBJECT + public: + QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget privateMessageTarget, QWidget* parent = nullptr); + virtual ~QtOccupantListWidget(); + void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions); + boost::signals2::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; + protected: + void contextMenuEvent(QContextMenuEvent* event); + private: + std::vector<ChatWindow::OccupantAction> availableOccupantActions_; }; } diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index 6bf3989..935d6f6 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -1,32 +1,35 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <Swift/QtUI/Roster/QtRosterWidget.h> #include <QContextMenuEvent> -#include <QMenu> -#include <QInputDialog> #include <QFileDialog> +#include <QInputDialog> +#include <QMenu> +#include <QMessageBox> +#include <QPushButton> -#include <Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h> #include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h> -#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h> #include <Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.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/QtContactEditWindow.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) { } @@ -35,102 +38,125 @@ QtRosterWidget::~QtRosterWidget() { } void QtRosterWidget::handleEditUserActionTriggered(bool /*checked*/) { - QModelIndexList selectedIndexList = getSelectedIndexes(); - if (selectedIndexList.empty()) { - return; - } - QModelIndex index = selectedIndexList[0]; - if (!index.isValid()) { - return; - } - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { - eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID())); - } + QModelIndexList selectedIndexList = getSelectedIndexes(); + if (selectedIndexList.empty()) { + return; + } + QModelIndex index = selectedIndexList[0]; + if (!index.isValid()) { + return; + } + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + eventStream_->send(std::make_shared<RequestContactEditorUIEvent>(contact->getJID())); + } } void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { - QModelIndex index = indexAt(event->pos()); - if (!index.isValid()) { - return; - } - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - QMenu contextMenu; - if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { - QAction* editContact = contextMenu.addAction(tr("Edit…")); - QAction* removeContact = contextMenu.addAction(tr("Remove")); - QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile")); - - QAction* unblockContact = NULL; - if (contact->blockState() == ContactRosterItem::IsBlocked) { - unblockContact = contextMenu.addAction(tr("Unblock")); - } - - QAction* blockContact = NULL; - if (contact->blockState() == ContactRosterItem::IsUnblocked) { - blockContact = contextMenu.addAction(tr("Block")); - } + QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) { + return; + } + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + QMenu contextMenu; + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + QAction* editContact = contextMenu.addAction(tr("Edit…")); + editContact->setEnabled(isOnline()); + QAction* removeContact = contextMenu.addAction(tr("Remove")); + removeContact->setEnabled(isOnline()); + QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile")); + + QAction* unblockContact = nullptr; + if (contact->blockState() == ContactRosterItem::IsBlocked || + contact->blockState() == ContactRosterItem::IsDomainBlocked) { + unblockContact = contextMenu.addAction(tr("Unblock")); + unblockContact->setEnabled(isOnline()); + } + + QAction* blockContact = nullptr; + 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")); - } + QAction* sendFile = nullptr; + 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")); - } + QAction* startWhiteboardChat = nullptr; + if (contact->supportsFeature(ContactRosterItem::WhiteboardFeature)) { + startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat")); + startWhiteboardChat->setEnabled(isOnline()); + } #endif - - QAction* result = contextMenu.exec(event->globalPos()); - if (result == editContact) { - eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID())); - } - else if (result == removeContact) { - if (QtContactEditWindow::confirmContactDeletion(contact->getJID())) { - eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID())); - } - } - else if (result == showProfileForContact) { - eventStream_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(contact->getJID())); - } - else if (unblockContact && result == unblockContact) { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID())); - } - else if (blockContact && result == blockContact) { - eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, contact->getJID())); - } + QAction* result = contextMenu.exec(event->globalPos()); + if (result == editContact) { + eventStream_->send(std::make_shared<RequestContactEditorUIEvent>(contact->getJID())); + } + else if (result == removeContact) { + if (QtContactEditWindow::confirmContactDeletion(contact->getJID())) { + eventStream_->send(std::make_shared<RemoveRosterItemUIEvent>(contact->getJID())); + } + } + else if (result == showProfileForContact) { + eventStream_->send(std::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(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID().getDomain())); + } + } else { + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID())); + } + } + else if (blockContact && result == blockContact) { + eventStream_->send(std::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, contact->getJID())); + } #ifdef SWIFT_EXPERIMENTAL_FT - else if (sendFile && result == sendFile) { - QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;")); - if (!fileName.isEmpty()) { - eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), Q2PSTRING(fileName))); - } - } + else if (sendFile && result == sendFile) { + QString fileName = QFileDialog::getOpenFileName(this, tr("Send File"), "", tr("All Files (*);;")); + if (!fileName.isEmpty()) { + eventStream_->send(std::make_shared<SendFileUIEvent>(contact->getJID(), Q2PSTRING(fileName))); + } + } #endif #ifdef SWIFT_EXPERIMENTAL_WB - else if (startWhiteboardChat && result == startWhiteboardChat) { - eventStream_->send(boost::make_shared<RequestWhiteboardUIEvent>(contact->getJID())); - } + else if (startWhiteboardChat && result == startWhiteboardChat) { + eventStream_->send(std::make_shared<RequestWhiteboardUIEvent>(contact->getJID())); + } #endif - } - else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) { - QAction* renameGroupAction = contextMenu.addAction(tr("Rename")); - QAction* result = contextMenu.exec(event->globalPos()); - if (result == renameGroupAction) { - renameGroup(group); - } - } + } + 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) { + renameGroup(group); + } + } } void QtRosterWidget::renameGroup(GroupRosterItem* group) { - bool ok; - QString newName = QInputDialog::getText(NULL, tr("Rename group"), tr("Enter a new name for group '%1':").arg(P2QSTRING(group->getDisplayName())), QLineEdit::Normal, P2QSTRING(group->getDisplayName()), &ok); - if (ok) { - eventStream_->send(boost::make_shared<RenameGroupUIEvent>(group->getDisplayName(), Q2PSTRING(newName))); - } + bool ok; + QString newName = QInputDialog::getText(nullptr, tr("Rename group"), tr("Enter a new name for group '%1':").arg(P2QSTRING(group->getDisplayName())), QLineEdit::Normal, P2QSTRING(group->getDisplayName()), &ok); + if (ok) { + eventStream_->send(std::make_shared<RenameGroupUIEvent>(group->getDisplayName(), Q2PSTRING(newName))); + } } } diff --git a/Swift/QtUI/Roster/QtRosterWidget.h b/Swift/QtUI/Roster/QtRosterWidget.h index 549fe92..2cf8315 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.h +++ b/Swift/QtUI/Roster/QtRosterWidget.h @@ -1,27 +1,27 @@ /* - * Copyright (c) 2011 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include "Swift/QtUI/Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> namespace Swift { class QtUIPreferences; class QtRosterWidget : public QtTreeWidget { - Q_OBJECT - public: - QtRosterWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); - virtual ~QtRosterWidget(); - public slots: - void handleEditUserActionTriggered(bool checked); - protected: - void contextMenuEvent(QContextMenuEvent* event); - private: - void renameGroup(GroupRosterItem* group); + Q_OBJECT + public: + QtRosterWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = nullptr); + virtual ~QtRosterWidget(); + public slots: + void handleEditUserActionTriggered(bool checked); + protected: + void contextMenuEvent(QContextMenuEvent* event); + private: + void renameGroup(GroupRosterItem* group); }; } diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 99f1f34..ac9f541 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -1,196 +1,266 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> + +#include <memory> -#include <boost/smart_ptr/make_shared.hpp> #include <boost/bind.hpp> -#include <QUrl> +#include <QFont> +#include <QLabel> #include <QMimeData> +#include <QObject> +#include <QTimer> +#include <QToolTip> +#include <QUrl> #include <Swiften/Base/Platform.h> + #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> -#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> -#include <QtSwiftUtil.h> -#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <Swift/QtUI/QtSwiftUtil.h> #include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/Roster/RosterModel.h> namespace Swift { -QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) { - eventStream_ = eventStream; - settings_ = settings; - model_ = new RosterModel(this); - setModel(model_); - delegate_ = new RosterDelegate(this, settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); - setItemDelegate(delegate_); - setHeaderHidden(true); +QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget messageTarget, QWidget* parent) : QTreeView(parent), eventStream_(eventStream), settings_(settings), messageTarget_(messageTarget) { + model_ = new RosterModel(this, settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)); + setModel(model_); + delegate_ = new RosterDelegate(this, settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + setItemDelegate(delegate_); + setHeaderHidden(true); #ifdef SWIFT_PLATFORM_MACOSX - setAlternatingRowColors(true); + setAlternatingRowColors(true); #endif - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - expandAll(); - setAnimated(true); - setIndentation(0); + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + expandAll(); + setAnimated(true); + setIndentation(0); #ifdef SWIFT_EXPERIMENTAL_FT - setAcceptDrops(true); + setAcceptDrops(true); #endif - setDragEnabled(true); - setRootIsDecorated(true); - connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); - connect(model_, SIGNAL(itemExpanded(const QModelIndex&, bool)), this, SLOT(handleModelItemExpanded(const QModelIndex&, bool))); - connect(this, SIGNAL(expanded(const QModelIndex&)), this, SLOT(handleExpanded(const QModelIndex&))); - connect(this, SIGNAL(collapsed(const QModelIndex&)), this, SLOT(handleCollapsed(const QModelIndex&))); - connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleClicked(const QModelIndex&))); + setDragEnabled(true); + setRootIsDecorated(true); + connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); + connect(model_, SIGNAL(itemExpanded(const QModelIndex&, bool)), this, SLOT(handleModelItemExpanded(const QModelIndex&, bool))); + connect(this, SIGNAL(expanded(const QModelIndex&)), this, SLOT(handleExpanded(const QModelIndex&))); + connect(this, SIGNAL(collapsed(const QModelIndex&)), this, SLOT(handleCollapsed(const QModelIndex&))); + connect(this, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleClicked(const QModelIndex&))); - settings_->onSettingChanged.connect(boost::bind(&QtTreeWidget::handleSettingChanged, this, _1)); + settings_->onSettingChanged.connect(boost::bind(&QtTreeWidget::handleSettingChanged, this, _1)); + + QFont lato = font(); + lato.setFamily("Lato"); + setFont(lato); } QtTreeWidget::~QtTreeWidget() { - settings_->onSettingChanged.disconnect(boost::bind(&QtTreeWidget::handleSettingChanged, this, _1)); - delete model_; - delete delegate_; + settings_->onSettingChanged.disconnect(boost::bind(&QtTreeWidget::handleSettingChanged, this, _1)); + delete model_; + delete delegate_; } void QtTreeWidget::handleSettingChanged(const std::string& setting) { - if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) { - delegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); - repaint(); - } + if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) { + delegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + repaint(); + } +} + +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; - model_->setRoster(roster); - expandAll(); + roster_ = roster; + model_->setRoster(roster); + expandAll(); +} + +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_; + return treeRoot_; } void QtTreeWidget::handleClicked(const QModelIndex& index) { - GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); - if (item) { - setExpanded(index, !isExpanded(index)); - } - currentChanged(index, QModelIndex()); + GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); + if (item) { + setExpanded(index, !isExpanded(index)); + } + currentChanged(index, QModelIndex()); } QModelIndexList QtTreeWidget::getSelectedIndexes() const { - // Not using selectedIndexes(), because this seems to cause a crash in Qt (4.7.0) in the - // QModelIndexList destructor. - // This is a workaround posted in http://www.qtcentre.org/threads/16933 (although this case - // was resolved by linking against the debug libs, ours isn't, and we're not alone) - QItemSelection ranges = selectionModel()->selection(); - QModelIndexList selectedIndexList; - for (int i = 0; i < ranges.count(); ++i) { - QModelIndex parent = ranges.at(i).parent(); - int right = ranges.at(i).model()->columnCount(parent) - 1; - if (ranges.at(i).left() == 0 && ranges.at(i).right() == right) { - for (int r = ranges.at(i).top(); r <= ranges.at(i).bottom(); ++r) { - selectedIndexList.append(ranges.at(i).model()->index(r, 0, parent)); - } - } - } - return selectedIndexList; + // Not using selectedIndexes(), because this seems to cause a crash in Qt (4.7.0) in the + // QModelIndexList destructor. + // This is a workaround posted in http://www.qtcentre.org/threads/16933 (although this case + // was resolved by linking against the debug libs, ours isn't, and we're not alone) + QItemSelection ranges = selectionModel()->selection(); + QModelIndexList selectedIndexList; + for (int i = 0; i < ranges.count(); ++i) { + QModelIndex parent = ranges.at(i).parent(); + int right = ranges.at(i).model()->columnCount(parent) - 1; + if (ranges.at(i).left() == 0 && ranges.at(i).right() == right) { + for (int r = ranges.at(i).top(); r <= ranges.at(i).bottom(); ++r) { + selectedIndexList.append(ranges.at(i).model()->index(r, 0, parent)); + } + } + } + return selectedIndexList; } void QtTreeWidget::currentChanged(const QModelIndex& current, const QModelIndex& previous) { - RosterItem* item = NULL; - QModelIndexList selectedIndexList = getSelectedIndexes(); - if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) { - /* I didn't quite understand why using current didn't seem to work here.*/ - } - else if (current.isValid()) { - item = static_cast<RosterItem*>(current.internalPointer()); - item = dynamic_cast<ContactRosterItem*>(item); - } - onSomethingSelectedChanged(item); - QTreeView::currentChanged(current, previous); + RosterItem* item = nullptr; + QModelIndexList selectedIndexList = getSelectedIndexes(); + if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) { + /* I didn't quite understand why using current didn't seem to work here.*/ + } + else if (current.isValid()) { + item = static_cast<RosterItem*>(current.internalPointer()); + item = dynamic_cast<ContactRosterItem*>(item); + } + onSomethingSelectedChanged(item); + QTreeView::currentChanged(current, previous); } - void QtTreeWidget::handleItemActivated(const QModelIndex& index) { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - if (contact) { - eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(contact->getJID()))); - } + JID target = jidFromIndex(index); + if (target.isValid()) { + eventStream_->send(std::make_shared<RequestChatUIEvent>(target)); + } } void QtTreeWidget::dragEnterEvent(QDragEnterEvent *event) { - if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { - event->acceptProposedAction(); - } + if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) { + event->acceptProposedAction(); + } } void QtTreeWidget::dropEvent(QDropEvent *event) { - QModelIndex index = indexAt(event->pos()); - if (index.isValid()) { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { - if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { - QString filename = event->mimeData()->urls().at(0).toLocalFile(); - if (!filename.isEmpty()) { - eventStream_->send(boost::make_shared<SendFileUIEvent>(contact->getJID(), Q2PSTRING(filename))); - } - } - } - } + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + QString filename = event->mimeData()->urls().at(0).toLocalFile(); + if (!filename.isEmpty()) { + eventStream_->send(std::make_shared<SendFileUIEvent>(contact->getJID(), Q2PSTRING(filename))); + } + } + } + } } void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) { - QModelIndex index = indexAt(event->pos()); - if (index.isValid()) { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { - if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { - event->accept(); - return; - } - } - } - QTreeView::dragMoveEvent(event); + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) { + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { + if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { + event->accept(); + return; + } + } + } + QTreeView::dragMoveEvent(event); +} + +bool QtTreeWidget::event(QEvent* event) { + QChildEvent* childEvent = nullptr; + 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); } void QtTreeWidget::handleExpanded(const QModelIndex& index) { - GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); - if (item) { - item->setExpanded(true); - } + GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); + if (item) { + item->setExpanded(true); + } } void QtTreeWidget::handleCollapsed(const QModelIndex& index) { - GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); - if (item) { - item->setExpanded(false); - } + GroupRosterItem* item = dynamic_cast<GroupRosterItem*>(static_cast<RosterItem*>(index.internalPointer())); + if (item) { + item->setExpanded(false); + } } void QtTreeWidget::handleModelItemExpanded(const QModelIndex& index, bool shouldExpand) { - if (!index.isValid()) { - return; - } - bool alreadyRight = this->isExpanded(index) == shouldExpand; - if (alreadyRight) { - return; - } - setExpanded(index, shouldExpand); + if (!index.isValid()) { + return; + } + bool alreadyRight = this->isExpanded(index) == shouldExpand; + if (alreadyRight) { + return; + } + setExpanded(index, shouldExpand); } void QtTreeWidget::drawBranches(QPainter*, const QRect&, const QModelIndex&) const { } void QtTreeWidget::show() { - QWidget::show(); + QWidget::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())); + } + 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..331458a 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.h +++ b/Swift/QtUI/Roster/QtTreeWidget.h @@ -1,61 +1,80 @@ /* - * Copyright (c) 2010-2012 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #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 <QDropEvent> +#include <QModelIndex> +#include <QTreeView> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> + +#include <Swift/QtUI/Roster/RosterDelegate.h> +#include <Swift/QtUI/Roster/RosterModel.h> namespace Swift { class UIEventStream; class SettingsProvider; -class QtTreeWidget : public QTreeView{ - Q_OBJECT - public: - QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); - ~QtTreeWidget(); - void show(); - QtTreeWidgetItem* getRoot(); - void setRosterModel(Roster* roster); - Roster* getRoster() {return roster_;} - boost::signal<void (RosterItem*)> onSomethingSelectedChanged; - - private slots: - void handleItemActivated(const QModelIndex&); - void handleModelItemExpanded(const QModelIndex&, bool expanded); - void handleExpanded(const QModelIndex&); - void handleCollapsed(const QModelIndex&); - void handleClicked(const QModelIndex&); - void handleSettingChanged(const std::string& setting); - protected: - void dragEnterEvent(QDragEnterEvent* event); - void dropEvent(QDropEvent* event); - void dragMoveEvent(QDragMoveEvent* event); - - protected: - QModelIndexList getSelectedIndexes() const; - private: - void drawBranches(QPainter*, const QRect&, const QModelIndex&) const; - protected slots: - virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); - protected: - UIEventStream* eventStream_; - - private: - RosterModel* model_; - Roster* roster_; - RosterDelegate* delegate_; - QtTreeWidgetItem* treeRoot_; - SettingsProvider* settings_; +class QtTreeWidget : public QTreeView { + Q_OBJECT + public: + enum MessageTarget {MessageDefaultJID, MessageDisplayJID}; + + QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget messageTarget, QWidget* parent = nullptr); + ~QtTreeWidget(); + void show(); + QtTreeWidgetItem* getRoot(); + 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::signals2::signal<void (RosterItem*)> onSomethingSelectedChanged; + + private slots: + void handleItemActivated(const QModelIndex&); + void handleModelItemExpanded(const QModelIndex&, bool expanded); + void handleExpanded(const QModelIndex&); + void handleCollapsed(const QModelIndex&); + 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); + 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); + protected: + UIEventStream* eventStream_; + + private: + RosterModel* model_; + Roster* roster_ = nullptr; + RosterDelegate* delegate_; + QtTreeWidgetItem* treeRoot_ = nullptr; + SettingsProvider* settings_; + bool tooltipShown_ = false; + MessageTarget messageTarget_; + bool isOnline_ = false; }; } diff --git a/Swift/QtUI/Roster/RosterDelegate.cpp b/Swift/QtUI/Roster/RosterDelegate.cpp index aef588c..061982e 100644 --- a/Swift/QtUI/Roster/RosterDelegate.cpp +++ b/Swift/QtUI/Roster/RosterDelegate.cpp @@ -1,83 +1,83 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "RosterDelegate.h" +#include <Swift/QtUI/Roster/RosterDelegate.h> #include <QApplication> -#include <QPainter> -#include <QColor> +#include <QBitmap> #include <QBrush> +#include <QColor> +#include <QDebug> #include <QFontMetrics> +#include <QPainter> #include <QPainterPath> #include <QPolygon> -#include <qdebug.h> -#include <QBitmap> -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> -#include "QtTreeWidget.h" -#include "RosterModel.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <Swift/QtUI/Roster/RosterModel.h> namespace Swift { RosterDelegate::RosterDelegate(QtTreeWidget* tree, bool compact) : compact_(compact) { - tree_ = tree; - groupDelegate_ = new GroupItemDelegate(); + tree_ = tree; + groupDelegate_ = new GroupItemDelegate(); } RosterDelegate::~RosterDelegate() { - delete groupDelegate_; + delete groupDelegate_; } void RosterDelegate::setCompact(bool compact) { - compact_ = compact; - emit sizeHintChanged(QModelIndex()); + compact_ = compact; + emit sizeHintChanged(QModelIndex()); } - + QSize RosterDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - if (dynamic_cast<GroupRosterItem*>(item)) { - return groupDelegate_->sizeHint(option, index); - } - return contactSizeHint(option, index); + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (dynamic_cast<GroupRosterItem*>(item)) { + return groupDelegate_->sizeHint(option, index); + } + return contactSizeHint(option, index); } QSize RosterDelegate::contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { - return common_.contactSizeHint(option, index, compact_); + return common_.contactSizeHint(option, index, compact_); } void RosterDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - if (dynamic_cast<GroupRosterItem*>(item)) { - paintGroup(painter, option, index); - } else { - paintContact(painter, option, index); - } + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + if (dynamic_cast<GroupRosterItem*>(item)) { + paintGroup(painter, option, index); + } else { + paintContact(painter, option, index); + } } void RosterDelegate::paintGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - if (index.isValid()) { - groupDelegate_->paint(painter, option, index.data(Qt::DisplayRole).toString(), index.data(ChildCountRole).toInt(), tree_->isExpanded(index)); - } + if (index.isValid()) { + groupDelegate_->paint(painter, option, index.data(Qt::DisplayRole).toString(), index.data(ChildCountRole).toInt(), tree_->isExpanded(index)); + } } void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - QColor nameColor = index.data(Qt::TextColorRole).value<QColor>(); - QString avatarPath; - if (index.data(AvatarRole).isValid() && !index.data(AvatarRole).value<QString>().isNull()) { - avatarPath = index.data(AvatarRole).value<QString>(); - } - QIcon presenceIcon = index.data(PresenceIconRole).isValid() && !index.data(PresenceIconRole).value<QIcon>().isNull() - ? index.data(PresenceIconRole).value<QIcon>() - : QIcon(":/icons/offline.png"); - bool isIdle = index.data(IdleRole).isValid() ? index.data(IdleRole).toBool() : false; - QString name = index.data(Qt::DisplayRole).toString(); - QString statusText = index.data(StatusTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, isIdle, 0, compact_); + QColor nameColor = index.data(Qt::TextColorRole).value<QColor>(); + QString avatarPath; + if (index.data(AvatarRole).isValid() && !index.data(AvatarRole).value<QString>().isNull()) { + avatarPath = index.data(AvatarRole).value<QString>(); + } + QIcon presenceIcon = index.data(PresenceIconRole).isValid() && !index.data(PresenceIconRole).value<QIcon>().isNull() + ? index.data(PresenceIconRole).value<QIcon>() + : QIcon(":/icons/offline.png"); + bool isIdle = index.data(IdleRole).isValid() ? index.data(IdleRole).toBool() : false; + QString name = index.data(Qt::DisplayRole).toString(); + QString statusText = index.data(StatusTextRole).toString(); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, isIdle, 0, compact_); } } diff --git a/Swift/QtUI/Roster/RosterDelegate.h b/Swift/QtUI/Roster/RosterDelegate.h index c5db7ef..34c1569 100644 --- a/Swift/QtUI/Roster/RosterDelegate.h +++ b/Swift/QtUI/Roster/RosterDelegate.h @@ -1,35 +1,35 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QStyledItemDelegate> #include <QColor> #include <QFont> +#include <QStyledItemDelegate> -#include "GroupItemDelegate.h" -#include "DelegateCommons.h" +#include <Swift/QtUI/Roster/DelegateCommons.h> +#include <Swift/QtUI/Roster/GroupItemDelegate.h> namespace Swift { - class QtTreeWidget; - class RosterDelegate : public QStyledItemDelegate { - public: - RosterDelegate(QtTreeWidget* tree, bool compact); - ~RosterDelegate(); - 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: - QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; - void paintGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - bool compact_; - DelegateCommons common_; - GroupItemDelegate* groupDelegate_; - QtTreeWidget* tree_; - }; + class QtTreeWidget; + class RosterDelegate : public QStyledItemDelegate { + public: + RosterDelegate(QtTreeWidget* tree, bool compact); + ~RosterDelegate(); + 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: + QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paintGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + bool compact_; + DelegateCommons common_; + GroupItemDelegate* groupDelegate_; + QtTreeWidget* tree_; + }; } diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp index 3791ffa..ef4d778 100644 --- a/Swift/QtUI/Roster/RosterModel.cpp +++ b/Swift/QtUI/Roster/RosterModel.cpp @@ -1,263 +1,279 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "RosterModel.h" +#include <Swift/QtUI/Roster/RosterModel.h> #include <boost/bind.hpp> #include <QColor> #include <QIcon> #include <QMimeData> + #include <qdebug.h> -#include "Swiften/Elements/StatusShow.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include <Swift/Controllers/StatusUtil.h> #include <Swiften/Base/Path.h> +#include <Swiften/Elements/StatusShow.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/QtResourceHelper.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <Swift/QtUI/Roster/RosterTooltip.h> namespace Swift { -RosterModel::RosterModel(QtTreeWidget* view) : view_(view) { - roster_ = NULL; +RosterModel::RosterModel(QtTreeWidget* view, bool screenReaderMode) : roster_(nullptr), view_(view), screenReader_(screenReaderMode) { + const int tooltipAvatarSize = 96; // maximal suggested size according to XEP-0153 + cachedImageScaler_ = new QtScaledAvatarCache(tooltipAvatarSize); } RosterModel::~RosterModel() { + delete cachedImageScaler_; } void RosterModel::setRoster(Roster* roster) { - roster_ = roster; - if (roster_) { - roster->onChildrenChanged.connect(boost::bind(&RosterModel::handleChildrenChanged, this, _1)); - roster->onDataChanged.connect(boost::bind(&RosterModel::handleDataChanged, this, _1)); - } - reLayout(); + roster_ = roster; + if (roster_) { + roster->onChildrenChanged.connect(boost::bind(&RosterModel::handleChildrenChanged, this, _1)); + roster->onDataChanged.connect(boost::bind(&RosterModel::handleDataChanged, this, _1)); + } + reLayout(); } void RosterModel::reLayout() { - //emit layoutChanged(); - beginResetModel(); - endResetModel(); // TODO: Not sure if this isn't too early? - if (!roster_) { - return; - } - foreach (RosterItem* item, roster_->getRoot()->getDisplayedChildren()) { - GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(item); - if (!child) continue; - emit itemExpanded(index(child), child->isExpanded()); - } + //emit layoutChanged(); + beginResetModel(); + endResetModel(); // TODO: Not sure if this isn't too early? + if (!roster_) { + return; + } + for (auto item : roster_->getRoot()->getDisplayedChildren()) { + GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(item); + if (!child) continue; + emit itemExpanded(index(child), child->isExpanded()); + } } void RosterModel::handleChildrenChanged(GroupRosterItem* /*group*/) { - reLayout(); + reLayout(); } void RosterModel::handleDataChanged(RosterItem* item) { - Q_ASSERT(item); - QModelIndex modelIndex = index(item); - if (modelIndex.isValid()) { - emit dataChanged(modelIndex, modelIndex); - } + Q_ASSERT(item); + QModelIndex modelIndex = index(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; + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (dynamic_cast<GroupRosterItem*>(getItem(index)) == nullptr) { + flags |= Qt::ItemIsDragEnabled; + } + return flags; } int RosterModel::columnCount(const QModelIndex& /*parent*/) const { - return 1; + return 1; } RosterItem* RosterModel::getItem(const QModelIndex& index) const { - return index.isValid() ? static_cast<RosterItem*>(index.internalPointer()) : NULL; + return index.isValid() ? static_cast<RosterItem*>(index.internalPointer()) : nullptr; } QVariant RosterModel::data(const QModelIndex& index, int role) const { - RosterItem* item = getItem(index); - if (!item) return QVariant(); - - switch (role) { - case Qt::DisplayRole: return P2QSTRING(item->getDisplayName()); - case Qt::TextColorRole: return getTextColor(item); - case Qt::BackgroundColorRole: return getBackgroundColor(item); - case Qt::ToolTipRole: return getToolTip(item); - case StatusTextRole: return getStatusText(item); - case AvatarRole: return getAvatar(item); - case PresenceIconRole: return getPresenceIcon(item); - case ChildCountRole: return getChildCount(item); - case IdleRole: return getIsIdle(item); - default: return QVariant(); - } + RosterItem* item = getItem(index); + if (!item) return QVariant(); + + switch (role) { + case Qt::DisplayRole: return getScreenReaderTextOr(item, P2QSTRING(item->getDisplayName())); + case Qt::TextColorRole: return getTextColor(item); + case Qt::BackgroundColorRole: return getBackgroundColor(item); + case Qt::ToolTipRole: return getToolTip(item); + case StatusTextRole: return getStatusText(item); + case AvatarRole: return getAvatar(item); + 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); - return group ? group->getDisplayedChildren().size() : 0; + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); + return group ? group->getDisplayedChildren().size() : 0; } bool RosterModel::getIsIdle(RosterItem* item) const { - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - return contact ? !contact->getIdleText().empty() : false; + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + return contact ? !contact->getIdleText().empty() : false; } QColor RosterModel::intToColor(int color) const { - return QColor( - ((color & 0xFF0000)>>16), - ((color & 0xFF00)>>8), - (color & 0xFF)); + return QColor( + ((color & 0xFF0000)>>16), + ((color & 0xFF00)>>8), + (color & 0xFF)); } QColor RosterModel::getTextColor(RosterItem* item) const { - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - int color = 0; - if (contact) { - switch (contact->getStatusShow()) { - case StatusShow::Online: color = 0x000000; break; - case StatusShow::Away: color = 0x336699; break; - case StatusShow::XA: color = 0x336699; break; - case StatusShow::FFC: color = 0x000000; break; - case StatusShow::DND: color = 0x990000; break; - case StatusShow::None: color = 0x7F7F7F;break; - } - } - return intToColor(color); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + int color = 0; + if (contact) { + switch (contact->getStatusShow()) { + case StatusShow::Online: color = 0x595959; break; + case StatusShow::Away: color = 0x336699; break; + case StatusShow::XA: color = 0x336699; break; + case StatusShow::FFC: color = 0x595959; break; + case StatusShow::DND: color = 0x990000; break; + case StatusShow::None: color = 0x7F7F7F;break; + } + } + return intToColor(color); } QColor RosterModel::getBackgroundColor(RosterItem* item) const { - return dynamic_cast<ContactRosterItem*>(item) ? intToColor(0xFFFFFF) : intToColor(0x969696); + return dynamic_cast<ContactRosterItem*>(item) ? intToColor(0xFFFFFF) : intToColor(0x969696); } QString RosterModel::getToolTip(RosterItem* item) const { - QString tip(P2QSTRING(item->getDisplayName())); - 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); - } - if (!contact->getIdleText().empty()) { - tip += "\n " + tr("Idle since ") + P2QSTRING(contact->getIdleText()); - } - } - return tip; + QString tip(P2QSTRING(item->getDisplayName())); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact) { + return RosterTooltip::buildDetailedTooltip(contact, cachedImageScaler_); + } + return tip; } QString RosterModel::getAvatar(RosterItem* item) const { - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - if (!contact) { - return ""; - } - return P2QSTRING(pathToString(contact->getAvatarPath())); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (!contact) { + return ""; + } + return P2QSTRING(pathToString(contact->getAvatarPath())); } QString RosterModel::getStatusText(RosterItem* item) const { - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - if (!contact) return ""; - return P2QSTRING(contact->getStatusText()); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (!contact) return ""; + return P2QSTRING(contact->getStatusText()); +} + +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(); - if (contact->blockState() == ContactRosterItem::IsBlocked) { - return QIcon(":/icons/stop.png"); - } - - QString iconString; - switch (contact->getStatusShow()) { - case StatusShow::Online: iconString = "online";break; - case StatusShow::Away: iconString = "away";break; - case StatusShow::XA: iconString = "away";break; - case StatusShow::FFC: iconString = "online";break; - case StatusShow::DND: iconString = "dnd";break; - case StatusShow::None: iconString = "offline";break; - } - return QIcon(":/icons/" + iconString + ".png"); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (!contact) return QIcon(); + if (contact->blockState() == ContactRosterItem::IsBlocked || + contact->blockState() == ContactRosterItem::IsDomainBlocked) { + return QIcon(":/icons/stop.png"); + } + + return QIcon(statusShowTypeToIconPath(contact->getStatusShow())); } QModelIndex RosterModel::index(int row, int column, const QModelIndex& parent) const { - if (!roster_) { - return QModelIndex(); - } - GroupRosterItem* parentItem; - if (!parent.isValid()) { - //top level - parentItem = roster_->getRoot(); - } else { - parentItem = dynamic_cast<GroupRosterItem*>(getItem(parent)); - if (!parentItem) return QModelIndex(); - } - return static_cast<size_t>(row) < parentItem->getDisplayedChildren().size() ? createIndex(row, column, parentItem->getDisplayedChildren()[row]) : QModelIndex(); + if (!roster_) { + return QModelIndex(); + } + GroupRosterItem* parentItem; + if (!parent.isValid()) { + //top level + parentItem = roster_->getRoot(); + } else { + parentItem = dynamic_cast<GroupRosterItem*>(getItem(parent)); + if (!parentItem) return QModelIndex(); + } + return static_cast<size_t>(row) < parentItem->getDisplayedChildren().size() ? createIndex(row, column, parentItem->getDisplayedChildren()[row]) : QModelIndex(); } QModelIndex RosterModel::index(RosterItem* item) const { - GroupRosterItem* parent = item->getParent(); - /* Recursive check that it's ok to create such an item - Assuming there are more contacts in a group than groups in a - group, this could save a decent chunk of search time at startup.*/ - if (parent == NULL || roster_ == NULL || (parent != roster_->getRoot() && !index(parent).isValid())) { - return QModelIndex(); - } - for (size_t i = 0; i < parent->getDisplayedChildren().size(); i++) { - if (parent->getDisplayedChildren()[i] == item) { - return createIndex(i, 0, item); - } - } - return QModelIndex(); + GroupRosterItem* parent = item->getParent(); + /* Recursive check that it's ok to create such an item + Assuming there are more contacts in a group than groups in a + group, this could save a decent chunk of search time at startup.*/ + if (parent == nullptr || roster_ == nullptr || (parent != roster_->getRoot() && !index(parent).isValid())) { + return QModelIndex(); + } + for (size_t i = 0; i < parent->getDisplayedChildren().size(); i++) { + if (parent->getDisplayedChildren()[i] == item) { + return createIndex(i, 0, item); + } + } + return QModelIndex(); } QModelIndex RosterModel::parent(const QModelIndex& child) const { - if (!roster_ || !child.isValid()) { - return QModelIndex(); - } - - GroupRosterItem* parent = getItem(child)->getParent(); - return (parent != roster_->getRoot()) ? index(parent) : QModelIndex(); + if (!roster_ || !child.isValid()) { + return QModelIndex(); + } + + GroupRosterItem* parent = getItem(child)->getParent(); + return (parent != roster_->getRoot()) ? index(parent) : QModelIndex(); } int RosterModel::rowCount(const QModelIndex& parent) const { - if (!roster_) return 0; - RosterItem* item = parent.isValid() ? static_cast<RosterItem*>(parent.internalPointer()) : roster_->getRoot(); - Q_ASSERT(item); - GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); - int count = group ? group->getDisplayedChildren().size() : 0; -// qDebug() << "rowCount = " << count << " where parent.isValid() == " << parent.isValid() << ", group == " << (group ? P2QSTRING(group->getDisplayName()) : "*contact*"); - return count; + if (!roster_) return 0; + RosterItem* item = parent.isValid() ? static_cast<RosterItem*>(parent.internalPointer()) : roster_->getRoot(); + Q_ASSERT(item); + GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); + int count = group ? group->getDisplayedChildren().size() : 0; +// qDebug() << "rowCount = " << count << " where parent.isValid() == " << parent.isValid() << ", group == " << (group ? P2QSTRING(group->getDisplayName()) : "*contact*"); + return count; } QMimeData* RosterModel::mimeData(const QModelIndexList& indexes) const { - QMimeData* data = QAbstractItemModel::mimeData(indexes); - - ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(getItem(indexes.first())); - if (item == NULL) { - return data; - } - - QByteArray itemData; - QDataStream dataStream(&itemData, QIODevice::WriteOnly); - - // jid, chatName, activity, statusType, avatarPath - dataStream << P2QSTRING(item->getJID().toString()); - dataStream << P2QSTRING(item->getDisplayName()); - dataStream << P2QSTRING(item->getStatusText()); - dataStream << item->getSimplifiedStatusShow(); - dataStream << P2QSTRING(item->getAvatarPath().string()); - data->setData("application/vnd.swift.contact-jid", itemData); - return data; + QMimeData* data = QAbstractItemModel::mimeData(indexes); + + ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(getItem(indexes.first())); + if (item == nullptr) { + 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 cae80c4..af0d43a 100644 --- a/Swift/QtUI/Roster/RosterModel.h +++ b/Swift/QtUI/Roster/RosterModel.h @@ -1,60 +1,71 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2013 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #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 { - StatusTextRole = Qt::UserRole, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2, - StatusShowTypeRole = Qt::UserRole + 3, - ChildCountRole = Qt::UserRole + 4, - IdleRole = Qt::UserRole + 5 - }; - - class QtTreeWidget; - - class RosterModel : public QAbstractItemModel { - Q_OBJECT - public: - RosterModel(QtTreeWidget* view); - ~RosterModel(); - void setRoster(Roster* swiftRoster); - Qt::ItemFlags flags(const QModelIndex& index) const; - int columnCount(const QModelIndex& parent = QModelIndex()) const; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; - QModelIndex index(RosterItem* item) const; - QModelIndex parent(const QModelIndex& index) const; - int rowCount(const QModelIndex& parent = QModelIndex()) const; - QMimeData* mimeData(const QModelIndexList& indexes) const; - - signals: - void itemExpanded(const QModelIndex& item, bool expanded); - private: - void handleDataChanged(RosterItem* item); - void handleChildrenChanged(GroupRosterItem* item); - RosterItem* getItem(const QModelIndex& index) const; - QColor intToColor(int color) const; - QColor getTextColor(RosterItem* item) const; - QColor getBackgroundColor(RosterItem* item) const; - QString getToolTip(RosterItem* item) const; - QString getAvatar(RosterItem* item) const; - QString getStatusText(RosterItem* item) const; - QIcon getPresenceIcon(RosterItem* item) const; - int getChildCount(RosterItem* item) const; - bool getIsIdle(RosterItem* item) const; - void reLayout(); - Roster* roster_; - QtTreeWidget* view_; - }; + enum RosterRoles { + StatusTextRole = Qt::UserRole, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2, + StatusShowTypeRole = Qt::UserRole + 3, + ChildCountRole = Qt::UserRole + 4, + IdleRole = Qt::UserRole + 5, + JIDRole = Qt::UserRole + 6, + DisplayJIDRole = Qt::UserRole + 7 + }; + + class QtTreeWidget; + + class RosterModel : public QAbstractItemModel { + Q_OBJECT + public: + 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; + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + QModelIndex index(RosterItem* item) const; + QModelIndex parent(const QModelIndex& index) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + + signals: + void itemExpanded(const QModelIndex& item, bool expanded); + private: + void handleDataChanged(RosterItem* item); + void handleChildrenChanged(GroupRosterItem* item); + RosterItem* getItem(const QModelIndex& index) const; + QColor intToColor(int color) const; + QColor getTextColor(RosterItem* item) const; + QColor getBackgroundColor(RosterItem* item) const; + QString getToolTip(RosterItem* item) const; + 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..ea4c9cd --- /dev/null +++ b/Swift/QtUI/Roster/RosterTooltip.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Roster/RosterTooltip.h> + +#include <QApplication> +#include <QObject> +#include <QString> + +#include <Swiften/Base/Path.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/StatusUtil.h> +#include <Swift/Controllers/Translator.h> + +#include <Swift/QtUI/QtResourceHelper.h> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUtilities.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.svg" : 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()); + + boost::posix_time::ptime idleTime = contact->getIdle(); + QString idleString; + if (!idleTime.is_not_a_date_time()) { + idleString = QObject::tr("Idle since %1").arg(P2QSTRING(Swift::Translator::getInstance()->ptimeToHumanReadableString(idleTime))); + idleString = htmlEscape(idleString) + "<br/>"; + } + + boost::posix_time::ptime lastSeenTime = contact->getOfflineSince(); + QString lastSeen; + if (!lastSeenTime.is_not_a_date_time()) { + lastSeen = QObject::tr("Last seen %1").arg(P2QSTRING(Swift::Translator::getInstance()->ptimeToHumanReadableString(lastSeenTime))); + 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; + for (const auto& 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 = ""; + for (const auto& 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 = ""; + for (const auto& org : vcard->getOrganizations()) { + QString field = buildVCardField(false, QObject::tr("Organization"), htmlEscape(P2QSTRING(org.name))); + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + for (const auto& 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..642809f --- /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/Roster/main.cpp b/Swift/QtUI/Roster/main.cpp index 7f5e3d1..c0402e8 100644 --- a/Swift/QtUI/Roster/main.cpp +++ b/Swift/QtUI/Roster/main.cpp @@ -1,66 +1,67 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #include <QtGui> -#include "QtTreeWidget.h" -#include "QtTreeWidgetFactory.h" -#include "Swiften/Elements/StatusShow.h" +#include <Swiften/Elements/StatusShow.h> + +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <Swift/QtUI/Roster/QtTreeWidgetFactory.h> int main(int argc, char *argv[]) { - QApplication app(argc, argv); + QApplication app(argc, argv); + + //Swift::RosterModel model; + + //QTreeView view; + //view.setModel(&model); + //view.setWindowTitle("A roster"); + //view.show(); - //Swift::RosterModel model; + Swift::QtTreeWidgetFactory treeWidgetFactory; + Swift::QtTreeWidget* tree = dynamic_cast<Swift::QtTreeWidget*>(treeWidgetFactory.createTreeWidget()); + tree->show(); + QList<Swift::QtTreeWidgetItem*> item3s; + for (int i = 0; i < 500; i++) { + Swift::QtTreeWidgetItem* group = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(tree)); + group->setText("People"); + group->setBackgroundColor(0xBBBBBB); + Swift::QtTreeWidgetItem* item1 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); + Swift::QtTreeWidgetItem* item2 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); + Swift::QtTreeWidgetItem* item3 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); + Swift::QtTreeWidgetItem* item4 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); + item1->setText("Remko"); + item2->setText("Kevin"); + item3->setText("Cath"); + item4->setText("KimTypo"); + item4->setText("Kim"); + item3s.push_back(item3); + } - //QTreeView view; - //view.setModel(&model); - //view.setWindowTitle("A roster"); - //view.show(); + Swift::QtTreeWidgetItem* group = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(tree)); + group->setText("Many People"); - Swift::QtTreeWidgetFactory treeWidgetFactory; - Swift::QtTreeWidget* tree = dynamic_cast<Swift::QtTreeWidget*>(treeWidgetFactory.createTreeWidget()); - tree->show(); - QList<Swift::QtTreeWidgetItem*> item3s; - for (int i = 0; i < 500; i++) { - Swift::QtTreeWidgetItem* group = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(tree)); - group->setText("People"); - group->setBackgroundColor(0xBBBBBB); - Swift::QtTreeWidgetItem* item1 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); - Swift::QtTreeWidgetItem* item2 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); - Swift::QtTreeWidgetItem* item3 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); - Swift::QtTreeWidgetItem* item4 = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); - item1->setText("Remko"); - item2->setText("Kevin"); - item3->setText("Cath"); - item4->setText("KimTypo"); - item4->setText("Kim"); - item3s.push_back(item3); - } - - Swift::QtTreeWidgetItem* group = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(tree)); - group->setText("Many People"); - - Swift::QtTreeWidgetItem* person350; - Swift::QtTreeWidgetItem* person1200; + Swift::QtTreeWidgetItem* person350; + Swift::QtTreeWidgetItem* person1200; - for (int i = 0; i < 1500; i++) { - Swift::QtTreeWidgetItem* item = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); - item->setText(Q2PSTRING(QString("Some person %1").arg(i))); - item->setStatusShow(Swift::StatusShow::Away); - if (i == 350) person350 = item; - if (i == 1200) person1200 = item; - } + for (int i = 0; i < 1500; i++) { + Swift::QtTreeWidgetItem* item = dynamic_cast<Swift::QtTreeWidgetItem*>(treeWidgetFactory.createTreeWidgetItem(group)); + item->setText(Q2PSTRING(QString("Some person %1").arg(i))); + item->setStatusShow(Swift::StatusShow::Away); + if (i == 350) person350 = item; + if (i == 1200) person1200 = item; + } - for (int i = 0; i < item3s.size(); i++) { - item3s[i]->setStatusShow(Swift::StatusShow::XA); - } + for (int i = 0; i < item3s.size(); i++) { + item3s[i]->setStatusShow(Swift::StatusShow::XA); + } - person350->setStatusShow(Swift::StatusShow::DND); - person1200->setStatusShow(Swift::StatusShow::Online); + person350->setStatusShow(Swift::StatusShow::DND); + person1200->setStatusShow(Swift::StatusShow::Online); - return app.exec(); + return app.exec(); } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 6835872..96979c0 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -1,56 +1,59 @@ -import os, shutil, datetime, re, time +import os, datetime, re, time import Version - -def generateDefaultTheme(dir) : - sourceDir = dir.abspath - result = "<!-- WARNING: This file is automatically generated. Any changes will be overwritten. -->\n" - result += "<RCC version =\"1.0\">" - result += "<qresource prefix=\"/themes/Default\">" - for (path, dirs, files) in os.walk(sourceDir) : - for file in files : - filePath = os.path.join(path,file) - result += "<file alias=\"%(alias)s\">%(path)s</file>" % { - "alias": filePath[len(sourceDir)+1:], - "path": filePath - } - result += "</qresource>" - result += "</RCC>" - return result +import SCons.Util + +def generateQRCTheme(dir, prefix) : + sourceDir = dir.abspath + result = "<!-- WARNING: This file is automatically generated. Any changes will be overwritten. -->\n" + result += "<RCC version =\"1.0\">" + result += "<qresource prefix=\"/themes/" + prefix + "\">" + for (path, dirs, files) in os.walk(sourceDir) : + #skip the Noto emoji fonts in Windows. No need to package them since they aren't used + if "Noto" in path and not env["PLATFORM"] == "linux" : + continue + dirs.sort() + files.sort() + for file in files : + filePath = os.path.join(path,file) + result += "<file alias=\"%(alias)s\">%(path)s</file>" % { + "alias": filePath[len(sourceDir)+1:], + "path": filePath + } + result += "</qresource>" + result += "</RCC>" + return result Import("env") -myenv = env.Clone() +myenv = env.Clone(tools = [ 'textfile' ]) # Disable warnings that affect Qt -myenv["CXXFLAGS"] = filter(lambda x : x != "-Wfloat-equal", myenv["CXXFLAGS"]) +myenv["CXXFLAGS"] = list(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.Append(CXXFLAGS = ["-Wno-float-equal", "-Wno-shorten-64-to-32", "-Wno-conversion"]) myenv.UseFlags(env["SWIFT_CONTROLLERS_FLAGS"]) myenv.UseFlags(env["SWIFTOOLS_FLAGS"]) if myenv["HAVE_XSS"] : - myenv.UseFlags(env["XSS_FLAGS"]) + myenv.UseFlags(env["XSS_FLAGS"]) if env["PLATFORM"] == "posix" : - myenv.Append(LIBS = ["X11"]) + myenv.Append(LIBS = ["X11"]) if myenv["HAVE_SPARKLE"] : - myenv.UseFlags(env["SPARKLE_FLAGS"]) + myenv.UseFlags(env["SPARKLE_FLAGS"]) myenv.UseFlags(env["SWIFTEN_FLAGS"]) myenv.UseFlags(env["SWIFTEN_DEP_FLAGS"]) if myenv.get("HAVE_BREAKPAD") : - myenv.UseFlags(env["BREAKPAD_FLAGS"]) + myenv.UseFlags(env["BREAKPAD_FLAGS"]) if myenv.get("HAVE_GROWL", False) : - myenv.UseFlags(myenv["GROWL_FLAGS"]) - myenv.Append(CPPDEFINES = ["HAVE_GROWL"]) + myenv.UseFlags(myenv["GROWL_FLAGS"]) + myenv.Append(CPPDEFINES = ["HAVE_GROWL"]) if myenv["swift_mobile"] : - myenv.Append(CPPDEFINES = ["SWIFT_MOBILE"]) -if myenv.get("HAVE_SNARL", False) : - myenv.UseFlags(myenv["SNARL_FLAGS"]) - myenv.Append(CPPDEFINES = ["HAVE_SNARL"]) + myenv.Append(CPPDEFINES = ["SWIFT_MOBILE"]) if myenv.get("HAVE_HUNSPELL", True): - myenv.Append(CPPDEFINES = ["HAVE_HUNSPELL"]) - myenv.UseFlags(myenv["HUNSPELL_FLAGS"]) + myenv.Append(CPPDEFINES = ["HAVE_HUNSPELL"]) + myenv.UseFlags(myenv["HUNSPELL_FLAGS"]) if env["PLATFORM"] == "win32" : - myenv.Append(LIBS = ["cryptui"]) + myenv.Append(LIBS = ["cryptui"]) myenv.UseFlags(myenv["PLATFORM_FLAGS"]) myenv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"]) @@ -59,159 +62,220 @@ myenv.Tool("wix", toolpath = ["#/BuildTools/SCons/Tools"]) myenv.Tool("textfile", toolpath = ["#/BuildTools/SCons/Tools"]) qt4modules = ['QtCore', 'QtWebKit', 'QtGui'] if myenv["qt5"] : - qt_version = '5' - qt4modules += ['QtWidgets', 'QtWebKitWidgets', 'QtMultimedia'] + qt_version = '5' + # QtSvg is required so the image format plugin for SVG images is installed + # correctly by Qt's deployment tools. + qt4modules += ['QtWidgets', 'QtWebKitWidgets', 'QtMultimedia', 'QtSvg'] + if env["PLATFORM"] != "win32" and env["PLATFORM"] != "darwin" : + qt4modules += ['QtX11Extras'] else : - qt_version = '4' + qt_version = '4' if env["PLATFORM"] == "posix" : - qt4modules += ["QtDBus"] + qt4modules += ["QtDBus"] if env["PLATFORM"] != "win32" and env["PLATFORM"] != "darwin" : - qt4modules += ["QtNetwork"] + qt4modules += ["QtNetwork"] myenv.EnableQt4Modules(qt4modules, debug = False, version = qt_version) + +## Qt related unit tests +testQtEnv = env.Clone(); +testQtEnv.Tool("qt4", toolpath = ["#/BuildTools/SCons/Tools"]) +testQtEnv.EnableQt4Modules(qt4modules, debug = False, version = qt_version) +env["SWIFT_QTUI_TEST_FLAGS"] = {} +for key in ["CCFLAGS", "CPPFLAGS", "CPPPATH", "CPPDEFINES", "LIBS", "LINKFLAGS", "LIBPATH"] : + if key in testQtEnv: + env["SWIFT_QTUI_TEST_FLAGS"][key] = testQtEnv[key] + +env.Append(UNITTEST_SOURCES = [ + File("UnitTest/QtUtilitiesTest.cpp") +]) + myenv.Append(CPPPATH = ["."]) +# Qt requires applications to be build with the -fPIC flag on some 32-bit Linux distributions. +if env["PLATFORM"] == "posix" : + testEnv = myenv.Clone() + conf = Configure(testEnv) + if conf.CheckDeclaration("QT_REDUCE_RELOCATIONS", "#include <QtCore/qconfig.h>") and conf.CheckDeclaration("__i386__"): + myenv.AppendUnique(CXXFLAGS = "-fPIC") + testEnv = conf.Finish() + if env["PLATFORM"] == "win32" : - #myenv.Append(LINKFLAGS = ["/SUBSYSTEM:CONSOLE"]) - myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"]) - myenv.Append(LIBS = "qtmain") - if myenv.get("HAVE_SCHANNEL", 0) : - myenv.Append(LIBS = "Cryptui") - myenv.Append(CPPDEFINES = "HAVE_SCHANNEL") - if env["debug"] and not env["optimize"]: - myenv.Append(LINKFLAGS = ["/NODEFAULTLIB:msvcrt"]) + #myenv.Append(LINKFLAGS = ["/SUBSYSTEM:CONSOLE"]) + myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"]) + myenv.Append(LIBS = "qtmain") + if myenv.get("HAVE_SCHANNEL", 0) : + myenv.Append(LIBS = "Cryptui") + myenv.Append(CPPDEFINES = "HAVE_SCHANNEL") + +if env["PLATFORM"] == "darwin" and env["HAVE_SPARKLE"] : + myenv.Append(LINKFLAGS = ["-Wl,-rpath,@loader_path/../Frameworks"]) -myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("#/Swift/resources/themes/Default")))) +myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateQRCTheme(myenv.Dir("#/Swift/resources/themes/Default"), "Default"))) sources = [ "main.cpp", + "ChatList/ChatListDelegate.cpp", + "ChatList/ChatListModel.cpp", + "ChatList/ChatListMUCItem.cpp", + "ChatList/ChatListRecentItem.cpp", + "ChatList/ChatListWhiteboardItem.cpp", + "ChatList/QtChatListWindow.cpp", + "ChattablesModel.cpp", + "ChatSnippet.cpp", + "EventViewer/EventDelegate.cpp", + "EventViewer/EventModel.cpp", + "EventViewer/QtEvent.cpp", + "EventViewer/QtEventWindow.cpp", + "EventViewer/TwoLineDelegate.cpp", + "FlowLayout.cpp", + "MessageSnippet.cpp", + "MUCSearch/MUCSearchDelegate.cpp", + "MUCSearch/MUCSearchEmptyItem.cpp", + "MUCSearch/MUCSearchModel.cpp", + "MUCSearch/MUCSearchRoomItem.cpp", + "MUCSearch/MUCSearchServiceItem.cpp", + "MUCSearch/QtLeafSortFilterProxyModel.cpp", + "MUCSearch/QtMUCSearchWindow.cpp", + "ServerList/ServerListDelegate.cpp", + "ServerList/ServerListModel.cpp", + "ServerList/QtServerListView.cpp", + "qrc_DefaultTheme.cc", + "qrc_Swift.cc", "QtAboutWidget.cpp", - "QtSpellCheckerWindow.cpp", + "QtAddBookmarkWindow.cpp", + "QtAdHocCommandWindow.cpp", + "QtAdHocCommandWithJIDWindow.cpp", + "QtAffiliationEditor.cpp", "QtAvatarWidget.cpp", - "QtUIFactory.cpp", + "QtBlockListEditorWindow.cpp", + "QtBookmarkDetailWindow.cpp", + "QtCachedImageScaler.cpp", + "QtChatOverview.cpp", + "QtChatOverviewBundle.cpp", + "QtChatOverviewDelegate.cpp", + "QtChatTabs.cpp", + "QtChatTabsBase.cpp", + "QtChatTheme.cpp", + "QtChatView.cpp", + "QtChatWindow.cpp", "QtChatWindowFactory.cpp", + "QtChatWindowJSBridge.cpp", + "QtCheckBoxStyledItemDelegate.cpp", "QtClickableLabel.cpp", + "QtClosableLineEdit.cpp", + "QtColorSelectionStyledItemDelegate.cpp", + "QtColorToolButton.cpp", + "QtConnectionSettingsWindow.cpp", + "QtContactEditWidget.cpp", + "QtContactEditWindow.cpp", + "QtEditBookmarkWindow.cpp", + "QtElidingLabel.cpp", + "QtEmojiCell.cpp", + "QtEmojisGrid.cpp", + "QtEmojisScroll.cpp", + "QtEmojisSelector.cpp", + "QtEmoticonsGrid.cpp", + "QtExpandedListView.cpp", + "QtFdpFormSubmitWindow.cpp", + "QtFileTransferListItemModel.cpp", + "QtFileTransferListWidget.cpp", + "QtFormResultItemModel.cpp", + "QtFormWidget.cpp", + "QtHighlightNotificationConfigDialog.cpp", + "QtHistoryWindow.cpp", + "QtJoinMUCWindow.cpp", + "QtLineEdit.cpp", + "QtListWidget.cpp", "QtLoginWindow.cpp", "QtMainWindow.cpp", - "QtProfileWindow.cpp", - "QtBlockListEditorWindow.cpp", + "QtMUCConfigurationWindow.cpp", "QtNameWidget.cpp", + "QtPlainChatView.cpp", + "QtProfileWindow.cpp", + "QtRecentEmojisGrid.cpp", + "QtResourceHelper.cpp", + "QtRosterHeader.cpp", + "QtScaledAvatarCache.cpp", "QtSettingsProvider.cpp", + "QtSingleWindow.cpp", + "QtSoundPlayer.cpp", + "QtSoundSelectionStyledItemDelegate.cpp", + "QtSpellCheckerWindow.cpp", + "QtSpellCheckHighlighter.cpp", "QtStatusWidget.cpp", - "QtScaledAvatarCache.cpp", + "QtSubscriptionRequestWindow.cpp", "QtSwift.cpp", - "QtURIHandler.cpp", - "QtChatWindow.cpp", - "QtChatView.cpp", - "QtWebKitChatView.cpp", - "QtChatTheme.cpp", - "QtChatTabs.cpp", - "QtSoundPlayer.cpp", "QtSystemTray.cpp", - "QtCachedImageScaler.cpp", "QtTabbable.cpp", "QtTabWidget.cpp", "QtTextEdit.cpp", - "QtXMLConsoleWidget.cpp", - "QtHistoryWindow.cpp", - "QtFileTransferListWidget.cpp", - "QtFileTransferListItemModel.cpp", - "QtAdHocCommandWindow.cpp", + "QtUIFactory.cpp", + "QtUISettingConstants.cpp", + "QtUpdateFeedSelectionDialog.cpp", + "QtURIHandler.cpp", + "QtURLValidator.cpp", "QtUtilities.cpp", - "QtBookmarkDetailWindow.cpp", - "QtAddBookmarkWindow.cpp", - "QtEditBookmarkWindow.cpp", - "QtContactEditWindow.cpp", - "QtContactEditWidget.cpp", - "QtSingleWindow.cpp", - "QtHighlightEditorWidget.cpp", - "QtHighlightRulesItemModel.cpp", - "QtHighlightRuleWidget.cpp", - "QtColorToolButton.cpp", - "ChatSnippet.cpp", - "MessageSnippet.cpp", - "SystemMessageSnippet.cpp", - "QtElidingLabel.cpp", - "QtFormWidget.cpp", - "QtFormResultItemModel.cpp", - "QtLineEdit.cpp", - "QtJoinMUCWindow.cpp", - "QtConnectionSettingsWindow.cpp", - "Roster/RosterModel.cpp", - "Roster/QtTreeWidget.cpp", -# "Roster/QtTreeWidgetItem.cpp", - "Roster/RosterDelegate.cpp", - "Roster/GroupItemDelegate.cpp", + "QtWebKitChatView.cpp", + "QtWebView.cpp", + "QtXMLConsoleWidget.cpp", "Roster/DelegateCommons.cpp", - "Roster/QtRosterWidget.cpp", + "Roster/GroupItemDelegate.cpp", + "Roster/QtFilterWidget.cpp", "Roster/QtOccupantListWidget.cpp", - "EventViewer/EventModel.cpp", - "EventViewer/EventDelegate.cpp", - "EventViewer/TwoLineDelegate.cpp", - "EventViewer/QtEventWindow.cpp", - "EventViewer/QtEvent.cpp", - "ChatList/QtChatListWindow.cpp", - "ChatList/ChatListModel.cpp", - "ChatList/ChatListDelegate.cpp", - "ChatList/ChatListMUCItem.cpp", - "ChatList/ChatListRecentItem.cpp", - "ChatList/ChatListWhiteboardItem.cpp", - "MUCSearch/QtMUCSearchWindow.cpp", - "MUCSearch/MUCSearchModel.cpp", - "MUCSearch/MUCSearchRoomItem.cpp", - "MUCSearch/MUCSearchEmptyItem.cpp", - "MUCSearch/MUCSearchDelegate.cpp", - "UserSearch/ContactListDelegate.cpp", - "UserSearch/ContactListModel.cpp", - "UserSearch/QtContactListWidget.cpp", + "Roster/QtRosterWidget.cpp", + "Roster/QtTreeWidget.cpp", + "Roster/RosterDelegate.cpp", + "Roster/RosterModel.cpp", + "Roster/RosterTooltip.cpp", + "SystemMessageSnippet.cpp", + "Trellis/QtDNDTabBar.cpp", + "Trellis/QtDynamicGridLayout.cpp", + "Trellis/QtGridSelectionDialog.cpp", + "UserSearch/ContactListDelegate.cpp", + "UserSearch/ContactListModel.cpp", + "UserSearch/QtContactListWidget.cpp", "UserSearch/QtSuggestingJIDInput.cpp", - "UserSearch/QtUserSearchFirstPage.cpp", - "UserSearch/QtUserSearchFirstMultiJIDPage.cpp", + "UserSearch/QtUserSearchDetailsPage.cpp", "UserSearch/QtUserSearchFieldsPage.cpp", + "UserSearch/QtUserSearchFirstMultiJIDPage.cpp", + "UserSearch/QtUserSearchFirstPage.cpp", "UserSearch/QtUserSearchResultsPage.cpp", - "UserSearch/QtUserSearchDetailsPage.cpp", "UserSearch/QtUserSearchWindow.cpp", - "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", - "QtWebView.cpp", - "qrc_DefaultTheme.cc", - "qrc_Swift.cc", - "QtChatWindowJSBridge.cpp", - "QtMUCConfigurationWindow.cpp", - "QtAffiliationEditor.cpp", - "QtUISettingConstants.cpp", - "QtURLValidator.cpp" - ] + "UserSearch/UserSearchModel.cpp", + "Whiteboard/ColorWidget.cpp", + "Whiteboard/FreehandLineItem.cpp", + "Whiteboard/GView.cpp", + "Whiteboard/QtWhiteboardWindow.cpp", + "Whiteboard/TextDialog.cpp" +] + +if env["PLATFORM"] == "win32" : + sources.extend(["qrc_SwiftWindows.cc"]) # 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" + "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") @@ -222,52 +286,52 @@ myenv.Uic4("QtProfileWindow.ui") # Determine the version myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") 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] + 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_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", - "#/Swift/resources/Windows/Swift.res" - ] + 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", + "#/Swift/resources/Windows/Swift.res" + ] if env["PLATFORM"] == "posix" : - sources += [ - "FreeDesktopNotifier.cpp", - "QtDBUSURIHandler.cpp", - ] + sources += [ + "FreeDesktopNotifier.cpp", + "QtDBUSURIHandler.cpp", + ] if env["PLATFORM"] == "darwin" : - sources += ["CocoaApplicationActivateHelper.mm"] - sources += ["CocoaUIHelpers.mm"] + sources += ["CocoaApplicationActivateHelper.mm"] + sources += ["CocoaUIHelpers.mm"] if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" : - swiftProgram = myenv.Program("Swift", sources) + swiftProgram = myenv.Program("Swift", sources) else : - sources += ["QtCertificateViewerDialog.cpp"]; - myenv.Uic4("QtCertificateViewerDialog.ui"); - swiftProgram = myenv.Program("swift-im", sources) + sources += ["QtCertificateViewerDialog.cpp"]; + myenv.Uic4("QtCertificateViewerDialog.ui"); + swiftProgram = myenv.Program("swift-im", sources) if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" : - openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp") + openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp") else : - openURIProgram = [] + openURIProgram = [] myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui") myenv.Uic4("UserSearch/QtUserSearchWizard.ui") @@ -280,20 +344,29 @@ myenv.Uic4("QtAffiliationEditor.ui") myenv.Uic4("QtJoinMUCWindow.ui") myenv.Uic4("QtHistoryWindow.ui") myenv.Uic4("QtConnectionSettings.ui") -myenv.Uic4("QtHighlightRuleWidget.ui") -myenv.Uic4("QtHighlightEditorWidget.ui") +myenv.Uic4("QtHighlightNotificationConfigDialog.ui") myenv.Uic4("QtBlockListEditorWindow.ui") myenv.Uic4("QtSpellCheckerWindow.ui") +myenv.Uic4("QtUpdateFeedSelectionDialog.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") +if env["PLATFORM"] == "win32" : + myenv.Qrc("SwiftWindows.qrc") + # Resources commonResources = { - "": ["#/Swift/resources/sounds"] + "": ["#/Swift/resources/sounds"] } + +## COPYING file generation myenv["TEXTFILESUFFIX"] = "" -myenv.MyTextfile(target = "COPYING", source = [myenv.File("../../COPYING.gpl"), myenv.File("../../COPYING.thirdparty")], LINESEPARATOR = "\n\n========\n\n\n") + +copying_files = [myenv.File("../../COPYING.gpl"), myenv.File("../../COPYING.thirdparty"), myenv.File("../../COPYING.dependencies")] +if env["PLATFORM"] == "darwin" and env["HAVE_SPARKLE"] : + copying_files.append(env["SPARKLE_COPYING"]) +myenv.Textfile(target = "COPYING", source = copying_files, LINESEPARATOR = "\n\n========\n\n\n") ################################################################################ # Translation @@ -302,136 +375,166 @@ myenv.MyTextfile(target = "COPYING", source = [myenv.File("../../COPYING.gpl"), # Collect available languages translation_languages = [] for file in os.listdir(Dir("#/Swift/Translations").abspath) : - if file.startswith("swift_") and file.endswith(".ts") : - translation_languages.append(file[6:-3]) + if file.startswith("swift_") and file.endswith(".ts") : + translation_languages.append(file[6:-3]) # Generate translation modules translation_sources = [env.File("#/Swift/Translations/swift.ts").abspath] translation_modules = [] for lang in translation_languages : - translation_resource = "#/Swift/resources/translations/swift_" + lang + ".qm" - translation_source = "#/Swift/Translations/swift_" + lang + ".ts" - translation_sources.append(env.File(translation_source).abspath) - translation_modules.append(env.File(translation_resource).abspath) - myenv.Qm(translation_resource, translation_source) - commonResources["translations"] = commonResources.get("translations", []) + [translation_resource] + translation_resource = "#/Swift/resources/translations/swift_" + lang + ".qm" + translation_source = "#/Swift/Translations/swift_" + lang + ".ts" + translation_sources.append(env.File(translation_source).abspath) + translation_modules.append(env.File(translation_resource).abspath) + myenv.Qm(translation_resource, translation_source) + commonResources["translations"] = commonResources.get("translations", []) + [translation_resource] # LUpdate translation (if requested) if ARGUMENTS.get("update_translations", False) : - myenv.Precious(translation_sources) - remove_obsolete_option = "" - if ARGUMENTS.get("remove_obsolete_translations", False) : - remove_obsolete_option = " -no-obsolete" - 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) + myenv.Precious(translation_sources) + remove_obsolete_option = "" + codecfortr = "" + if ARGUMENTS.get("remove_obsolete_translations", False) : + remove_obsolete_option = " -no-obsolete" + if qt_version == '4': + codecfortr = "-codecfortr UTF-8" + 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 + " -recursive Swift -ts " + translation_source, cmdstr = "$QT4_LUPDATECOMSTR")]) + myenv.AlwaysBuild(t) # NSIS installation script if env["PLATFORM"] == "win32" : - nsis_translation_install_script = "" - nsis_translation_uninstall_script = "" - for lang in translation_languages : - nsis_translation_install_script += "File \"..\\..\\QtUI\\Swift\\translations\\swift_" + lang + ".qm\"\n" - nsis_translation_uninstall_script += "delete $INSTDIR\\translations\\swift_" + lang + ".qm\n" - myenv.WriteVal("../Packaging/nsis/translations-install.nsh", myenv.Value(nsis_translation_install_script)) - myenv.WriteVal("../Packaging/nsis/translations-uninstall.nsh", myenv.Value(nsis_translation_uninstall_script)) - + nsis_translation_install_script = "" + nsis_translation_uninstall_script = "" + for lang in translation_languages : + nsis_translation_install_script += "File \"..\\..\\QtUI\\Swift\\translations\\swift_" + lang + ".qm\"\n" + nsis_translation_uninstall_script += "delete $INSTDIR\\translations\\swift_" + lang + ".qm\n" + myenv.WriteVal("../Packaging/nsis/translations-install.nsh", myenv.Value(nsis_translation_install_script)) + myenv.WriteVal("../Packaging/nsis/translations-uninstall.nsh", myenv.Value(nsis_translation_uninstall_script)) + ################################################################################ if env["PLATFORM"] == "darwin" : - frameworks = [] - if env["HAVE_SPARKLE"] : - frameworks.append(env["SPARKLE_FRAMEWORK"]) - if env["HAVE_GROWL"] : - frameworks.append(env["GROWL_FRAMEWORK"]) - commonResources[""] = commonResources.get("", []) + ["#/Swift/resources/MacOSX/Swift.icns"] - app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True) - if env["DIST"] : - myenv.Command(["#/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", "") : - env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram + openURIProgram) - env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm") - icons_path = os.path.join(env["SWIFT_INSTALLDIR"], "share", "icons", "hicolor") - env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm") - env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "#/Swift/resources/logo/logo-icon.svg") - for i in ["16", "22", "24", "64", "128"] : - env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "#/Swift/resources/logo/logo-icon-" + i + ".png") - env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "#/Swift/resources/swift.desktop") - for dir, resource in commonResources.items() : - env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "swift", dir), resource) - -if env["PLATFORM"] == "win32" : - if env["DIST"] or ARGUMENTS.get("dump_trace") : - commonResources[""] = commonResources.get("", []) + [ - #os.path.join(env["OPENSSL_DIR"], "bin", "ssleay32.dll"), - #os.path.join(env["OPENSSL_DIR"], "bin", "libeay32.dll"), - "#/Swift/resources/images", - ] - 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', 'QtV8', 'QtMultimedia', 'QtSql', 'QtSensors', 'QtWidgets', 'QtWebKitWidgets', 'QtMultimediaWidgets', 'QtOpenGL', 'QtPrintSupport'] - qtlibs = [lib.replace('Qt', 'Qt5') for lib in qtlibs] - qtlibs += ['icuin51', 'icuuc51', 'icudt51', 'libGLESv2', 'libEGL'] - qtplugins["platforms"] = ['windows'] - qtplugins["accessible"] = ["taccessiblewidgets"] - windowsBundleFiles = myenv.WindowsBundle("Swift", - resources = commonResources, - qtplugins = qtplugins, - qtlibs = qtlibs, - qtversion = qt_version) - - if env["DIST"] : - #myenv.Append(NSIS_OPTIONS = [ - # "/DmsvccRedistributableDir=\"" + env["vcredist"] + "\"", - # "/DbuildVersion=" + myenv["SWIFT_VERSION"] - # ]) - #myenv.Nsis("../Packaging/nsis/swift.nsi") - if env["SCONS_STAGE"] == "build" and env.get("wix_bindir", None): - def convertToRTF(env, target, source) : - infile = open(source[0].abspath, 'r') - outfile = open(target[0].abspath, 'w') - outfile.write('{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\fs16\\f0\\pard\n') - for line in infile: - for char in line.decode("utf-8") : - if ord(char) > 127 : - # FIXME: This is incorrect, because it only works for latin1. - # The correct way is \u<decimal utf16 point>? , but this is more - # work - outfile.write("\\'%X" % ord(char)) - else : - outfile.write(char) - outfile.write('\\par ') - outfile.write('}') - outfile.close() - infile.close() - copying = env.Command(["Swift/COPYING.rtf"], ["COPYING"], convertToRTF) - - wixvariables = { - 'VCCRTFile': env["vcredist"], - 'Version': str(myenv["SWIFT_VERSION_MAJOR"]) + "." + str(myenv["SWIFT_VERSION_MINOR"]) + "." + str(myenv["SWIFT_VERSION_PATCH"]) - } - wixincludecontent = "<Include>" - for key in wixvariables: - wixincludecontent += "<?define %s = \"%s\" ?>" % (key, wixvariables[key]) - wixincludecontent += "</Include>" - myenv.WriteVal("..\\Packaging\\Wix\\variables.wxs", env.Value(wixincludecontent)) - myenv.WiX_Heat('..\\Packaging\\WiX\\gen_files.wxs', windowsBundleFiles + copying) - myenv.WiX_Candle('..\\Packaging\\WiX\\Swift.wixobj', '..\\Packaging\\WiX\\Swift.wxs') - myenv.WiX_Candle('..\\Packaging\\WiX\\gen_files.wixobj', '..\\Packaging\\WiX\\gen_files.wxs') - myenv.WiX_Light('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.msi', ['..\\Packaging\\WiX\\gen_files.wixobj','..\\Packaging\\WiX\\Swift.wixobj']) - - if myenv["debug"] : - myenv.InstallAs('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.pdb', "Swift.pdb") + frameworks = [] + if env["HAVE_SPARKLE"] : + frameworks.append(env["SPARKLE_FRAMEWORK"]) + if env["HAVE_GROWL"] : + frameworks.append(env["GROWL_FRAMEWORK"]) + commonResources[""] = commonResources.get("", []) + ["#/Swift/resources/MacOSX/Swift.icns"] + app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True, sparklePublicDSAKey = myenv["SWIFT_SPARKLE_PUBLIC_DSA_KEY"]) + if env["DIST"] : + myenv.Command(["#/Packages/Swift/Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR " + "\"$CODE_SIGN_IDENTITY\""]) + 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", "") : + env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram + openURIProgram) + env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm") + icons_path = os.path.join(env["SWIFT_INSTALLDIR"], "share", "icons", "hicolor") + env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm") + env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "#/Swift/resources/logo/logo-icon.svg") + for i in ["16", "22", "24", "64", "128"] : + env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "#/Swift/resources/logo/logo-icon-" + i + ".png") + env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "#/Swift/resources/swift.desktop") + for dir, resource in commonResources.items() : + env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "swift", dir), resource) +if env["PLATFORM"] == "win32" : + if env["DIST"] or ARGUMENTS.get("dump_trace") : + commonResources[""] = commonResources.get("", []) + [ + #os.path.join(env["OPENSSL_DIR"], "bin", "ssleay32.dll"), + #os.path.join(env["OPENSSL_DIR"], "bin", "libeay32.dll"), + "#/Swift/resources/images", + ] + 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', 'QtSvg', 'QtWidgets', 'QtWebChannel', '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, + qtplugins = qtplugins, + qtlibs = qtlibs, + qtversion = qt_version) + + if env["DIST"] : + #myenv.Append(NSIS_OPTIONS = [ + # "/DmsvccRedistributableDir=\"" + env["vcredist"] + "\"", + # "/DbuildVersion=" + myenv["SWIFT_VERSION"] + # ]) + #myenv.Nsis("../Packaging/nsis/swift.nsi") + if env["SCONS_STAGE"] == "build" and env.get("wix_bindir", None): + def convertToRTF(env, target, source) : + if SCons.Util.PY3: + infile = open(source[0].abspath, 'r', encoding="utf8") + outfile = open(target[0].abspath, 'w', encoding="utf8") + else: + infile = open(source[0].abspath, 'r') + outfile = open(target[0].abspath, 'w') + outfile.write('{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\fs16\\f0\\pard\n') + for line in infile: + line = line if SCons.Util.PY3 else line.decode("utf-8") + for char in line : + if ord(char) > 127 : + # FIXME: This is incorrect, because it only works for latin1. + # The correct way is \u<decimal utf16 point>? , but this is more + # work + outfile.write("\\'%X" % ord(char)) + else : + outfile.write(char) + outfile.write('\\par ') + outfile.write('}') + outfile.close() + infile.close() + copying = env.Command(["Swift/COPYING.rtf"], ["COPYING"], convertToRTF) + if env.get("vcredistdir", "") : + vcredistdir = os.path.dirname(env["vcredistdir"]) + else: + vcredistdir = os.path.dirname(env["vcredist"])+"\\..\\" + ("x86" if env["win_target_arch"] == "x86" else "x64") + "\\Microsoft.VC"+env.get("MSVC_VERSION", "").replace(".","")[:3]+".CRT\\" + wixvariables = { + 'VCCRTFile': env["vcredist"], + 'VCCRTPath': vcredistdir, + 'Version': str(myenv["SWIFT_VERSION_MAJOR"]) + "." + str(myenv["SWIFT_VERSION_MINOR"]) + "." + str(myenv["SWIFT_VERSION_PATCH"]), + 'MsvcVersion': str(env.get("MSVC_VERSION", "").replace(".","")[:3]), + 'MsvcDotVersion': str(env.get("MSVC_VERSION", "")[:4]) + } + wixincludecontent = "<Include>" + for key in wixvariables: + wixincludecontent += "<?define %s = \"%s\" ?>" % (key, wixvariables[key]) + wixincludecontent += "</Include>" + myenv.WriteVal("..\\Packaging\\Wix\\variables.wxs", env.Value(wixincludecontent)) + 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') + lightTask = myenv.WiX_Light('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.msi', ['..\\Packaging\\WiX\\gen_files.wixobj','..\\Packaging\\WiX\\Swift.wixobj']) + if myenv.get("SIGNTOOL_KEY_PFX", None) and myenv.get("SIGNTOOL_TIMESTAMP_URL", None) : + def signToolAction(target = None, source = None, env = None): + signresult = 0 + for x in range (1, 4) : + print("Attemping to sign the packages [%s]" % x) + signresult = env.Execute('signtool.exe sign /fd SHA256 /f "${SIGNTOOL_KEY_PFX}" /t "${SIGNTOOL_TIMESTAMP_URL}" /d "Swift Installer" ' + str(target[0])) + if signresult != 1 : + break + #If all 3 attemps to sign the package failed, stop the build. + if signresult == 1 : + print("Error: The build has failed to sign the installer package") + Exit(1) + if signresult == 2 : + print("Signing was completed with warnings.") + + myenv.AddPostAction(lightTask, signToolAction) + + if myenv["debug"] : + myenv.InstallAs('#/Packages/Swift/Swift-' + myenv["SWIFT_VERSION"] + '.pdb', "Swift.pdb") diff --git a/Swift/QtUI/ServerList/QtServerListView.cpp b/Swift/QtUI/ServerList/QtServerListView.cpp new file mode 100644 index 0000000..c22680f --- /dev/null +++ b/Swift/QtUI/ServerList/QtServerListView.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/ServerList/QtServerListView.h> + +namespace Swift { + +QtServerListView::QtServerListView() { + QPalette newPalette = palette(); + //TODO move color and theme variables to a shared location. + newPalette.setColor(QPalette::Base, { 38, 81, 112 }); + setAutoFillBackground(true); + setPalette(newPalette); + delegate_ = std::make_unique<ServerListDelegate>(); + setItemDelegate(delegate_.get()); + setMaximumWidth(widgetWidth); + setMinimumWidth(widgetWidth); + setFrameStyle(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSelectionMode(QAbstractItemView::NoSelection); +} + +QtServerListView::~QtServerListView() { + +} + +} diff --git a/Swift/QtUI/ServerList/QtServerListView.h b/Swift/QtUI/ServerList/QtServerListView.h new file mode 100644 index 0000000..bd625aa --- /dev/null +++ b/Swift/QtUI/ServerList/QtServerListView.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <memory> + +#include <QListView> + +#include <Swift/QtUI/ServerList/ServerListDelegate.h> + +namespace Swift { + class QtServerListView : public QListView { + Q_OBJECT + public: + QtServerListView(); + virtual ~QtServerListView(); + private: + std::unique_ptr<ServerListDelegate> delegate_; + static const int widgetWidth = 82; + }; +} diff --git a/Swift/QtUI/ServerList/ServerListDelegate.cpp b/Swift/QtUI/ServerList/ServerListDelegate.cpp new file mode 100644 index 0000000..2afb4ea --- /dev/null +++ b/Swift/QtUI/ServerList/ServerListDelegate.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/ServerList/ServerListDelegate.h> + +#include <QApplication> +#include <QBitmap> +#include <QBrush> +#include <QColor> +#include <QDebug> +#include <QFileInfo> +#include <QFontMetrics> +#include <QPainter> +#include <QPainterPath> +#include <QPen> +#include <QPolygon> + +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/ServerList/ServerListModel.h> + +namespace Swift { + +ServerListDelegate::ServerListDelegate() { + +} + +ServerListDelegate::~ServerListDelegate() { + +} + +void ServerListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + QColor bgColor(38, 81, 112); + painter->fillRect(option.rect, bgColor); + SwiftAccountData::SwiftAccountDataItem* item = static_cast<SwiftAccountData::SwiftAccountDataItem*>(index.internalPointer()); + paintServerAvatar(painter, option, item->iconPath_, item->status_, false, item->unreadCount_); +} + +QSize ServerListDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { + //TODO Make this configurable. + return QSize(75, 75); +} + +void ServerListDelegate::paintServerAvatar(QPainter* painter, const QStyleOptionViewItem& option, const QString& avatarPath, const StatusShow& /*serverPresence*/, bool isIdle, size_t unreadCount) const { + painter->save(); + QRect fullRegion(option.rect); + if (option.state & QStyle::State_Selected) { + painter->fillRect(fullRegion, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } + auto secondLineColor = painter->pen().color(); + secondLineColor.setAlphaF(0.7); + + QRect presenceRegion(QPoint(common_.farLeftMargin, fullRegion.top() + common_.horizontalMargin), QSize(presenceIconWidth, presenceIconHeight)); + QRect idleIconRegion(QPoint(common_.farLeftMargin, fullRegion.top()), QSize(presenceIconWidth * 2, presenceIconHeight - common_.verticalMargin)); + int calculatedAvatarSize = fullRegion.height() - common_.verticalMargin; + //This overlaps the presenceIcon, so must be painted first + QRect avatarRegion(QPoint(presenceRegion.right() - common_.presenceIconWidth / 2, presenceRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize)); + + QPixmap avatarPixmap; + if (!avatarPath.isEmpty()) { + QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath); + if (QFileInfo(scaledAvatarPath).exists()) { + avatarPixmap.load(scaledAvatarPath); + } + } + if (avatarPixmap.isNull()) { + avatarPixmap = QPixmap(":/icons/avatar.svg").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap); + //Paint the presence status over the top of the avatar + //FIXME enable drawing status when ServerPresence data are available. + /*{ + //TODO make the colors consistent with chattables work from QtChatOverviewDelegate::paint, copying for now + const auto green = QColor(124, 243, 145); + const auto yellow = QColor(243, 243, 0); + const auto red = QColor(255, 45, 71); + const auto grey = QColor(159, 159, 159); + QColor color = grey; + switch (serverPresence.getType()) { + case StatusShow::Online: color = green; break; + case StatusShow::FFC: color = green; break; + case StatusShow::Away: color = yellow; break; + case StatusShow::XA: color = yellow; break; + case StatusShow::DND: color = red; break; + case StatusShow::None: color = grey; break; + } + QPen pen(color); + pen.setWidth(1); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(pen); + painter->setBrush(QBrush(color, Qt::SolidPattern)); + painter->drawEllipse(presenceRegion); + }*/ + + if (isIdle) { + common_.idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + } + + if (unreadCount > 0) { + QRect unreadRect(avatarRegion.right() - common_.unreadCountSize - common_.horizontalMargin, avatarRegion.top(), common_.unreadCountSize, common_.unreadCountSize); + QPen pen(QColor("black")); + pen.setWidth(1); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(pen); + painter->setBrush(QBrush(QColor("red"), Qt::SolidPattern)); + painter->drawEllipse(unreadRect); + painter->setBackgroundMode(Qt::TransparentMode); + painter->setPen(QColor("white")); + common_.drawElidedText(painter, unreadRect, QString("%1").arg(unreadCount), Qt::AlignCenter); + } + painter->restore(); +} + +} diff --git a/Swift/QtUI/ServerList/ServerListDelegate.h b/Swift/QtUI/ServerList/ServerListDelegate.h new file mode 100644 index 0000000..3f8b72b --- /dev/null +++ b/Swift/QtUI/ServerList/ServerListDelegate.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QStyledItemDelegate> + +#include <Swiften/Elements/StatusShow.h> + +#include <Swift/QtUI/Roster/DelegateCommons.h> + +namespace Swift { + + class ServerListDelegate : public QStyledItemDelegate { + public: + ServerListDelegate(); + ~ServerListDelegate() override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + private: + void paintServerAvatar(QPainter* painter, const QStyleOptionViewItem& option, const QString& avatarPath, const StatusShow& presence, bool isIdle, size_t unreadCount) const; + private: + DelegateCommons common_; + static const int presenceIconHeight = 12; + static const int presenceIconWidth = 12; + }; + +} diff --git a/Swift/QtUI/ServerList/ServerListModel.cpp b/Swift/QtUI/ServerList/ServerListModel.cpp new file mode 100644 index 0000000..e5dc35e --- /dev/null +++ b/Swift/QtUI/ServerList/ServerListModel.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +//#include <Swift/QtUI/ServerList/ServerListModel.h> +#include <QBrush> +#include <QColor> +#include <QMimeData> + +#include "ServerListModel.h" + +namespace Swift { + +ServerListModel::ServerListModel() { +} + +ServerListModel::~ServerListModel() { +} + +QVariant ServerListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) { + return QVariant(); + } + SwiftAccountData::SwiftAccountDataItem* item = static_cast<SwiftAccountData::SwiftAccountDataItem*>(index.internalPointer()); + switch (role) { + case Qt::DisplayRole: return QString(item->userJID_.toBare().toString().c_str()); + case Qt::BackgroundRole: return QBrush(Qt::transparent); + case Qt::ToolTipRole: return QString(item->userJID_.toBare().toString().c_str()); + default: return QVariant(); + } +} + +QModelIndex ServerListModel::index(int row, int column, const QModelIndex& /*parent*/) const { + if (!modelData_ || static_cast<size_t>(row) >= modelData_->size()) { + return QModelIndex(); + } + return createIndex(row, column, modelData_->getAccount(row)); +} + +QModelIndex ServerListModel::parent(const QModelIndex& /*index*/) const { + return QModelIndex(); +} + +QMimeData* ServerListModel::mimeData(const QModelIndexList& indexes) const { + return QAbstractItemModel::mimeData(indexes); +} + +int ServerListModel::rowCount(const QModelIndex& /*parent*/) const { + if (!modelData_) { + return 0; + } + return modelData_->size(); +} + +int ServerListModel::columnCount(const QModelIndex& /*parent*/) const { + if (!modelData_) { + return 0; + } + return 1; +} + +void ServerListModel::handleDataChanged() { + emit layoutChanged(); +} + +} diff --git a/Swift/QtUI/ServerList/ServerListModel.h b/Swift/QtUI/ServerList/ServerListModel.h new file mode 100644 index 0000000..ae89ade --- /dev/null +++ b/Swift/QtUI/ServerList/ServerListModel.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <boost/signals2.hpp> + +#include <QAbstractItemModel> + +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/JID/JID.h> + +namespace Swift { + + class SwiftAccountData { + public: + struct SwiftAccountDataItem { + SwiftAccountDataItem(std::string serverID) : serverID_(serverID) {} + //FIXME eliminate serverID_, the userJID_ will be the ID when we connect with the actual data. + std::string serverID_; + QString iconPath_; + JID userJID_; + size_t unreadCount_ = 0; + StatusShow status_ = StatusShow::None; + boost::signals2::scoped_connection dataChangedConnection_; + boost::signals2::signal<void ()> onDataChanged; + void handleChangeStatusRequest(StatusShow::Type show, const std::string& /*statusText*/) { + status_ = show; + onDataChanged(); + } + }; + public: + SwiftAccountData() {} + ~SwiftAccountData() { + for (auto account : accounts_) { + delete account; + } + } + //FIXME make addAccount with SwiftAccountDataItem, and added after a succesfull connection to the server has been established. + void addAccount(JID userJID) { + SwiftAccountDataItem* newItem = new SwiftAccountDataItem(userJID); + newItem->dataChangedConnection_ = newItem->onDataChanged.connect(boost::bind(&SwiftAccountData::handleDataChanged, this)); + accounts_.push_back(newItem); + } + SwiftAccountDataItem* getAccount(int index) { + if (index >= accounts_.size()) { + return nullptr; + } + return accounts_[index]; + } + size_t size() { + return accounts_.size(); + } + public: + boost::signals2::signal<void()> onDataChanged; + private: + void handleDataChanged() { + onDataChanged(); + } + private: + QList<SwiftAccountDataItem*> accounts_; + }; + + class ServerListModel : public QAbstractItemModel { + Q_OBJECT + public: + ServerListModel(); + ~ServerListModel() override; + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + + QMimeData* mimeData(const QModelIndexList& indexes) const override; + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + void setModelData(SwiftAccountData* data) { modelData_ = data; } + void handleDataChanged(); + private: + SwiftAccountData* modelData_; + }; +} diff --git a/Swift/QtUI/Swift.qrc b/Swift/QtUI/Swift.qrc index eeef80d..b04503c 100644 --- a/Swift/QtUI/Swift.qrc +++ b/Swift/QtUI/Swift.qrc @@ -2,8 +2,10 @@ <RCC version="1.0"> <qresource> <file alias="logo-icon-16.png">../resources/logo/logo-icon-16.png</file> + <file alias="logo-icon-16-win.png">../resources/logo/logo-icon-16-win.png</file> <file alias="logo-chat-16.png">../resources/logo/logo-chat-16.png</file> - <file alias="logo-shaded-text.256.png">../resources/logo/logo-shaded-text.256.png</file> + <file alias="logo-shaded-text.png">../resources/logo/logo-shaded-text.png</file> + <file alias="logo-icon.svg">../resources/logo/logo-icon.svg</file> <file alias="icons/online.png">../resources/icons/online.png</file> <file alias="icons/connecting.mng">../resources/icons/connecting.mng</file> <file alias="icons/away.png">../resources/icons/away.png</file> @@ -15,12 +17,13 @@ <file alias="icons/warn.png">../resources/icons/warn.png</file> <file alias="icons/check.png">../resources/icons/check.png</file> <file alias="icons/throbber.gif">../resources/icons/throbber.gif</file> - <file alias="icons/avatar.png">../resources/icons/avatar.png</file> + <file alias="icons/avatar.svg">../resources/icons/avatar.svg</file> <file alias="icons/no-avatar.png">../resources/icons/no-avatar.png</file> <file alias="icons/tray-standard.png">../resources/icons/tray-standard.png</file> <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="ChangeLog.md">../ChangeLog.md</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> @@ -42,5 +45,8 @@ <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> + <file alias="icons/delivery-success.svg">../resources/icons/delivery-success.svg</file> + <file alias="icons/delivery-failure.svg">../resources/icons/delivery-failure.svg</file> + <file alias="icons/delivery-warning.svg">../resources/icons/delivery-warning.svg</file> </qresource> </RCC> diff --git a/Swift/QtUI/SwiftUpdateFeeds.h b/Swift/QtUI/SwiftUpdateFeeds.h new file mode 100644 index 0000000..a6476f5 --- /dev/null +++ b/Swift/QtUI/SwiftUpdateFeeds.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <Swiften/Base/Platform.h> + +namespace Swift { + +namespace UpdateFeeds { + const std::string StableChannel = "stable"; + const std::string TestingChannel = "testing"; + const std::string DevelopmentChannel = "development"; + +#if defined(SWIFTEN_PLATFORM_MACOSX) + const std::string StableAppcastFeed = "https://swift.im/downloads/swift-stable-appcast-mac.xml"; + const std::string TestingAppcastFeed = "https://swift.im/downloads/swift-testing-appcast-mac.xml"; + const std::string DevelopmentAppcastFeed = "https://swift.im/downloads/swift-development-appcast-mac.xml"; +#else + const std::string StableAppcastFeed = ""; + const std::string TestingAppcastFeed = ""; + const std::string DevelopmentAppcastFeed = ""; +#endif +} + +} diff --git a/Swift/QtUI/SwiftWindows.qrc b/Swift/QtUI/SwiftWindows.qrc new file mode 100644 index 0000000..72e50d3 --- /dev/null +++ b/Swift/QtUI/SwiftWindows.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource> + <file alias="qt/etc/qt.conf">qt-windows.conf</file> + </qresource> +</RCC> diff --git a/Swift/QtUI/SystemMessageSnippet.cpp b/Swift/QtUI/SystemMessageSnippet.cpp index 39349bc..eeb6b9a 100644 --- a/Swift/QtUI/SystemMessageSnippet.cpp +++ b/Swift/QtUI/SystemMessageSnippet.cpp @@ -1,25 +1,26 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "SystemMessageSnippet.h" +#include <Swift/QtUI/SystemMessageSnippet.h> #include <QDateTime> namespace Swift { -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, direction))); - } - content_ = theme->getStatus(); - - content_.replace("%direction%", directionToCSS(direction)); - content_.replace("%message%", wrapResizable("<span class='swift_message'>" + escape(message) + "</span>")); - content_.replace("%shortTime%", wrapResizable(escape(time.toString("h:mm")))); - content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); +SystemMessageSnippet::SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme, const QString& id, Direction direction) : ChatSnippet(appendToPrevious) { + if (appendToPrevious) { + setContinuationFallbackSnippet(std::make_shared<SystemMessageSnippet>(message, time, false, theme, id, direction)); + } + content_ = theme->getStatus(); + + content_.replace("%direction%", directionToCSS(direction)); + content_.replace("%message%", wrapResizable("<span class='swift_message'>" + escape(message) + "</span>")); + content_.replace("%shortTime%", wrapResizable(escape(time.toString("h:mm")))); + content_.replace("%time%", wrapResizable("<span class='swift_time'>" + timeToEscapedString(time) + "</span>")); + content_.replace("%id%", id); } SystemMessageSnippet::~SystemMessageSnippet() { diff --git a/Swift/QtUI/SystemMessageSnippet.h b/Swift/QtUI/SystemMessageSnippet.h index 34b0d40..c0d4d2f 100644 --- a/Swift/QtUI/SystemMessageSnippet.h +++ b/Swift/QtUI/SystemMessageSnippet.h @@ -1,30 +1,30 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QString> -#include "ChatSnippet.h" +#include <Swift/QtUI/ChatSnippet.h> class QDateTime; namespace Swift { - class SystemMessageSnippet : public ChatSnippet { - public: - SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme, Direction direction); - virtual ~SystemMessageSnippet(); + class SystemMessageSnippet : public ChatSnippet { + public: + SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme, const QString& id, Direction direction); + virtual ~SystemMessageSnippet(); - const QString& getContent() const {return content_;} + const QString& getContent() const {return content_;} - /*QString getContinuationElementID() const { - return "insert"; - };*/ + /*QString getContinuationElementID() const { + return "insert"; + };*/ - private: - QString content_; - }; + private: + QString content_; + }; } diff --git a/Swift/QtUI/Trellis/QtDNDTabBar.cpp b/Swift/QtUI/Trellis/QtDNDTabBar.cpp new file mode 100644 index 0000000..3ae2124 --- /dev/null +++ b/Swift/QtUI/Trellis/QtDNDTabBar.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2014-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Trellis/QtDNDTabBar.h> + +#include <cassert> + +#include <QDrag> +#include <QDropEvent> +#include <QMimeData> +#include <QMouseEvent> +#include <QPainter> +#include <QTabWidget> + +namespace Swift { + +QtDNDTabBar::QtDNDTabBar(QWidget* parent) : QTabBar(parent) { + setAcceptDrops(true); + + // detect the default tab bar height; + insertTab(0, ""); + defaultTabHeight = QTabBar::sizeHint().height(); + removeTab(0); +} + +QtDNDTabBar::~QtDNDTabBar() { + +} + +int QtDNDTabBar::getDragIndex() const { + return dragIndex; +} + +QString QtDNDTabBar::getDragText() const { + return dragText; +} + +QWidget* QtDNDTabBar::getDragWidget() const { + return dragWidget; +} + +QSize QtDNDTabBar::sizeHint() const { + QSize hint = QTabBar::sizeHint(); + if (hint.isEmpty()) { + hint = QSize(parentWidget()->width(), defaultTabHeight); + } + return hint; +} + +QSize QtDNDTabBar::tabSizeHint(int index) const { + QSize tabSize = QTabBar::tabSizeHint(index); +#if defined(Q_OS_MAC) + // With multiple tabs having the same label in a QTabBar, the size hint computed by + // Qt on OS X is too small and it is elided even though there is enough horizontal + // space available. We work around this issue by adding the width of a letter to the + // size hint. + tabSize += QSize(QFontMetrics(font()).width("I"), 0); +#endif + return tabSize; +} + +void QtDNDTabBar::dragEnterEvent(QDragEnterEvent* dragEnterEvent) { + QtDNDTabBar* sourceTabBar = dynamic_cast<QtDNDTabBar*>(dragEnterEvent->source()); + if (sourceTabBar) { + dragEnterEvent->acceptProposedAction(); + } +} + +void QtDNDTabBar::dropEvent(QDropEvent* dropEvent) { + auto sourceTabBar = dynamic_cast<QtDNDTabBar*>(dropEvent->source()); + if (sourceTabBar && dropEvent->mimeData() && dropEvent->mimeData()->data("action") == QByteArray("application/tab-detach")) { + int targetTabIndex = tabAt(dropEvent->pos()); + QRect rect = tabRect(targetTabIndex); + if (targetTabIndex >= 0 && (dropEvent->pos().x() - rect.left() - rect.width()/2 > 0)) { + targetTabIndex++; + } + + QWidget* tab = sourceTabBar->getDragWidget(); + assert(tab); + QTabWidget* targetTabWidget = dynamic_cast<QTabWidget*>(parentWidget()); + + QString tabText = sourceTabBar->getDragText(); + + /* + * When you add a widget to an empty QTabWidget, it's automatically made the current widget. + * Making the widget the current widget, widget->show() is called for the widget. Directly reacting + * to that event, and adding the widget again to the QTabWidget results in undefined behavior. For + * example the tab label is shown but the widget is neither has the old nor in the new QTabWidget as + * parent. Blocking signals on the QWidget to be added to a QTabWidget prevents this behavior. + */ + targetTabWidget->setUpdatesEnabled(false); + tab->blockSignals(true); + targetTabWidget->insertTab(targetTabIndex, tab, tabText); + dropEvent->acceptProposedAction(); + tab->blockSignals(false); + targetTabWidget->setUpdatesEnabled(true); + onDropSucceeded(); + } +} + +bool QtDNDTabBar::event(QEvent* event) { + QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event); + if (mouseEvent) { + QWidget* childAtPoint = window()->childAt(mapTo(window(), mouseEvent->pos())); + QtDNDTabBar* underMouse = dynamic_cast<QtDNDTabBar*>(childAtPoint); + if (!underMouse && childAtPoint) { + underMouse = dynamic_cast<QtDNDTabBar*>(childAtPoint->parent()); + } + if (!underMouse && currentIndex() >= 0) { + // detach and drag + + // stop move event + QMouseEvent* finishMoveEvent = new QMouseEvent (QEvent::MouseMove, mouseEvent->pos (), Qt::NoButton, Qt::NoButton, Qt::NoModifier); + QTabBar::event(finishMoveEvent); + delete finishMoveEvent; + finishMoveEvent = nullptr; + + // start drag + QDrag* drag = new QDrag(this); + QMimeData* mimeData = new QMimeData; + + // distinguish tab-reordering drops from other ones + mimeData->setData("action", "application/tab-detach") ; + drag->setMimeData(mimeData); + + // set drag image + QRect rect = tabRect( currentIndex() ); +#if QT_VERSION >= 0x050000 + QPixmap pixmap = grab(rect); +#else + QPixmap pixmap = QPixmap::grabWidget(this, rect); +#endif + QPixmap targetPixmap (pixmap.size ()); + QPainter painter (&targetPixmap); + painter.setOpacity(0.9); + painter.drawPixmap(0,0, pixmap); + painter.end (); + drag->setPixmap (targetPixmap); + + drag->setHotSpot(QPoint(drag->pixmap().width()/2, drag->pixmap().height())); + + dragIndex = currentIndex(); + dragText = tabText(dragIndex); + dragWidget = dynamic_cast<QTabWidget*>(parent())->widget(dragIndex); + assert(dragWidget); + dynamic_cast<QTabWidget*>(parent())->removeTab(currentIndex()); + Qt::DropAction dropAction = drag->exec(); + if (dropAction == Qt::IgnoreAction) { + // aborted drag, put tab back in place + // disable event handling during the insert for the tab to prevent infinite recursion (stack overflow) + dragWidget->blockSignals(true); + dynamic_cast<QTabWidget*>(parent())->insertTab(dragIndex, dragWidget, dragText); + dragWidget->blockSignals(false); + } + return true; + } + } + return QTabBar::event(event); +} + +} diff --git a/Swift/QtUI/Trellis/QtDNDTabBar.h b/Swift/QtUI/Trellis/QtDNDTabBar.h new file mode 100644 index 0000000..6de04d5 --- /dev/null +++ b/Swift/QtUI/Trellis/QtDNDTabBar.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QTabBar> + +#include <Swift/QtUI/QtTabWidget.h> + +namespace Swift { + +class QtDNDTabBar : public QTabBar { + Q_OBJECT + public: + explicit QtDNDTabBar(QWidget* parent = nullptr); + virtual ~QtDNDTabBar(); + + int getDragIndex() const; + QString getDragText() const; + QWidget* getDragWidget() const; + + virtual QSize sizeHint() const; + + friend class QtTabWidget; + signals: + void onDropSucceeded(); + + protected: + virtual void dragEnterEvent(QDragEnterEvent* dragEnterEvent); + virtual void dropEvent(QDropEvent* dropEvent); + virtual bool event(QEvent* event); + virtual QSize tabSizeHint(int index) const; + + private: + int defaultTabHeight; + int dragIndex = -1; + QString dragText; + QWidget* dragWidget = nullptr; +}; + +} diff --git a/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp b/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp new file mode 100644 index 0000000..53e2733 --- /dev/null +++ b/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2014-2019 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Trellis/QtDynamicGridLayout.h> + +#include <cassert> + +#include <QApplication> +#include <QEvent> +#include <QGridLayout> +#include <QLayoutItem> +#include <QtDebug> + +#include <Swiften/Base/Log.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTabWidget.h> +#include <Swift/QtUI/QtTabbable.h> +#include <Swift/QtUI/Trellis/QtDNDTabBar.h> + +namespace Swift { + +QtDynamicGridLayout::QtDynamicGridLayout(bool future, QWidget* parent, bool enableDND) : QWidget(parent), dndEnabled_(enableDND), movingTab_(nullptr), future_(future) { + gridLayout_ = new QGridLayout(this); + setContentsMargins(0,0,0,0); + setDimensions(QSize(1,1)); + connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*,QWidget*))); +} + +QtDynamicGridLayout::~QtDynamicGridLayout() { +} + +int QtDynamicGridLayout::addTab(QtTabbable* tab, const QString& title) { + assert(gridLayout_->rowCount() > 0 && gridLayout_->columnCount() > 0); + + QPoint lastPos(0,0); + if (tabPositions_.contains(P2QSTRING(tab->getID()))) { + lastPos = tabPositions_[P2QSTRING(tab->getID())]; + } + + lastPos = QPoint(qMin(lastPos.x(), gridLayout_->columnCount() - 1), qMin(lastPos.y(), gridLayout_->rowCount() - 1)); + + QLayoutItem* item = gridLayout_->itemAtPosition(lastPos.y(), lastPos.x()); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(item ? item->widget() : nullptr); + if (tabWidget) { + tabWidget->addTab(tab, title); + } + tab->setEmphasiseFocus(getDimension().width() > 1 || getDimension().height() > 1); + if (future_) { + showHideFirstTabs(); // FIXME: Putting it here as a workaround until I work out why it doesn't work initially + } + return tabWidget ? indexOf(tab) : -1; +} + +int QtDynamicGridLayout::count() const { + int count = 0; + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + if (tabWidget) { + count += tabWidget->count(); + } + } + } + return count; +} + +QWidget* QtDynamicGridLayout::widget(int index) const { + QWidget* widgetAtIndex = nullptr; + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem ? layoutItem->widget() : nullptr); + if (tabWidget) { + if (index < tabWidget->count()) { + widgetAtIndex = tabWidget->widget(index); + return widgetAtIndex; + } + else { + index -= tabWidget->count(); + } + } + } + } + return widgetAtIndex; +} + +int QtDynamicGridLayout::indexOf(const QWidget* widget) const { + int index = 0; + if (widget) { + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + if (tabWidget) { + for (int n = 0; n < tabWidget->count(); n++) { + QWidget* nthWidget = tabWidget->widget(n); + if (nthWidget == widget) { + return index; + } + index++; + } + } + } + } + } + return -1; +} + +void QtDynamicGridLayout::handleApplicationFocusChanged(QWidget*, QWidget* newFocus) { + if (movingTab_ || resizing_) { + return; + } + + if (newFocus) { + if (isAncestorOf(newFocus)) { + QtTabbable *newTab = dynamic_cast<QtTabbable*>(newFocus->parentWidget()); + if (newTab) { + onCurrentIndexChanged(currentIndex()); + } + } + } +} + +int QtDynamicGridLayout::currentIndex() const { + return indexOf(currentWidget()); +} + +void QtDynamicGridLayout::setCurrentIndex(int index) { + int tabIndex = -1; + QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex); + if (tabIndex >= 0) { + tabWidget->setCurrentIndex(tabIndex); + if (!tabWidget->hasFocus()) { + tabWidget->widget(tabIndex)->setFocus(Qt::TabFocusReason); + } + } else { + assert(false); + } +} + +void QtDynamicGridLayout::removeTab(int index) { + int tabIndex = -1; + QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex); + if (tabWidget) { + QWidget* tab = tabWidget->widget(tabIndex); + tabWidget->removeTab(tabIndex); + tab->setParent(nullptr); + } +} + +/** + * This event filter serves the purpose of filtering out all QEvent::Show events targeted at + * all widgets excepts the currently moving widget. + * It is required because of the way Qt internally implements the QTabBar::moveTab method. + * It does not move the actual tab in the underlying structure, but instead removes it from + * a stacked layout and later adds it again. + * Both the remove and insert produce a lot signal emission and focus changes. Most of which + * the application MUST NOT react on because of the QTabBar and the corresponding QTabWidget + * being out of sync in an inconsistent state. + */ +bool QtDynamicGridLayout::eventFilter(QObject* object, QEvent* event) { + QtTabbable* tab = qobject_cast<QtTabbable*>(object); + if (!tab) { + return false; + } + if (tab && (tab != movingTab_)) { + if (event->type() == QEvent::Show) { + return true; + } + } + return false; +} + +QWidget* QtDynamicGridLayout::currentWidget() const { + QWidget* current = nullptr; + current = focusWidget(); + while (current && !dynamic_cast<QtTabbable*>(current)) { + if (current->parentWidget()) { + current = current->parentWidget(); + } else { + current = nullptr; + break; + } + } + if (!current) { + current = widget(0); + } + return current; +} + +void QtDynamicGridLayout::setCurrentWidget(QWidget* widget) { + if (widget) { + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + if (tabWidget) { + for (int n = 0; n < tabWidget->count(); n++) { + if (tabWidget->widget(n) == widget) { + tabWidget->setCurrentWidget(widget); + } + } + } + } + } + } +} + +QtTabWidget* QtDynamicGridLayout::indexToTabWidget(int index, int& tabIndex) { + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + if (tabWidget) { + if (index < tabWidget->count()) { + tabIndex = index; + return tabWidget; + } + else { + index -= tabWidget->count(); + if (index < 0) { + qWarning() << "Called QtDynamicGridLayout::setCurrentIndex with index out of bounds: index = " << index; + tabIndex = -1; + return nullptr; + } + } + } + } + } + tabIndex = -1; + return nullptr; +} + +bool QtDynamicGridLayout::isDNDEnabled() const { + return dndEnabled_; +} + +QHash<QString, QPoint> QtDynamicGridLayout::getTabPositions() const { + return tabPositions_; +} + +void QtDynamicGridLayout::setTabPositions(const QHash<QString, QPoint> positions) { + tabPositions_ = positions; +} + +QSize QtDynamicGridLayout::getDimension() const { + return QSize(gridLayout_->columnCount(), gridLayout_->rowCount()); +} + +void QtDynamicGridLayout::setDimensions(const QSize& dim) { + resizing_ = true; + assert(dim.width() > 0 && dim.height() > 0); + setUpdatesEnabled(false); + + QWidget* restoredWidget = currentWidget(); + + QGridLayout* oldLayout = dynamic_cast<QGridLayout*>(layout()); + QGridLayout* newLayout = new QGridLayout(this); + newLayout->setSpacing(4); + newLayout->setContentsMargins(0,0,0,0); + + int oldWidth = oldLayout->columnCount(); + int oldHeight = oldLayout->rowCount(); + int maxCol = qMax(oldWidth, dim.width()); + int minCol = qMin(oldWidth, dim.width()); + int maxRow = qMax(oldHeight, dim.height()); + int minRow = qMin(oldHeight, dim.height()); + + for (int row = 0; row < maxRow; row++) { + for (int col = 0; col < maxCol; col++) { + QLayoutItem* oldItem = oldLayout->itemAtPosition(row, col); + QLayoutItem* newItem = newLayout->itemAtPosition(row, col); + bool removeRow = !(row < dim.height()); + bool removeCol = !(col < dim.width()); + + if (removeCol || removeRow) { + if (oldItem) { + int squeezeRow = removeRow ? (minRow - 1) : row; + int squeezeCol = removeCol ? (minCol - 1) : col; + newItem = newLayout->itemAtPosition(squeezeRow, squeezeCol); + if (!newItem) { + newLayout->addWidget(createDNDTabWidget(this), squeezeRow, squeezeCol); + newItem = newLayout->itemAtPosition(squeezeRow, squeezeCol); + } + QtTabWidget* oldTabWidget = dynamic_cast<QtTabWidget*>(oldItem->widget()); + QtTabWidget* newTabWidget = dynamic_cast<QtTabWidget*>(newItem->widget()); + assert(oldTabWidget && newTabWidget); + + oldTabWidget->hide(); + while(oldTabWidget->count()) { + QIcon icon = oldTabWidget->tabIcon(0); + QString text = oldTabWidget->tabText(0); + QWidget* movingTab = oldTabWidget->widget(0); + //If handling was allowed, QtChatTabs::handleWidgetShown() would be triggered when newTabWidget has no tabs. + //That would access indices of the gridLayout_ that are null because they have been migrated to the newLayout. + movingTab->blockSignals(true); + newTabWidget->addTab(movingTab, icon, text); + movingTab->blockSignals(false); + } + delete oldTabWidget; + } + } else { + if (oldItem) { + newLayout->addWidget(oldItem->widget(), row, col); + newItem = newLayout->itemAtPosition(row, col); + } else { + newLayout->addWidget(createDNDTabWidget(this), row, col); + } + } + } + } + + for (int col = 0; col < dim.width(); col++) { + newLayout->setColumnStretch(col, 1); + } + for (int row = 0; row < dim.height(); row++) { + newLayout->setRowStretch(row, 1); + } + + setUpdatesEnabled(true); + delete layout(); + setLayout(newLayout); + gridLayout_ = newLayout; + + resizing_ = false; + setCurrentWidget(restoredWidget); + + updateEmphasiseFocusOnTabs(); + + if (future_) { + showHideFirstTabs(); + } +} + +void QtDynamicGridLayout::showHideFirstTabs() { + int tmp; + auto firstTabs = indexToTabWidget(0, tmp); + + if (firstTabs) { + if (gridLayout_->columnCount() == 1 && gridLayout_->rowCount() == 1) { + firstTabs->tabBar()->hide(); + } + else { + firstTabs->tabBar()->show(); + } + } +} + +void QtDynamicGridLayout::updateEmphasiseFocusOnTabs() { + const auto currentDimensions = getDimension(); + + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + assert(tabWidget); + for (int index = 0; index < tabWidget->count(); index++) { + QtTabbable* tab = dynamic_cast<QtTabbable*>(tabWidget->widget(index)); + assert(tab); + tab->setEmphasiseFocus(currentDimensions.height() > 1 || currentDimensions.width() > 1); + } + } + } +} + +void QtDynamicGridLayout::moveCurrentTabRight() { + int index = currentIndex(); + if (index >= 0) { + int tabIndex = -1; + QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex); + assert(tabWidget); + int newTabIndex = (tabIndex + 1) % tabWidget->count(); + moveTab(tabWidget, tabIndex, newTabIndex); + } +} + +void QtDynamicGridLayout::moveCurrentTabLeft() { + int index = currentIndex(); + if (index >= 0) { + int tabIndex = -1; + QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex); + assert(tabWidget); + int newTabIndex = (tabWidget->count() + tabIndex - 1) % tabWidget->count(); + moveTab(tabWidget, tabIndex, newTabIndex); + } +} + +void QtDynamicGridLayout::moveCurrentTabToNextGroup() { + int index = currentIndex(); + if (index >= 0) { + int tabIndex = -1; + QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex); + + int row = -1; + int col = -1; + int tmp; + gridLayout_->getItemPosition(gridLayout_->indexOf(tabWidget), &row, &col, &tmp, &tmp); + + // calculate next cell + col++; + if (!(col < gridLayout_->columnCount())) { + col = 0; + row++; + if (!(row < gridLayout_->rowCount())) { + row = 0; + } + } + + QtTabWidget* targetTabWidget = dynamic_cast<QtTabWidget*>(gridLayout_->itemAtPosition(row, col)->widget()); + assert(tabWidget); + assert(targetTabWidget); + + // fetch tab information + QWidget* tab = tabWidget->widget(tabIndex); + QString tabText = tabWidget->tabText(tabIndex); + + // move tab + tab->blockSignals(true); + targetTabWidget->addTab(tab, tabText); + tab->blockSignals(false); + tab->setFocus(Qt::TabFocusReason); + + updateTabPositions(); + } +} + +void QtDynamicGridLayout::moveCurrentTabToPreviousGroup() { + int index = currentIndex(); + if (index >= 0) { + int tabIndex = -1; + QtTabWidget* tabWidget = indexToTabWidget(index, tabIndex); + + int row = -1; + int col = -1; + int tmp; + gridLayout_->getItemPosition(gridLayout_->indexOf(tabWidget), &row, &col, &tmp, &tmp); + + // calculate next cell + col--; + if (col < 0) { + col = gridLayout_->columnCount() - 1; + row--; + if (row < 0) { + row = gridLayout_->rowCount() - 1; + } + } + + QtTabWidget* targetTabWidget = dynamic_cast<QtTabWidget*>(gridLayout_->itemAtPosition(row, col)->widget()); + assert(tabWidget); + assert(targetTabWidget); + + // fetch tab information + QWidget* tab = tabWidget->widget(tabIndex); + QString tabText = tabWidget->tabText(tabIndex); + + // move tab + tab->blockSignals(true); + targetTabWidget->addTab(tab, tabText); + tab->blockSignals(false); + tab->setFocus(Qt::TabFocusReason); + + updateTabPositions(); + } +} + +void QtDynamicGridLayout::handleTabCloseRequested(int index) { + updateTabPositions(); + QtTabWidget* tabWidgetSender = dynamic_cast<QtTabWidget*>(sender()); + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + if (tabWidget == tabWidgetSender) { + tabCloseRequested(index); + } + else { + index += tabWidget->count(); + } + } + } +} + +void QtDynamicGridLayout::handleTabCurrentChanged(int index) { + if (movingTab_) { + return; + } + + if (index >= 0) { + QTabWidget* sendingTabWidget = dynamic_cast<QTabWidget*>(sender()); + assert(sendingTabWidget); + sendingTabWidget->widget(index)->setFocus(); + } +} + +void QtDynamicGridLayout::updateTabPositions() { + for (int y = 0; y < gridLayout_->rowCount(); y++) { + for (int x = 0; x < gridLayout_->columnCount(); x++) { + QLayoutItem* layoutItem = gridLayout_->itemAtPosition(y, x); + QtTabWidget* tabWidget = dynamic_cast<QtTabWidget*>(layoutItem->widget()); + assert(tabWidget); + for (int index = 0; index < tabWidget->count(); index++) { + QtTabbable* tab = dynamic_cast<QtTabbable*>(tabWidget->widget(index)); + assert(tab); + tabPositions_.insert(P2QSTRING(tab->getID()), QPoint(x, y)); + } + } + } +} + +void QtDynamicGridLayout::moveTab(QtTabWidget* tabWidget, int oldIndex, int newIndex) { +#if QT_VERSION >= 0x040500 + SWIFT_LOG_ASSERT(movingTab_ == nullptr, error); + movingTab_ = qobject_cast<QtTabbable*>(tabWidget->widget(oldIndex)); + SWIFT_LOG_ASSERT(movingTab_ != nullptr, error); + + if (movingTab_) { + // Install event filter that filters out events issued during the internal movement of the + // tab but not targeted at the moving tab. + qApp->installEventFilter(this); + + tabWidget->tabBar()->moveTab(oldIndex, newIndex); + + qApp->removeEventFilter(this); + SWIFT_LOG_ASSERT(movingTab_ == tabWidget->widget(newIndex), error); + } + movingTab_ = nullptr; + tabWidget->widget(newIndex)->setFocus(); +#else +#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled. +#endif +} + +QtTabWidget* QtDynamicGridLayout::createDNDTabWidget(QWidget* parent) { + QtTabWidget* tab = new QtTabWidget(parent); + if (dndEnabled_) { + QtDNDTabBar* tabBar = new QtDNDTabBar(tab); + connect(tabBar, SIGNAL(onDropSucceeded()), this, SLOT(updateTabPositions())); + tab->setTabBar(tabBar); + } + tab->setUsesScrollButtons(true); + tab->setElideMode(Qt::ElideRight); +#if QT_VERSION >= 0x040500 + /*For Macs, change the tab rendering.*/ + tab->setDocumentMode(true); + /*Closable tabs are only in Qt4.5 and later*/ + tab->setTabsClosable(true); + tab->setMovable(true); + connect(tab, SIGNAL(tabCloseRequested(int)), this, SLOT(handleTabCloseRequested(int))); + connect(tab, SIGNAL(currentChanged(int)), this, SLOT(handleTabCurrentChanged(int))); +#else +#warning Qt 4.5 or later is needed. Trying anyway, some things will be disabled. +#endif + return tab; +} + +} diff --git a/Swift/QtUI/Trellis/QtDynamicGridLayout.h b/Swift/QtUI/Trellis/QtDynamicGridLayout.h new file mode 100644 index 0000000..f3a2e96 --- /dev/null +++ b/Swift/QtUI/Trellis/QtDynamicGridLayout.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QGridLayout> +#include <QHash> +#include <QPoint> +#include <QSize> +#include <QString> +#include <QWidget> + +namespace Swift { + class QtTabbable; + class QtTabWidget; + + class QtDynamicGridLayout : public QWidget { + Q_OBJECT + public: + explicit QtDynamicGridLayout(bool future, QWidget* parent = nullptr, bool enableDND = false); + virtual ~QtDynamicGridLayout(); + + QSize getDimension() const; + + // emulate QtTabWidget API + int addTab(QtTabbable* tab, const QString& title); + void removeTab(int index); + int count() const; + + QWidget* widget(int index) const; + QWidget* currentWidget() const; + void setCurrentWidget(QWidget* widget); + + QtTabWidget* indexToTabWidget(int index, int& tabIndex); + + int indexOf(const QWidget* widget) const; + int currentIndex() const; + void setCurrentIndex(int index); + + bool isDNDEnabled() const; + + QHash<QString, QPoint> getTabPositions() const; + void setTabPositions(const QHash<QString, QPoint> positions); + + bool eventFilter(QObject* object, QEvent* event); + + signals: + void tabCloseRequested(int index); + void onCurrentIndexChanged(int newIndex); + + public slots: + void setDimensions(const QSize& dim); + + // Tab Management + void moveCurrentTabRight(); + void moveCurrentTabLeft(); + void moveCurrentTabToNextGroup(); + void moveCurrentTabToPreviousGroup(); + + void updateTabPositions(); + + private slots: + void handleTabCloseRequested(int index); + void handleTabCurrentChanged(int index); + void handleApplicationFocusChanged(QWidget* oldFocus, QWidget* newFocus); + + private: + void moveTab(QtTabWidget* tabWidget, int oldIndex, int newIndex); + QtTabWidget* createDNDTabWidget(QWidget* parent); + void updateEmphasiseFocusOnTabs(); + void showHideFirstTabs(); + + private: + QGridLayout *gridLayout_; + bool dndEnabled_; + QHash<QString, QPoint> tabPositions_; + QtTabbable* movingTab_; + bool resizing_ = false; + bool future_ = false; + }; +} diff --git a/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp b/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp new file mode 100644 index 0000000..e922e07 --- /dev/null +++ b/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2014-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Trellis/QtGridSelectionDialog.h> + +#include <QApplication> +#include <QCursor> +#include <QPaintEvent> +#include <QPainter> +#include <QStyle> +#include <QStyleOption> + +namespace Swift { + +QtGridSelectionDialog::QtGridSelectionDialog(QWidget* parent) : QWidget(parent), descriptionText(tr("Select the number of rows and columns for your layout. You can change the size by moving the mouse or cursor keys.")) { + frameSize = QSize(28,28) * 2; + maxGridSize = QSize(7,7); + minGridSize = QSize(1,1); + currentGridSize = QSize(1,1); + padding = 4; + setWindowFlags(Qt::FramelessWindowHint); + setCursor(Qt::SizeAllCursor); + horizontalMargin = style()->pixelMetric(QStyle::PM_LayoutLeftMargin); + verticalMargin = style()->pixelMetric(QStyle::PM_LayoutBottomMargin); +} + +QSize QtGridSelectionDialog::sizeHint() const { + // PM_MenuVMargin | frameSize | ( padding | frameSize ) * | PM_MenuVMargin + int width = horizontalMargin + frameSize.width() + (padding + frameSize.width()) * (currentGridSize.width() - 1) + horizontalMargin; + int height = verticalMargin + frameSize.height() + (padding + frameSize.height()) * (currentGridSize.height() - 1) + verticalMargin; + + height += getDescriptionTextHeight(width); + + return QSize(width, height); +} + +void QtGridSelectionDialog::setCurrentGridSize(const QSize& size) { + currentGridSize = size; + emit currentGridSizeChanged(size); +} + +QSize QtGridSelectionDialog::getCurrentGridSize() const { + return currentGridSize; +} + +void QtGridSelectionDialog::setMinGridSize(const QSize& size) { + minGridSize = size; + emit minGridSizeChanged(size); +} + +QSize QtGridSelectionDialog::getMinGridSize() const { + return minGridSize; +} + +void QtGridSelectionDialog::setMaxGridSize(const QSize& size) { + maxGridSize = size; + emit maxGridSizeChanged(size); +} + +QSize QtGridSelectionDialog::getMaxGridSize() const { + return maxGridSize; +} + +QSize QtGridSelectionDialog::getFrameSize() const { + return frameSize; +} + +int QtGridSelectionDialog::getDescriptionTextHeight() const { + auto width = horizontalMargin + frameSize.width() + (padding + frameSize.width()) * (currentGridSize.width() - 1) + horizontalMargin; + return getDescriptionTextHeight(width); +} + +int QtGridSelectionDialog::getDescriptionTextHeight(int width) const { + // Height of descriptive centered text below trellis + auto fontMetrics = QFontMetrics(QApplication::font()); + auto descriptionBB = fontMetrics.boundingRect(QRect(0, 0, width - 2 * horizontalMargin, 1000), Qt::TextWordWrap, descriptionText, 0, nullptr); + + return (descriptionBB.height() + descriptionBB.y()); +} + +void QtGridSelectionDialog::keyReleaseEvent(QKeyEvent* event) { + if (event) { + QSize newGridSize = currentGridSize; + if (event->key() == Qt::Key_Up) { + newGridSize += QSize(0, -1); + } + else if (event->key() == Qt::Key_Down) { + newGridSize += QSize(0, 1); + } + else if (event->key() == Qt::Key_Left) { + newGridSize += QSize(-1, 0); + } + else if (event->key() == Qt::Key_Right) { + newGridSize += QSize(1, 0); + } + else if (event->key() == Qt::Key_Return) { + hide(); + setCurrentGridSize(currentGridSize); + } + else if (event->key() == Qt::Key_Escape) { + hide(); + } + + if (minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize) != currentGridSize) { + currentGridSize = minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize); + + QSize newSizeHint = sizeHint(); + resize(newSizeHint); + QCursor::setPos(mapToGlobal(QPoint(newSizeHint.width(), newSizeHint.height()- getDescriptionTextHeight()) - QPoint(frameSize.width() / 2, frameSize.height() / 2))); + } + } +} + +void QtGridSelectionDialog::mousePressEvent(QMouseEvent*) { + hide(); + setCurrentGridSize(currentGridSize); +} + +void QtGridSelectionDialog::paintEvent(QPaintEvent*) { + QPainter painter(this); + // draw grid + QRect gridCell = QRect(QPoint(0,0), frameSize); + painter.setBrush(palette().highlight()); + painter.setPen(Qt::NoPen); + for (int x = 0; x < currentGridSize.width(); x++) { + for (int y = 0; y < currentGridSize.height(); y++) { + int xPos = horizontalMargin + (x * (frameSize.width() + padding)); + int yPos = verticalMargin + (y * (frameSize.height() + padding)); + gridCell.moveTo(QPoint(xPos, yPos)); + painter.drawRect(gridCell); + } + } + + // draw description text + auto fontMetrics = QFontMetrics(QApplication::font()); + auto descriptionBB = fontMetrics.boundingRect(QRect(0,0, width() - 2 * horizontalMargin,0), Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, descriptionText, 0, nullptr); + + QStyleOption opt; + opt.initFrom(this); + int textY = verticalMargin + (currentGridSize.height() * (frameSize.height() + padding)); + int textX = (size().width() - descriptionBB.width()) / 2; + style()->drawItemText(&painter, QRect(textX, textY, descriptionBB.width(), descriptionBB.height()), Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, opt.palette, true, descriptionText, foregroundRole()); +} + +void QtGridSelectionDialog::showEvent(QShowEvent*) { + timerId = startTimer(1000 / 25); +} + +void QtGridSelectionDialog::hideEvent(QHideEvent*) { + killTimer(timerId); +} + +void QtGridSelectionDialog::timerEvent(QTimerEvent*) { + + const QPoint diff = QCursor::pos() - frameGeometry().topLeft() - QPoint(horizontalMargin, verticalMargin); + const auto toleranceFactor = 4; // Ratio of how far (1/tolerance) the mouse should move for the next frame to be plotted + // dx, dy - mouse position with respect to first top-left square + const auto dx = diff.x(); + const auto dy = diff.y(); + // width, height - dimension of each square with padding + const auto width = frameSize.width() + padding; + const auto height = frameSize.height() + padding; + // xThreshold, yThreshold - how far the mouse should be moved so that a new square is added or the existing one is hidden + const auto xThreshold = width / toleranceFactor; + const auto yThreshold = height / toleranceFactor; + + const auto getSize = [](int length, int threshold, int delta) { + if (delta < length + threshold) { + return 1; + } + else { + return (delta + (length - threshold)) / length; + } + }; + const QSize newGridSize(getSize(width, xThreshold, dx), getSize(height, yThreshold, dy)); + + if (minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize) != currentGridSize) { + currentGridSize = minGridSize.expandedTo(newGridSize).boundedTo(maxGridSize); + resize(sizeHint()); + } +} +bool QtGridSelectionDialog::event(QEvent* event) { + // Hide the window when it becomes a non-top-level window. + if (event->type() == QEvent::WindowDeactivate) { + hide(); + return true; + } + return QWidget::event(event); +} + +} diff --git a/Swift/QtUI/Trellis/QtGridSelectionDialog.h b/Swift/QtUI/Trellis/QtGridSelectionDialog.h new file mode 100644 index 0000000..c448979 --- /dev/null +++ b/Swift/QtUI/Trellis/QtGridSelectionDialog.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <QSize> +#include <QWidget> + +namespace Swift { + + class QtGridSelectionDialog : public QWidget { + Q_OBJECT + + Q_PROPERTY(QSize currentGridSize READ getCurrentGridSize WRITE setCurrentGridSize NOTIFY currentGridSizeChanged) + Q_PROPERTY(QSize minGridSize READ getMinGridSize WRITE setMinGridSize NOTIFY minGridSizeChanged) + Q_PROPERTY(QSize maxGridSize READ getMaxGridSize WRITE setMaxGridSize NOTIFY maxGridSizeChanged) + public: + explicit QtGridSelectionDialog(QWidget* parent = nullptr); + + virtual QSize sizeHint() const; + + void setCurrentGridSize(const QSize& size); + QSize getCurrentGridSize() const; + void setMinGridSize(const QSize& size); + QSize getMinGridSize() const; + void setMaxGridSize(const QSize& size); + QSize getMaxGridSize() const; + + QSize getFrameSize() const; + int getDescriptionTextHeight() const; + + signals: + void currentGridSizeChanged(QSize); + void minGridSizeChanged(QSize); + void maxGridSizeChanged(QSize); + + protected: + void keyReleaseEvent(QKeyEvent* event); + void mousePressEvent(QMouseEvent* event); + void paintEvent(QPaintEvent* event); + void showEvent(QShowEvent* event); + void hideEvent(QHideEvent* event); + bool event(QEvent* event); + void timerEvent(QTimerEvent* event); + + private: + int getDescriptionTextHeight(int width) const; + + private: + int padding; + int horizontalMargin; + int verticalMargin; + int timerId = -1; + + QSize frameSize; + + QSize currentGridSize; + QSize minGridSize; + QSize maxGridSize; + + const QString descriptionText; + }; +} diff --git a/Swift/QtUI/UnitTest/QtUtilitiesTest.cpp b/Swift/QtUI/UnitTest/QtUtilitiesTest.cpp new file mode 100644 index 0000000..45d2239 --- /dev/null +++ b/Swift/QtUI/UnitTest/QtUtilitiesTest.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <gtest/gtest.h> + +#include <QDateTime> +#include <QLocale> + +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/QtUtilities.cpp> + +TEST(QtUtilitiesTest, testDSTawareness) { + QLocale::setDefault(QLocale(QLocale::English, QLocale::Germany)); + + auto beforeDSTpoint = QDateTime(QDate(2017, 3, 26), QTime(0, 0)); + + ASSERT_EQ(23 * 60 * 60, QtUtilities::secondsToNextMidnight(beforeDSTpoint)); +} + + +TEST(QtUtilitiesTest, testNoDSTChange) { + QLocale::setDefault(QLocale(QLocale::English, QLocale::Germany)); + + auto beforeDSTpoint = QDateTime(QDate(2017, 3, 23), QTime(0, 0)); + + ASSERT_EQ(24 * 60 * 60, QtUtilities::secondsToNextMidnight(beforeDSTpoint)); +} diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.cpp b/Swift/QtUI/UserSearch/ContactListDelegate.cpp index 29cab83..75e25a1 100644 --- a/Swift/QtUI/UserSearch/ContactListDelegate.cpp +++ b/Swift/QtUI/UserSearch/ContactListDelegate.cpp @@ -4,10 +4,18 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file 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> +#include <Swift/QtUI/UserSearch/ContactListModel.h> namespace Swift { @@ -18,29 +26,29 @@ ContactListDelegate::~ContactListDelegate() { } void ContactListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - if (!index.isValid()) { - return; - } - const Contact* contact = static_cast<Contact*>(index.internalPointer()); - QColor nameColor = index.data(Qt::TextColorRole).value<QColor>(); - QString avatarPath = index.data(ContactListModel::AvatarRole).value<QString>(); - QIcon presenceIcon =index.data(ChatListRecentItem::PresenceIconRole).isValid() && !index.data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull() - ? index.data(ChatListRecentItem::PresenceIconRole).value<QIcon>() - : QIcon(":/icons/offline.png"); - QString name = P2QSTRING(contact->name); - QString statusText = P2QSTRING(contact->jid.toString()); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, 0, compact_); + 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); + 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; + compact_ = compact; } } diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.h b/Swift/QtUI/UserSearch/ContactListDelegate.h index 7680aba..208246a 100644 --- a/Swift/QtUI/UserSearch/ContactListDelegate.h +++ b/Swift/QtUI/UserSearch/ContactListDelegate.h @@ -13,18 +13,18 @@ namespace Swift { class ContactListDelegate : public QStyledItemDelegate { - public: - ContactListDelegate(bool compact); - virtual ~ContactListDelegate(); + 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; + 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); + public slots: + void setCompact(bool compact); - private: - bool compact_; - DelegateCommons common_; + private: + bool compact_; + DelegateCommons common_; }; } diff --git a/Swift/QtUI/UserSearch/ContactListModel.cpp b/Swift/QtUI/UserSearch/ContactListModel.cpp index 6523a4d..5d8aa6c 100644 --- a/Swift/QtUI/UserSearch/ContactListModel.cpp +++ b/Swift/QtUI/UserSearch/ContactListModel.cpp @@ -4,170 +4,109 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #include <Swift/QtUI/UserSearch/ContactListModel.h> -#include <Swift/QtUI/QtSwiftUtil.h> +#include <QMimeData> + #include <Swiften/Base/Path.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Elements/StatusShow.h> -#include <QMimeData> +#include <Swift/QtUI/QtResourceHelper.h> +#include <Swift/QtUI/QtSwiftUtil.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) { } -ContactListModel::ContactListModel(bool editable) : QAbstractItemModel(), editable_(editable) { +void ContactListModel::setList(const std::vector<Contact::ref>& list) { + emit layoutAboutToBeChanged(); + contacts_ = list; + emit layoutChanged(); } -void ContactListModel::setList(const std::vector<Contact>& list) { - emit layoutAboutToBeChanged(); - contacts_ = list; - emit layoutChanged(); +const std::vector<Contact::ref>& ContactListModel::getList() const { + return contacts_; } -const std::vector<Contact>& 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; + 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; + return editable_ ? 2 : 1; } QVariant ContactListModel::data(const QModelIndex& index, int role) const { - if (boost::numeric_cast<size_t>(index.row()) < contacts_.size()) { - const Contact& contact = contacts_[index.row()]; - if (role == Qt::EditRole) { - return P2QSTRING(contact.jid.toString()); - } - return dataForContact(contact, role); - } else { - return QVariant(); - } -} - -bool ContactListModel::dropMimeData(const QMimeData* data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/) { - if (!data->hasFormat("application/vnd.swift.contact-jid")) { - return false; - } - - QByteArray dataBytes = data->data("application/vnd.swift.contact-jid"); - QDataStream dataStream(&dataBytes, QIODevice::ReadOnly); - QString jidString; - QString displayName; - QString statusText; - StatusShow::Type statusType; - QString avatarPath; - - dataStream >> jidString; - dataStream >> displayName; - dataStream >> statusText; - dataStream >> statusType; - dataStream >> avatarPath; - - JID jid = JID(Q2PSTRING(jidString)); - - foreach(const Contact& contact, contacts_) { - if (contact.jid == jid) { - return false; - } - } - - emit layoutAboutToBeChanged(); - contacts_.push_back(Contact(Q2PSTRING(displayName), jid, statusType, Q2PSTRING(avatarPath))); - emit layoutChanged(); - - onJIDsDropped(std::vector<JID>(1, jid)); - onListChanged(getList()); - - return true; + if ((boost::numeric_cast<size_t>(index.row()) < contacts_.size()) && (index.column() == 0)) { + 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(); - } + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } - return boost::numeric_cast<size_t>(row) < contacts_.size() ? createIndex(row, column, (void*)&(contacts_[row])) : 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(); + if (!index.isValid()) { + return QModelIndex(); + } + return QModelIndex(); } int ContactListModel::rowCount(const QModelIndex& /*parent*/) const { - return contacts_.size(); + 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; + if (boost::numeric_cast<size_t>(row) < contacts_.size()) { + emit layoutAboutToBeChanged(); + contacts_.erase(contacts_.begin() + row); + emit layoutChanged(); + onListChanged(getList()); + return true; + } + return false; } -QVariant ContactListModel::dataForContact(const Contact& contact, int role) const { - switch (role) { - case Qt::DisplayRole: return P2QSTRING(contact.name); - case DetailTextRole: return P2QSTRING(contact.jid.toString()); - case AvatarRole: return QVariant(P2QSTRING(pathToString(contact.avatarPath))); - case PresenceIconRole: return getPresenceIconForContact(contact); - default: return QVariant(); - } +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& contact) const { - QString iconString; - switch (contact.statusType) { - case StatusShow::Online: iconString = "online";break; - case StatusShow::Away: iconString = "away";break; - case StatusShow::XA: iconString = "away";break; - case StatusShow::FFC: iconString = "online";break; - case StatusShow::DND: iconString = "dnd";break; - case StatusShow::None: iconString = "offline";break; - } - return QIcon(":/icons/" + iconString + ".png"); +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 index e7f4a0b..026b01b 100644 --- a/Swift/QtUI/UserSearch/ContactListModel.h +++ b/Swift/QtUI/UserSearch/ContactListModel.h @@ -4,55 +4,63 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <vector> + #include <boost/bind.hpp> -#include <Swiften/Base/boost_bsignals.h> +#include <boost/signals2.hpp> #include <QAbstractItemModel> #include <Swift/Controllers/Contact.h> -#include <Swift/QtUI/ChatList/ChatListItem.h> + #include <Swift/QtUI/ChatList/ChatListGroupItem.h> +#include <Swift/QtUI/ChatList/ChatListItem.h> #include <Swift/QtUI/ChatList/ChatListRecentItem.h> namespace Swift { - class ContactListModel : public QAbstractItemModel { - Q_OBJECT - public: - enum ContactRoles { - DetailTextRole = Qt::UserRole, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2 - }; - - public: - ContactListModel(bool editable); - - void setList(const std::vector<Contact>& list); - const std::vector<Contact>& getList() const; - - Qt::ItemFlags flags(const QModelIndex& index) const; - int columnCount(const QModelIndex& parent = QModelIndex()) const; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); - QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex& index) const; - int rowCount(const QModelIndex& parent = QModelIndex()) const; - bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()); - - private: - QVariant dataForContact(const Contact& contact, int role) const; - QIcon getPresenceIconForContact(const Contact& contact) const; - - signals: - void onListChanged(std::vector<Contact> list); - void onJIDsDropped(const std::vector<JID>& contact); - - private: - bool editable_; - std::vector<Contact> contacts_; - }; + 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 index 899c592..73a8482 100644 --- a/Swift/QtUI/UserSearch/QtContactListWidget.cpp +++ b/Swift/QtUI/UserSearch/QtContactListWidget.cpp @@ -4,102 +4,100 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file 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 <QHeaderView> + #include <Swift/Controllers/Settings/SettingsProvider.h> -#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> -#include <QHeaderView> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h> +#include <Swift/QtUI/UserSearch/ContactListDelegate.h> +#include <Swift/QtUI/UserSearch/ContactListModel.h> namespace Swift { QtContactListWidget::QtContactListWidget(QWidget* parent, SettingsProvider* settings) : QTreeView(parent), settings_(settings), limited_(false) { - contactListModel_ = new ContactListModel(true); - setModel(contactListModel_); + contactListModel_ = new ContactListModel(true); + setModel(contactListModel_); - connect(contactListModel_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(handleListChanged(std::vector<Contact>))); - connect(contactListModel_, SIGNAL(onListChanged(std::vector<Contact>)), this, SIGNAL(onListChanged(std::vector<Contact>))); - connect(contactListModel_, SIGNAL(onJIDsDropped(std::vector<JID>)), this, SIGNAL(onJIDsAdded(std::vector<JID>))); + 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); - setDragEnabled(true); - setAcceptDrops(true); - setDropIndicatorShown(true); - setUniformRowHeights(true); + setSelectionMode(QAbstractItemView::SingleSelection); + setSelectionBehavior(QAbstractItemView::SelectRows); + setUniformRowHeights(true); - setAlternatingRowColors(true); - setIndentation(0); - setHeaderHidden(true); - setExpandsOnDoubleClick(false); - setItemsExpandable(false); - setEditTriggers(QAbstractItemView::DoubleClicked); + 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()); + contactListDelegate_ = new ContactListDelegate(settings->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + removableItemDelegate_ = new QtRemovableItemDelegate(style()); - setItemDelegateForColumn(0, contactListDelegate_); - setItemDelegateForColumn(1, removableItemDelegate_); + setItemDelegateForColumn(0, contactListDelegate_); + setItemDelegateForColumn(1, removableItemDelegate_); - int closeIconWidth = fontMetrics().height(); - header()->resizeSection(1, closeIconWidth); + header()->resizeSection(1, removableItemDelegate_->sizeHint(QStyleOptionViewItem(), QModelIndex()).width()); - header()->setStretchLastSection(false); + header()->setStretchLastSection(false); #if QT_VERSION >= 0x050000 - header()->setSectionResizeMode(0, QHeaderView::Stretch); + header()->setSectionResizeMode(0, QHeaderView::Stretch); #else - header()->setResizeMode(0, QHeaderView::Stretch); + header()->setResizeMode(0, QHeaderView::Stretch); #endif } QtContactListWidget::~QtContactListWidget() { - delete contactListDelegate_; - delete removableItemDelegate_; + delete contactListDelegate_; + delete removableItemDelegate_; + delete contactListModel_; +} + +void QtContactListWidget::setList(const std::vector<Contact::ref>& list) { + contactListModel_->setList(list); } -void QtContactListWidget::setList(const std::vector<Contact>& list) { - contactListModel_->setList(list); +std::vector<Contact::ref> QtContactListWidget::getList() const { + return contactListModel_->getList(); } -std::vector<Contact> 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; - if (limited) { - handleListChanged(getList()); - } else { - setAcceptDrops(true); - setDropIndicatorShown(true); - } + limited_ = limited; } -void QtContactListWidget::updateContacts(const std::vector<Contact>& contactUpdates) { - std::vector<Contact> contacts = contactListModel_->getList(); - foreach(const Contact& contactUpdate, contactUpdates) { - for(size_t n = 0; n < contacts.size(); n++) { - if (contactUpdate.jid == contacts[n].jid) { - contacts[n] = contactUpdate; - break; - } - } - } - contactListModel_->setList(contacts); +bool QtContactListWidget::isFull() const { + return limited_ && (getList().size() == 1); } -void QtContactListWidget::handleListChanged(std::vector<Contact> list) { - if (limited_) { - setAcceptDrops(list.size() <= 1); - setDropIndicatorShown(list.size() <= 1); - } +void QtContactListWidget::updateContacts(const std::vector<Contact::ref>& contactUpdates) { + std::vector<Contact::ref> contacts = contactListModel_->getList(); + for (const auto& contactUpdate : contactUpdates) { + for (auto&& contact : contacts) { + if (contactUpdate->jid == contact->jid) { + contact = contactUpdate; + break; + } + } + } + contactListModel_->setList(contacts); } void QtContactListWidget::handleSettingsChanged(const std::string&) { - contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); } } diff --git a/Swift/QtUI/UserSearch/QtContactListWidget.h b/Swift/QtUI/UserSearch/QtContactListWidget.h index f360a91..f2483c3 100644 --- a/Swift/QtUI/UserSearch/QtContactListWidget.h +++ b/Swift/QtUI/UserSearch/QtContactListWidget.h @@ -4,18 +4,24 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <vector> +#include <QDragEnterEvent> +#include <QDragMoveEvent> +#include <QDropEvent> #include <QTreeView> -#include <Swift/Controllers/Contact.h> #include <Swiften/Base/Log.h> -#include <QDragEnterEvent> -#include <QDragMoveEvent> -#include <QDropEvent> +#include <Swift/Controllers/Contact.h> namespace Swift { @@ -25,34 +31,33 @@ class SettingsProvider; class QtRemovableItemDelegate; class QtContactListWidget : public QTreeView { - Q_OBJECT + Q_OBJECT public: - QtContactListWidget(QWidget* parent, SettingsProvider* settings); - virtual ~QtContactListWidget(); + QtContactListWidget(QWidget* parent, SettingsProvider* settings); + virtual ~QtContactListWidget(); - void setList(const std::vector<Contact>& list); - std::vector<Contact> getList() const; - void setMaximumNoOfContactsToOne(bool limited); + 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>& contactUpdates); + void updateContacts(const std::vector<Contact::ref>& contactUpdates); signals: - void onListChanged(std::vector<Contact> list); - void onJIDsAdded(const std::vector<JID>& jids); - -private slots: - void handleListChanged(std::vector<Contact> list); + void onListChanged(std::vector<Contact::ref> list); + void onJIDsAdded(const std::vector<JID>& jids); private: - void handleSettingsChanged(const std::string&); + void handleSettingsChanged(const std::string&); private: - SettingsProvider* settings_; - ContactListModel* contactListModel_; - ContactListDelegate* contactListDelegate_; - QtRemovableItemDelegate* removableItemDelegate_; - bool limited_; + SettingsProvider* settings_; + ContactListModel* contactListModel_; + ContactListDelegate* contactListDelegate_; + QtRemovableItemDelegate* removableItemDelegate_; + bool limited_; }; } diff --git a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp index ca65dca..e55ee80 100644 --- a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp +++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp @@ -4,179 +4,212 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file 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 <boost/signals2.hpp> #include <QAbstractItemView> #include <QApplication> #include <QDesktopWidget> #include <QKeyEvent> +#include <QtGlobal> +#include <Swift/Controllers/Settings/SettingsProvider.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/UserSearch/ContactListDelegate.h> +#include <Swift/QtUI/UserSearch/ContactListModel.h> namespace Swift { -QtSuggestingJIDInput::QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings) : QLineEdit(parent), settings_(settings), currentContact_(NULL) { - treeViewPopup_ = new QTreeView(); - treeViewPopup_->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); - //treeViewPopup_->setAttribute(Qt::WA_ShowWithoutActivating); - treeViewPopup_->setAlternatingRowColors(true); - treeViewPopup_->setIndentation(0); - treeViewPopup_->setHeaderHidden(true); - treeViewPopup_->setExpandsOnDoubleClick(false); - treeViewPopup_->setItemsExpandable(false); - treeViewPopup_->setSelectionMode(QAbstractItemView::SingleSelection); - treeViewPopup_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - treeViewPopup_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - QSizePolicy policy(treeViewPopup_->sizePolicy()); - policy.setVerticalPolicy(QSizePolicy::Expanding); - treeViewPopup_->setSizePolicy(policy); - treeViewPopup_->hide(); - treeViewPopup_->setFocusProxy(this); - connect(treeViewPopup_, SIGNAL(clicked(QModelIndex)), this, SLOT(handleClicked(QModelIndex))); - connect(treeViewPopup_, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(handleClicked(QModelIndex))); - - contactListModel_ = new ContactListModel(false); - treeViewPopup_->setModel(contactListModel_); - - contactListDelegate_ = new ContactListDelegate(settings->getSetting(QtUISettingConstants::COMPACT_ROSTER)); - treeViewPopup_->setItemDelegate(contactListDelegate_); - - settings_->onSettingChanged.connect(boost::bind(&QtSuggestingJIDInput::handleSettingsChanged, this, _1)); +QtSuggestingJIDInput::QtSuggestingJIDInput(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_; + settings_->onSettingChanged.disconnect(boost::bind(&QtSuggestingJIDInput::handleSettingsChanged, this, _1)); + delete treeViewPopup_; + delete contactListDelegate_; + delete contactListModel_; } -const Contact* QtSuggestingJIDInput::getContact() { - if (currentContact_ != NULL) { - return currentContact_; - } else { - if (!text().isEmpty()) { - JID jid(Q2PSTRING(text())); - if (jid.isValid()) { - manualContact_.name = jid.toString(); - manualContact_.jid = jid; - return &manualContact_; - } - } - return NULL; - } +Contact::ref QtSuggestingJIDInput::getContact() { + if (!!currentContact_) { + return currentContact_; + } + + if (!text().isEmpty()) { + JID jid(Q2PSTRING(text())); + if (jid.isValid()) { + Contact::ref manualContact = std::make_shared<Contact>(); + manualContact->name = jid.toString(); + manualContact->jid = jid; + return manualContact; + } + } + return std::shared_ptr<Contact>(); } -void QtSuggestingJIDInput::setSuggestions(const std::vector<Contact>& suggestions) { - contactListModel_->setList(suggestions); - positionPopup(); - if (!suggestions.empty()) { - treeViewPopup_->setCurrentIndex(contactListModel_->index(0, 0)); - showPopup(); - } else { - currentContact_ = NULL; - } +void QtSuggestingJIDInput::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_->getList()[index.row()]; - setText(P2QSTRING(currentContact_->jid.toString())); - hidePopup(); - clearFocus(); - } else { - currentContact_ = NULL; - } - editingDone(); - } else { - QLineEdit::keyPressEvent(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::hideEvent(QHideEvent* /* event */) { + // Hide our popup when we are hidden (can happen when a dialog is closed by the user). + treeViewPopup_->hide(); } 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(); - } + /* 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)); - } + if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) { + contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); + } } void QtSuggestingJIDInput::handleClicked(const QModelIndex& index) { - if (index.isValid()) { - currentContact_ = &contactListModel_->getList()[index.row()]; - setText(P2QSTRING(currentContact_->jid.toString())); - hidePopup(); - } + 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); + 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(); + 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(); + disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*))); + treeViewPopup_->hide(); + + // Give focus back to input widget because the hide() call passes the focus to the wrong widget. + setFocus(); +#if defined(Q_OS_MAC) + // This workaround is needed on OS X, to bring the dialog containing this widget back to the front after + // the popup is hidden. Ubuntu 16.04 and Windows 8 do not have this issue. + window()->raise(); +#endif } } diff --git a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h index 673621c..402667d 100644 --- a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h +++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h @@ -4,8 +4,16 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once +#include <boost/signals2.hpp> + #include <QLineEdit> #include <QTreeView> @@ -18,40 +26,44 @@ class SettingsProvider; class ContactListModel; class QtSuggestingJIDInput : public QLineEdit { - Q_OBJECT - public: - QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings); - virtual ~QtSuggestingJIDInput(); + Q_OBJECT + public: + QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings); + virtual ~QtSuggestingJIDInput(); + + Contact::ref getContact(); + + void setSuggestions(const std::vector<Contact::ref>& suggestions); - const Contact* getContact(); + void clear(); - void setSuggestions(const std::vector<Contact>& suggestions); + boost::signals2::signal<void (const Contact::ref&)> onUserSelected; - signals: - void editingDone(); + signals: + void editingDone(); - protected: - virtual void keyPressEvent(QKeyEvent* event); + protected: + virtual void keyPressEvent(QKeyEvent* event); + virtual void hideEvent(QHideEvent* event); - private: - void handleSettingsChanged(const std::string& setting); + private: + void handleSettingsChanged(const std::string& setting); - private slots: - void handleClicked(const QModelIndex& index); - void handleApplicationFocusChanged(QWidget* old, QWidget* now); + private slots: + void handleClicked(const QModelIndex& index); + void handleApplicationFocusChanged(QWidget* old, QWidget* now); - private: - void positionPopup(); - void showPopup(); - void hidePopup(); + private: + void positionPopup(); + void showPopup(); + void hidePopup(); - private: - SettingsProvider* settings_; - ContactListModel* contactListModel_; - QTreeView* treeViewPopup_; - ContactListDelegate* contactListDelegate_; - Contact manualContact_; - const Contact* currentContact_; + 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..96b6031 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.cpp @@ -1,16 +1,17 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h" - -#include <QVBoxLayout> +#include <Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h> #include <boost/bind.hpp> +#include <boost/signals2.hpp> + +#include <QLabel> +#include <QVBoxLayout> -#include <Swiften/Base/boost_bsignals.h> #include <Swiften/JID/JID.h> #include <Swift/QtUI/QtContactEditWidget.h> @@ -18,10 +19,10 @@ namespace Swift { QtUserSearchDetailsPage::QtUserSearchDetailsPage(const std::set<std::string>& groups) { - QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(new QLabel(tr("Please choose a name for the contact, and select the groups you want to add the contact to."))); - editWidget = new QtContactEditWidget(groups, this); - layout->addWidget(editWidget); + QVBoxLayout* layout = new QVBoxLayout(this); + layout->addWidget(new QLabel(tr("Please choose a name for the contact, and select the groups you want to add the contact to."))); + editWidget = new QtContactEditWidget(groups, this); + layout->addWidget(editWidget); } QtUserSearchDetailsPage::~QtUserSearchDetailsPage() { @@ -29,27 +30,27 @@ QtUserSearchDetailsPage::~QtUserSearchDetailsPage() { } void QtUserSearchDetailsPage::setJID(const JID& jid) { - contactJID = jid; + contactJID = jid; } void QtUserSearchDetailsPage::setNameSuggestions(const std::vector<std::string>& nameSuggestions) { - editWidget->setNameSuggestions(nameSuggestions); + editWidget->setNameSuggestions(nameSuggestions); } void QtUserSearchDetailsPage::setName(const std::string& name) { - editWidget->setName(name); + editWidget->setName(name); } std::set<std::string> QtUserSearchDetailsPage::getSelectedGroups() { - return editWidget->getSelectedGroups(); + return editWidget->getSelectedGroups(); } std::string QtUserSearchDetailsPage::getName() { - return editWidget->getName(); + return editWidget->getName(); } void QtUserSearchDetailsPage::clear() { - editWidget->clear(); + editWidget->clear(); } } diff --git a/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h b/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h index 63fd241..7185bce 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h @@ -1,44 +1,45 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWizardPage> - #include <set> #include <string> #include <vector> +#include <QWizardPage> + #include <Swiften/Elements/VCard.h> #include <Swiften/JID/JID.h> + #include <Swift/QtUI/UserSearch/ui_QtUserSearchFieldsPage.h> namespace Swift { - class QtContactEditWidget; + class QtContactEditWidget; - class QtUserSearchDetailsPage : public QWizardPage { - Q_OBJECT - public: - QtUserSearchDetailsPage(const std::set<std::string>& availableGroups); - virtual ~QtUserSearchDetailsPage(); + class QtUserSearchDetailsPage : public QWizardPage { + Q_OBJECT + public: + QtUserSearchDetailsPage(const std::set<std::string>& availableGroups); + virtual ~QtUserSearchDetailsPage(); - void setJID(const JID& jid); - void setNameSuggestions(const std::vector<std::string>& nameSuggestions); - void setName(const std::string& name); + void setJID(const JID& jid); + void setNameSuggestions(const std::vector<std::string>& nameSuggestions); + void setName(const std::string& name); - std::set<std::string> getSelectedGroups(); - std::string getName(); + std::set<std::string> getSelectedGroups(); + std::string getName(); - void clear(); + void clear(); - signals: - void onUserTriggersFinish(); + signals: + void onUserTriggersFinish(); - private: - QtContactEditWidget* editWidget; - JID contactJID; - }; + private: + QtContactEditWidget* editWidget; + JID contactJID; + }; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp index 13858a7..3a6028d 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.cpp @@ -1,42 +1,42 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h" +#include <Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h> namespace Swift { -QtUserSearchFieldsPage::QtUserSearchFieldsPage() : formWidget_(0) { - setupUi(this); +QtUserSearchFieldsPage::QtUserSearchFieldsPage() : formWidget_(nullptr) { + setupUi(this); } bool QtUserSearchFieldsPage::isComplete() const { - if (formWidget_) { - return formWidget_->isEnabled(); - } else { - return nickInput_->isEnabled() || firstInput_->isEnabled() || lastInput_->isEnabled() || emailInput_->isEnabled(); - } + if (formWidget_) { + return formWidget_->isEnabled(); + } else { + return nickInput_->isEnabled() || firstInput_->isEnabled() || lastInput_->isEnabled() || emailInput_->isEnabled(); + } } QtFormWidget* QtUserSearchFieldsPage::getFormWidget() { - return formWidget_; + return formWidget_; } void QtUserSearchFieldsPage::setFormWidget(QtFormWidget *widget) { - if (formWidget_) { - delete formWidget_; - formWidget_ = NULL; - } - if (widget) { - formContainer_->layout()->addWidget(widget); - } - formWidget_ = widget; + if (formWidget_) { + delete formWidget_; + formWidget_ = nullptr; + } + if (widget) { + formContainer_->layout()->addWidget(widget); + } + formWidget_ = widget; } void QtUserSearchFieldsPage::emitCompletenessCheck() { - emit completeChanged(); + emit completeChanged(); } } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h index 066205b..4089d05 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,23 +9,22 @@ #include <QWizardPage> #include <Swift/QtUI/QtFormWidget.h> - #include <Swift/QtUI/UserSearch/ui_QtUserSearchFieldsPage.h> namespace Swift { - class QtUserSearchFieldsPage : public QWizardPage, public Ui::QtUserSearchFieldsPage { - Q_OBJECT - public: - QtUserSearchFieldsPage(); - virtual bool isComplete() const; + class QtUserSearchFieldsPage : public QWizardPage, public Ui::QtUserSearchFieldsPage { + Q_OBJECT + public: + QtUserSearchFieldsPage(); + virtual bool isComplete() const; - QtFormWidget* getFormWidget(); - void setFormWidget(QtFormWidget *widget); + QtFormWidget* getFormWidget(); + void setFormWidget(QtFormWidget *widget); - public slots: - void emitCompletenessCheck(); + public slots: + void emitCompletenessCheck(); - private: - QtFormWidget *formWidget_; - }; + private: + QtFormWidget *formWidget_; + }; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp index b1e9a12..8656db7 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp @@ -4,53 +4,99 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h" +/* + * Copyright (c) 2014-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h> + +#include <QMessageBox> +#include <QMimeData> +#include <QUrl> -#include "Swift/QtUI/QtSwiftUtil.h" -#include <Swift/QtUI/UserSearch/QtContactListWidget.h> #include <Swift/Controllers/Settings/SettingsProvider.h> + +#include <Swift/QtUI/QtSwiftUtil.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; - } + setupUi(this); + setTitle(title); + QString introText = ""; + switch (type) { + case UserSearchWindow::Type::AddContact: + introText = tr("Add another user to your contact list"); + break; + case UserSearchWindow::Type::ChatToContact: + introText = tr("Chat to another user"); + break; + case UserSearchWindow::Type::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)); + 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_); + contactList_ = new QtContactListWidget(this, settings); + horizontalLayout_5->addWidget(contactList_); - jid_ = new QtSuggestingJIDInput(this, settings); - horizontalLayout_6->insertWidget(0, jid_); + jid_ = new QtSuggestingJIDInput(this, settings); + horizontalLayout_6->insertWidget(0, jid_); - connect(contactList_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(emitCompletenessCheck())); - connect(jid_, SIGNAL(editingDone()), this, SLOT(handleEditingDone())); + 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(); + return !contactList_->getList().empty(); +} + +void QtUserSearchFirstMultiJIDPage::reset() { + jid_->clear(); + reason_->clear(); } void QtUserSearchFirstMultiJIDPage::emitCompletenessCheck() { - emit completeChanged(); + emit completeChanged(); } void QtUserSearchFirstMultiJIDPage::handleEditingDone() { - addContactButton_->setFocus(); + 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 index 427995e..d9147db 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h @@ -4,37 +4,50 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QWizardPage> -#include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstMultiJIDPage.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> +#include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstMultiJIDPage.h> + namespace Swift { - class UserSearchModel; - class UserSearchDelegate; - class UserSearchResult; - class UIEventStream; - class QtContactListWidget; - class ContactSuggester; - class AvatarManager; - class VCardManager; - class SettingsProvider; - class QtSuggestingJIDInput; - - class QtUserSearchFirstMultiJIDPage : public QWizardPage, public Ui::QtUserSearchFirstMultiJIDPage { - Q_OBJECT - public: - QtUserSearchFirstMultiJIDPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings); - virtual bool isComplete() const; - - public slots: - void emitCompletenessCheck(); - void handleEditingDone(); - - public: - QtContactListWidget* contactList_; - QtSuggestingJIDInput* jid_; - }; + 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 index 4a87f41..b72c071 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui @@ -65,7 +65,7 @@ <bool>false</bool> </property> <property name="text"> - <string>Add Contact</string> + <string>Add to list</string> </property> <property name="default"> <bool>false</bool> diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp index af53a26..24e79e4 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp @@ -1,44 +1,49 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/UserSearch/QtUserSearchFirstPage.h" - -#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/QtUI/UserSearch/QtUserSearchFirstPage.h> #include <Swiften/Base/Log.h> +#include <Swift/QtUI/QtSwiftUtil.h> + namespace Swift { 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())); + 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::Type::AddContact ? tr("Add another user to your contact list") : tr("Chat to another user"))); + jid_ = new QtSuggestingJIDInput(this, settings); + horizontalLayout_2->addWidget(jid_); + jidWarning_ = new QLabel(this); + jidWarning_->setPixmap(QPixmap(":icons/warn.png")); + jidWarning_->hide(); + horizontalLayout_2->addWidget(jidWarning_); + setTabOrder(byJID_, jid_); + setTabOrder(jid_, byLocalSearch_); + setTabOrder(byLocalSearch_, byRemoteSearch_); + connect(jid_, SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck())); + connect(jid_, SIGNAL(editingDone()), this, SLOT(emitCompletenessCheck())); + connect(service_->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck())); } bool QtUserSearchFirstPage::isComplete() const { - bool complete = false; - if (byJID_->isChecked()) { - complete = JID(Q2PSTRING(jid_->text().trimmed())).isValid(); - } else if (byLocalSearch_->isChecked()) { - complete = true; - } else if (byRemoteSearch_->isChecked()) { - complete = JID(Q2PSTRING(service_->currentText().trimmed())).isValid(); - } - return complete; + bool complete = false; + if (byJID_->isChecked()) { + complete = JID(Q2PSTRING(jid_->text().trimmed())).isValid() && jidWarning_->toolTip().isEmpty(); + } else if (byLocalSearch_->isChecked()) { + complete = true; + } else if (byRemoteSearch_->isChecked()) { + complete = JID(Q2PSTRING(service_->currentText().trimmed())).isValid(); + } + return complete; } void QtUserSearchFirstPage::emitCompletenessCheck() { - emit completeChanged(); + emit completeChanged(); } } diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h index d7487b0..2e73e9e 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h @@ -1,32 +1,33 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once #include <QWizardPage> -#include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstPage.h> #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> #include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h> +#include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstPage.h> namespace Swift { - class UserSearchModel; - class UserSearchDelegate; - class UserSearchResult; - class UIEventStream; + class UserSearchModel; + class UserSearchDelegate; + class UserSearchResult; + class UIEventStream; - class QtUserSearchFirstPage : public QWizardPage, public Ui::QtUserSearchFirstPage { - Q_OBJECT - public: - QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings); - virtual bool isComplete() const; - public slots: - void emitCompletenessCheck(); - public: - QtSuggestingJIDInput* jid_; - }; + class QtUserSearchFirstPage : public QWizardPage, public Ui::QtUserSearchFirstPage { + Q_OBJECT + public: + QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings); + virtual bool isComplete() const; + public slots: + void emitCompletenessCheck(); + public: + QtSuggestingJIDInput* jid_; + QLabel* jidWarning_; + }; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.cpp index dcd79d2..c8a9ad8 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.cpp @@ -1,39 +1,39 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/UserSearch/QtUserSearchResultsPage.h" +#include <Swift/QtUI/UserSearch/QtUserSearchResultsPage.h> namespace Swift { QtUserSearchResultsPage::QtUserSearchResultsPage() { - setupUi(this); - connect(results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(emitCompletenessCheck())); - connect(results_, SIGNAL(activated(const QModelIndex&)), this, SIGNAL(onUserTriggersContinue())); - connect(results_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(emitCompletenessCheck())); - connect(results_, SIGNAL(entered(const QModelIndex&)), this, SLOT(emitCompletenessCheck())); - results_->setExpandsOnDoubleClick(false); - setNoResults(false); + setupUi(this); + connect(results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(emitCompletenessCheck())); + connect(results_, SIGNAL(activated(const QModelIndex&)), this, SIGNAL(onUserTriggersContinue())); + connect(results_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(emitCompletenessCheck())); + connect(results_, SIGNAL(entered(const QModelIndex&)), this, SLOT(emitCompletenessCheck())); + results_->setExpandsOnDoubleClick(false); + setNoResults(false); } bool QtUserSearchResultsPage::isComplete() const { - return results_->currentIndex().isValid(); + return results_->currentIndex().isValid(); } void QtUserSearchResultsPage::setNoResults(bool noResults) { - if (noResults) { - results_->setEnabled(false); - noResults_->show(); - } else { - results_->setEnabled(true); - noResults_->hide(); - } + if (noResults) { + results_->setEnabled(false); + noResults_->show(); + } else { + results_->setEnabled(true); + noResults_->hide(); + } } void QtUserSearchResultsPage::emitCompletenessCheck() { - emit completeChanged(); + emit completeChanged(); } } diff --git a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.h b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.h index ec52bf9..33227e6 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchResultsPage.h +++ b/Swift/QtUI/UserSearch/QtUserSearchResultsPage.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -11,15 +11,15 @@ #include <Swift/QtUI/UserSearch/ui_QtUserSearchResultsPage.h> namespace Swift { - class QtUserSearchResultsPage : public QWizardPage, public Ui::QtUserSearchResultsPage { - Q_OBJECT - public: - QtUserSearchResultsPage(); - virtual bool isComplete() const; - void setNoResults(bool noResults); - signals: - void onUserTriggersContinue(); - public slots: - void emitCompletenessCheck(); - }; + class QtUserSearchResultsPage : public QWizardPage, public Ui::QtUserSearchResultsPage { + Q_OBJECT + public: + QtUserSearchResultsPage(); + virtual bool isComplete() const; + void setNoResults(bool noResults); + signals: + void onUserTriggersContinue(); + public slots: + void emitCompletenessCheck(); + }; } diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp index d06fa19..4489bc0 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp @@ -1,556 +1,652 @@ /* - * Copyright (c) 2010-2013 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/UserSearch/QtUserSearchWindow.h" +#include <Swift/QtUI/UserSearch/QtUserSearchWindow.h> + +#include <memory> + +#include <boost/bind.hpp> #include <QItemDelegate> #include <QModelIndex> -#include <QWizardPage> #include <QMovie> -#include <boost/smart_ptr/make_shared.hpp> +#include <QWizardPage> -#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/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/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + #include <Swift/QtUI/QtFormResultItemModel.h> -#include <Swift/QtUI/UserSearch/QtUserSearchFirstPage.h> -#include <Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/UserSearch/QtContactListWidget.h> +#include <Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h> #include <Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h> +#include <Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h> +#include <Swift/QtUI/UserSearch/QtUserSearchFirstPage.h> #include <Swift/QtUI/UserSearch/QtUserSearchResultsPage.h> -#include <Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h> -#include <Swift/QtUI/UserSearch/QtContactListWidget.h> - -#include <Swiften/Base/Log.h> +#include <Swift/QtUI/UserSearch/UserSearchDelegate.h> +#include <Swift/QtUI/UserSearch/UserSearchModel.h> namespace Swift { -QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider) : eventStream_(eventStream), type_(type), model_(NULL), settings_(settingsProvider), searchNext_(false), supportsImpromptu_(false) { - setupUi(this); +QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider) : eventStream_(eventStream), type_(type), model_(nullptr), firstPage_(nullptr), firstMultiJIDPage_(nullptr), settings_(settingsProvider), searchNext_(false), supportsImpromptu_(false) { + setupUi(this); #ifndef Q_OS_MAC - setWindowIcon(QIcon(":/logo-icon-16.png")); +#ifdef Q_OS_WIN32 + setWindowIcon(QIcon(":/logo-icon-16-win.png")); +#else + setWindowIcon(QIcon(":/logo-icon-16.png")); +#endif #endif - 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(); - - setFirstPage(title); - setSecondPage(); - setThirdPage(); - - detailsPage_ = new QtUserSearchDetailsPage(groups); - setPage(4, detailsPage_); - - connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(handleCurrentChanged(int))); - connect(this, SIGNAL(accepted()), this, SLOT(handleAccepted())); - clear(); + QString title; + switch(type) { + case Type::AddContact: + title = tr("Add Contact"); + break; + case Type::ChatToContact: + title = tr("Chat to Users"); + break; + case Type::InviteToChat: + title = tr("Add Users to Chat"); + break; + } + setWindowTitle(title); + + delegate_ = new UserSearchDelegate(this); + + setFirstPage(title); + setSecondPage(); + setThirdPage(); + + detailsPage_ = new QtUserSearchDetailsPage(groups); + setPage(4, detailsPage_); + + connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(handleCurrentChanged(int))); + connect(this, SIGNAL(accepted()), this, SLOT(handleAccepted())); + clear(); } QtUserSearchWindow::~QtUserSearchWindow() { - delete model_; + delete model_; } void QtUserSearchWindow::handleCurrentChanged(int page) { - searchNext_ = false; - resultsPage_->emitCompletenessCheck(); - if (page == 1 && lastPage_ == 3) { - addSearchedJIDToList(getContactJID()); - setSecondPage(); - } - else if (page == 2 && lastPage_ == 1) { - setError(""); - /* next won't be called if JID is selected */ - JID server = getServerToSearch(); - clearForm(); - onFormRequested(server); - setThirdPage(); - } - else if (page == 3 && lastPage_ == 2) { - JID server = getServerToSearch(); - handleSearch(); - - if (type_ == AddContact) { - bool remote = firstPage_->byRemoteSearch_->isChecked(); - firstPage_->byRemoteSearch_->setChecked(remote); - firstPage_->service_->setEditText(P2QSTRING(server.toString())); - } else { - bool remote = firstMultiJIDPage_->byRemoteSearch_->isChecked(); - setFirstPage(); - firstMultiJIDPage_->byRemoteSearch_->setChecked(remote); - firstMultiJIDPage_->service_->setEditText(P2QSTRING(server.toString())); - } - } - else if (page == 4) { - detailsPage_->clear(); - detailsPage_->setJID(getContactJID()); - onNameSuggestionRequested(getContactJID()); - } - lastPage_ = page; + searchNext_ = false; + + // Check preconditions per type. + if (type_ == Type::AddContact) { + assert(firstPage_); + assert(!firstMultiJIDPage_); + } + else { + assert(!firstPage_); + assert(firstMultiJIDPage_); + } + + if (type_ != Type::AddContact) { + firstMultiJIDPage_->reset(); + } + resultsPage_->emitCompletenessCheck(); + if (type_ != Type::AddContact && page == 1 && lastPage_ == 3) { + addSearchedJIDToList(getContact()); + setSecondPage(); + } + else if (page == 2 && lastPage_ == 1) { + setError(""); + /* next won't be called if JID is selected */ + JID server = getServerToSearch(); + clearForm(); + onFormRequested(server); + setThirdPage(); + } + else if (page == 3 && lastPage_ == 2) { + JID server = getServerToSearch(); + handleSearch(); + + if (type_ == 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); + if (remote) { + firstMultiJIDPage_->service_->setEditText(P2QSTRING(server.toString())); + } + } + } + else if (page == 4) { + detailsPage_->clear(); + detailsPage_->setJID(getContactJID()); + onNameSuggestionRequested(getContactJID()); + } + lastPage_ = 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_; - } + if (type_ == 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; - std::vector<JID> jids; - switch(type_) { - case AddContact: - jid = getContactJID(); - eventStream_->send(boost::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups())); - break; - case ChatToContact: - if (contactVector_.size() == 1) { - boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(contactVector_[0].jid)); - eventStream_->send(event); - break; - } - - foreach(const Contact& contact, contactVector_) { - jids.push_back(contact.jid); - } - - eventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(jids, JID(), Q2PSTRING(firstMultiJIDPage_->reason_->text()))); - break; - case InviteToChat: - foreach(const Contact& contact, contactVector_) { - jids.push_back(contact.jid); - } - eventStream_->send(boost::make_shared<InviteToMUCUIEvent>(roomJID_, jids, Q2PSTRING(firstMultiJIDPage_->reason_->text()))); - break; - } + JID jid; + std::vector<JID> jids; + switch(type_) { + case Type::AddContact: + jid = getContactJID(); + eventStream_->send(std::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups())); + break; + case Type::ChatToContact: + if (contactVector_.size() == 1) { + std::shared_ptr<UIEvent> event(new RequestChatUIEvent(contactVector_[0]->jid)); + eventStream_->send(event); + break; + } + + for (Contact::ref contact : contactVector_) { + jids.push_back(contact->jid); + } + + eventStream_->send(std::make_shared<CreateImpromptuMUCUIEvent>(jids, JID(), Q2PSTRING(firstMultiJIDPage_->reason_->text()))); + break; + case Type::InviteToChat: + for (Contact::ref contact : contactVector_) { + jids.push_back(contact->jid); + } + eventStream_->send(std::make_shared<InviteToMUCUIEvent>(originatorJID_, jids, Q2PSTRING(firstMultiJIDPage_->reason_->text()))); + break; + } } void QtUserSearchWindow::handleContactSuggestionRequested(const QString& text) { - std::string stdText = Q2PSTRING(text); - onContactSuggestionsRequested(stdText); + std::string stdText = Q2PSTRING(text); + onContactSuggestionsRequested(stdText); } void QtUserSearchWindow::addContact() { - if (firstMultiJIDPage_->jid_->getContact() != 0) { - Contact contact = *(firstMultiJIDPage_->jid_->getContact()); - contactVector_.push_back(contact); - } - firstMultiJIDPage_->contactList_->setList(contactVector_); - firstMultiJIDPage_->emitCompletenessCheck(); - if (type_ == ChatToContact) { - firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); - } + auto contactToAdd = firstMultiJIDPage_->jid_->getContact(); + if (!!contactToAdd) { + contactVector_.push_back(contactToAdd); + firstMultiJIDPage_->jid_->clear(); + } + + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->emitCompletenessCheck(); + if (type_ == Type::ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); + } + + if (!!contactToAdd && contactToAdd->jid.isValid() && contactToAdd->statusType == StatusShow::None) { + onJIDUpdateRequested({contactToAdd->jid}); + } +} + +void QtUserSearchWindow::setWarning(const boost::optional<std::string>& message) { + if (message) { + firstPage_->jidWarning_->setToolTip(P2QSTRING((*message))); + firstPage_->jidWarning_->setAccessibleDescription(P2QSTRING((*message))); + firstPage_->jidWarning_->show(); + } + else { + firstPage_->jidWarning_->setToolTip(""); + firstPage_->jidWarning_->setAccessibleDescription(""); + firstPage_->jidWarning_->hide(); + } + firstPage_->emitCompletenessCheck(); } int QtUserSearchWindow::nextId() const { - if (type_ == AddContact) { - switch (currentId()) { - case 1: return firstPage_->byJID_->isChecked() ? (type_ == AddContact ? 4 : -1) : 2; - case 2: return 3; - case 3: return type_ == AddContact ? 4 : -1; - case 4: return -1; - default: return -1; - } - } else { - switch (currentId()) { - case 1: return searchNext_ ? 2 : -1; - case 2: return 3; - case 3: return 1; - case 4: return -1; - default: return -1; - } - } + if (type_ == Type::AddContact) { + switch (currentId()) { + case 1: return firstPage_->byJID_->isChecked() ? (type_ == Type::AddContact ? 4 : -1) : 2; + case 2: return 3; + case 3: return type_ == Type::AddContact ? 4 : -1; + case 4: return -1; + default: return -1; + } + } else { + switch (currentId()) { + case 1: return searchNext_ ? 2 : -1; + case 2: return 3; + case 3: return 1; + case 4: return -1; + default: return -1; + } + } } void QtUserSearchWindow::handleFirstPageRadioChange() { - if (firstPage_->byJID_->isChecked()) { - firstPage_->jid_->setText(""); - firstPage_->jid_->setEnabled(true); - firstPage_->service_->setEnabled(false); - restart(); - } - else if (firstPage_->byRemoteSearch_->isChecked()) { - firstPage_->service_->setEditText(""); - firstPage_->jid_->setEnabled(false); - firstPage_->service_->setEnabled(true); - //firstPage_->jid_->setText(""); - restart(); - } - else { - firstPage_->jid_->setEnabled(false); - firstPage_->service_->setEnabled(false); - restart(); - } + if (firstPage_->byJID_->isChecked()) { + firstPage_->jid_->setText(""); + firstPage_->jid_->setEnabled(true); + firstPage_->service_->setEnabled(false); + restart(); + } + else if (firstPage_->byRemoteSearch_->isChecked()) { + firstPage_->service_->setEditText(""); + firstPage_->jid_->setEnabled(false); + firstPage_->service_->setEnabled(true); + //firstPage_->jid_->setText(""); + restart(); + } + else { + firstPage_->jid_->setEnabled(false); + firstPage_->service_->setEnabled(false); + restart(); + } } void QtUserSearchWindow::handleSearch() { - boost::shared_ptr<SearchPayload> search(new SearchPayload()); - if (fieldsPage_->getFormWidget()) { - search->setForm(fieldsPage_->getFormWidget()->getCompletedForm()); - } else { - if (fieldsPage_->nickInput_->isEnabled()) { - search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text())); - } - if (fieldsPage_->firstInput_->isEnabled()) { - search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text())); - } - if (fieldsPage_->lastInput_->isEnabled()) { - search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text())); - } - if (fieldsPage_->emailInput_->isEnabled()) { - search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text())); - } - } - onSearchRequested(search, getServerToSearch()); + std::shared_ptr<SearchPayload> search(new SearchPayload()); + if (fieldsPage_->getFormWidget()) { + search->setForm(fieldsPage_->getFormWidget()->getCompletedForm()); + search->getForm()->clearEmptyTextFields(); + } else { + if (fieldsPage_->nickInput_->isEnabled() && !fieldsPage_->nickInput_->text().isEmpty()) { + search->setNick(Q2PSTRING(fieldsPage_->nickInput_->text())); + } + if (fieldsPage_->firstInput_->isEnabled() && !fieldsPage_->firstInput_->text().isEmpty()) { + search->setFirst(Q2PSTRING(fieldsPage_->firstInput_->text())); + } + if (fieldsPage_->lastInput_->isEnabled() && !fieldsPage_->lastInput_->text().isEmpty()) { + search->setLast(Q2PSTRING(fieldsPage_->lastInput_->text())); + } + if (fieldsPage_->emailInput_->isEnabled() && !fieldsPage_->emailInput_->text().isEmpty()) { + search->setEMail(Q2PSTRING(fieldsPage_->emailInput_->text())); + } + } + onSearchRequested(search, getServerToSearch()); } JID QtUserSearchWindow::getContactJID() const { - JID jid; - - bool useSearchResult; - if (type_ == AddContact) { - useSearchResult = !firstPage_->byJID_->isChecked(); - } else { - useSearchResult = true; - } - - if (useSearchResult) { - if (dynamic_cast<UserSearchModel*>(model_)) { - UserSearchResult* userItem = static_cast<UserSearchResult*>(resultsPage_->results_->currentIndex().internalPointer()); - if (userItem) { /* Remember to leave this if we change to dynamic cast */ - jid = userItem->getJID(); - } - } else { - int row = resultsPage_->results_->currentIndex().row(); - - Form::FormItem item = dynamic_cast<QtFormResultItemModel*>(model_)->getForm()->getItems().at(row); - JID fallbackJid; - foreach(FormField::ref field, item) { - if (field->getType() == FormField::JIDSingleType) { - jid = JID(field->getJIDSingleValue()); - break; - } - if (field->getName() == "jid") { - fallbackJid = field->getValues()[0]; - } - } - if (!jid.isValid()) { - jid = fallbackJid; - } - } - } - else { - jid = JID(Q2PSTRING(firstPage_->jid_->text().trimmed())); - } - return jid; -} - -void QtUserSearchWindow::addSearchedJIDToList(const JID& jid) { - Contact contact(jid, jid.toString(), StatusShow::None, ""); - contactVector_.push_back(contact); - firstMultiJIDPage_->contactList_->setList(contactVector_); - firstMultiJIDPage_->emitCompletenessCheck(); - if (type_ == ChatToContact) { - firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); - } + JID jid; + + bool useSearchResult; + if (type_ == Type::AddContact) { + useSearchResult = !firstPage_->byJID_->isChecked(); + } else { + useSearchResult = true; + } + + if (useSearchResult) { + if (dynamic_cast<UserSearchModel*>(model_)) { + UserSearchResult* userItem = static_cast<UserSearchResult*>(resultsPage_->results_->currentIndex().internalPointer()); + if (userItem) { /* Remember to leave this if we change to dynamic cast */ + jid = userItem->getJID(); + } + } else if (dynamic_cast<QtFormResultItemModel*>(model_)) { + int row = resultsPage_->results_->currentIndex().row(); + + Form::FormItem item = dynamic_cast<QtFormResultItemModel*>(model_)->getForm()->getItems().at(row); + JID fallbackJid; + for (FormField::ref field : item) { + if (field->getType() == FormField::JIDSingleType) { + jid = JID(field->getJIDSingleValue()); + break; + } + if (field->getName() == "jid") { + fallbackJid = field->getValues()[0]; + } + } + if (!jid.isValid()) { + jid = fallbackJid; + } + } + } + else { + jid = JID(Q2PSTRING(firstPage_->jid_->text().trimmed())); + } + return jid; +} + +Contact::ref QtUserSearchWindow::getContact() const { + return std::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(); - QWidget::show(); + clear(); + if (type_ == Type::AddContact) { + setWarning(boost::optional<std::string>()); + } + QWidget::show(); + raise(); } void QtUserSearchWindow::addSavedServices(const std::vector<JID>& services) { - if (type_ == AddContact) { - firstPage_->service_->clear(); - foreach (JID jid, services) { - firstPage_->service_->addItem(P2QSTRING(jid.toString())); - } - firstPage_->service_->clearEditText(); - } else { - firstMultiJIDPage_->service_->clear(); - foreach (JID jid, services) { - firstMultiJIDPage_->service_->addItem(P2QSTRING(jid.toString())); - } - firstMultiJIDPage_->service_->clearEditText(); - } -} - -void QtUserSearchWindow::setSearchFields(boost::shared_ptr<SearchPayload> fields) { - fieldsPage_->fetchingThrobber_->hide(); - fieldsPage_->fetchingThrobber_->movie()->stop(); - fieldsPage_->fetchingLabel_->hide(); - - fieldsPage_->instructionsLabel_->setText(fields->getInstructions() ? P2QSTRING(fields->getInstructions().get()) : "Enter search terms"); - if (fields->getForm()) { - fieldsPage_->setFormWidget(new QtFormWidget(fields->getForm(), fieldsPage_)); - } else { - fieldsPage_->setFormWidget(NULL); - 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++) { - legacySearchWidgets[i]->setVisible(enabled[i]); - legacySearchWidgets[i]->setEnabled(enabled[i]); - } - } - fieldsPage_->emitCompletenessCheck(); + if (type_ == Type::AddContact) { + firstPage_->service_->clear(); + for (auto&& jid : services) { + firstPage_->service_->addItem(P2QSTRING(jid.toString())); + } + firstPage_->service_->clearEditText(); + } else { + firstMultiJIDPage_->service_->clear(); + for (auto&& jid : services) { + firstMultiJIDPage_->service_->addItem(P2QSTRING(jid.toString())); + } + firstMultiJIDPage_->service_->clearEditText(); + } +} + +void QtUserSearchWindow::setSearchFields(std::shared_ptr<SearchPayload> fields) { + fieldsPage_->fetchingThrobber_->hide(); + fieldsPage_->fetchingThrobber_->movie()->stop(); + fieldsPage_->fetchingLabel_->hide(); + + fieldsPage_->instructionsLabel_->setText(fields->getInstructions() ? P2QSTRING(fields->getInstructions().get()) : "Enter search terms"); + if (fields->getForm()) { + fieldsPage_->setFormWidget(new QtFormWidget(fields->getForm(), fieldsPage_)); + } else { + fieldsPage_->setFormWidget(nullptr); + 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++) { + legacySearchWidgets[i]->setVisible(enabled[i]); + legacySearchWidgets[i]->setEnabled(enabled[i]); + } + } + fieldsPage_->emitCompletenessCheck(); } void QtUserSearchWindow::setNameSuggestions(const std::vector<std::string>& suggestions) { - if (detailsPage_) { - detailsPage_->setNameSuggestions(suggestions); - } + if (detailsPage_) { + detailsPage_->setNameSuggestions(suggestions); + } } void QtUserSearchWindow::prepopulateJIDAndName(const JID& jid, const std::string& name) { - firstPage_->jid_->setText(P2QSTRING(jid.toBare().toString())); - detailsPage_->setJID(jid); - lastPage_ = 1; - restart(); - next(); - detailsPage_->setName(name); + firstPage_->jid_->setText(P2QSTRING(jid.toBare().toString())); + detailsPage_->setJID(jid); + lastPage_ = 1; + restart(); + next(); + detailsPage_->setName(name); } -void QtUserSearchWindow::setContactSuggestions(const std::vector<Contact>& suggestions) { - if (type_ == AddContact) { - firstPage_->jid_->setSuggestions(suggestions); - } else { - firstMultiJIDPage_->jid_->setSuggestions(suggestions); - } +void QtUserSearchWindow::setContactSuggestions(const std::vector<Contact::ref>& suggestions) { + if (type_ == Type::AddContact) { + firstPage_->jid_->setSuggestions(suggestions); + } else { + firstMultiJIDPage_->jid_->setSuggestions(suggestions); + } } void QtUserSearchWindow::setJIDs(const std::vector<JID> &jids) { - foreach(JID jid, jids) { - addSearchedJIDToList(jid); - } - onJIDUpdateRequested(jids); + for (auto&& jid : jids) { + addSearchedJIDToList(std::make_shared<Contact>("", jid, StatusShow::None, "")); + } + onJIDUpdateRequested(jids); +} + +void QtUserSearchWindow::setOriginator(const JID& originator) { + originatorJID_ = originator; } void QtUserSearchWindow::setRoomJID(const JID& roomJID) { - roomJID_ = roomJID; + roomJID_ = roomJID; } std::string QtUserSearchWindow::getReason() const { - return Q2PSTRING(firstMultiJIDPage_->reason_->text()); + return Q2PSTRING(firstMultiJIDPage_->reason_->text()); } std::vector<JID> QtUserSearchWindow::getJIDs() const { - std::vector<JID> jids; - foreach (const Contact& contact, contactVector_) { - jids.push_back(contact.jid); - } - return jids; + std::vector<JID> jids; + for (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>& contacts) { - if (type_ != AddContact) { - firstMultiJIDPage_->contactList_->updateContacts(contacts); - } + supportsImpromptu_ = supportsImpromptu; + if (type_ == Type::ChatToContact) { + firstMultiJIDPage_->contactList_->setMaximumNoOfContactsToOne(!supportsImpromptu_); + } +} + +void QtUserSearchWindow::updateContacts(const std::vector<Contact::ref>& contacts) { + if (type_ != Type::AddContact) { + firstMultiJIDPage_->contactList_->updateContacts(contacts); + } +} + +void QtUserSearchWindow::addContacts(const std::vector<Contact::ref>& contacts) { + if (type_ != Type::AddContact) { + /* prevent duplicate JIDs from appearing in the contact list */ + for (Contact::ref newContact : contacts) { + bool found = false; + for (Contact::ref oldContact : contactVector_) { + if (newContact->jid == oldContact->jid) { + found = true; + break; + } + } + if (!found) { + contactVector_.push_back(newContact); + } + } + if (type_ != Type::InviteToChat && !supportsImpromptu_ && contactVector_.size() > 1) { + contactVector_.resize(1); /* can't chat with more than one user */ + } + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->emitCompletenessCheck(); + if (type_ == 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(); + searchNext_ = true; + next(); } -void QtUserSearchWindow::handleListChanged(std::vector<Contact> list) { - contactVector_ = list; - if (type_ == ChatToContact) { - firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); - } +void QtUserSearchWindow::handleListChanged(std::vector<Contact::ref> list) { + contactVector_ = list; + if (type_ == Type::ChatToContact) { + firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1)); + } } void QtUserSearchWindow::handleJIDsAdded(std::vector<JID> jids) { - onJIDUpdateRequested(jids); + onJIDAddRequested(jids); } void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results) { - UserSearchModel *newModel = new UserSearchModel(); - newModel->setResults(results); - resultsPage_->results_->setModel(newModel); - resultsPage_->results_->setItemDelegate(delegate_); - resultsPage_->results_->setHeaderHidden(true); - delete model_; - model_ = newModel; - resultsPage_->setNoResults(model_->rowCount() == 0); - resultsPage_->emitCompletenessCheck(); + UserSearchModel *newModel = new UserSearchModel(); + newModel->setResults(results); + resultsPage_->results_->setModel(newModel); + resultsPage_->results_->setItemDelegate(delegate_); + resultsPage_->results_->setHeaderHidden(true); + delete model_; + model_ = newModel; + resultsPage_->setNoResults(model_->rowCount() == 0); + resultsPage_->emitCompletenessCheck(); } void QtUserSearchWindow::setResultsForm(Form::ref results) { - QtFormResultItemModel *newModel = new QtFormResultItemModel(this); - newModel->setForm(results); - resultsPage_->results_->setModel(newModel); - resultsPage_->results_->setItemDelegate(new QItemDelegate()); - resultsPage_->results_->setHeaderHidden(false); + QtFormResultItemModel *newModel = new QtFormResultItemModel(this); + newModel->setForm(results); + resultsPage_->results_->setModel(newModel); + resultsPage_->results_->setItemDelegate(new QItemDelegate()); + resultsPage_->results_->setHeaderHidden(false); #if QT_VERSION >= 0x050000 - resultsPage_->results_->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + resultsPage_->results_->header()->setSectionResizeMode(QHeaderView::ResizeToContents); #else - resultsPage_->results_->header()->setResizeMode(QHeaderView::ResizeToContents); + resultsPage_->results_->header()->setResizeMode(QHeaderView::ResizeToContents); #endif - delete model_; - model_ = newModel; - resultsPage_->setNoResults(model_->rowCount() == 0); - resultsPage_->emitCompletenessCheck(); + delete model_; + model_ = newModel; + resultsPage_->setNoResults(model_->rowCount() == 0); + resultsPage_->emitCompletenessCheck(); } void QtUserSearchWindow::setSelectedService(const JID& jid) { - myServer_ = jid; + myServer_ = jid; +} + +void QtUserSearchWindow::handleJIDEditingDone() { + onJIDEditFieldChanged(JID(Q2PSTRING(firstPage_->jid_->text()))); } void QtUserSearchWindow::setFirstPage(QString title) { - if (page(1) != 0) { - removePage(1); - } - if (type_ == AddContact) { - firstPage_ = new QtUserSearchFirstPage(type_, title.isEmpty() ? firstPage_->title() : title, settings_); - connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString))); - connect(firstPage_->byJID_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); - connect(firstPage_->byLocalSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); - connect(firstPage_->byRemoteSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange())); + if (page(1) != nullptr) { + removePage(1); + } + if (type_ == Type::AddContact) { + firstPage_ = new QtUserSearchFirstPage(type_, title.isEmpty() ? firstPage_->title() : title, settings_); + connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString))); + connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleJIDEditingDone()), Qt::UniqueConnection); + 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")); + firstPage_->jid_->setPlaceholderText(tr("alice@wonderland.lit")); #endif - firstPage_->service_->setEnabled(false); - setPage(1, firstPage_); - } else { - firstMultiJIDPage_ = new QtUserSearchFirstMultiJIDPage(type_, title.isEmpty() ? firstMultiJIDPage_->title() : title, settings_); - connect(firstMultiJIDPage_->addContactButton_, SIGNAL(clicked()), this, SLOT(addContact())); - connect(firstMultiJIDPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString))); - connect(firstMultiJIDPage_->addViaSearchButton_, SIGNAL(clicked()), this, SLOT(handleAddViaSearch())); - connect(firstMultiJIDPage_->contactList_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(handleListChanged(std::vector<Contact>))); - connect(firstMultiJIDPage_->contactList_, SIGNAL(onJIDsAdded(std::vector<JID>)), this, SLOT(handleJIDsAdded(std::vector<JID>))); - setPage(1, firstMultiJIDPage_); - } + 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_); + if (page(2) != nullptr) { + 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(); + if (page(3) != nullptr) { + removePage(3); + } + resultsPage_ = new QtUserSearchResultsPage(); #ifdef SWIFT_PLATFORM_MACOSX - resultsPage_->results_->setAlternatingRowColors(true); + 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_); + if (type_ == Type::AddContact) { + connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next())); + } + else { + connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next())); + } + setPage(3, resultsPage_); } void QtUserSearchWindow::clearForm() { - fieldsPage_->fetchingThrobber_->show(); - fieldsPage_->fetchingThrobber_->movie()->start(); - fieldsPage_->fetchingLabel_->show(); - 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++) { - legacySearchWidgets[i]->hide(); - if (QLineEdit* edit = qobject_cast<QLineEdit*>(legacySearchWidgets[i])) { - edit->clear(); - } - } - fieldsPage_->emitCompletenessCheck(); + fieldsPage_->fetchingThrobber_->show(); + fieldsPage_->fetchingThrobber_->movie()->start(); + fieldsPage_->fetchingLabel_->show(); + QWidget* legacySearchWidgets[8] = {fieldsPage_->nickInputLabel_, fieldsPage_->nickInput_, fieldsPage_->firstInputLabel_, fieldsPage_->firstInput_, fieldsPage_->lastInputLabel_, fieldsPage_->lastInput_, fieldsPage_->emailInputLabel_, fieldsPage_->emailInput_}; + for (auto&& legacySearchWidget : legacySearchWidgets) { + legacySearchWidget->hide(); + if (QLineEdit* edit = qobject_cast<QLineEdit*>(legacySearchWidget)) { + edit->clear(); + } + } + fieldsPage_->emitCompletenessCheck(); } void QtUserSearchWindow::clear() { - QString howText; - if (type_ == AddContact) { - firstPage_->errorLabel_->setVisible(false); - howText = QString(tr("How would you like to find the user to add?")); - firstPage_->howLabel_->setText(howText); - firstPage_->byJID_->setChecked(true); - handleFirstPageRadioChange(); - } else { - contactVector_.clear(); - firstMultiJIDPage_->contactList_->setList(contactVector_); - firstMultiJIDPage_->errorLabel_->setVisible(false); - if (type_ == ChatToContact) { - howText = QString(tr("Who would you like to chat to?")); - } else if (type_ == InviteToChat) { - howText = QString(tr("Who do you want to invite to the chat?")); - } - firstMultiJIDPage_->howLabel_->setText(howText); - } - clearForm(); - resultsPage_->results_->setModel(NULL); - delete model_; - model_ = NULL; - restart(); - lastPage_ = 1; + QString howText; + if (type_ == Type::AddContact) { + firstPage_->errorLabel_->setVisible(false); + howText = QString(tr("How would you like to find the user to add?")); + firstPage_->howLabel_->setText(howText); + firstPage_->byJID_->setChecked(true); + handleFirstPageRadioChange(); + } else { + contactVector_.clear(); + firstMultiJIDPage_->contactList_->setList(contactVector_); + firstMultiJIDPage_->errorLabel_->setVisible(false); + if (type_ == Type::ChatToContact) { + howText = QString(tr("List of participants:")); + } else if (type_ == Type::InviteToChat) { + howText = QString(tr("Who do you want to invite to the chat?")); + } + firstMultiJIDPage_->howLabel_->setText(howText); + firstMultiJIDPage_->groupBox->setEnabled(true); + } + clearForm(); + resultsPage_->results_->setModel(nullptr); + delete model_; + model_ = nullptr; + restart(); + lastPage_ = 1; } 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; - } + if (error.isEmpty()) { + if (type_ == Type::AddContact) { + firstPage_->errorLabel_->hide(); + } else { + firstMultiJIDPage_->errorLabel_->hide(); + } + } + else { + if (type_ == 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; + } } void QtUserSearchWindow::setSearchError(bool error) { - if (error) { - setError(tr("Error while searching")); - } + if (error) { + setError(tr("Error while searching")); + } } void QtUserSearchWindow::setServerSupportsSearch(bool support) { - if (!support) { - setError(tr("This server doesn't support searching for users.")); - } + if (!support) { + setError(tr("This server doesn't support searching for users.")); + } } } diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h index e5a9f80..f67712e 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h @@ -1,98 +1,109 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2018 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QWizard> #include <set> -#include <Swift/QtUI/UserSearch/ui_QtUserSearchWizard.h> +#include <QAbstractItemModel> +#include <QWizard> + #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> +#include <Swift/QtUI/UserSearch/ui_QtUserSearchWizard.h> + namespace Swift { - class UserSearchModel; - class UserSearchDelegate; - class UserSearchResult; - class UIEventStream; - class QtUserSearchFirstPage; - class QtUserSearchFirstMultiJIDPage; - class QtUserSearchFieldsPage; - class QtUserSearchResultsPage; - class QtUserSearchDetailsPage; - class QtFormResultItemModel; - class SettingsProvider; + class UserSearchModel; + class UserSearchDelegate; + class UserSearchResult; + class UIEventStream; + class QtUserSearchFirstPage; + class QtUserSearchFirstMultiJIDPage; + class QtUserSearchFieldsPage; + class QtUserSearchResultsPage; + class QtUserSearchDetailsPage; + class QtFormResultItemModel; + class SettingsProvider; - class QtUserSearchWindow : public QWizard, public UserSearchWindow, private Ui::QtUserSearchWizard { - Q_OBJECT - public: - QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider); - virtual ~QtUserSearchWindow(); + class QtUserSearchWindow : public QWizard, public UserSearchWindow, private Ui::QtUserSearchWizard { + Q_OBJECT + public: + QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider); + virtual ~QtUserSearchWindow() override; - virtual void addSavedServices(const std::vector<JID>& services); + virtual void addSavedServices(const std::vector<JID>& services) override; - virtual void clear(); - virtual void show(); - virtual void setResults(const std::vector<UserSearchResult>& results); - virtual void setResultsForm(Form::ref results); - virtual void setSelectedService(const JID& jid); - virtual void setServerSupportsSearch(bool error); - virtual void setSearchError(bool error); - virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields); - virtual void setNameSuggestions(const std::vector<std::string>& suggestions); - virtual void prepopulateJIDAndName(const JID& jid, const std::string& name); - virtual void setContactSuggestions(const std::vector<Contact>& suggestions); - virtual void setJIDs(const std::vector<JID> &jids); - virtual void setRoomJID(const JID &roomJID); - virtual std::string getReason() const; - virtual std::vector<JID> getJIDs() const; - virtual void setCanStartImpromptuChats(bool supportsImpromptu); - virtual void updateContacts(const std::vector<Contact> &contacts); + virtual void clear() override; + virtual void show() override; + virtual void setResults(const std::vector<UserSearchResult>& results) override; + virtual void setResultsForm(Form::ref results) override; + virtual void setSelectedService(const JID& jid) override; + virtual void setServerSupportsSearch(bool error) override; + virtual void setSearchError(bool error) override; + virtual void setSearchFields(std::shared_ptr<SearchPayload> fields) override; + virtual void setNameSuggestions(const std::vector<std::string>& suggestions) override; + virtual void prepopulateJIDAndName(const JID& jid, const std::string& name) override; + virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) override; + virtual void setJIDs(const std::vector<JID> &jids) override; + virtual void setOriginator(const JID& originator) override; + virtual void setRoomJID(const JID &roomJID) override; + virtual std::string getReason() const override; + virtual std::vector<JID> getJIDs() const override; + virtual void setCanStartImpromptuChats(bool supportsImpromptu) override; + virtual void updateContacts(const std::vector<Contact::ref> &contacts) override; + virtual void addContacts(const std::vector<Contact::ref>& contacts) override; + virtual void setCanSupplyDescription(bool allowed) override; + virtual void setWarning(const boost::optional<std::string>& message) override; - protected: - virtual int nextId() const; + protected: + virtual int nextId() const override; - private slots: - void handleFirstPageRadioChange(); - virtual void handleCurrentChanged(int); - virtual void handleAccepted(); - void handleContactSuggestionRequested(const QString& text); - void addContact(); - void handleAddViaSearch(); - void handleListChanged(std::vector<Contact> list); - void handleJIDsAdded(std::vector<JID> jids); + private slots: + void handleFirstPageRadioChange(); + void handleCurrentChanged(int); + void handleAccepted(); + void handleContactSuggestionRequested(const QString& text); + void addContact(); + void handleAddViaSearch(); + void handleListChanged(std::vector<Contact::ref> list); + void handleJIDsAdded(std::vector<JID> jids); + void handleJIDEditingDone(); - private: - void setFirstPage(QString title = ""); - void setSecondPage(); - void setThirdPage(); + private: + void setFirstPage(QString title = ""); + void setSecondPage(); + void setThirdPage(); - private: - void clearForm(); - void setError(const QString& error); - JID getServerToSearch(); - void handleSearch(); - JID getContactJID() const; - void addSearchedJIDToList(const JID& jid); + private: + void clearForm(); + void setError(const QString& error); + JID getServerToSearch(); + void handleSearch(); + JID getContactJID() const; + Contact::ref getContact() const; + void addSearchedJIDToList(const Contact::ref& contact); + void handleOnSearchedJIDSelected(const Contact::ref& contact); - private: - UIEventStream* eventStream_; - UserSearchWindow::Type type_; - QAbstractItemModel* model_; - UserSearchDelegate* delegate_; - QtUserSearchFirstPage* firstPage_; - QtUserSearchFirstMultiJIDPage* firstMultiJIDPage_; - QtUserSearchFieldsPage* fieldsPage_; - QtUserSearchResultsPage* resultsPage_; - QtUserSearchDetailsPage* detailsPage_; - JID myServer_; - JID roomJID_; - int lastPage_; - std::vector<Contact> contactVector_; - SettingsProvider* settings_; - bool searchNext_; - bool supportsImpromptu_; - }; + private: + UIEventStream* eventStream_; + UserSearchWindow::Type type_; + QAbstractItemModel* model_; + UserSearchDelegate* delegate_; + QtUserSearchFirstPage* firstPage_; + QtUserSearchFirstMultiJIDPage* firstMultiJIDPage_; + QtUserSearchFieldsPage* fieldsPage_; + QtUserSearchResultsPage* resultsPage_; + QtUserSearchDetailsPage* detailsPage_; + JID myServer_; + JID roomJID_; + JID originatorJID_; + int lastPage_; + std::vector<Contact::ref> contactVector_; + SettingsProvider* settings_; + bool searchNext_; + bool supportsImpromptu_; + }; } diff --git a/Swift/QtUI/UserSearch/UserSearchDelegate.cpp b/Swift/QtUI/UserSearch/UserSearchDelegate.cpp index 812c1c3..8c7ca9a 100644 --- a/Swift/QtUI/UserSearch/UserSearchDelegate.cpp +++ b/Swift/QtUI/UserSearch/UserSearchDelegate.cpp @@ -1,24 +1,24 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include <QPen> +#include <Swift/QtUI/UserSearch/UserSearchDelegate.h> + +#include <QFontMetrics> +#include <QModelIndex> #include <QPainter> +#include <QPen> #include <QStyleOptionViewItem> -#include <QModelIndex> -#include <QFontMetrics> -#include "Swift/QtUI/UserSearch/UserSearchDelegate.h" -//#include "Swift/QtUI/Roster/GroupItemDelegate.h" #include <Swift/Controllers/Chat/UserSearchController.h> + #include <Swift/QtUI/UserSearch/UserSearchModel.h> -//#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h" namespace Swift { -UserSearchDelegate::UserSearchDelegate() { +UserSearchDelegate::UserSearchDelegate(QObject* parent) : QStyledItemDelegate(parent) { } @@ -27,44 +27,44 @@ UserSearchDelegate::~UserSearchDelegate() { } QSize UserSearchDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const { - //UserSearchItem* item = static_cast<UserSearchItem*>(index.internalPointer()); - QFontMetrics nameMetrics(common_.nameFont); - QFontMetrics statusMetrics(common_.detailFont); - int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); - return QSize(150, sizeByText); + //UserSearchItem* item = static_cast<UserSearchItem*>(index.internalPointer()); + QFontMetrics nameMetrics(common_.nameFont); + QFontMetrics statusMetrics(common_.detailFont); + int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); + return QSize(150, sizeByText); } -void UserSearchDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - UserSearchResult* item = static_cast<UserSearchResult*>(index.internalPointer()); - painter->save(); - QRect fullRegion(option.rect); - if (option.state & QStyle::State_Selected) { - painter->fillRect(fullRegion, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } - else { - QColor nameColor = UserSearchModel::data(item, Qt::TextColorRole).value<QColor> (); - painter->setPen(QPen(nameColor)); - } - - QFontMetrics nameMetrics(common_.nameFont); - painter->setFont(common_.nameFont); - int extraFontWidth = nameMetrics.width("H"); - int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; - QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); - - int nameHeight = nameMetrics.height() + common_.verticalMargin; - QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); - - painter->drawText(nameRegion, Qt::AlignTop, UserSearchModel::data(item, Qt::DisplayRole).toString()); - - painter->setFont(common_.detailFont); - painter->setPen(QPen(QColor(160, 160, 160))); - - QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); - painter->drawText(detailRegion, Qt::AlignTop, UserSearchModel::data(item, UserSearchModel::DetailTextRole).toString()); - - painter->restore(); +void UserSearchDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { + UserSearchResult* item = static_cast<UserSearchResult*>(index.internalPointer()); + painter->save(); + QRect fullRegion(option.rect); + if (option.state & QStyle::State_Selected) { + painter->fillRect(fullRegion, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } + else { + QColor nameColor = UserSearchModel::data(item, Qt::TextColorRole).value<QColor> (); + painter->setPen(QPen(nameColor)); + } + + QFontMetrics nameMetrics(common_.nameFont); + painter->setFont(common_.nameFont); + int extraFontWidth = nameMetrics.width("H"); + int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2; + QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0)); + + int nameHeight = nameMetrics.height() + common_.verticalMargin; + QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); + + painter->drawText(nameRegion, Qt::AlignTop, UserSearchModel::data(item, Qt::DisplayRole).toString()); + + painter->setFont(common_.detailFont); + painter->setPen(QPen(QColor(160, 160, 160))); + + QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); + painter->drawText(detailRegion, Qt::AlignTop, UserSearchModel::data(item, UserSearchModel::DetailTextRole).toString()); + + painter->restore(); } } diff --git a/Swift/QtUI/UserSearch/UserSearchDelegate.h b/Swift/QtUI/UserSearch/UserSearchDelegate.h index 69848a8..a5639ec 100644 --- a/Swift/QtUI/UserSearch/UserSearchDelegate.h +++ b/Swift/QtUI/UserSearch/UserSearchDelegate.h @@ -1,27 +1,30 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <QStyledItemDelegate> #include <QPainter> #include <QStyleOptionViewItem> +#include <QStyledItemDelegate> #include <Swift/QtUI/Roster/DelegateCommons.h> namespace Swift { - class UserSearchDelegate : public QStyledItemDelegate { - public: - UserSearchDelegate(); - ~UserSearchDelegate(); - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const; - private: - DelegateCommons common_; - }; + class UserSearchDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + UserSearchDelegate(QObject* parent = nullptr); + virtual ~UserSearchDelegate(); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const; + private: + DelegateCommons common_; + }; } diff --git a/Swift/QtUI/UserSearch/UserSearchModel.cpp b/Swift/QtUI/UserSearch/UserSearchModel.cpp index aafd789..b6ac3cf 100644 --- a/Swift/QtUI/UserSearch/UserSearchModel.cpp +++ b/Swift/QtUI/UserSearch/UserSearchModel.cpp @@ -1,12 +1,12 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "Swift/QtUI/UserSearch/UserSearchModel.h" +#include <Swift/QtUI/UserSearch/UserSearchModel.h> -#include "Swift/QtUI/QtSwiftUtil.h" +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { @@ -14,76 +14,76 @@ UserSearchModel::UserSearchModel() { } void UserSearchModel::clear() { - emit layoutAboutToBeChanged(); - results_.clear(); - emit layoutChanged(); + emit layoutAboutToBeChanged(); + results_.clear(); + emit layoutChanged(); } void UserSearchModel::setResults(const std::vector<UserSearchResult>& results) { - clear(); - emit layoutAboutToBeChanged(); - results_ = results; - emit layoutChanged(); + clear(); + emit layoutAboutToBeChanged(); + results_ = results; + emit layoutChanged(); } int UserSearchModel::columnCount(const QModelIndex& /*parent*/) const { - return 1; + return 1; } QVariant UserSearchModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) return QVariant(); - UserSearchResult* result = static_cast<UserSearchResult*>(index.internalPointer()); - return data(result, role); + if (!index.isValid()) return QVariant(); + UserSearchResult* result = static_cast<UserSearchResult*>(index.internalPointer()); + return data(result, role); } QVariant UserSearchModel::data(UserSearchResult* item, int role) { - switch (role) { - case Qt::DisplayRole: return QVariant(nameLine(item)); - case DetailTextRole: return QVariant(detailLine(item)); - default: return QVariant(); - } + switch (role) { + case Qt::DisplayRole: return QVariant(nameLine(item)); + case DetailTextRole: return QVariant(detailLine(item)); + default: return QVariant(); + } } QString UserSearchModel::nameLine(UserSearchResult* item) { - QString result; - const std::map<std::string, std::string> fields = item->getFields(); - std::map<std::string, std::string>::const_iterator first = fields.find("first"); - if (first != fields.end()) { - result += P2QSTRING((*first).second); - } - std::map<std::string, std::string>::const_iterator last = fields.find("last"); - if (last != fields.end()) { - if (!result.isEmpty()) { - result += " "; - } - result += P2QSTRING((*last).second); - } - if (result.isEmpty()) { - result = P2QSTRING(item->getJID().toString()); - } - return result; + QString result; + const std::map<std::string, std::string> fields = item->getFields(); + std::map<std::string, std::string>::const_iterator first = fields.find("first"); + if (first != fields.end()) { + result += P2QSTRING((*first).second); + } + std::map<std::string, std::string>::const_iterator last = fields.find("last"); + if (last != fields.end()) { + if (!result.isEmpty()) { + result += " "; + } + result += P2QSTRING((*last).second); + } + if (result.isEmpty()) { + result = P2QSTRING(item->getJID().toString()); + } + return result; } QString UserSearchModel::detailLine(UserSearchResult* item) { - return P2QSTRING(item->getJID().toString()); + return P2QSTRING(item->getJID().toString()); } QModelIndex UserSearchModel::index(int row, int column, const QModelIndex & parent) const { - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } - return row < static_cast<int>(results_.size()) ? createIndex(row, column, reinterpret_cast<void*>(const_cast<UserSearchResult*>(&(results_[row])))) : QModelIndex(); + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + return row < static_cast<int>(results_.size()) ? createIndex(row, column, reinterpret_cast<void*>(const_cast<UserSearchResult*>(&(results_[row])))) : QModelIndex(); } QModelIndex UserSearchModel::parent(const QModelIndex& /*index*/) const { - return QModelIndex(); + return QModelIndex(); } int UserSearchModel::rowCount(const QModelIndex& parentIndex) const { - if (!parentIndex.isValid()) { - return results_.size(); - } - return 0; + if (!parentIndex.isValid()) { + return results_.size(); + } + return 0; } } diff --git a/Swift/QtUI/UserSearch/UserSearchModel.h b/Swift/QtUI/UserSearch/UserSearchModel.h index b547033..a3940da 100644 --- a/Swift/QtUI/UserSearch/UserSearchModel.h +++ b/Swift/QtUI/UserSearch/UserSearchModel.h @@ -1,41 +1,41 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once -#include <boost/shared_ptr.hpp> +#include <memory> #include <QAbstractItemModel> #include <QList> -#include "Swift/Controllers/Chat/UserSearchController.h" +#include <Swift/Controllers/Chat/UserSearchController.h> namespace Swift { - class UserSearchModel : public QAbstractItemModel { - Q_OBJECT - public: - enum UserItemRoles { - DetailTextRole = Qt::UserRole/*, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2, - StatusShowTypeRole = Qt::UserRole + 3*/ - }; - UserSearchModel(); - void clear(); - void setResults(const std::vector<UserSearchResult>& results); - int columnCount(const QModelIndex& parent = QModelIndex()) const; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; - static QVariant data(UserSearchResult* item, int role); - QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex& index) const; - int rowCount(const QModelIndex& parent = QModelIndex()) const; - private: - static QString nameLine(UserSearchResult* item); - static QString detailLine(UserSearchResult* item); - std::vector<UserSearchResult> results_; - }; + class UserSearchModel : public QAbstractItemModel { + Q_OBJECT + public: + enum UserItemRoles { + DetailTextRole = Qt::UserRole/*, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2, + StatusShowTypeRole = Qt::UserRole + 3*/ + }; + UserSearchModel(); + void clear(); + void setResults(const std::vector<UserSearchResult>& results); + int columnCount(const QModelIndex& parent = QModelIndex()) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + static QVariant data(UserSearchResult* item, int role); + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + int rowCount(const QModelIndex& parent = QModelIndex()) const; + private: + static QString nameLine(UserSearchResult* item); + static QString detailLine(UserSearchResult* item); + std::vector<UserSearchResult> results_; + }; } diff --git a/Swift/QtUI/Whiteboard/ColorWidget.cpp b/Swift/QtUI/Whiteboard/ColorWidget.cpp index e96b760..7669ecc 100644 --- a/Swift/QtUI/Whiteboard/ColorWidget.cpp +++ b/Swift/QtUI/Whiteboard/ColorWidget.cpp @@ -4,34 +4,41 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + + +#include <Swift/QtUI/Whiteboard/ColorWidget.h> -#include "ColorWidget.h" -#include <QPainter> #include <QMouseEvent> +#include <QPainter> 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(); - } - } + 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 index ae1af0f..56ada0c 100644 --- a/Swift/QtUI/Whiteboard/ColorWidget.h +++ b/Swift/QtUI/Whiteboard/ColorWidget.h @@ -4,30 +4,36 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <QWidget> namespace Swift { - class ColorWidget : public QWidget { - Q_OBJECT - public: - ColorWidget(QWidget* parent = 0); - QSize sizeHint() const; + class ColorWidget : public QWidget { + Q_OBJECT + public: + ColorWidget(QWidget* parent = nullptr); + QSize sizeHint() const; - public slots: - void setColor(QColor color); + public slots: + void setColor(QColor color); - private: - QColor color; + private: + QColor color; - protected: - void paintEvent(QPaintEvent* /*event*/); - void mouseReleaseEvent(QMouseEvent* event); + protected: + void paintEvent(QPaintEvent* /*event*/); + void mouseReleaseEvent(QMouseEvent* event); - signals: - void clicked(); + signals: + void clicked(); - }; + }; } diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.cpp b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp index 8821062..4ac93b8 100644 --- a/Swift/QtUI/Whiteboard/FreehandLineItem.cpp +++ b/Swift/QtUI/Whiteboard/FreehandLineItem.cpp @@ -4,94 +4,99 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "FreehandLineItem.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +#include <Swift/QtUI/Whiteboard/FreehandLineItem.h> namespace Swift { - FreehandLineItem::FreehandLineItem(QGraphicsItem* parent) : QGraphicsItem(parent) { - } + FreehandLineItem::FreehandLineItem(QGraphicsItem* parent) : QGraphicsItem(parent) { + } - QRectF FreehandLineItem::boundingRect() const - { - return boundRect; - } + 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::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::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); + 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); + points_.append(point); - prepareGeometryChange(); - boundRect |= rect; - } + 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; - } + 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); - } + void FreehandLineItem::setPen(const QPen& pen) + { + pen_ = pen; + update(boundRect); + } - QPen FreehandLineItem::pen() const - { - return pen_; - } + QPen FreehandLineItem::pen() const + { + return pen_; + } - const QVector<QPointF>& FreehandLineItem::points() const { - return points_; - } + const QVector<QPointF>& FreehandLineItem::points() const { + return points_; + } - int FreehandLineItem::type() const { - return Type; - } + int FreehandLineItem::type() const { + return Type; + } } diff --git a/Swift/QtUI/Whiteboard/FreehandLineItem.h b/Swift/QtUI/Whiteboard/FreehandLineItem.h index b1af3d1..5b83d95 100644 --- a/Swift/QtUI/Whiteboard/FreehandLineItem.h +++ b/Swift/QtUI/Whiteboard/FreehandLineItem.h @@ -4,30 +4,37 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once +#include <iostream> + #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; + class FreehandLineItem : public QGraphicsItem { + public: + enum {Type = UserType + 1}; + FreehandLineItem(QGraphicsItem* parent = nullptr); + QRectF boundingRect() const; + void paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/ = nullptr); + 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; - }; + private: + QPen pen_; + QVector<QPointF> points_; + QRectF boundRect; + }; } diff --git a/Swift/QtUI/Whiteboard/GView.cpp b/Swift/QtUI/Whiteboard/GView.cpp index d725cbb..80061fd 100644 --- a/Swift/QtUI/Whiteboard/GView.cpp +++ b/Swift/QtUI/Whiteboard/GView.cpp @@ -4,496 +4,500 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "GView.h" -#include <QtSwiftUtil.h> +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Whiteboard/GView.h> + +#include <Swift/QtUI/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()); - } - } - } + GView::GView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent), zValue(0), mousePressed(false), brush(QColor(Qt::white)), defaultBrush(QColor(Qt::white)), mode(GView::Select), lastItem(nullptr), selectionRect(nullptr), textDialog(nullptr) { + } + + 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 = nullptr; + 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 = nullptr; + selectionRect = nullptr; + 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 != nullptr) { + 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 != nullptr) { + 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 != nullptr) { + 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 != nullptr) { + 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 == nullptr) { + 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 = nullptr; + 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 != nullptr) { + pen = defaultPen; + brush = defaultBrush; + scene()->removeItem(selectionRect); + delete selectionRect; + selectionRect = nullptr; + lineWidthChanged(pen.width()); + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + } + + void GView::deselect(QString id) { + if (selectionRect != nullptr) { + QGraphicsItem* item = getItem(id); + if (item && selectionRect->data(1).value<QGraphicsItem*>() == item) { + pen = defaultPen; + brush = defaultBrush; + scene()->removeItem(selectionRect); + delete selectionRect; + selectionRect = nullptr; + lineWidthChanged(pen.width()); + lineColorChanged(pen.color()); + brushColorChanged(brush.color()); + } + } + } } diff --git a/Swift/QtUI/Whiteboard/GView.h b/Swift/QtUI/Whiteboard/GView.h index 6a4fd2f..4af5a53 100644 --- a/Swift/QtUI/Whiteboard/GView.h +++ b/Swift/QtUI/Whiteboard/GView.h @@ -4,72 +4,81 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once -#include <QGraphicsView> +#include <iostream> + #include <QGraphicsLineItem> +#include <QGraphicsView> #include <QMouseEvent> #include <QPen> -#include <iostream> + #include <Swiften/Base/IDGenerator.h> -#include "TextDialog.h" -#include "FreehandLineItem.h" +#include <Swift/QtUI/Whiteboard/FreehandLineItem.h> +#include <Swift/QtUI/Whiteboard/TextDialog.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); + 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 = nullptr); + 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); + + 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); - public slots: - void moveUpSelectedItem(); - void moveDownSelectedItem(); + 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(); + private slots: + void handleTextItemModified(QGraphicsTextItem*); - 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; + private: + void changePenAndBrush(QGraphicsItem* item, QPen pen, QBrush brush); + void setActualPenAndBrushFromItem(QGraphicsItem* item); + void deselect(); - 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); - }; + 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; + }; } diff --git a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp index 89de95e..62f5b89 100644 --- a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp +++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.cpp @@ -4,391 +4,402 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "QtWhiteboardWindow.h" +/* + * Copyright (c) 2015-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Whiteboard/QtWhiteboardWindow.h> #include <iostream> +#include <memory> #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 <QLabel> +#include <QMessageBox> + +#include <Swiften/Elements/Whiteboard/WhiteboardDeleteOperation.h> +#include <Swiften/Elements/Whiteboard/WhiteboardInsertOperation.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 <Swiften/Elements/WhiteboardPayload.h> +#include <Swiften/Whiteboard/WhiteboardSession.h> -#include <QMessageBox> -#include <QLabel> +#include <Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h> namespace Swift { - QtWhiteboardWindow::QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession) : QWidget() { + QtWhiteboardWindow::QtWhiteboardWindow(WhiteboardSession::ref whiteboardSession) : QWidget() { #ifndef Q_OS_MAC - setWindowIcon(QIcon(":/logo-icon-16.png")); +#ifdef Q_OS_WIN32 + setWindowIcon(QIcon(":/logo-icon-16-win.png")); +#else + setWindowIcon(QIcon(":/logo-icon-16.png")); +#endif #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(); - } - } + 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 = std::dynamic_pointer_cast<WhiteboardInsertOperation>(operation); + if (insertOp) { + WhiteboardElementDrawingVisitor visitor(graphicsView, operation->getPos(), GView::New); + insertOp->getElement()->accept(visitor); + } + + WhiteboardUpdateOperation::ref updateOp = std::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 = std::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(), nullptr, "Select pen color", QColorDialog::ShowAlphaChannel); + if(color.isValid()) + graphicsView->setLineColor(color); + } + + void QtWhiteboardWindow::showBrushColorDialog() + { + QColor color = QColorDialog::getColor(graphicsView->getBrushColor(), nullptr, "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 != nullptr) { + QLine line = lineItem->line().toLine(); + QColor color = lineItem->pen().color(); + WhiteboardLineElement::ref element = std::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 != nullptr) { + WhiteboardFreehandPathElement::ref element = std::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 != nullptr) { + QRectF rect = rectItem->rect(); + WhiteboardRectElement::ref element = std::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 != nullptr) { + QPointF point = textItem->pos(); + WhiteboardTextElement::ref element = std::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 = std::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 = std::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 = std::make_shared<WhiteboardInsertOperation>(); + insertOp->setPos(pos); + insertOp->setElement(el); + whiteboardSession_->sendOperation(insertOp); + } else { + WhiteboardUpdateOperation::ref updateOp = std::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 = std::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 index 3957bb7..21aa7ca 100644 --- a/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h +++ b/Swift/QtUI/Whiteboard/QtWhiteboardWindow.h @@ -4,85 +4,91 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#pragma once +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ -#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h> -#include <Swiften/Elements/Message.h> -#include <Swiften/Whiteboard/WhiteboardSession.h> -#include <Swiften/Elements/Whiteboard/WhiteboardElement.h> +#pragma once -#include <QWidget> -#include <QGraphicsView> +#include <QCloseEvent> +#include <QColorDialog> #include <QGraphicsScene> -#include <QVBoxLayout> -#include <QHBoxLayout> +#include <QGraphicsView> #include <QGridLayout> +#include <QHBoxLayout> #include <QPainter> #include <QPushButton> #include <QSpinBox> -#include <QColorDialog> #include <QToolButton> -#include <QCloseEvent> +#include <QVBoxLayout> +#include <QWidget> + +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/Whiteboard/WhiteboardElement.h> +#include <Swiften/Whiteboard/WhiteboardSession.h> + +#include <Swift/Controllers/UIInterfaces/WhiteboardWindow.h> -#include "GView.h" -#include "ColorWidget.h" +#include <Swift/QtUI/Whiteboard/ColorWidget.h> +#include <Swift/QtUI/Whiteboard/GView.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); + 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 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: + 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; + private: + QGraphicsScene* scene; + GView* graphicsView; + QVBoxLayout* layout; + QVBoxLayout* sidebarLayout; + QHBoxLayout* hLayout; + QGridLayout* toolboxLayout; + QHBoxLayout* strokeLayout; + QHBoxLayout* fillLayout; + ColorWidget* strokeColor; + ColorWidget* fillColor; + 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_; - }; + std::string lastOpID; + WhiteboardSession::ref whiteboardSession_; + }; } diff --git a/Swift/QtUI/Whiteboard/TextDialog.cpp b/Swift/QtUI/Whiteboard/TextDialog.cpp index 021895a..c15e7b4 100644 --- a/Swift/QtUI/Whiteboard/TextDialog.cpp +++ b/Swift/QtUI/Whiteboard/TextDialog.cpp @@ -4,49 +4,55 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "TextDialog.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/Whiteboard/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); - } + 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 index 64a0fe2..513d381 100644 --- a/Swift/QtUI/Whiteboard/TextDialog.h +++ b/Swift/QtUI/Whiteboard/TextDialog.h @@ -4,40 +4,46 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once +#include <iostream> + #include <QDialog> -#include <QVBoxLayout> -#include <QHBoxLayout> #include <QDialogButtonBox> -#include <QLineEdit> #include <QGraphicsTextItem> +#include <QHBoxLayout> +#include <QLineEdit> #include <QSpinBox> - -#include <iostream> +#include <QVBoxLayout> 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); - }; + class TextDialog : public QDialog + { + Q_OBJECT + public: + TextDialog(QGraphicsTextItem* item, QWidget* parent = nullptr); + + 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 index 74c6c1d..5a5aefa 100644 --- a/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h +++ b/Swift/QtUI/Whiteboard/WhiteboardElementDrawingVisitor.h @@ -4,184 +4,192 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once #include <Swiften/Elements/Whiteboard/WhiteboardElementVisitor.h> +#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h> #include <Swiften/Elements/Whiteboard/WhiteboardLineElement.h> #include <Swiften/Elements/Whiteboard/WhiteboardPolygonElement.h> +#include <Swiften/Elements/Whiteboard/WhiteboardRectElement.h> #include <Swiften/Elements/Whiteboard/WhiteboardTextElement.h> -#include <Swiften/Elements/Whiteboard/WhiteboardEllipseElement.h> -#include <Swiften/Elements/Whiteboard/WhiteboardFreehandPathElement.h> + +#include <Swift/QtUI/QtSwiftUtil.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_; - }; + 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 == nullptr && 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 index 161ff1d..ec39c38 100644 --- a/Swift/QtUI/WinUIHelpers.cpp +++ b/Swift/QtUI/WinUIHelpers.cpp @@ -4,58 +4,62 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#include "WinUIHelpers.h" +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <Swift/QtUI/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> +#include <memory> 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); + 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(); + std::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 index d34d236..36f5c9e 100644 --- a/Swift/QtUI/WinUIHelpers.h +++ b/Swift/QtUI/WinUIHelpers.h @@ -4,16 +4,23 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #pragma once -#include <Swiften/TLS/Certificate.h> #include <QWidget> +#include <Swiften/TLS/Certificate.h> + namespace Swift { class WinUIHelpers { public: - static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); + static void displayCertificateChainAsSheet(QWidget* parent, const std::vector<Certificate::ref>& chain); }; } diff --git a/Swift/QtUI/WindowsNotifier.cpp b/Swift/QtUI/WindowsNotifier.cpp index 212f0ca..d6e8ba9 100644 --- a/Swift/QtUI/WindowsNotifier.cpp +++ b/Swift/QtUI/WindowsNotifier.cpp @@ -1,54 +1,50 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ -#include "WindowsNotifier.h" +#include <Swift/QtUI/WindowsNotifier.h> -#include <QSystemTrayIcon> #include <cassert> #include <iostream> + #include <boost/bind.hpp> -#include "QtWin32NotifierWindow.h" -#include "QtSwiftUtil.h" +#include <QSystemTrayIcon> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtWin32NotifierWindow.h> namespace Swift { WindowsNotifier::WindowsNotifier(const std::string& name, const boost::filesystem::path& icon, QSystemTrayIcon* tray) : tray(tray) { - notifierWindow = new QtWin32NotifierWindow(); - snarlNotifier = new SnarlNotifier(name, notifierWindow, icon); - connect(tray, SIGNAL(messageClicked()), SLOT(handleMessageClicked())); + notifierWindow = new QtWin32NotifierWindow(); + connect(tray, SIGNAL(messageClicked()), SLOT(handleMessageClicked())); } WindowsNotifier::~WindowsNotifier() { - delete snarlNotifier; - delete notifierWindow; + delete notifierWindow; } void WindowsNotifier::showMessage(Type type, const std::string& subject, const std::string& description, const boost::filesystem::path& picture, boost::function<void()> callback) { - if (snarlNotifier->isAvailable()) { - snarlNotifier->showMessage(type, subject, description, picture, callback); - return; - } - std::vector<Notifier::Type> defaultTypes = getDefaultTypes(); - if (std::find(defaultTypes.begin(), defaultTypes.end(), type) == defaultTypes.end()) { - return; - } - lastCallback = callback; - int timeout = (type == IncomingMessage || type == SystemMessage) ? DEFAULT_MESSAGE_NOTIFICATION_TIMEOUT_SECONDS : DEFAULT_STATUS_NOTIFICATION_TIMEOUT_SECONDS; - tray->showMessage(P2QSTRING(subject), P2QSTRING(description), type == SystemMessage ? QSystemTrayIcon::Information : QSystemTrayIcon::NoIcon, timeout * 1000); + std::vector<Notifier::Type> defaultTypes = getDefaultTypes(); + if (std::find(defaultTypes.begin(), defaultTypes.end(), type) == defaultTypes.end()) { + return; + } + lastCallback = callback; + int timeout = (type == IncomingMessage || type == SystemMessage) ? DEFAULT_MESSAGE_NOTIFICATION_TIMEOUT_SECONDS : DEFAULT_STATUS_NOTIFICATION_TIMEOUT_SECONDS; + tray->showMessage(P2QSTRING(subject), P2QSTRING(description), type == SystemMessage ? QSystemTrayIcon::Information : QSystemTrayIcon::NoIcon, timeout * 1000); } void WindowsNotifier::handleMessageClicked() { - if (lastCallback) { - lastCallback(); - } + if (lastCallback) { + lastCallback(); + } } void WindowsNotifier::purgeCallbacks() { - lastCallback = boost::function<void()>(); + lastCallback = boost::function<void()>(); } } diff --git a/Swift/QtUI/WindowsNotifier.h b/Swift/QtUI/WindowsNotifier.h index b2b5577..945ef6b 100644 --- a/Swift/QtUI/WindowsNotifier.h +++ b/Swift/QtUI/WindowsNotifier.h @@ -1,7 +1,7 @@ /* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ #pragma once @@ -9,29 +9,30 @@ #include <map> #include <QObject> + #include <SwifTools/Notifier/Notifier.h> -#include <SwifTools/Notifier/SnarlNotifier.h> class QSystemTrayIcon; namespace Swift { - class WindowsNotifier : public QObject, public Notifier { - Q_OBJECT + class Win32NotifierWindow; + + class WindowsNotifier : public QObject, public Notifier { + Q_OBJECT - public: - WindowsNotifier(const std::string& name, const boost::filesystem::path& icon, QSystemTrayIcon* tray); - ~WindowsNotifier(); + public: + WindowsNotifier(const std::string& name, const boost::filesystem::path& icon, QSystemTrayIcon* tray); + ~WindowsNotifier(); - 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(); + 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(); - private slots: - void handleMessageClicked(); + private slots: + void handleMessageClicked(); - private: - QSystemTrayIcon* tray; - Win32NotifierWindow* notifierWindow; - SnarlNotifier* snarlNotifier; - boost::function<void()> lastCallback; - }; + private: + QSystemTrayIcon* tray; + Win32NotifierWindow* notifierWindow; + boost::function<void()> lastCallback; + }; } diff --git a/Swift/QtUI/main.cpp b/Swift/QtUI/main.cpp index d734713..3e20e23 100644 --- a/Swift/QtUI/main.cpp +++ b/Swift/QtUI/main.cpp @@ -1,98 +1,132 @@ /* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2010-2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ +#include <cstdlib> +#include <iostream> +#include <locale> +#include <memory> +#include <sstream> + +#include <boost/date_time/gregorian/gregorian.hpp> +#include <boost/program_options.hpp> #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> -#include <boost/program_options.hpp> #include <boost/version.hpp> -#include <iostream> + #include <QApplication> -#include <QTextCodec> -#include <QTranslator> #include <QLocale> #include <QStringList> +#include <QTextCodec> +#include <QTranslator> + +#include <Swiften/Base/Path.h> -#include <Swift/Controllers/Translator.h> #include <Swift/Controllers/ApplicationInfo.h> #include <Swift/Controllers/BuildVersion.h> +#include <Swift/Controllers/Translator.h> + #include <SwifTools/Application/PlatformApplicationPathProvider.h> #include <SwifTools/CrashReporter.h> -#include <stdlib.h> -#include <Swiften/Base/Path.h> -#include "QtSwift.h" -#include "QtTranslator.h" -#include "QtSwiftUtil.h" +#include <Swift/QtUI/QtSwift.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTranslator.h> int main(int argc, char* argv[]) { - QApplication app(argc, argv); + Swift::PlatformApplicationPathProvider applicationPathProvider(SWIFT_APPLICATION_NAME); + + // Set crash report prefix to include date and version. + std::stringstream prefix; - Swift::PlatformApplicationPathProvider applicationPathProvider(SWIFT_APPLICATION_NAME); + // This date_facet will be cleaned up by the stringstream. + auto outputFacet = new boost::gregorian::date_facet(); + outputFacet->format("%Y-%m-%d"); + prefix.imbue(std::locale(std::locale::classic(), outputFacet)); - Swift::CrashReporter crashReporter(applicationPathProvider.getDataDir() / "crashes"); + prefix << buildVersion << "_" << boost::gregorian::date(boost::gregorian::day_clock::local_day()) << "_"; + + Swift::CrashReporter crashReporter(applicationPathProvider.getDataDir() / "crashes", prefix.str()); #if QT_VERSION < 0x050000 - QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); #endif - // Parse program options - boost::program_options::options_description desc = Swift::QtSwift::getOptionsDescription(); - boost::program_options::variables_map vm; - try { - boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); - } catch (const boost::program_options::unknown_option& option) { + // Parse program options + boost::program_options::options_description desc = Swift::QtSwift::getOptionsDescription(); + boost::program_options::variables_map vm; + try { + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + } catch (const boost::program_options::unknown_option& option) { #if BOOST_VERSION >= 104200 - std::cout << "Ignoring unknown option " << option.get_option_name() << " but continuing." << std::endl; + std::cout << "Ignoring unknown option " << option.get_option_name() << " but continuing." << std::endl; #else - std::cout << "Error: " << option.what() << " (continuing)" << std::endl; + std::cout << "Error: " << option.what() << " (continuing)" << std::endl; #endif - } - boost::program_options::notify(vm); - if (vm.count("help") > 0) { - std::cout << SWIFT_APPLICATION_NAME << " is an instant messaging client for the XMPP network." << std::endl; - std::cout << std::endl; - std::cout << "Usage: " << argv[0] << " [OPTIONS]..." << std::endl; - std::cout << std::endl; - std::cout << desc << std::endl; - return 1; - } - if (vm.count("version") > 0) { - std::cout << SWIFT_APPLICATION_NAME << " " << buildVersion << std::endl; - return 0; - } - - // Translation + } + boost::program_options::notify(vm); + if (vm.count("help") > 0) { + std::cout << SWIFT_APPLICATION_NAME << " is an instant messaging client for the XMPP network." << std::endl; + std::cout << std::endl; + std::cout << "Usage: " << argv[0] << " [OPTIONS]..." << std::endl; + std::cout << std::endl; + std::cout << desc << std::endl; + return 1; + } + if (vm.count("version") > 0) { + std::cout << SWIFT_APPLICATION_NAME << " " << buildVersion << std::endl; + return 0; + } + + // Translation #if QT_VERSION < 0x050000 - QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); + QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8")); #endif - boost::filesystem::path someTranslationPath = applicationPathProvider.getResourcePath("/translations/swift_en.qm"); + boost::filesystem::path someTranslationPath = applicationPathProvider.getResourcePath("/translations/swift_en.qm"); - QTranslator qtTranslator; - if (!someTranslationPath.empty()) { + 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))); - } + std::string language; + if (vm.count("language") > 0) { + try { + language = vm["language"].as<std::string>(); + } catch (const boost::bad_any_cast&) { + } + } + if (!language.empty()) { + qtTranslator.load(QString(SWIFT_APPLICATION_NAME).toLower() + "_" + P2QSTRING(language), P2QSTRING(Swift::pathToString(someTranslationPath.parent_path()))); + } + else { + qtTranslator.load(QLocale::system(), QString(SWIFT_APPLICATION_NAME).toLower(), "_", P2QSTRING(Swift::pathToString(someTranslationPath.parent_path()))); + } #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))); + //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); + } + + QApplication app(argc, argv); + app.installTranslator(&qtTranslator); + QtTranslator swiftTranslator; + Swift::Translator::setInstance(&swiftTranslator); + +#if QT_VERSION < 0x050501 + /* According to Qt documenation, Qt is suppsoed to set the applications layout + * direction based on the translatable QT_LAYOUT_DIRECTION string. There is a + * bug in Qt prior version 5.5.1, i.e. QTBUG-43447, thus we set the layout + * direction manually based on the tranlsated QT_LAYOUT_DIRECTION string. + */ + app.setLayoutDirection(QGuiApplication::tr("QT_LAYOUT_DIRECTION") == QLatin1String("RTL") ? Qt::RightToLeft : Qt::LeftToRight); +#endif - Swift::QtSwift swift(vm); - int result = app.exec(); + Swift::QtSwift swift(vm); + int result = app.exec(); - Swift::Translator::setInstance(NULL); + Swift::Translator::setInstance(nullptr); - return result; + return result; } diff --git a/Swift/QtUI/qt-windows.conf b/Swift/QtUI/qt-windows.conf new file mode 100644 index 0000000..2620cdc --- /dev/null +++ b/Swift/QtUI/qt-windows.conf @@ -0,0 +1,2 @@ +[Platforms] +WindowsArguments = dpiawareness=0
\ No newline at end of file diff --git a/Swift/QtUI/swift-open-uri.cpp b/Swift/QtUI/swift-open-uri.cpp index 2d5ef19..23a8851 100644 --- a/Swift/QtUI/swift-open-uri.cpp +++ b/Swift/QtUI/swift-open-uri.cpp @@ -1,30 +1,31 @@ /* - * Copyright (c) 2011 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. + * Copyright (c) 2011-2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. */ +#include <iostream> + #include <QCoreApplication> #include <QDBusConnection> #include <QDBusMessage> -#include <iostream> int main(int argc, char* argv[]) { - QCoreApplication app(argc, argv); + QCoreApplication app(argc, argv); - QDBusConnection bus = QDBusConnection::sessionBus(); - if (!bus.isConnected()) { - return -1; - } - if (argc != 2) { - std::cerr << "Usage: " << argv[0] << " uri" << std::endl; - return -1; - } + QDBusConnection bus = QDBusConnection::sessionBus(); + if (!bus.isConnected()) { + return -1; + } + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " uri" << std::endl; + return -1; + } - QDBusMessage msg = QDBusMessage::createMethodCall("im.swift.Swift.URIHandler", "/", "im.swift.Swift.URIHandler", "openURI"); - msg << argv[1]; + QDBusMessage msg = QDBusMessage::createMethodCall("im.swift.Swift.URIHandler", "/", "im.swift.Swift.URIHandler", "openURI"); + msg << argv[1]; - bus.call(msg); + bus.call(msg); - return 0; + return 0; } |