diff options
Diffstat (limited to 'Swift/QtUI')
70 files changed, 2223 insertions, 734 deletions
diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp index 3caed57..2fd05c4 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.cpp +++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp @@ -86,11 +86,9 @@ void QtChatListWindow::handleClicked(const QModelIndex& index) { void QtChatListWindow::setupContextMenus() { mucMenu_ = new QMenu(); - onlineOnlyActions_ << mucMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); onlineOnlyActions_ << mucMenu_->addAction(tr("Edit Bookmark"), this, SLOT(handleEditBookmark())); onlineOnlyActions_ << mucMenu_->addAction(tr("Remove Bookmark"), this, SLOT(handleRemoveBookmark())); emptyMenu_ = new QMenu(); - onlineOnlyActions_ << emptyMenu_->addAction(tr("Add New Bookmark"), this, SLOT(handleAddBookmark())); } void QtChatListWindow::handleItemActivated(const QModelIndex& index) { @@ -132,7 +130,7 @@ void QtChatListWindow::setRecents(const std::list<ChatListWindow::Chat>& recents model_->setRecents(recents); } -void QtChatListWindow::setUnreadCount(int unread) { +void QtChatListWindow::setUnreadCount(size_t unread) { emit onCountUpdated(unread); } @@ -146,22 +144,6 @@ void QtChatListWindow::handleRemoveBookmark() { eventStream_->send(std::make_shared<RemoveMUCBookmarkUIEvent>(mucItem->getBookmark())); } -void QtChatListWindow::handleAddBookmarkFromRecents() { - const ChatListRecentItem* item = dynamic_cast<const ChatListRecentItem*>(contextMenuItem_); - if (item) { - const ChatListWindow::Chat& chat = item->getChat(); - MUCBookmark bookmark(chat.jid, chat.jid.toBare().toString()); - bookmark.setNick(chat.nick); - bookmark.setPassword(chat.password); - eventStream_->send(std::make_shared<AddMUCBookmarkUIEvent>(bookmark)); - } -} - -void QtChatListWindow::handleAddBookmark() { - (new QtAddBookmarkWindow(eventStream_))->show(); -} - - void QtChatListWindow::handleEditBookmark() { const ChatListMUCItem* mucItem = dynamic_cast<const ChatListMUCItem*>(contextMenuItem_); if (!mucItem) return; @@ -208,11 +190,8 @@ void QtChatListWindow::contextMenuEvent(QContextMenuEvent* event) { if (mucItem) { contextMenuItem_ = mucItem; bookmarkAction = mucRecentsMenu.addAction(tr("Edit Bookmark"), this, SLOT(handleEditBookmark())); + bookmarkAction->setEnabled(isOnline_); } - else { - bookmarkAction = mucRecentsMenu.addAction(tr("Add to Bookmarks"), this, SLOT(handleAddBookmarkFromRecents())); - } - bookmarkAction->setEnabled(isOnline_); mucRecentsMenu.addAction(tr("Clear recents"), this, SLOT(handleClearRecentsRequested())); mucRecentsMenu.exec(QCursor::pos()); return; diff --git a/Swift/QtUI/ChatList/QtChatListWindow.h b/Swift/QtUI/ChatList/QtChatListWindow.h index 834e318..3322001 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.h +++ b/Swift/QtUI/ChatList/QtChatListWindow.h @@ -27,18 +27,16 @@ namespace Swift { void removeWhiteboardSession(const JID& jid); void setBookmarksEnabled(bool enabled); void setRecents(const std::list<ChatListWindow::Chat>& recents); - void setUnreadCount(int unread); + void setUnreadCount(size_t unread); void clearBookmarks(); virtual void setOnline(bool isOnline); signals: - void onCountUpdated(int count); + void onCountUpdated(size_t count); private slots: void handleItemActivated(const QModelIndex&); - void handleAddBookmark(); void handleEditBookmark(); void handleRemoveBookmark(); - void handleAddBookmarkFromRecents(); void handleClicked(const QModelIndex& index); void handleSettingChanged(const std::string& setting); void handleClearRecentsRequested(); 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/FlowLayout.cpp b/Swift/QtUI/FlowLayout.cpp index c42b7e1..8a12841 100644 --- a/Swift/QtUI/FlowLayout.cpp +++ b/Swift/QtUI/FlowLayout.cpp @@ -49,7 +49,6 @@ ****************************************************************************/ #include <QtWidgets> - #include "FlowLayout.h" FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) : QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing) @@ -108,12 +107,12 @@ QLayoutItem *FlowLayout::takeAt(int index) if (index >= 0 && index < itemList.size()) return itemList.takeAt(index); else - return 0; + return nullptr; } Qt::Orientations FlowLayout::expandingDirections() const { - return 0; + return nullptr; } bool FlowLayout::hasHeightForWidth() const @@ -192,7 +191,7 @@ int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const return -1; } else if (parent->isWidgetType()) { QWidget *pw = static_cast<QWidget *>(parent); - return pw->style()->pixelMetric(pm, 0, pw); + 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 index 1453d45..29d527f 100644 --- a/Swift/QtUI/FlowLayout.h +++ b/Swift/QtUI/FlowLayout.h @@ -59,7 +59,7 @@ 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(); + ~FlowLayout() override; void addItem(QLayoutItem *item) override; int horizontalSpacing() const; diff --git a/Swift/QtUI/QtBookmarkDetailWindow.cpp b/Swift/QtUI/QtBookmarkDetailWindow.cpp index 920e94e..efa0e25 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.cpp +++ b/Swift/QtUI/QtBookmarkDetailWindow.cpp @@ -42,7 +42,7 @@ boost::optional<MUCBookmark> QtBookmarkDetailWindow::createBookmarkFromForm() { MUCBookmark bookmark(room, name); std::string nick(Q2PSTRING(nick_->text())); std::string password(Q2PSTRING(password_->text())); - bookmark.setAutojoin(autojoin_->isChecked()); + bookmark.setAutojoin(true); if (!nick.empty()) { bookmark.setNick(nick); } @@ -68,12 +68,6 @@ void QtBookmarkDetailWindow::createFormFromBookmark(const MUCBookmark& bookmark) if (bookmark.getPassword()) { password_->setText(P2QSTRING((*bookmark.getPassword()))); } - - if (bookmark.getAutojoin()) { - autojoin_->setCheckState(Qt::Checked); - } else { - autojoin_->setCheckState(Qt::Unchecked); - } } } diff --git a/Swift/QtUI/QtBookmarkDetailWindow.ui b/Swift/QtUI/QtBookmarkDetailWindow.ui index be55686..affb7e4 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.ui +++ b/Swift/QtUI/QtBookmarkDetailWindow.ui @@ -82,29 +82,6 @@ <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> diff --git a/Swift/QtUI/QtCachedImageScaler.cpp b/Swift/QtUI/QtCachedImageScaler.cpp index e1f540b..445d113 100644 --- a/Swift/QtUI/QtCachedImageScaler.cpp +++ b/Swift/QtUI/QtCachedImageScaler.cpp @@ -21,7 +21,7 @@ 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); + std::string suffix = "." + std::to_string(size); scaledImagePath = stringToPath(pathToString(scaledImagePath) + suffix); if (!boost::filesystem::exists(scaledImagePath)) { QImage image(P2QSTRING(pathToString(imagePath))); 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 f4d0d46..edd0b87 100644 --- a/Swift/QtUI/QtChatTabs.cpp +++ b/Swift/QtUI/QtChatTabs.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -24,6 +24,7 @@ #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> @@ -33,13 +34,13 @@ #include <Swift/QtUI/Trellis/QtGridSelectionDialog.h> namespace Swift { -QtChatTabs::QtChatTabs(bool singleWindow, SettingsProvider* settingsProvider, bool trellisMode) : QWidget(), singleWindow_(singleWindow), settingsProvider_(settingsProvider), trellisMode_(trellisMode), dynamicGrid_(nullptr), gridSelectionDialog_(nullptr) { +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")); #else setAttribute(Qt::WA_ShowWithoutActivating); #endif - dynamicGrid_ = new QtDynamicGridLayout(this, trellisMode); + 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))); @@ -199,13 +200,8 @@ void QtChatTabs::handleTabClosing() { if (widget && ((index = dynamicGrid_->indexOf(widget)) >= 0)) { dynamicGrid_->removeTab(index); if (dynamicGrid_->count() == 0) { - if (!singleWindow_) { - hide(); - } - else { - setWindowTitle(""); - onTitleChanged(""); - } + setWindowTitle(""); + onTitleChanged(""); } else { handleTabTitleUpdated(dynamicGrid_->currentWidget()); @@ -425,4 +421,8 @@ void QtChatTabs::checkForFirstShow() { } } +QSize QtChatTabs::sizeHint() const { + return QSize(600, 600); +} + } diff --git a/Swift/QtUI/QtChatTabs.h b/Swift/QtUI/QtChatTabs.h index 0c12d96..6a758ca 100644 --- a/Swift/QtUI/QtChatTabs.h +++ b/Swift/QtUI/QtChatTabs.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -26,13 +26,14 @@ namespace Swift { class QtChatTabs : public QWidget, public QtChatTabsBase { Q_OBJECT public: - QtChatTabs(bool singleWindow, SettingsProvider* settingsProvider, bool trellisMode); + 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(); @@ -65,7 +66,6 @@ namespace Swift { void checkForFirstShow(); private: - bool singleWindow_; SettingsProvider* settingsProvider_; bool trellisMode_; QtDynamicGridLayout* dynamicGrid_; diff --git a/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.cpp b/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.cpp deleted file mode 100644 index 40ab17f..0000000 --- a/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2015-2016 Isode Limited. - * All rights reserved. - * See the COPYING file for more information. - */ - -#include <Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h> - -#include <cassert> - -#include <QApplication> -#include <QShortcut> - -#include <Swiften/Base/Log.h> - -#include <Swift/QtUI/QtTabbable.h> - -namespace Swift { - -QtChatTabsShortcutOnlySubstitute::QtChatTabsShortcutOnlySubstitute() : QWidget() { - -} - -QtChatTabsShortcutOnlySubstitute::~QtChatTabsShortcutOnlySubstitute() { - -} - -void QtChatTabsShortcutOnlySubstitute::addTab(QtTabbable* tab) { - 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(new QShortcut(QKeySequence(tr("CTRL+W", "Close chat tab.")), tab), SIGNAL(activated()), this, SLOT(handleCloseTabShortcut())); - connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageUp), tab), SIGNAL(activated()), tab, SIGNAL(requestPreviousTab())); - connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_PageDown), tab), SIGNAL(activated()), tab, SIGNAL(requestNextTab())); - connect(new QShortcut(QKeySequence(Qt::ALT + Qt::Key_A), tab), SIGNAL(activated()), tab, SIGNAL(requestActiveTab())); -} - -void QtChatTabsShortcutOnlySubstitute::handleCloseTabShortcut() { - QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender()->parent()); - SWIFT_LOG_ASSERT(senderTab, debug) << "No window to close." << std::endl; - if (senderTab) { - senderTab->close(); - } -} - -void QtChatTabsShortcutOnlySubstitute::handleRequestedNextTab() { - QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender()); - - QList<QtTabbable*> tabs = tabbableWindows(); - - int currentIndex = tabs.indexOf(senderTab); - assert(currentIndex >= 0); - - QtTabbable* nextTab = tabs.at((currentIndex + 1) % tabs.size()); - nextTab->activateWindow(); -} - -void QtChatTabsShortcutOnlySubstitute::handleRequestedActiveTab() { - QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender()); - - QtTabbable::AlertType types[] = {QtTabbable::WaitingActivity, QtTabbable::ImpendingActivity}; - - QList<QtTabbable*> tabs = tabbableWindows(); - - for (auto& type : types) { - int startIndex = tabs.indexOf(senderTab); - int currentIndex = startIndex; - - do { - currentIndex = (currentIndex + 1) % tabs.size(); - QtTabbable* currentTab = tabs.at(currentIndex); - if (currentTab->getWidgetAlertState() == type) { - currentTab->activateWindow(); - return; - } - } while (startIndex != currentIndex); - } -} - -void QtChatTabsShortcutOnlySubstitute::handleRequestedPreviousTab() { - QtTabbable* senderTab = dynamic_cast<QtTabbable*>(sender()); - - QList<QtTabbable*> tabs = tabbableWindows(); - - int currentIndex = tabs.indexOf(senderTab); - assert(currentIndex >= 0); - - QtTabbable* previousTab = tabs.at((currentIndex + tabs.size() - 1) % tabs.size()); - previousTab->activateWindow(); -} - -QList<QtTabbable*> QtChatTabsShortcutOnlySubstitute::tabbableWindows() const { - QList<QWidget*> windows = qApp->topLevelWidgets(); - - QList<QtTabbable*> tabbables; - for (auto topLevelWidget : windows) { - QtTabbable* tabbable = dynamic_cast<QtTabbable*>(topLevelWidget); - if (tabbable) { - tabbables << tabbable; - } - } - - return tabbables; -} - -} - diff --git a/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h b/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h deleted file mode 100644 index b330fe7..0000000 --- a/Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2015-2016 Isode Limited. - * All rights reserved. - * See the COPYING file for more information. - */ - -#pragma once - -#include <QList> -#include <QWidget> - -#include <Swift/QtUI/QtChatTabsBase.h> - -class QShortcut; - -namespace Swift { - -class QtChatTabsShortcutOnlySubstitute : public QWidget, public QtChatTabsBase { - Q_OBJECT - - public: - QtChatTabsShortcutOnlySubstitute(); - virtual ~QtChatTabsShortcutOnlySubstitute(); - - virtual void addTab(QtTabbable* tab); - - private slots: - void handleCloseTabShortcut(); - void handleRequestedNextTab(); - void handleRequestedActiveTab(); - void handleRequestedPreviousTab(); - - private: - QList<QtTabbable*> tabbableWindows() const; - - private: - QList<QShortcut*> shortcuts_; -}; - -} diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 874f710..82c65ce 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -111,8 +111,11 @@ QtChatWindow::QtChatWindow(const QString& contact, QtChatTheme* theme, UIEventSt messageLog_ = new QtPlainChatView(this, eventStream_); } else { - messageLog_ = new QtWebKitChatView(this, eventStream_, theme, this); // I accept that passing the ChatWindow in so that the view can call the signals is somewhat inelegant, but it saves a lot of boilerplate. This patch is unpleasant enough already. So let's fix this soon (it at least needs fixing by the time history is sorted), but not now. + 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); @@ -152,11 +155,15 @@ QtChatWindow::QtChatWindow(const QString& contact, QtChatTheme* theme, UIEventSt connect(input_, SIGNAL(lostFocus()), this, SLOT(handleTextInputLostFocus())); connect(input_, SIGNAL(itemDropped(QDropEvent*)), this, SLOT(dropEvent(QDropEvent*))); QPushButton* emojisButton_ = new QPushButton(this); - -#ifdef SWIFTEN_PLATFORM_MACOSX emojisButton_->setText("\xF0\x9F\x98\x83"); -#else - emojisButton_->setIcon(QIcon(":/emoticons/smile.png")); + +#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 connect(emojisButton_, SIGNAL(clicked()), this, SLOT(handleEmojisButtonClicked())); @@ -353,6 +360,10 @@ QByteArray QtChatWindow::getSplitterState() { void QtChatWindow::handleChangeSplitterState(QByteArray state) { logRosterSplitter_->restoreState(state); +#ifdef SWIFTEN_PLATFORM_MACOSX + logRosterSplitter_->setHandleWidth(0); +#endif + logRosterSplitter_->setChildrenCollapsible(false); } void QtChatWindow::handleSplitterMoved(int, int) { @@ -494,7 +505,7 @@ void QtChatWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); } -void QtChatWindow::setUnreadMessageCount(int count) { +void QtChatWindow::setUnreadMessageCount(size_t count) { if (unreadCount_ != count) { unreadCount_ = count; updateTitleWithUnreadCount(); @@ -536,7 +547,7 @@ void QtChatWindow::flash() { emit requestFlash(); } -int QtChatWindow::getCount() { +size_t QtChatWindow::getCount() { return unreadCount_; } @@ -693,7 +704,7 @@ void QtChatWindow::handleEmojisButtonClicked() { 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::unique_ptr<QMenu>(new QMenu()); + emojisMenu_ = std::make_unique<QMenu>(); emojisMenu_->setLayout(emojisLayout); emojisMenu_->adjustSize(); @@ -716,7 +727,17 @@ void QtChatWindow::handleEmojiClicked(QString emoji) { void QtChatWindow::handleTextInputReceivedFocus() { lastLineTracker_.setHasFocus(true); input_->setFocus(); - onAllMessagesRead(); + 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() { @@ -730,6 +751,7 @@ void QtChatWindow::handleActionButtonClicked() { QAction* affiliations = nullptr; QAction* destroy = nullptr; QAction* invite = nullptr; + QAction* leave = nullptr; QAction* block = nullptr; QAction* unblock = nullptr; @@ -783,6 +805,10 @@ void QtChatWindow::handleActionButtonClicked() { 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; } } } @@ -834,6 +860,9 @@ void QtChatWindow::handleActionButtonClicked() { else if (result == invite) { onInviteToChat(std::vector<JID>()); } + else if (result == leave) { + close(); + } else if (result == block) { onBlockUserRequest(); } @@ -867,14 +896,16 @@ void QtChatWindow::setCanInitiateImpromptuChats(bool supportsImpromptu) { } void QtChatWindow::showBookmarkWindow(const MUCBookmark& bookmark) { - if (roomBookmarkState_ == RoomNotBookmarked) { - QtAddBookmarkWindow* window = new QtAddBookmarkWindow(eventStream_, bookmark); + if (roomBookmarkState_ != RoomNotBookmarked) { + QtEditBookmarkWindow* window = new QtEditBookmarkWindow(eventStream_, bookmark); window->show(); } +#ifndef NOT_YET else { - QtEditBookmarkWindow* window = new QtEditBookmarkWindow(eventStream_, bookmark); + QtAddBookmarkWindow* window = new QtAddBookmarkWindow(eventStream_, bookmark); window->show(); } +#endif // ! NOT_YET } std::string QtChatWindow::getID() const { @@ -984,4 +1015,60 @@ 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 361d7c6..b876d1e 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -34,6 +34,7 @@ class QComboBox; class QLabel; class QSplitter; class QPushButton; +class QTimer; namespace Swift { class QtChatView; @@ -93,6 +94,7 @@ namespace Swift { 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); @@ -104,7 +106,7 @@ namespace Swift { void show(); bool isVisible() const; void activate(); - void setUnreadMessageCount(int count); + void setUnreadMessageCount(size_t count); void convertToMUC(MUCType mucType); // TreeWidget *getTreeWidget(); void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels); @@ -117,7 +119,7 @@ namespace Swift { void setContactChatState(ChatState::ChatStateType state); void setRosterModel(Roster* roster); void setTabComplete(TabComplete* completer); - int getCount(); + 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); @@ -194,8 +196,12 @@ namespace Swift { void resetDayChangeTimer(); + void setChatSecurityMarking(const std::string& markingValue, const std::string& markingForegroundColorValue, const std::string& markingBackgroundColorValue); + void removeChatSecurityMarking(); + void handleFocusTimerTick(); + private: - int unreadCount_; + size_t unreadCount_; bool contactIsTyping_; LastLineTracker lastLineTracker_; std::string id_; @@ -241,5 +247,8 @@ namespace Swift { 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 49cfe4d..33c8b94 100644 --- a/Swift/QtUI/QtChatWindowFactory.cpp +++ b/Swift/QtUI/QtChatWindowFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -11,7 +11,6 @@ #include <SwifTools/EmojiMapper.h> #include <Swift/QtUI/QtChatTabs.h> -#include <Swift/QtUI/QtChatTabsBase.h> #include <Swift/QtUI/QtChatTheme.h> #include <Swift/QtUI/QtChatWindow.h> #include <Swift/QtUI/QtEmojisSelector.h> @@ -23,22 +22,17 @@ namespace Swift { static const QString SPLITTER_STATE = "mucSplitterState"; static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry"; -QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabsBase* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticonsMap) : themePath_(themePath), emoticonsMap_(emoticonsMap) { +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; - QtChatTabs* fullTabs = dynamic_cast<QtChatTabs*>(tabs_); - if (splitter) { - assert(fullTabs && "Netbook mode and no-tabs interface is not supported!"); - splitter->addWidget(fullTabs); - } else if (fullTabs) { - QVariant chatTabsGeometryVariant = qtOnlySettings_->getQSettings()->value(CHAT_TABS_GEOMETRY); - if (chatTabsGeometryVariant.isValid()) { - fullTabs->restoreGeometry(chatTabsGeometryVariant.toByteArray()); - } - connect(fullTabs, SIGNAL(geometryChanged()), this, SLOT(handleWindowGeometryChanged())); + 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() { @@ -60,7 +54,7 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray))); QVariant splitterState = qtOnlySettings_->getQSettings()->value(SPLITTER_STATE); - if(splitterState.isValid()) { + if (splitterState.isValid()) { chatWindow->handleChangeSplitterState(splitterState.toByteArray()); } diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h index 2bb6a90..3e4dca3 100644 --- a/Swift/QtUI/QtChatWindowFactory.h +++ b/Swift/QtUI/QtChatWindowFactory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -24,7 +24,7 @@ #include <Swift/QtUI/QtSettingsProvider.h> namespace Swift { - class QtChatTabsBase; + class QtChatTabs; class QtChatTheme; class UIEventStream; class QtUIPreferences; @@ -32,7 +32,7 @@ namespace Swift { class QtChatWindowFactory : public QObject, public ChatWindowFactory { Q_OBJECT public: - QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabsBase* tabs, const QString& themePath, const std::map<std::string, std::string>& emoticonsMap); + 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: @@ -44,7 +44,7 @@ namespace Swift { QString themePath_; SettingsProvider* settings_; QtSettingsProvider* qtOnlySettings_; - QtChatTabsBase* tabs_; + QtChatTabs* tabs_; QtChatTheme* theme_; std::map<std::string, std::string> emoticonsMap_; }; diff --git a/Swift/QtUI/QtDBUSURIHandler.cpp b/Swift/QtUI/QtDBUSURIHandler.cpp index 34659f4..a1446c3 100644 --- a/Swift/QtUI/QtDBUSURIHandler.cpp +++ b/Swift/QtUI/QtDBUSURIHandler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2016 Isode Limited. + * Copyright (c) 2011-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -19,7 +19,7 @@ using namespace Swift; namespace { class DBUSAdaptor: public QDBusAbstractAdaptor { Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler"); + Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler") public: DBUSAdaptor(QtDBUSURIHandler* uriHandler) : QDBusAbstractAdaptor(uriHandler), uriHandler(uriHandler) { } diff --git a/Swift/QtUI/QtEditBookmarkWindow.cpp b/Swift/QtUI/QtEditBookmarkWindow.cpp index 1d6b467..c724c97 100644 --- a/Swift/QtUI/QtEditBookmarkWindow.cpp +++ b/Swift/QtUI/QtEditBookmarkWindow.cpp @@ -12,7 +12,6 @@ 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()) : ""); } diff --git a/Swift/QtUI/QtEmojiCell.cpp b/Swift/QtUI/QtEmojiCell.cpp index 865f1f6..106e968 100644 --- a/Swift/QtUI/QtEmojiCell.cpp +++ b/Swift/QtUI/QtEmojiCell.cpp @@ -1,11 +1,13 @@ /* - * Copyright (c) 2016-2017 Isode Limited. + * 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> @@ -17,13 +19,20 @@ 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(boundingRect.width(), boundingRect.height())); - setFixedHeight(qMax(boundingRect.width(), boundingRect.height())); + setFixedWidth(qMax(sizeMultiplier*boundingRect.width(), sizeMultiplier*boundingRect.height())); + setFixedHeight(qMax(sizeMultiplier*boundingRect.width(), sizeMultiplier*boundingRect.height())); setFlat(true); setToolTip(shortname); diff --git a/Swift/QtUI/QtEmojisScroll.h b/Swift/QtUI/QtEmojisScroll.h index 959ab5f..f954c2d 100644 --- a/Swift/QtUI/QtEmojisScroll.h +++ b/Swift/QtUI/QtEmojisScroll.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 Isode Limited. + * Copyright (c) 2016-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -13,6 +13,6 @@ namespace Swift { class QtEmojisScroll : public QWidget { Q_OBJECT public: - QtEmojisScroll(QLayout* emojiLayout, QWidget *parent = 0); + QtEmojisScroll(QLayout* emojiLayout, QWidget* parent = nullptr); }; } diff --git a/Swift/QtUI/QtEmojisSelector.cpp b/Swift/QtUI/QtEmojisSelector.cpp index 62ed862..fe2f235 100644 --- a/Swift/QtUI/QtEmojisSelector.cpp +++ b/Swift/QtUI/QtEmojisSelector.cpp @@ -24,7 +24,6 @@ namespace Swift { QtEmojisSelector::QtEmojisSelector(QSettings* settings, const std::map<std::string, std::string>& emoticonsMap, QWidget* parent) : QTabWidget(parent), settings_(settings), emoticonsMap_(emoticonsMap) { -#ifdef SWIFTEN_PLATFORM_MACOSX recentEmojisGrid_ = addRecentTab(); connect(recentEmojisGrid_, SIGNAL(onEmojiSelected(QString)), this, SLOT(emojiClickedSlot(QString))); @@ -34,17 +33,13 @@ namespace Swift { connect(grid, SIGNAL(onEmojiSelected(QString)), this, SLOT(emojiClickedSlot(QString))); } } - loadSettings(); -#else - setupEmoticonsTab(); -#endif + //The size of an emoji cell varies depending the OS, 42 is the ceil value. + setFixedSize(QSize(EmojiMapper::emojisInCategory.size() * 42, 300)); } QtEmojisSelector::~QtEmojisSelector() { -#ifdef SWIFTEN_PLATFORM_MACOSX writeSettings(); -#endif } QtRecentEmojisGrid* QtEmojisSelector::addRecentTab() { diff --git a/Swift/QtUI/QtEmojisSelector.h b/Swift/QtUI/QtEmojisSelector.h index 7ac11d0..1a64cf4 100644 --- a/Swift/QtUI/QtEmojisSelector.h +++ b/Swift/QtUI/QtEmojisSelector.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 Isode Limited. + * Copyright (c) 2016-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -20,7 +20,7 @@ namespace Swift { class QtEmojisSelector : public QTabWidget { Q_OBJECT public: - QtEmojisSelector(QSettings* settings, const std::map<std::string, std::string>& emoticonsMap, QWidget * parent = 0); + QtEmojisSelector(QSettings* settings, const std::map<std::string, std::string>& emoticonsMap, QWidget* parent = nullptr); ~QtEmojisSelector(); public slots: 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/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp index 96c2da0..860ddfa 100644 --- a/Swift/QtUI/QtFormWidget.cpp +++ b/Swift/QtUI/QtFormWidget.cpp @@ -147,7 +147,7 @@ QWidget* QtFormWidget::createWidget(FormField::ref field, const FormField::Type std::string indexString; if (index) { /* for multi-item forms we need to distinguish between the different rows */ - indexString = boost::lexical_cast<std::string>(index); + indexString = std::to_string(index); } fields_[field->getName() + indexString] = widget; return widget; diff --git a/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp b/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp index 19274a2..0521a2d 100644 --- a/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp +++ b/Swift/QtUI/QtHighlightNotificationConfigDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 Isode Limited. + * Copyright (c) 2016-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -50,7 +50,7 @@ QtHighlightNotificationConfigDialog::QtHighlightNotificationConfigDialog(QtSetti } }); connect(ui_.userHighlightTreeWidget, &QTreeWidget::currentItemChanged, [&](QTreeWidgetItem* current, QTreeWidgetItem* ) { - ui_.removeUserHighlightPushButton->setEnabled(current != 0); + ui_.removeUserHighlightPushButton->setEnabled(current != nullptr); }); // keyword highlight edit slots @@ -72,7 +72,7 @@ QtHighlightNotificationConfigDialog::QtHighlightNotificationConfigDialog(QtSetti } }); connect(ui_.keywordHighlightTreeWidget, &QTreeWidget::currentItemChanged, [&](QTreeWidgetItem* current, QTreeWidgetItem* ) { - ui_.removeKeywordHighlightPushButton->setEnabled(current != 0); + ui_.removeKeywordHighlightPushButton->setEnabled(current != nullptr); }); // setup slots for main dialog buttons diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp index 77a7f12..0e7e89d 100644 --- a/Swift/QtUI/QtHistoryWindow.cpp +++ b/Swift/QtUI/QtHistoryWindow.cpp @@ -49,7 +49,7 @@ QtHistoryWindow::QtHistoryWindow(SettingsProvider* settings, UIEventStream* even idCounter_ = 0; delete ui_.conversation_; - conversation_ = new QtWebKitChatView(nullptr, nullptr, theme_, this, true); // Horrible unsafe. Do not do this. FIXME + 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); @@ -131,7 +131,7 @@ void QtHistoryWindow::addMessage(const std::string &message, const std::string & 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_++); + std::string id = "id" + std::to_string(idCounter_++); QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); diff --git a/Swift/QtUI/QtJoinMUCWindow.cpp b/Swift/QtUI/QtJoinMUCWindow.cpp index 13de1c9..550ae4a 100644 --- a/Swift/QtUI/QtJoinMUCWindow.cpp +++ b/Swift/QtUI/QtJoinMUCWindow.cpp @@ -49,7 +49,7 @@ void QtJoinMUCWindow::handleJoin() { 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.joinAutomatically->isChecked(), !ui.instantRoom->isChecked())); + uiEventStream->send(std::make_shared<JoinMUCUIEvent>(room, password, lastSetNick, !ui.instantRoom->isChecked())); hide(); } diff --git a/Swift/QtUI/QtJoinMUCWindow.ui b/Swift/QtUI/QtJoinMUCWindow.ui index 24d6ab8..96f1d17 100644 --- a/Swift/QtUI/QtJoinMUCWindow.ui +++ b/Swift/QtUI/QtJoinMUCWindow.ui @@ -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> @@ -124,7 +117,6 @@ <tabstop>nickName</tabstop> <tabstop>password</tabstop> <tabstop>instantRoom</tabstop> - <tabstop>joinAutomatically</tabstop> <tabstop>joinButton</tabstop> </tabstops> <resources/> 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 654e921..8fdce4d 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -573,4 +573,8 @@ void QtLoginWindow::handleOpenConnectionOptions() { } } +QSize QtLoginWindow::sizeHint() const { + return QSize(250, 600); +} + } diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 674e1e3..d3c2601 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -54,12 +54,15 @@ namespace Swift { void hide(); QtMenus getMenus() const; virtual void quit(); + QSize sizeHint() const; signals: void geometryChanged(); - private slots: + public slots: void loginClicked(); + + private slots: void handleCertficateChecked(bool); void handleQuit(); void handleShowXMLConsole(); diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 0c1dd97..92488ae 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -19,6 +19,7 @@ #include <QListWidgetItem> #include <QMenuBar> #include <QPushButton> +#include <QScrollArea> #include <QTabWidget> #include <QToolBar> @@ -26,6 +27,7 @@ #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> @@ -35,6 +37,7 @@ #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> @@ -52,7 +55,7 @@ namespace Swift { -QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist, bool enableAdHocCommandOnJID) : QWidget(), MainWindow(false), loginMenus_(loginMenus) { +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)); @@ -66,11 +69,22 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr connect(meView_, SIGNAL(onShowCertificateInfo()), this, SLOT(handleShowCertificateInfo())); tabs_ = new QtTabWidget(this); -#if QT_VERSION >= 0x040500 tabs_->setDocumentMode(true); -#endif 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_); @@ -82,9 +96,7 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr 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))); @@ -103,8 +115,11 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr 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 tabBarCombo_->addItem(tr("Notices")); tabBarCombo_->setCurrentIndex(tabs_->currentIndex()); mainLayout->addWidget(tabBarCombo_); @@ -185,6 +200,13 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr } 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())); @@ -423,5 +445,8 @@ void QtMainWindow::setBlockingCommandAvailable(bool 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 c46fdfc..b285831 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -12,6 +12,7 @@ #include <QMenu> #include <QWidget> +#include <Swift/Controllers/Chat/Chattables.h> #include <Swift/Controllers/UIInterfaces/MainWindow.h> #include <Swift/QtUI/ChatList/QtChatListWindow.h> @@ -19,44 +20,45 @@ #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 QtChatOverview; class QtRosterWidget; - class TreeWidget; - class UIEventStream; class QtTabWidget; - class SettingsProvider; 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, bool enableAdHocCommandOnJID); - virtual ~QtMainWindow(); + 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); - void setMyJID(const JID& jid); - void setMyAvatarPath(const std::string& path); - void setMyStatusText(const std::string& status); - void setMyStatusType(StatusShow::Type type); - void setMyContactRosterItem(std::shared_ptr<ContactRosterItem> contact); - void setConnecting(); - void setStreamEncryptionStatus(bool tlsInPlaceAndValid); - void openCertificateDialog(const std::vector<Certificate::ref>& chain); + 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); - void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands); - void setBlockingCommandAvailable(bool isAvailable); + 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); @@ -79,8 +81,10 @@ namespace Swift { void handleShowCertificateInfo(); void handleEditBlockingList(); void handleSomethingSelectedChanged(bool itemSelected); + void handleSubmitFormActionTriggered(); private: + Chattables& chattables_; SettingsProvider* settings_; QtLoginWindow::QtMenus loginMenus_; std::vector<QMenu*> menus_; @@ -98,6 +102,7 @@ namespace Swift { QMenu* serverAdHocMenu_; QtTabWidget* tabs_; QComboBox* tabBarCombo_; + QtChatOverview* chatOverview_; QWidget* contactsTabWidget_; QWidget* eventsTabWidget_; QtEventWindow* eventWindow_; @@ -106,5 +111,6 @@ namespace Swift { std::vector<DiscoItems::Item> serverAdHocCommands_; QList<QAction*> serverAdHocCommandActions_; QList<QAction*> onlineOnlyActions_; + QAction* submitFormAction_; }; } diff --git a/Swift/QtUI/QtPlainChatView.cpp b/Swift/QtUI/QtPlainChatView.cpp index 7e9c857..cdee1e6 100644 --- a/Swift/QtUI/QtPlainChatView.cpp +++ b/Swift/QtUI/QtPlainChatView.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2017 Isode Limited. + * Copyright (c) 2013-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -42,7 +42,7 @@ QtPlainChatView::QtPlainChatView(QtChatWindow *window, UIEventStream* eventStrea QtPlainChatView::~QtPlainChatView() { } -QString chatMessageToString(const ChatWindow::ChatMessage& message) { +static QString chatMessageToString(const ChatWindow::ChatMessage& message) { QString result; for (auto&& part : message.getParts()) { std::shared_ptr<ChatWindow::ChatTextMessagePart> textPart; @@ -178,7 +178,7 @@ void QtPlainChatView::setAckState(const std::string& /*id*/, ChatWindow::AckStat 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" + boost::lexical_cast<std::string>(idGenerator_++); + const std::string ftId = "ft" + std::to_string(idGenerator_++); const std::string sizeString = formatSize(sizeInBytes); FileTransfer* transfer; @@ -327,7 +327,7 @@ 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, false, action->isImpromptu_, action->isContinuation_)); + eventStream_->send(std::make_shared<JoinMUCUIEvent>(action->jid_.toString(), action->password_, boost::optional<std::string>(), false, action->isImpromptu_, action->isContinuation_)); delete action->parent_; } } diff --git a/Swift/QtUI/QtSingleWindow.cpp b/Swift/QtUI/QtSingleWindow.cpp index 1fba497..af7e552 100644 --- a/Swift/QtUI/QtSingleWindow.cpp +++ b/Swift/QtUI/QtSingleWindow.cpp @@ -1,13 +1,21 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * 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 <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 { @@ -16,31 +24,49 @@ 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); + 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))); + 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); } -void QtSingleWindow::handleSplitterMoved(int, int) { +void QtSingleWindow::handleSplitterMoved() { QList<QVariant> variantValues; QList<int> intValues = sizes(); for (const auto& value : intValues) { @@ -50,17 +76,18 @@ void QtSingleWindow::handleSplitterMoved(int, int) { } void QtSingleWindow::restoreSplitters() { - QList<QVariant> variantValues = settings_->getQSettings()->value(SINGLE_WINDOW_SPLITS).toList(); - QList<int> intValues; - for (auto&& value : variantValues) { - intValues.append(value.toInt()); + 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(); } - setSizes(intValues); -} - -void QtSingleWindow::insertAtFront(QWidget* widget) { - insertWidget(0, widget); - restoreSplitters(); } void QtSingleWindow::handleGeometryChanged() { @@ -76,4 +103,24 @@ void QtSingleWindow::moveEvent(QMoveEvent*) { 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::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 804be65..a707cd3 100644 --- a/Swift/QtUI/QtSingleWindow.h +++ b/Swift/QtUI/QtSingleWindow.h @@ -1,29 +1,41 @@ /* - * Copyright (c) 2010-2012 Isode Limited. + * 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 QtChatTabs; + class QtLoginWindow; class QtSettingsProvider; + class QtServerListView; + class ServerListModel; class QtSingleWindow : public QSplitter { Q_OBJECT public: QtSingleWindow(QtSettingsProvider* settings); virtual ~QtSingleWindow(); - void insertAtFront(QWidget* widget); - void addWidget(QWidget* widget); + void addAccount(QtLoginWindow* widget, QtChatTabs* tabs); + + signals: + void wantsToAddAccount(); + protected: void resizeEvent(QResizeEvent*); void moveEvent(QMoveEvent*); private slots: - void handleSplitterMoved(int, int); + void handleSplitterMoved(); void handleTabsTitleChanged(const QString& title); + void handleListItemClicked(const QModelIndex&); private: void handleGeometryChanged(); void restoreSplitters(); @@ -31,6 +43,11 @@ namespace Swift { private: QtSettingsProvider* settings_; + SwiftAccountData accountData_; + QtServerListView* serverList_; + ServerListModel* serverListModel_; + QStackedWidget* loginWindows_; + QStackedWidget* tabs_; }; } diff --git a/Swift/QtUI/QtStatusWidget.cpp b/Swift/QtUI/QtStatusWidget.cpp index b175e5c..5e2ba5f 100644 --- a/Swift/QtUI/QtStatusWidget.cpp +++ b/Swift/QtUI/QtStatusWidget.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -8,9 +8,6 @@ #include <algorithm> -#include <boost/lambda/bind.hpp> -#include <boost/lambda/lambda.hpp> - #include <QApplication> #include <QBoxLayout> #include <QComboBox> @@ -32,8 +29,6 @@ #include <Swift/QtUI/QtLineEdit.h> #include <Swift/QtUI/QtSwiftUtil.h> -namespace lambda = boost::lambda; - namespace Swift { QtStatusWidget::QtStatusWidget(StatusCache* statusCache, QWidget *parent) : QWidget(parent), statusCache_(statusCache), editCursor_(Qt::IBeamCursor), viewCursor_(Qt::PointingHandCursor) { @@ -153,8 +148,9 @@ void QtStatusWidget::generateList() { } 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(), - savedStatus.second == lambda::_1 && savedStatus.first == lambda::bind(&statusShowTypeToFriendlyName, lambda::_1)) != allTypes_.end()) { + 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_); diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 7daa5ce..8de5d70 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -22,8 +22,10 @@ #include <Swiften/Base/Path.h> #include <Swiften/Base/Paths.h> #include <Swiften/Base/Platform.h> +#include <Swiften/Base/String.h> #include <Swiften/Client/Client.h> #include <Swiften/Elements/Presence.h> +#include <Swiften/StringCodecs/Base64.h> #include <Swiften/TLS/TLSContextFactory.h> #include <SwifTools/Application/PlatformApplicationPathProvider.h> @@ -33,7 +35,7 @@ #include <Swift/Controllers/ApplicationInfo.h> #include <Swift/Controllers/BuildVersion.h> -#include <Swift/Controllers/MainController.h> +#include <Swift/Controllers/AccountController.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Settings/SettingsProviderHierachy.h> #include <Swift/Controllers/Settings/XMLSettingsProvider.h> @@ -42,8 +44,6 @@ #include <Swift/Controllers/Storages/FileStoragesFactory.h> #include <Swift/QtUI/QtChatTabs.h> -#include <Swift/QtUI/QtChatTabsBase.h> -#include <Swift/QtUI/QtChatTabsShortcutOnlySubstitute.h> #include <Swift/QtUI/QtChatWindowFactory.h> #include <Swift/QtUI/QtLoginWindow.h> #include <Swift/QtUI/QtSingleWindow.h> @@ -92,16 +92,15 @@ po::options_description QtSwift::getOptionsDescription() { ("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") - ("enable-jid-adhocs", "Enable AdHoc commands to custom JID's.") + ("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") #endif ("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; } @@ -162,32 +161,28 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa qtSettings_ = new QtSettingsProvider(); xmlSettings_ = loadSettingsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "system-settings.xml"))); - settingsHierachy_ = new SettingsProviderHierachy(); - settingsHierachy_->addProviderToTopOfStack(xmlSettings_); - settingsHierachy_->addProviderToTopOfStack(qtSettings_); + settingsHierarchy_ = new SettingsProviderHierachy(); + settingsHierarchy_->addProviderToTopOfStack(xmlSettings_); + settingsHierarchy_->addProviderToTopOfStack(qtSettings_); - networkFactories_.getTLSContextFactory()->setDisconnectOnCardRemoval(settingsHierachy_->getSetting(SettingConstants::DISCONNECT_ON_CARD_REMOVAL)); + networkFactories_.getTLSContextFactory()->setDisconnectOnCardRemoval(settingsHierarchy_->getSetting(SettingConstants::DISCONNECT_ON_CARD_REMOVAL)); - std::map<std::string, std::string> emoticons; - loadEmoticonsFile(":/emoticons/emoticons.txt", emoticons); - loadEmoticonsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "emoticons.txt")), 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_ = nullptr; + splitter_ = new QtSingleWindow(qtSettings_); + connect(splitter_, SIGNAL(wantsToAddAccount()), this, SLOT(handleWantsToAddAccount())); + + if (options.count("debug")) { + Log::setLogLevel(Swift::Log::debug); } - 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("enable-future")) { + settingsHierarchy_->storeSetting(SettingConstants::FUTURE, true); } - if (options.count("debug")) { - Log::setLogLevel(Swift::Log::debug); + if (options.count("disable-future")) { + settingsHierarchy_->storeSetting(SettingConstants::FUTURE, false); } if (options.count("logfile")) { @@ -199,6 +194,8 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa SWIFT_LOG(error) << "Error while retrieving the specified log file name from the command line" << std::endl; } } + //TODO this old option can be purged + useDelayForLatency_ = options.count("latency-debug") > 0; // Load fonts std::vector<std::string> fontNames = { @@ -228,19 +225,19 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa SWIFT_LOG_ASSERT(error != -1, error) << "Failed to load font " << fontPath << std::endl; } - bool enableAdHocCommandOnJID = options.count("enable-jid-adhocs") > 0; - tabs_ = nullptr; - if (options.count("no-tabs") && !splitter_) { - tabs_ = new QtChatTabsShortcutOnlySubstitute(); - } - else { - tabs_ = new QtChatTabs(splitter_ != nullptr, settingsHierachy_, true); - } - bool startMinimized = options.count("start-minimized") > 0; +#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 << std::endl; + 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()); - chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, ":/themes/Default/", emoticons); soundPlayer_ = new QtSoundPlayer(applicationPathProvider_); // Ugly, because the dock depends on the tray, but the temporary @@ -276,50 +273,24 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa statusCache_ = new StatusCache(applicationPathProvider_); - if (splitter_) { - splitter_->show(); - } + splitter_->show(); PlatformAutoUpdaterFactory autoUpdaterFactory; - if (autoUpdaterFactory.isSupported() && settingsHierachy_->getSetting(QtUISettingConstants::ENABLE_SOFTWARE_UPDATES) - && !settingsHierachy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL).empty()) { - autoUpdater_ = autoUpdaterFactory.createAutoUpdater(updateChannelToFeed(settingsHierachy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL))); + 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)); - settingsHierachy_->onSettingChanged.connect([&](const std::string& path) { + settingsHierarchy_->onSettingChanged.connect([&](const std::string& path) { if (path == QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL.getKey()) { - autoUpdater_->setAppcastFeed(updateChannelToFeed(settingsHierachy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL))); + autoUpdater_->setAppcastFeed(updateChannelToFeed(settingsHierarchy_->getSetting(QtUISettingConstants::SOFTWARE_UPDATE_CHANNEL))); autoUpdater_->checkForUpdates(); } }); } - - 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_, autoUpdater_, startMinimized, !emoticons.empty(), enableAdHocCommandOnJID); - 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); - } - + migrateLastLoginAccount(); + restoreAccounts(); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(handleAboutToQuit())); } @@ -328,17 +299,15 @@ QtSwift::~QtSwift() { for (auto* factory : uiFactories_) { delete factory; } - for (auto* controller : mainControllers_) { + for (auto* controller : accountControllers_) { delete controller; } delete notifier_; for (auto* tray : systemTrays_) { delete tray; } - delete tabs_; - delete chatWindowFactory_; delete splitter_; - delete settingsHierachy_; + delete settingsHierarchy_; delete qtSettings_; delete xmlSettings_; delete statusCache_; @@ -375,4 +344,139 @@ void QtSwift::handleAutoUpdaterStateChanged(AutoUpdater::State updatedState) { } } +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 a7dc3cf..811b6e4 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -1,17 +1,19 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once +#include <map> #include <string> #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> #include <Swiften/Base/Platform.h> +#include <Swiften/Client/ClientOptions.h> #include <Swiften/EventLoop/Qt/QtEventLoop.h> #include <Swiften/Network/BoostNetworkFactories.h> #include <Swiften/TLS/PlatformTLSFactories.h> @@ -35,27 +37,28 @@ namespace po = boost::program_options; class QSplitter; namespace Swift { - class QtUIFactory; - class CertificateStorageFactory; - class Dock; - class Notifier; - class StoragesFactory; class ApplicationPathProvider; class AvatarStorage; class CapsStorage; - class MainController; - class QtSystemTray; - class QtChatTabsBase; + class CertificateStorageFactory; + class Dock; + class EventLoop; + class AccountController; + class Notifier; + class QtChatTabs; class QtChatWindowFactory; - class QtSoundPlayer; + class QtLoginWindow; class QtMUCSearchWindowFactory; + class QtSingleWindow; + class QtSoundPlayer; + class QtSystemTray; + class QtUIFactory; class QtUserSearchWindowFactory; - class EventLoop; - class URIHandler; class SettingsProviderHierachy; - class XMLSettingsProvider; class StatusCache; - class QtSingleWindow; + class StoragesFactory; + class URIHandler; + class XMLSettingsProvider; class QtSwift : public QObject { Q_OBJECT @@ -67,28 +70,35 @@ namespace Swift { 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<MainController*> mainControllers_; + std::vector<AccountController*> accountControllers_; std::vector<QtSystemTray*> systemTrays_; std::vector<QtUIFactory*> uiFactories_; QtSettingsProvider* qtSettings_; XMLSettingsProvider* xmlSettings_; - SettingsProviderHierachy* settingsHierachy_; + SettingsProviderHierachy* settingsHierarchy_; QtSingleWindow* splitter_; QtSoundPlayer* soundPlayer_; Dock* dock_; URIHandler* uriHandler_; - QtChatTabsBase* tabs_; ApplicationPathProvider* applicationPathProvider_; StoragesFactory* storagesFactory_; CertificateStorageFactory* certificateStorageFactory_; @@ -97,6 +107,9 @@ namespace Swift { 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_; diff --git a/Swift/QtUI/QtTabbable.h b/Swift/QtUI/QtTabbable.h index 5837702..63c60f4 100644 --- a/Swift/QtUI/QtTabbable.h +++ b/Swift/QtUI/QtTabbable.h @@ -19,7 +19,7 @@ namespace Swift { bool isWidgetSelected(); virtual AlertType getWidgetAlertState() {return NoActivity;} - virtual int getCount() {return 0;} + virtual size_t getCount() {return 0;} virtual std::string getID() const = 0; virtual void setEmphasiseFocus(bool /*emphasise*/) {} diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 583c477..93fca5f 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -19,10 +19,10 @@ #include <Swift/QtUI/QtAdHocCommandWindow.h> #include <Swift/QtUI/QtBlockListEditorWindow.h> #include <Swift/QtUI/QtChatTabs.h> -#include <Swift/QtUI/QtChatTabsBase.h> #include <Swift/QtUI/QtChatWindow.h> #include <Swift/QtUI/QtChatWindowFactory.h> #include <Swift/QtUI/QtContactEditWindow.h> +#include <Swift/QtUI/QtFdpFormSubmitWindow.h> #include <Swift/QtUI/QtFileTransferListWidget.h> #include <Swift/QtUI/QtHighlightNotificationConfigDialog.h> #include <Swift/QtUI/QtHistoryWindow.h> @@ -41,30 +41,32 @@ namespace Swift { -QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabsBase* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, AutoUpdater* autoUpdater, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID) : settings(settings), qtOnlySettings(qtOnlySettings), tabsBase(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(nullptr), loginWindow(nullptr), statusCache(statusCache), autoUpdater(autoUpdater), startMinimized(startMinimized), emoticonsExist_(emoticonsExist), enableAdHocCommandOnJID_(enableAdHocCommandOnJID) { - chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE); - historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE); - this->tabs = dynamic_cast<QtChatTabs*>(tabsBase); +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() << std::endl; - for (auto chat : chatWindows) { + SWIFT_LOG(debug) << "Entering QtUIFactory destructor. chatWindows size:" << chatWindows_.size() << std::endl; + for (auto chat : chatWindows_) { SWIFT_LOG_ASSERT(chat.isNull(), debug) << "QtUIFactory has active chat windows and has not been reset properly" << std::endl; } + delete chatWindowFactory_; } XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { QtXMLConsoleWidget* widget = new QtXMLConsoleWidget(); - tabsBase->addTab(widget); + tabs_->addTab(widget); showTabs(); widget->show(); return widget; } HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) { - QtHistoryWindow* window = new QtHistoryWindow(settings, uiEventStream); - tabsBase->addTab(window); + QtHistoryWindow* window = new QtHistoryWindow(settings_, uiEventStream); + tabs_->addTab(window); showTabs(); connect(window, SIGNAL(fontResized(int)), this, SLOT(handleHistoryWindowFontResized(int))); @@ -75,53 +77,39 @@ HistoryWindow* QtUIFactory::createHistoryWindow(UIEventStream* uiEventStream) { void QtUIFactory::handleHistoryWindowFontResized(int size) { historyFontSize_ = size; - settings->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size); + settings_->storeSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE, size); } FileTransferListWidget* QtUIFactory::createFileTransferListWidget() { QtFileTransferListWidget* widget = new QtFileTransferListWidget(); - tabsBase->addTab(widget); + tabs_->addTab(widget); showTabs(); widget->show(); return widget; } -MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) { - lastMainWindow = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_, enableAdHocCommandOnJID_); - if (tabs) { - tabs->setViewMenu(lastMainWindow->getMenus()[0]); - } - 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_, autoUpdater); - 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()); + if (loginWindow_) { + return loginWindow_; } - connect(loginWindow, SIGNAL(geometryChanged()), this, SLOT(handleLoginWindowGeometryChanged())); - if (startMinimized) loginWindow->hide(); -#endif - return loginWindow; -} - -void QtUIFactory::handleLoginWindowGeometryChanged() { - qtOnlySettings->getQSettings()->setValue("loginWindowGeometry", loginWindow->saveGeometry()); + 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() { @@ -129,27 +117,27 @@ MUCSearchWindow* QtUIFactory::createMUCSearchWindow() { } ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eventStream) { - QtChatWindow* window = dynamic_cast<QtChatWindow*>(chatWindowFactory->createChatWindow(contact, eventStream)); + 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(), + chatWindows_.erase(std::remove_if(chatWindows_.begin(), chatWindows_.end(), [](QPointer<QtChatWindow>& window) { return window.isNull(); - }), chatWindows.end()); + }), chatWindows_.end()); - chatWindows.push_back(window); + chatWindows_.push_back(window); connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int))); - window->handleFontResized(chatFontSize); + 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) { + for (auto&& existingWindow : chatWindows_) { if (!existingWindow.isNull()) { existingWindow->handleFontResized(size); } @@ -157,7 +145,7 @@ void QtUIFactory::handleChatWindowFontResized(int size) { } UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) { - return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings); + return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings_); } JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) { @@ -177,7 +165,7 @@ WhiteboardWindow* QtUIFactory::createWhiteboardWindow(std::shared_ptr<Whiteboard } HighlightEditorWindow* QtUIFactory::createHighlightEditorWindow() { - return new QtHighlightNotificationConfigDialog(qtOnlySettings); + return new QtHighlightNotificationConfigDialog(qtOnlySettings_); } BlockListEditorWidget *QtUIFactory::createBlockListEditorWidget() { @@ -188,11 +176,13 @@ AdHocCommandWindow* QtUIFactory::createAdHocCommandWindow(std::shared_ptr<Outgoi return new QtAdHocCommandWindow(command); } +std::unique_ptr<FdpFormSubmitWindow> QtUIFactory::createFdpFormSubmitWindow() { + return std::make_unique<QtFdpFormSubmitWindow>(); +} + void QtUIFactory::showTabs() { - if (tabs) { - if (!tabs->isVisible()) { - tabs->show(); - } + if (!tabs_->isVisible()) { + tabs_->show(); } } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index 9989101..04836fe 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -17,8 +17,9 @@ class QSplitter; namespace Swift { class AutoUpdater; + class Chattables; + class FdpFormSubmitWindow; class QtChatTabs; - class QtChatTabsBase; class QtChatTheme; class QtChatWindow; class QtChatWindowFactory; @@ -35,11 +36,11 @@ namespace Swift { class QtUIFactory : public QObject, public UIFactory { Q_OBJECT public: - QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabsBase* tabs, QtSingleWindow* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, AutoUpdater* autoUpdater, bool startMinimized, bool emoticonsExist, bool enableAdHocCommandOnJID); + 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(UIEventStream* eventStream); + virtual MainWindow* createMainWindow(Chattables& chattables, UIEventStream* eventStream); virtual LoginWindow* createLoginWindow(UIEventStream* eventStream); virtual EventWindow* createEventWindow(); virtual ChatListWindow* createChatListWindow(UIEventStream*); @@ -54,9 +55,9 @@ namespace Swift { virtual HighlightEditorWindow* createHighlightEditorWindow(); virtual BlockListEditorWidget* createBlockListEditorWidget(); virtual AdHocCommandWindow* createAdHocCommandWindow(std::shared_ptr<OutgoingAdHocCommandSession> command); + virtual std::unique_ptr<FdpFormSubmitWindow> createFdpFormSubmitWindow(); private slots: - void handleLoginWindowGeometryChanged(); void handleChatWindowFontResized(int); void handleHistoryWindowFontResized(int); @@ -64,23 +65,22 @@ namespace Swift { void showTabs(); private: - SettingsProviderHierachy* settings; - QtSettingsProvider* qtOnlySettings; - QtChatTabsBase* tabsBase; - QtChatTabs* tabs; - QtSingleWindow* netbookSplitter; - QtSystemTray* systemTray; - QtChatWindowFactory* chatWindowFactory; + 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; - bool startMinimized; - int chatFontSize; + 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/QtVCardWidget/QtVCardFieldInfo.h b/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h index 093357a..fa42c49 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h +++ b/Swift/QtUI/QtVCardWidget/QtVCardFieldInfo.h @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2016 Isode Limited. + * Copyright (c) 2016-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -36,7 +36,7 @@ } \ \ virtual bool testInstance(QWidget* widget) const { \ - return dynamic_cast<FIELD_CLASS*>(widget) != 0; \ + return dynamic_cast<FIELD_CLASS*>(widget) != nullptr; \ } \ }; diff --git a/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp index 290feca..1cd5505 100644 --- a/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp +++ b/Swift/QtUI/QtVCardWidget/QtVCardWidget.cpp @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2014-2016 Isode Limited. + * Copyright (c) 2014-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -358,7 +358,7 @@ int QtVCardWidget::fieldTypeInstances(std::shared_ptr<QtVCardFieldInfo> fieldTyp return instances; } -void layoutDeleteChildren(QLayout *layout) { +static void layoutDeleteChildren(QLayout *layout) { while(layout->count() > 0) { QLayoutItem* child; if ((child = layout->takeAt(0)) != nullptr) { diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp index ea9a9c6..bca9e2e 100644 --- a/Swift/QtUI/QtWebKitChatView.cpp +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -11,9 +11,9 @@ #include <QDesktopWidget> #include <QEventLoop> #include <QFile> +#include <QFileDevice> #include <QFileDialog> #include <QFileInfo> -#include <QFileDevice> #include <QInputDialog> #include <QKeyEvent> #include <QMessageBox> @@ -28,6 +28,8 @@ #include <Swiften/Base/Log.h> #include <Swiften/StringCodecs/Base64.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> @@ -51,12 +53,15 @@ const QString QtWebKitChatView::ButtonFileTransferSendRequest = QString("filetra 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) { +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); @@ -555,10 +560,13 @@ std::string QtWebKitChatView::addMessage( QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); + 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(label->getDisplayMarking()))); + htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(messageMarkingValue))); } QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; @@ -569,15 +577,16 @@ std::string QtWebKitChatView::addMessage( QString highlightSpanEnd = highlightWholeMessage ? "</span>" : ""; htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ; - bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf); + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf, label); QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.svg" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); - std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + 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; } @@ -600,6 +609,10 @@ QString QtWebKitChatView::buildChatWindowButton(const QString& name, const QStri 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 @@ -645,7 +658,7 @@ std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, con 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" + boost::lexical_cast<std::string>(idCounter_++); + 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; @@ -679,7 +692,7 @@ std::string QtWebKitChatView::addWhiteboardRequest(const QString& contact, bool "</div>"; } QString qAvatarPath = "qrc:/icons/avatar.svg"; - std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++); + 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; @@ -798,9 +811,23 @@ void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgume QString elementID = arg3; QString isImpromptu = arg4; QString isContinuation = arg5; - eventStream_->send(std::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true"))); + 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) << " )" << std::endl; } @@ -822,7 +849,7 @@ void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessa } QString errorMessageHTML(chatMessageToHTML(errorMessage)); - std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + 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))); previousMessageWasSelf_ = false; @@ -835,7 +862,7 @@ std::string QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& me } QString messageHTML = chatMessageToHTML(message); - std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + std::string id = "id" + std::to_string(idCounter_++); addMessageBottom(std::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, P2QSTRING(id), getActualDirection(message, direction))); previousMessageKind_ = PreviousMessageWasSystem; @@ -901,7 +928,7 @@ void QtWebKitChatView::addPresenceMessage(const ChatWindow::ChatMessage& message } QString messageHTML = chatMessageToHTML(message); - std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + 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; @@ -958,7 +985,9 @@ void QtWebKitChatView::setAckState(std::string const& id, ChatWindow::AckState s 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; + 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); } @@ -978,12 +1007,19 @@ void QtWebKitChatView::setMessageReceiptState(const std::string& id, ChatWindow: setReceiptXML(P2QSTRING(id), xml); } -bool QtWebKitChatView::appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) { +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; } diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h index 802c216..f0b4459 100644 --- a/Swift/QtUI/QtWebKitChatView.h +++ b/Swift/QtUI/QtWebKitChatView.h @@ -13,8 +13,6 @@ #include <QWebElement> #include <QWidget> -#include <Swiften/Base/Override.h> - #include <Swift/Controllers/UIInterfaces/ChatWindow.h> #include <Swift/QtUI/ChatSnippet.h> @@ -30,6 +28,7 @@ namespace Swift { class QtChatWindowJSBridge; class UIEventStream; class QtChatWindow; + class SettingsProvider; class QtWebKitChatView : public QtChatView { Q_OBJECT @@ -43,43 +42,45 @@ namespace Swift { 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, bool disableAutoScroll = false); - ~QtWebKitChatView(); + 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) SWIFTEN_OVERRIDE; + 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) SWIFTEN_OVERRIDE; - - virtual std::string 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) SWIFTEN_OVERRIDE; - virtual void replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& id, ChatWindow::TimestampBehaviour timestampBehaviour) SWIFTEN_OVERRIDE; - virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time) SWIFTEN_OVERRIDE; - virtual void replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour timestampBehaviour) SWIFTEN_OVERRIDE; - virtual void setAckState(const std::string& id, ChatWindow::AckState state) SWIFTEN_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) SWIFTEN_OVERRIDE; - virtual void setFileTransferProgress(std::string, const int percentageDone) SWIFTEN_OVERRIDE; - virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") SWIFTEN_OVERRIDE; - virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) SWIFTEN_OVERRIDE; - virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) SWIFTEN_OVERRIDE; - virtual void setWhiteboardSessionStatus(const std::string& id, const ChatWindow::WhiteboardSessionState state) SWIFTEN_OVERRIDE; - virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) SWIFTEN_OVERRIDE; - - virtual void showEmoticons(bool show) SWIFTEN_OVERRIDE; + 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() SWIFTEN_OVERRIDE; + virtual void addLastSeenLine() override; private: // previously public, now private void replaceLastMessage(const QString& newMessage, const ChatWindow::TimestampBehaviour timestampBehaviour); @@ -116,9 +117,9 @@ namespace Swift { void resetTopInsertPoint(); void increaseFontSize(int numSteps = 1); void decreaseFontSize(); - virtual void resizeFont(int fontSizeSteps) SWIFTEN_OVERRIDE; - virtual void scrollToBottom() SWIFTEN_OVERRIDE; - virtual void handleKeyPressEvent(QKeyEvent* event) SWIFTEN_OVERRIDE; + virtual void resizeFont(int fontSizeSteps) override; + virtual void scrollToBottom() override; + virtual void handleKeyPressEvent(QKeyEvent* event) override; private slots: void handleViewLoadFinished(bool); @@ -152,15 +153,16 @@ namespace Swift { const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight); - bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf); + 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) SWIFTEN_OVERRIDE; + void resizeEvent(QResizeEvent* event) override; private: void headerEncode(); @@ -191,5 +193,7 @@ namespace Swift { 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 967be1a..24636ed 100644 --- a/Swift/QtUI/QtWebView.cpp +++ b/Swift/QtUI/QtWebView.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Isode Limited. + * Copyright (c) 2010-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -7,8 +7,6 @@ #include <Swift/QtUI/QtWebView.h> -#include <boost/numeric/conversion/cast.hpp> - #include <QFocusEvent> #include <QKeyEvent> #include <QKeySequence> @@ -48,7 +46,7 @@ void QtWebView::keyPressEvent(QKeyEvent* event) { modifiers, event->text(), event->isAutoRepeat(), - boost::numeric_cast<unsigned short>(event->count())); + event->count()); QWebView::keyPressEvent(translatedEvent); delete translatedEvent; } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 54f0450..a2ad9b1 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -7,6 +7,11 @@ def generateQRCTheme(dir, prefix) : 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>" % { @@ -22,9 +27,9 @@ Import("env") myenv = env.Clone() # 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"]) @@ -110,131 +115,140 @@ myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateQRCTheme(myenv.Dir("#/Swi 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", - "QtPlainChatView.cpp", - "QtChatTheme.cpp", - "QtChatTabs.cpp", - "QtChatTabsBase.cpp", - "QtChatTabsShortcutOnlySubstitute.cpp", - "QtSoundPlayer.cpp", "QtSystemTray.cpp", - "QtCachedImageScaler.cpp", "QtTabbable.cpp", "QtTabWidget.cpp", "QtTextEdit.cpp", - "QtXMLConsoleWidget.cpp", - "QtHistoryWindow.cpp", - "QtFileTransferListWidget.cpp", - "QtFileTransferListItemModel.cpp", - "QtAdHocCommandWindow.cpp", - "QtAdHocCommandWithJIDWindow.cpp", + "QtUIFactory.cpp", + "QtUISettingConstants.cpp", + "QtUpdateFeedSelectionDialog.cpp", + "QtURIHandler.cpp", + "QtURLValidator.cpp", "QtUtilities.cpp", - "QtBookmarkDetailWindow.cpp", - "QtAddBookmarkWindow.cpp", - "QtEditBookmarkWindow.cpp", - "QtEmojisGrid.cpp", - "QtEmojiCell.cpp", - "QtEmojisScroll.cpp", - "QtEmojisSelector.cpp", - "QtRecentEmojisGrid.cpp", - "QtEmoticonsGrid.cpp", - "QtContactEditWindow.cpp", - "QtContactEditWidget.cpp", - "QtSingleWindow.cpp", - "QtColorToolButton.cpp", - "QtClosableLineEdit.cpp", - "QtHighlightNotificationConfigDialog.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/RosterDelegate.cpp", - "Roster/GroupItemDelegate.cpp", + "QtWebKitChatView.cpp", + "QtWebView.cpp", + "QtXMLConsoleWidget.cpp", "Roster/DelegateCommons.cpp", + "Roster/GroupItemDelegate.cpp", "Roster/QtFilterWidget.cpp", - "Roster/QtRosterWidget.cpp", "Roster/QtOccupantListWidget.cpp", + "Roster/QtRosterWidget.cpp", + "Roster/QtTreeWidget.cpp", + "Roster/RosterDelegate.cpp", + "Roster/RosterModel.cpp", "Roster/RosterTooltip.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/MUCSearchDelegate.cpp", - "MUCSearch/MUCSearchEmptyItem.cpp", - "MUCSearch/MUCSearchModel.cpp", - "MUCSearch/MUCSearchRoomItem.cpp", - "MUCSearch/MUCSearchServiceItem.cpp", - "MUCSearch/QtLeafSortFilterProxyModel.cpp", - "MUCSearch/QtMUCSearchWindow.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", + "UserSearch/UserSearchModel.cpp", + "Whiteboard/ColorWidget.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", - "QtResourceHelper.cpp", - "QtSpellCheckHighlighter.cpp", - "QtUpdateFeedSelectionDialog.cpp", - "Trellis/QtDynamicGridLayout.cpp", - "Trellis/QtDNDTabBar.cpp", - "Trellis/QtGridSelectionDialog.cpp", - "QtCheckBoxStyledItemDelegate.cpp", - "QtColorSelectionStyledItemDelegate.cpp", - "QtSoundSelectionStyledItemDelegate.cpp" + "Whiteboard/TextDialog.cpp" ] if env["PLATFORM"] == "win32" : @@ -478,9 +492,16 @@ if env["PLATFORM"] == "win32" : 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"], - 'Version': str(myenv["SWIFT_VERSION_MAJOR"]) + "." + str(myenv["SWIFT_VERSION_MINOR"]) + "." + str(myenv["SWIFT_VERSION_PATCH"]) + '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: @@ -496,16 +517,16 @@ if env["PLATFORM"] == "win32" : def signToolAction(target = None, source = None, env = None): signresult = 0 for x in range (1, 4) : - print "Attemping to sign the packages [%s]" % x + 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" + print("Error: The build has failed to sign the installer package") Exit(1) if signresult == 2 : - print "Signing was completed with warnings." + print("Signing was completed with warnings.") myenv.AddPostAction(lightTask, signToolAction) 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/Trellis/QtDynamicGridLayout.cpp b/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp index 2509b3f..2402529 100644 --- a/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp +++ b/Swift/QtUI/Trellis/QtDynamicGridLayout.cpp @@ -23,7 +23,7 @@ namespace Swift { -QtDynamicGridLayout::QtDynamicGridLayout(QWidget* parent, bool enableDND) : QWidget(parent), dndEnabled_(enableDND), movingTab_(nullptr) { +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)); @@ -49,6 +49,9 @@ int QtDynamicGridLayout::addTab(QtTabbable* tab, const QString& title) { 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; } @@ -328,6 +331,24 @@ void QtDynamicGridLayout::setDimensions(const QSize& dim) { 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() { diff --git a/Swift/QtUI/Trellis/QtDynamicGridLayout.h b/Swift/QtUI/Trellis/QtDynamicGridLayout.h index 682ae41..f3a2e96 100644 --- a/Swift/QtUI/Trellis/QtDynamicGridLayout.h +++ b/Swift/QtUI/Trellis/QtDynamicGridLayout.h @@ -20,7 +20,7 @@ namespace Swift { class QtDynamicGridLayout : public QWidget { Q_OBJECT public: - explicit QtDynamicGridLayout(QWidget* parent = nullptr, bool enableDND = false); + explicit QtDynamicGridLayout(bool future, QWidget* parent = nullptr, bool enableDND = false); virtual ~QtDynamicGridLayout(); QSize getDimension() const; @@ -71,6 +71,7 @@ namespace Swift { void moveTab(QtTabWidget* tabWidget, int oldIndex, int newIndex); QtTabWidget* createDNDTabWidget(QWidget* parent); void updateEmphasiseFocusOnTabs(); + void showHideFirstTabs(); private: QGridLayout *gridLayout_; @@ -78,5 +79,6 @@ namespace Swift { 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 index 0533edf..e922e07 100644 --- a/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp +++ b/Swift/QtUI/Trellis/QtGridSelectionDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2016 Isode Limited. + * Copyright (c) 2014-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -76,7 +76,7 @@ int QtGridSelectionDialog::getDescriptionTextHeight() const { 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, 0); + auto descriptionBB = fontMetrics.boundingRect(QRect(0, 0, width - 2 * horizontalMargin, 1000), Qt::TextWordWrap, descriptionText, 0, nullptr); return (descriptionBB.height() + descriptionBB.y()); } @@ -136,7 +136,7 @@ void QtGridSelectionDialog::paintEvent(QPaintEvent*) { // 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, 0); + 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); diff --git a/Swift/QtUI/UserSearch/ContactListModel.cpp b/Swift/QtUI/UserSearch/ContactListModel.cpp index 6ef85d7..5d8aa6c 100644 --- a/Swift/QtUI/UserSearch/ContactListModel.cpp +++ b/Swift/QtUI/UserSearch/ContactListModel.cpp @@ -5,7 +5,7 @@ */ /* - * Copyright (c) 2014-2016 Isode Limited. + * Copyright (c) 2014-2018 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -22,32 +22,6 @@ 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) { } diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h index fe536ab..f67712e 100644 --- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h +++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h @@ -11,8 +11,6 @@ #include <QAbstractItemModel> #include <QWizard> -#include <Swiften/Base/Override.h> - #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> #include <Swift/QtUI/UserSearch/ui_QtUserSearchWizard.h> @@ -34,34 +32,34 @@ namespace Swift { Q_OBJECT public: QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider); - virtual ~QtUserSearchWindow(); + virtual ~QtUserSearchWindow() override; - virtual void addSavedServices(const std::vector<JID>& services) SWIFTEN_OVERRIDE; + virtual void addSavedServices(const std::vector<JID>& services) override; - virtual void clear() SWIFTEN_OVERRIDE; - virtual void show() SWIFTEN_OVERRIDE; - virtual void setResults(const std::vector<UserSearchResult>& results) SWIFTEN_OVERRIDE; - virtual void setResultsForm(Form::ref results) SWIFTEN_OVERRIDE; - virtual void setSelectedService(const JID& jid) SWIFTEN_OVERRIDE; - virtual void setServerSupportsSearch(bool error) SWIFTEN_OVERRIDE; - virtual void setSearchError(bool error) SWIFTEN_OVERRIDE; - virtual void setSearchFields(std::shared_ptr<SearchPayload> fields) SWIFTEN_OVERRIDE; - virtual void setNameSuggestions(const std::vector<std::string>& suggestions) SWIFTEN_OVERRIDE; - virtual void prepopulateJIDAndName(const JID& jid, const std::string& name) SWIFTEN_OVERRIDE; - virtual void setContactSuggestions(const std::vector<Contact::ref>& suggestions) SWIFTEN_OVERRIDE; - virtual void setJIDs(const std::vector<JID> &jids) SWIFTEN_OVERRIDE; - virtual void setOriginator(const JID& originator) SWIFTEN_OVERRIDE; - virtual void setRoomJID(const JID &roomJID) SWIFTEN_OVERRIDE; - virtual std::string getReason() const SWIFTEN_OVERRIDE; - virtual std::vector<JID> getJIDs() const SWIFTEN_OVERRIDE; - virtual void setCanStartImpromptuChats(bool supportsImpromptu) SWIFTEN_OVERRIDE; - virtual void updateContacts(const std::vector<Contact::ref> &contacts) SWIFTEN_OVERRIDE; - virtual void addContacts(const std::vector<Contact::ref>& contacts) SWIFTEN_OVERRIDE; - virtual void setCanSupplyDescription(bool allowed) SWIFTEN_OVERRIDE; - virtual void setWarning(const boost::optional<std::string>& message) SWIFTEN_OVERRIDE; + 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 SWIFTEN_OVERRIDE; + virtual int nextId() const override; private slots: void handleFirstPageRadioChange(); |