From 3097037c3dc882be2efe64dce6aefeea43d5f040 Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Mon, 30 Apr 2018 21:41:47 +0300 Subject: [PATCH] Add prototype room settings menu --- CMakeLists.txt | 2 + include/Cache.h | 8 ++ include/ChatPage.h | 1 + include/MainWindow.h | 6 ++ include/TopRoomBar.h | 4 +- include/dialogs/RoomSettings.hpp | 88 ++++++++++++++++++ include/ui/Painter.h | 2 +- resources/styles/nheko-dark.qss | 5 ++ resources/styles/nheko.qss | 5 ++ src/Cache.cc | 22 +++-- src/CommunitiesListItem.cc | 3 +- src/MainWindow.cc | 22 +++++ src/TopRoomBar.cc | 6 ++ src/dialogs/RoomSettings.cpp | 147 +++++++++++++++++++++++++++++++ 14 files changed, 309 insertions(+), 12 deletions(-) create mode 100644 include/dialogs/RoomSettings.hpp create mode 100644 src/dialogs/RoomSettings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 864f42a1..e1d835f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,7 @@ set(SRC_FILES src/dialogs/Logout.cc src/dialogs/ReadReceipts.cc src/dialogs/ReCaptcha.cpp + src/dialogs/RoomSettings.cpp # Emoji src/emoji/Category.cc @@ -222,6 +223,7 @@ qt5_wrap_cpp(MOC_HEADERS include/dialogs/Logout.h include/dialogs/ReadReceipts.h include/dialogs/ReCaptcha.hpp + include/dialogs/RoomSettings.hpp # Emoji include/emoji/Category.h diff --git a/include/Cache.h b/include/Cache.h index a92f6bc5..db5dba00 100644 --- a/include/Cache.h +++ b/include/Cache.h @@ -64,6 +64,8 @@ struct RoomInfo std::string avatar_url; //! Whether or not the room is an invite. bool is_invite = false; + //! Total number of members in the room. + int16_t member_count = 0; }; inline void @@ -73,6 +75,9 @@ to_json(json &j, const RoomInfo &info) j["topic"] = info.topic; j["avatar_url"] = info.avatar_url; j["is_invite"] = info.is_invite; + + if (info.member_count != 0) + j["member_count"] = info.member_count; } inline void @@ -82,6 +87,9 @@ from_json(const json &j, RoomInfo &info) info.topic = j.at("topic"); info.avatar_url = j.at("avatar_url"); info.is_invite = j.at("is_invite"); + + if (j.count("member_count")) + info.member_count = j.at("member_count"); } //! Basic information per member; diff --git a/include/ChatPage.h b/include/ChatPage.h index 831f3933..1582db06 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -80,6 +80,7 @@ public: } QSharedPointer userSettings() { return userSettings_; } + QSharedPointer cache() { return cache_; } signals: void contentLoaded(); diff --git a/include/MainWindow.h b/include/MainWindow.h index d747f9b4..08d7e53e 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -50,6 +50,7 @@ class JoinRoom; class LeaveRoom; class Logout; class ReCaptcha; +class RoomSettings; } class MainWindow : public QMainWindow @@ -68,6 +69,7 @@ public: std::function callback); void openJoinRoomDialog(std::function callback); void openLogoutDialog(std::function callback); + void openRoomSettings(const QString &room_id = ""); protected: void closeEvent(QCloseEvent *event); @@ -147,4 +149,8 @@ private: QSharedPointer logoutModal_; //! Logout dialog. QSharedPointer logoutDialog_; + //! Room settings modal. + QSharedPointer roomSettingsModal_; + //! Room settings dialog. + QSharedPointer roomSettingsDialog_; }; diff --git a/include/TopRoomBar.h b/include/TopRoomBar.h index 12fd0645..37a0b61b 100644 --- a/include/TopRoomBar.h +++ b/include/TopRoomBar.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -66,8 +65,9 @@ private: QLabel *nameLabel_; Label *topicLabel_; - QMenu *menu_; + Menu *menu_; QAction *leaveRoom_; + QAction *roomSettings_; QAction *inviteUsers_; FlatButton *settingsBtn_; diff --git a/include/dialogs/RoomSettings.hpp b/include/dialogs/RoomSettings.hpp new file mode 100644 index 00000000..fbbc2e02 --- /dev/null +++ b/include/dialogs/RoomSettings.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include "Cache.h" + +class FlatButton; +class TextField; +class Avatar; +class QPixmap; +class QLayout; +class QLabel; + +template +class QSharedPointer; + +class TopSection : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + +public: + TopSection(const RoomInfo &info, const QImage &img, QWidget *parent = nullptr) + : QWidget{parent} + , info_{std::move(info)} + { + textColor_ = palette().color(QPalette::Text); + avatar_ = QPixmap::fromImage(img.scaled( + AvatarSize, AvatarSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + + QSize sizeHint() const override + { + QFont font; + font.setPixelSize(18); + return QSize(200, AvatarSize + QFontMetrics(font).ascent() + 6 * Padding); + } + + QColor textColor() const { return textColor_; } + void setTextColor(QColor &color) { textColor_ = color; } + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + static constexpr int AvatarSize = 72; + static constexpr int Padding = 5; + + RoomInfo info_; + QPixmap avatar_; + QColor textColor_; +}; + +namespace dialogs { + +class RoomSettings : public QFrame +{ + Q_OBJECT +public: + RoomSettings(const QString &room_id, + QSharedPointer cache, + QWidget *parent = nullptr); + +signals: + void closing(); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + static constexpr int AvatarSize = 64; + + void setAvatar(const QImage &img) { avatarImg_ = img; } + + QSharedPointer cache_; + + // Button section + FlatButton *saveBtn_; + FlatButton *cancelBtn_; + + RoomInfo info_; + QString room_id_; + QImage avatarImg_; +}; + +} // dialogs diff --git a/include/ui/Painter.h b/include/ui/Painter.h index 9558b004..8de39651 100644 --- a/include/ui/Painter.h +++ b/include/ui/Painter.h @@ -103,7 +103,7 @@ public: drawPixmap(region, pix); } - void drawLetterAvatar(const QChar &c, + void drawLetterAvatar(const QString &c, const QColor &penColor, const QColor &brushColor, int w, diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss index 28850711..89582348 100644 --- a/resources/styles/nheko-dark.qss +++ b/resources/styles/nheko-dark.qss @@ -136,6 +136,7 @@ dialogs--Logout, dialogs--ReCaptcha, dialogs--LeaveRoom, dialogs--CreateRoom, +dialogs--RoomSettings, dialogs--InviteUsers, dialogs--ReadReceipts, dialogs--JoinRoom, @@ -147,6 +148,10 @@ dialogs--JoinRoom > QLineEdit { color: #caccd1; } +TopSection { + qproperty-textColor: #caccd1; +} + QListWidget, WelcomePage, LoginPage, diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss index e1cb839f..6538a780 100644 --- a/resources/styles/nheko.qss +++ b/resources/styles/nheko.qss @@ -138,6 +138,7 @@ dialogs--Logout, dialogs--ReCaptcha, dialogs--LeaveRoom, dialogs--CreateRoom, +dialogs--RoomSettings, dialogs--InviteUsers, dialogs--ReadReceipts, dialogs--JoinRoom, @@ -147,6 +148,10 @@ QListWidget { color: #333; } +TopSection { + qproperty-textColor: #333; +} + WelcomePage, LoginPage, RegisterPage { diff --git a/src/Cache.cc b/src/Cache.cc index 699774ce..60181afc 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -538,9 +538,10 @@ Cache::getRoomInfo(const std::vector &rooms) // Check if the room is joined. if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room), data)) { try { - room_info.emplace( - QString::fromStdString(room), - json::parse(std::string(data.data(), data.size()))); + RoomInfo tmp = json::parse(std::string(data.data(), data.size())); + tmp.member_count = getMembersDb(txn, room).size(txn); + + room_info.emplace(QString::fromStdString(room), std::move(tmp)); } catch (const json::exception &e) { qWarning() << "failed to parse room info:" << QString::fromStdString(room) @@ -550,9 +551,12 @@ Cache::getRoomInfo(const std::vector &rooms) // Check if the room is an invite. if (lmdb::dbi_get(txn, invitesDb_, lmdb::val(room), data)) { try { - room_info.emplace( - QString::fromStdString(room), - json::parse(std::string(data.data(), data.size()))); + RoomInfo tmp = + json::parse(std::string(data.data(), data.size())); + tmp.member_count = getInviteMembersDb(txn, room).size(txn); + + room_info.emplace(QString::fromStdString(room), + std::move(tmp)); } catch (const json::exception &e) { qWarning() << "failed to parse room info for invite:" << QString::fromStdString(room) @@ -581,7 +585,8 @@ Cache::roomInfo(bool withInvites) // Gather info about the joined rooms. auto roomsCursor = lmdb::cursor::open(txn, roomsDb_); while (roomsCursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(std::move(room_data)); + RoomInfo tmp = json::parse(std::move(room_data)); + tmp.member_count = getMembersDb(txn, room_id).size(txn); result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp)); } roomsCursor.close(); @@ -590,7 +595,8 @@ Cache::roomInfo(bool withInvites) // Gather info about the invites. auto invitesCursor = lmdb::cursor::open(txn, invitesDb_); while (invitesCursor.get(room_id, room_data, MDB_NEXT)) { - RoomInfo tmp = json::parse(room_data); + RoomInfo tmp = json::parse(room_data); + tmp.member_count = getInviteMembersDb(txn, room_id).size(txn); result.insert(QString::fromStdString(std::move(room_id)), std::move(tmp)); } invitesCursor.close(); diff --git a/src/CommunitiesListItem.cc b/src/CommunitiesListItem.cc index e86216e8..0650c0cf 100644 --- a/src/CommunitiesListItem.cc +++ b/src/CommunitiesListItem.cc @@ -2,6 +2,7 @@ #include "Painter.h" #include "Ripple.h" #include "RippleOverlay.h" +#include "Utils.h" CommunitiesListItem::CommunitiesListItem(QSharedPointer community, QString community_id, @@ -74,7 +75,7 @@ CommunitiesListItem::paintEvent(QPaintEvent *) font.setPixelSize(conf::roomlist::fonts::communityBubble); p.setFont(font); - p.drawLetterAvatar(community_->getName()[0], + p.drawLetterAvatar(utils::firstChar(community_->getName()), avatarFgColor_, avatarBgColor_, width(), diff --git a/src/MainWindow.cc b/src/MainWindow.cc index c59ce2d9..66f956a5 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc @@ -41,6 +41,7 @@ #include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" #include "dialogs/Logout.h" +#include "dialogs/RoomSettings.hpp" MainWindow *MainWindow::instance_ = nullptr; @@ -262,6 +263,27 @@ MainWindow::hasActiveUser() settings.contains("auth/user_id"); } +void +MainWindow::openRoomSettings(const QString &room_id) +{ + const auto roomToSearch = room_id.isEmpty() ? chat_page_->currentRoom() : ""; + + qDebug() << "room settings" << roomToSearch; + + roomSettingsDialog_ = QSharedPointer( + new dialogs::RoomSettings(roomToSearch, chat_page_->cache(), this)); + + connect(roomSettingsDialog_.data(), &dialogs::RoomSettings::closing, this, [this]() { + roomSettingsModal_->hide(); + }); + + roomSettingsModal_ = + QSharedPointer(new OverlayModal(this, roomSettingsDialog_.data())); + roomSettingsModal_->setColor(QColor(30, 30, 30, 170)); + + roomSettingsModal_->show(); +} + void MainWindow::openLeaveRoomDialog(const QString &room_id) { diff --git a/src/TopRoomBar.cc b/src/TopRoomBar.cc index 3b60ab6a..beca1d51 100644 --- a/src/TopRoomBar.cc +++ b/src/TopRoomBar.cc @@ -95,8 +95,14 @@ TopRoomBar::TopRoomBar(QWidget *parent) MainWindow::instance()->openLeaveRoomDialog(); }); + roomSettings_ = new QAction(tr("Settings"), this); + connect(roomSettings_, &QAction::triggered, this, []() { + MainWindow::instance()->openRoomSettings(); + }); + menu_->addAction(inviteUsers_); menu_->addAction(leaveRoom_); + menu_->addAction(roomSettings_); connect(settingsBtn_, &QPushButton::clicked, this, [this]() { auto pos = mapToGlobal(settingsBtn_->pos()); diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp new file mode 100644 index 00000000..8fcbc82f --- /dev/null +++ b/src/dialogs/RoomSettings.cpp @@ -0,0 +1,147 @@ +#include "Avatar.h" +#include "Config.h" +#include "FlatButton.h" +#include "Painter.h" +#include "Utils.h" +#include "dialogs/RoomSettings.hpp" + +#include +#include +#include +#include +#include +#include +#include + +using namespace dialogs; + +RoomSettings::RoomSettings(const QString &room_id, QSharedPointer cache, QWidget *parent) + : QFrame(parent) + , cache_{cache} + , room_id_{std::move(room_id)} +{ + setMaximumWidth(385); + + try { + auto res = cache_->getRoomInfo({room_id_.toStdString()}); + info_ = res[room_id_]; + + setAvatar(QImage::fromData(cache_->image(info_.avatar_url))); + } catch (const lmdb::error &e) { + qWarning() << "failed to retrieve room info from cache" << room_id; + } + + auto layout = new QVBoxLayout(this); + layout->setSpacing(30); + layout->setMargin(20); + + saveBtn_ = new FlatButton("SAVE", this); + saveBtn_->setFontSize(conf::btn::fontSize); + cancelBtn_ = new FlatButton(tr("CANCEL"), this); + cancelBtn_->setFontSize(conf::btn::fontSize); + + auto btnLayout = new QHBoxLayout(); + btnLayout->setSpacing(0); + btnLayout->setMargin(0); + btnLayout->addStretch(1); + btnLayout->addWidget(saveBtn_); + btnLayout->addWidget(cancelBtn_); + + auto notifOptionLayout_ = new QHBoxLayout; + notifOptionLayout_->setMargin(5); + auto themeLabel_ = new QLabel(tr("Notifications"), this); + auto notifCombo = new QComboBox(this); + notifCombo->addItem("Nothing"); + notifCombo->addItem("Mentions only"); + notifCombo->addItem("All messages"); + themeLabel_->setStyleSheet("font-size: 15px;"); + + notifOptionLayout_->addWidget(themeLabel_); + notifOptionLayout_->addWidget(notifCombo, 0, Qt::AlignBottom | Qt::AlignRight); + + layout->addWidget(new TopSection(info_, avatarImg_, this)); + layout->addLayout(notifOptionLayout_); + layout->addLayout(btnLayout); + + connect(cancelBtn_, &FlatButton::clicked, this, &RoomSettings::closing); + connect(saveBtn_, &FlatButton::clicked, this, [this]() { emit closing(); }); +} + +void +RoomSettings::paintEvent(QPaintEvent *) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} + +void +TopSection::paintEvent(QPaintEvent *) +{ + Painter p(this); + PainterHighQualityEnabler hq(p); + + constexpr int textPadding = 23; + constexpr int textStartX = AvatarSize + 5 * Padding; + const int availableTextWidth = width() - textStartX; + + constexpr int nameFont = 15; + constexpr int membersFont = 14; + constexpr int labelFont = 18; + + QFont font; + font.setPixelSize(labelFont); + font.setWeight(70); + + p.setFont(font); + p.setPen(textColor()); + p.drawTextLeft(Padding, Padding, "Room settings"); + p.translate(0, textPadding + QFontMetrics(p.font()).ascent()); + + p.save(); + p.translate(textStartX, 2 * Padding); + + // Draw the name. + font.setPixelSize(membersFont); + const auto members = QString("%1 members").arg(info_.member_count); + + font.setPixelSize(nameFont); + const auto name = QFontMetrics(font).elidedText( + QString::fromStdString(info_.name), Qt::ElideRight, availableTextWidth - 4 * Padding); + + font.setWeight(60); + p.setFont(font); + p.drawTextLeft(0, 0, name); + + // Draw the number of members + p.translate(0, QFontMetrics(p.font()).ascent() + 2 * Padding); + + font.setPixelSize(membersFont); + font.setWeight(50); + p.setFont(font); + p.drawTextLeft(0, 0, members); + p.restore(); + + if (avatar_.isNull()) { + font.setPixelSize(AvatarSize / 2); + font.setWeight(60); + p.setFont(font); + + p.translate(Padding, Padding); + p.drawLetterAvatar(utils::firstChar(name), + QColor("white"), + QColor("black"), + AvatarSize + Padding, + AvatarSize + Padding, + AvatarSize); + } else { + QRect avatarRegion(Padding, Padding, AvatarSize, AvatarSize); + + QPainterPath pp; + pp.addEllipse(avatarRegion.center(), AvatarSize, AvatarSize); + + p.setClipPath(pp); + p.drawPixmap(avatarRegion, avatar_); + } +}