summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorJoe <rubberduckie3554@gmail.com>2019-11-02 00:27:54 -0400
committerJoe <rubberduckie3554@gmail.com>2019-11-02 00:27:54 -0400
commit35759d2c6cbc86bd6a5df2c96de5d2dd078742ec (patch)
treee97c5aafb4488755d617bdad2e5f969fe94386f0 /src
parentUpdate README with translation information (diff)
parentFix boost build in FreeBSD (diff)
downloadnheko-35759d2c6cbc86bd6a5df2c96de5d2dd078742ec.tar.xz
Merge branch '0.7.0-dev' of https://github.com/Nheko-Reborn/nheko into 0.7.0-dev
Diffstat (limited to 'src')
-rw-r--r--src/AvatarProvider.cpp55
-rw-r--r--src/AvatarProvider.h12
-rw-r--r--src/Cache.cpp110
-rw-r--r--src/Cache.h21
-rw-r--r--src/ChatPage.cpp127
-rw-r--r--src/ChatPage.h12
-rw-r--r--src/Logging.cpp2
-rw-r--r--src/Logging.h2
-rw-r--r--src/RoomInfoListItem.cpp19
-rw-r--r--src/RoomInfoListItem.h2
-rw-r--r--src/RoomList.cpp39
-rw-r--r--src/RoomList.h6
-rw-r--r--src/TopRoomBar.cpp5
-rw-r--r--src/TopRoomBar.h2
-rw-r--r--src/TypingDisplay.cpp17
-rw-r--r--src/UserInfoWidget.cpp20
-rw-r--r--src/UserInfoWidget.h2
-rw-r--r--src/UserMentionsWidget.cpp309
-rw-r--r--src/UserMentionsWidget.h164
-rw-r--r--src/UserSettingsPage.cpp23
-rw-r--r--src/UserSettingsPage.h16
-rw-r--r--src/Utils.cpp54
-rw-r--r--src/Utils.h60
-rw-r--r--src/dialogs/MemberList.cpp16
-rw-r--r--src/dialogs/ReadReceipts.cpp22
-rw-r--r--src/dialogs/RoomSettings.cpp23
-rw-r--r--src/dialogs/RoomSettings.h4
-rw-r--r--src/dialogs/UserMentions.cpp58
-rw-r--r--src/dialogs/UserMentions.h28
-rw-r--r--src/dialogs/UserProfile.cpp8
-rw-r--r--src/popups/PopupItem.cpp26
-rw-r--r--src/popups/UserMentions.cpp161
-rw-r--r--src/popups/UserMentions.h51
-rw-r--r--src/timeline/.TimelineItem.cpp.swpbin0 -> 114688 bytes
-rw-r--r--src/timeline/TimelineItem.cpp36
-rw-r--r--src/timeline/TimelineItem.h10
-rw-r--r--src/timeline/TimelineView.cpp28
-rw-r--r--src/ui/Avatar.cpp50
-rw-r--r--src/ui/Avatar.h7
-rw-r--r--src/ui/InfoMessage.cpp13
40 files changed, 736 insertions, 884 deletions
diff --git a/src/AvatarProvider.cpp b/src/AvatarProvider.cpp

index 57b61c75..ec745c04 100644 --- a/src/AvatarProvider.cpp +++ b/src/AvatarProvider.cpp
@@ -16,30 +16,44 @@ */ #include <QBuffer> +#include <QPixmapCache> #include <memory> +#include <unordered_map> #include "AvatarProvider.h" #include "Cache.h" #include "Logging.h" #include "MatrixClient.h" -namespace AvatarProvider { +static QPixmapCache avatar_cache; +namespace AvatarProvider { void -resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback callback) +resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback) { - const auto key = QString("%1 %2").arg(room_id).arg(user_id); - const auto avatarUrl = Cache::avatarUrl(room_id, user_id); + avatar_cache.setCacheLimit(1024 * 1024); - if (!Cache::AvatarUrls.contains(key) || !cache::client()) + const auto cacheKey = avatarUrl + "_size_" + size; + + if (!cache::client()) return; if (avatarUrl.isEmpty()) return; + QPixmap pixmap; + if (avatar_cache.find(cacheKey, &pixmap)) { + nhlog::net()->info("cached pixmap {}", avatarUrl.toStdString()); + callback(pixmap); + return; + } + auto data = cache::client()->image(avatarUrl); if (!data.isNull()) { - callback(QImage::fromData(data)); + pixmap.loadFromData(data); + avatar_cache.insert(cacheKey, pixmap); + nhlog::net()->info("loaded pixmap from disk cache {}", avatarUrl.toStdString()); + callback(pixmap); return; } @@ -47,7 +61,12 @@ resolve(const QString &room_id, const QString &user_id, QObject *receiver, Avata QObject::connect(proxy.get(), &AvatarProxy::avatarDownloaded, receiver, - [callback](const QByteArray &data) { callback(QImage::fromData(data)); }); + [callback, cacheKey](const QByteArray &data) { + QPixmap pm; + pm.loadFromData(data); + avatar_cache.insert(cacheKey, pm); + callback(pm); + }); mtx::http::ThumbOpts opts; opts.width = 256; @@ -67,8 +86,26 @@ resolve(const QString &room_id, const QString &user_id, QObject *receiver, Avata cache::client()->saveImage(opts.mxc_url, res); - auto data = QByteArray(res.data(), res.size()); - emit proxy->avatarDownloaded(data); + nhlog::net()->info("downloaded pixmap {}", opts.mxc_url); + + emit proxy->avatarDownloaded(QByteArray(res.data(), res.size())); }); } + +void +resolve(const QString &room_id, + const QString &user_id, + int size, + QObject *receiver, + AvatarCallback callback) +{ + const auto key = QString("%1 %2").arg(room_id).arg(user_id); + const auto avatarUrl = Cache::avatarUrl(room_id, user_id); + const auto cacheKey = avatarUrl + "_size_" + size; + + if (!Cache::AvatarUrls.contains(key) || !cache::client()) + return; + + resolve(avatarUrl, size, receiver, callback); +} } diff --git a/src/AvatarProvider.h b/src/AvatarProvider.h
index 4b4e15e9..47ed028e 100644 --- a/src/AvatarProvider.h +++ b/src/AvatarProvider.h
@@ -17,7 +17,7 @@ #pragma once -#include <QImage> +#include <QPixmap> #include <functional> class AvatarProxy : public QObject @@ -28,9 +28,15 @@ signals: void avatarDownloaded(const QByteArray &data); }; -using AvatarCallback = std::function<void(QImage)>; +using AvatarCallback = std::function<void(QPixmap)>; namespace AvatarProvider { void -resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback cb); +resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback cb); +void +resolve(const QString &room_id, + const QString &user_id, + int size, + QObject *receiver, + AvatarCallback cb); } diff --git a/src/Cache.cpp b/src/Cache.cpp
index 56c79678..083dbe89 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp
@@ -22,6 +22,7 @@ #include <QCoreApplication> #include <QFile> #include <QHash> +#include <QMap> #include <QSettings> #include <QStandardPaths> @@ -91,6 +92,7 @@ init(const QString &user_id) qRegisterMetaType<RoomSearchResult>(); qRegisterMetaType<RoomInfo>(); qRegisterMetaType<QMap<QString, RoomInfo>>(); + qRegisterMetaType<QMap<QString, mtx::responses::Notifications>>(); qRegisterMetaType<std::map<QString, RoomInfo>>(); qRegisterMetaType<std::map<QString, mtx::responses::Timeline>>(); @@ -1232,6 +1234,27 @@ Cache::roomMessages() return msgs; } +QMap<QString, mtx::responses::Notifications> +Cache::getTimelineMentions() +{ + // TODO: Should be read-only, but getMentionsDb will attempt to create a DB + // if it doesn't exist, throwing an error. + auto txn = lmdb::txn::begin(env_, nullptr); + + QMap<QString, mtx::responses::Notifications> notifs; + + auto room_ids = getRoomIds(txn); + + for (const auto &room_id : room_ids) { + auto roomNotifs = getTimelineMentionsForRoom(txn, room_id); + notifs[QString::fromStdString(room_id)] = roomNotifs; + } + + txn.commit(); + + return notifs; +} + mtx::responses::Timeline Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id) { @@ -1807,10 +1830,7 @@ Cache::searchRooms(const std::string &query, std::uint8_t max_items) std::vector<RoomSearchResult> results; for (auto it = items.begin(); it != end; it++) { - results.push_back( - RoomSearchResult{it->second.first, - it->second.second, - QImage::fromData(image(txn, it->second.second.avatar_url))}); + results.push_back(RoomSearchResult{it->second.first, it->second.second}); } txn.commit(); @@ -1935,6 +1955,88 @@ Cache::saveTimelineMessages(lmdb::txn &txn, } } +mtx::responses::Notifications +Cache::getTimelineMentionsForRoom(lmdb::txn &txn, const std::string &room_id) +{ + auto db = getMentionsDb(txn, room_id); + + if (db.size(txn) == 0) { + return mtx::responses::Notifications{}; + } + + mtx::responses::Notifications notif; + std::string event_id, msg; + + auto cursor = lmdb::cursor::open(txn, db); + + while (cursor.get(event_id, msg, MDB_NEXT)) { + auto obj = json::parse(msg); + + if (obj.count("event") == 0) + continue; + + mtx::responses::Notification notification; + mtx::responses::from_json(obj, notification); + + notif.notifications.push_back(notification); + } + cursor.close(); + + std::reverse(notif.notifications.begin(), notif.notifications.end()); + + return notif; +} + +//! Add all notifications containing a user mention to the db. +void +Cache::saveTimelineMentions(const mtx::responses::Notifications &res) +{ + QMap<std::string, QList<mtx::responses::Notification>> notifsByRoom; + + // Sort into room-specific 'buckets' + for (const auto &notif : res.notifications) { + json val = notif; + notifsByRoom[notif.room_id].push_back(notif); + } + + auto txn = lmdb::txn::begin(env_); + // Insert the entire set of mentions for each room at a time. + QMap<std::string, QList<mtx::responses::Notification>>::const_iterator it = + notifsByRoom.constBegin(); + auto end = notifsByRoom.constEnd(); + while (it != end) { + nhlog::db()->debug("Storing notifications for " + it.key()); + saveTimelineMentions(txn, it.key(), std::move(it.value())); + ++it; + } + + txn.commit(); +} + +void +Cache::saveTimelineMentions(lmdb::txn &txn, + const std::string &room_id, + const QList<mtx::responses::Notification> &res) +{ + auto db = getMentionsDb(txn, room_id); + + using namespace mtx::events; + using namespace mtx::events::state; + + for (const auto &notif : res) { + const auto event_id = utils::event_id(notif.event); + + // double check that we have the correct room_id... + if (room_id.compare(notif.room_id) != 0) { + return; + } + + json obj = notif; + + lmdb::dbi_put(txn, db, lmdb::val(event_id), lmdb::val(obj.dump())); + } +} + void Cache::markSentNotification(const std::string &event_id) { diff --git a/src/Cache.h b/src/Cache.h
index 65c5263d..0da49793 100644 --- a/src/Cache.h +++ b/src/Cache.h
@@ -32,6 +32,7 @@ #include <nlohmann/json.hpp> #include "Logging.h" +#include "MatrixClient.h" using mtx::events::state::JoinRule; @@ -152,7 +153,6 @@ struct RoomSearchResult { std::string room_id; RoomInfo info; - QImage img; }; Q_DECLARE_METATYPE(RoomSearchResult) @@ -323,6 +323,8 @@ public: std::map<QString, mtx::responses::Timeline> roomMessages(); + QMap<QString, mtx::responses::Notifications> getTimelineMentions(); + //! Retrieve all the user ids from a room. std::vector<std::string> roomMembers(const std::string &room_id); @@ -402,6 +404,9 @@ public: //! Check if we have sent a desktop notification for the given event id. bool isNotificationSent(const std::string &event_id); + //! Add all notifications containing a user mention to the db. + void saveTimelineMentions(const mtx::responses::Notifications &res); + //! Remove old unused data. void deleteOldMessages(); void deleteOldData() noexcept; @@ -470,6 +475,15 @@ private: lmdb::dbi &membersdb, const mtx::responses::InvitedRoom &room); + //! Add a notification containing a user mention to the db. + void saveTimelineMentions(lmdb::txn &txn, + const std::string &room_id, + const QList<mtx::responses::Notification> &res); + + //! Get timeline items that a user was mentions in for a given room + mtx::responses::Notifications getTimelineMentionsForRoom(lmdb::txn &txn, + const std::string &room_id); + QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb); QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb); @@ -660,6 +674,11 @@ private: return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE); } + lmdb::dbi getMentionsDb(lmdb::txn &txn, const std::string &room_id) + { + return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE); + } + //! Retrieves or creates the database that stores the open OLM sessions between our device //! and the given curve25519 key which represents another device. //! diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 18188429..21ded4b3 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp
@@ -35,7 +35,6 @@ #include "TopRoomBar.h" #include "TypingDisplay.h" #include "UserInfoWidget.h" -#include "UserMentionsWidget.h" #include "UserSettingsPage.h" #include "Utils.h" #include "ui/OverlayModal.h" @@ -44,7 +43,7 @@ #include "notifications/Manager.h" #include "dialogs/ReadReceipts.h" -#include "dialogs/UserMentions.h" +#include "popups/UserMentions.h" #include "timeline/TimelineViewManager.h" // TODO: Needs to be updated with an actual secret. @@ -90,13 +89,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) connect(sidebarActions_, &SideBarActions::joinRoom, this, &ChatPage::joinRoom); connect(sidebarActions_, &SideBarActions::createRoom, this, &ChatPage::createRoom); - user_info_widget_ = new UserInfoWidget(sideBar_); - // user_mentions_widget_ = new UserMentionsWidget(sideBar_); - room_list_ = new RoomList(sideBar_); + user_info_widget_ = new UserInfoWidget(sideBar_); + user_mentions_popup_ = new popups::UserMentions(); + room_list_ = new RoomList(sideBar_); connect(room_list_, &RoomList::joinRoom, this, &ChatPage::joinRoom); sideBarLayout_->addWidget(user_info_widget_); - // sideBarLayout_->addWidget(user_mentions_widget_); sideBarLayout_->addWidget(room_list_); sideBarLayout_->addWidget(sidebarActions_); @@ -155,21 +153,27 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) }); connect(top_bar_, &TopRoomBar::mentionsClicked, this, [this](const QPoint &mentionsPos) { - http::client()->notifications( - 1000, - "", - "highlight", - [this, mentionsPos](const mtx::responses::Notifications &res, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to retrieve notifications: {} ({})", - err->matrix_error.error, - static_cast<int>(err->status_code)); - return; - } + if (user_mentions_popup_->isVisible()) { + user_mentions_popup_->hide(); + } else { + showNotificationsDialog(mentionsPos); + http::client()->notifications( + 1000, + "", + "highlight", + [this, mentionsPos](const mtx::responses::Notifications &res, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn( + "failed to retrieve notifications: {} ({})", + err->matrix_error.error, + static_cast<int>(err->status_code)); + return; + } - emit highlightedNotifsRetrieved(std::move(res), mentionsPos); - }); + emit highlightedNotifsRetrieved(std::move(res), mentionsPos); + }); + } }); connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL); @@ -519,8 +523,16 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendDesktopNotifications); - connect( - this, &ChatPage::highlightedNotifsRetrieved, this, &ChatPage::showNotificationsDialog); + connect(this, + &ChatPage::highlightedNotifsRetrieved, + this, + [](const mtx::responses::Notifications &notif) { + try { + cache::client()->saveTimelineMentions(notif); + } catch (const lmdb::error &e) { + nhlog::db()->error("failed to save mentions: {}", e.what()); + } + }); connect(communitiesList_, &CommunitiesList::communityChanged, @@ -559,6 +571,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) &ChatPage::initializeEmptyViews, view_manager_, &TimelineViewManager::initWithMessages); + connect(this, + &ChatPage::initializeMentions, + user_mentions_popup_, + &popups::UserMentions::initializeMentions); connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { try { room_list_->cleanupInvites(cache::client()->invites()); @@ -758,12 +774,12 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) } void -ChatPage::updateTopBarAvatar(const QString &roomid, const QPixmap &img) +ChatPage::updateTopBarAvatar(const QString &roomid, const QString &img) { if (current_room_ != roomid) return; - top_bar_->updateRoomAvatar(img.toImage()); + top_bar_->updateRoomAvatar(img); } void @@ -791,7 +807,7 @@ ChatPage::changeTopRoomInfo(const QString &room_id) if (img.isNull()) top_bar_->updateRoomAvatarFromName(name); else - top_bar_->updateRoomAvatar(img); + top_bar_->updateRoomAvatar(avatar_url); } catch (const lmdb::error &e) { nhlog::ui()->error("failed to change top bar room info: {}", e.what()); @@ -831,6 +847,7 @@ ChatPage::loadStateFromCache() emit initializeEmptyViews(cache::client()->roomMessages()); emit initializeRoomList(cache::client()->roomInfo()); + emit initializeMentions(cache::client()->getTimelineMentions()); emit syncTags(cache::client()->roomInfo().toStdMap()); cache::client()->calculateRoomReadStatus(); @@ -987,32 +1004,15 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res) } void -ChatPage::showNotificationsDialog(const mtx::responses::Notifications &res, const QPoint &widgetPos) +ChatPage::showNotificationsDialog(const QPoint &widgetPos) { - // TODO: This should NOT BE A DIALOG. Make the TimelineView support - // creating a timeline view from notifications (similarly to how it can show history views) - auto notifDialog = new dialogs::UserMentions(); - for (const auto &item : res.notifications) { - const auto event_id = QString::fromStdString(utils::event_id(item.event)); + auto notifDialog = user_mentions_popup_; - try { - const auto room_id = QString::fromStdString(item.room_id); - const auto user_id = utils::event_sender(item.event); - const auto body = utils::event_body(item.event); - - notifDialog->pushItem(event_id, user_id, body, room_id); - - } catch (const lmdb::error &e) { - nhlog::db()->warn("error while sending desktop notification: {}", e.what()); - } - } notifDialog->setGeometry( widgetPos.x() - (width() / 10), widgetPos.y() + 25, width() / 5, height() / 2); - // notifDialog->move(widgetPos.x(), widgetPos.y()); - // notifDialog->setFixedWidth(width() / 10); - // notifDialog->setFixedHeight(height() / 2); + notifDialog->raise(); - notifDialog->show(); + notifDialog->showPopup(); } void @@ -1243,6 +1243,8 @@ ChatPage::sendTypingNotifications() void ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err) { + // TODO: Initial Sync should include mentions as well... + if (err) { const auto error = QString::fromStdString(err->matrix_error.error); const auto msg = tr("Please try to login again: %1").arg(error); @@ -1280,6 +1282,7 @@ ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::Request emit initializeViews(std::move(res.rooms)); emit initializeRoomList(cache::client()->roomInfo()); + emit initializeMentions(cache::client()->getTimelineMentions()); cache::client()->calculateRoomReadStatus(); emit syncTags(cache::client()->roomInfo().toStdMap()); @@ -1334,37 +1337,7 @@ ChatPage::getProfileInfo() emit setUserDisplayName(QString::fromStdString(res.display_name)); - if (cache::client()) { - auto data = cache::client()->image(res.avatar_url); - if (!data.isNull()) { - emit setUserAvatar(QImage::fromData(data)); - return; - } - } - - if (res.avatar_url.empty()) - return; - - http::client()->download( - res.avatar_url, - [this, res](const std::string &data, - const std::string &, - const std::string &, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to download user avatar: {} - {}", - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - return; - } - - if (cache::client()) - cache::client()->saveImage(res.avatar_url, data); - - emit setUserAvatar( - QImage::fromData(QByteArray(data.data(), data.size()))); - }); + emit setUserAvatar(QString::fromStdString(res.avatar_url)); }); http::client()->joined_groups( diff --git a/src/ChatPage.h b/src/ChatPage.h
index 3c97ba25..e41ae1ae 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h
@@ -19,6 +19,7 @@ #include <atomic> #include <boost/variant.hpp> +#include <mtx/responses.hpp> #include <QFrame> #include <QHBoxLayout> @@ -33,6 +34,7 @@ #include "MatrixClient.h" #include "Utils.h" #include "notifications/Manager.h" +#include "popups/UserMentions.h" class OverlayModal; class QuickSwitcher; @@ -44,7 +46,6 @@ class TimelineViewManager; class TopRoomBar; class TypingDisplay; class UserInfoWidget; -class UserMentionsWidget; class UserSettings; class NotificationsManager; @@ -128,7 +129,7 @@ signals: void ownProfileOk(); void setUserDisplayName(const QString &name); - void setUserAvatar(const QImage &avatar); + void setUserAvatar(const QString &avatar); void loggedOut(); void trySyncCb(); @@ -139,6 +140,7 @@ signals: void initializeRoomList(QMap<QString, RoomInfo>); void initializeViews(const mtx::responses::Rooms &rooms); void initializeEmptyViews(const std::map<QString, mtx::responses::Timeline> &msgs); + void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs); void syncUI(const mtx::responses::Rooms &rooms); void syncRoomlist(const std::map<QString, RoomInfo> &updates); void syncTags(const std::map<QString, RoomInfo> &updates); @@ -157,7 +159,7 @@ signals: private slots: void showUnreadMessageNotification(int count); - void updateTopBarAvatar(const QString &roomid, const QPixmap &img); + void updateTopBarAvatar(const QString &roomid, const QString &img); void changeTopRoomInfo(const QString &room_id); void logout(); void removeRoom(const QString &room_id); @@ -208,7 +210,7 @@ private: //! Send desktop notification for the received messages. void sendDesktopNotifications(const mtx::responses::Notifications &); - void showNotificationsDialog(const mtx::responses::Notifications &, const QPoint &point); + void showNotificationsDialog(const QPoint &point); QStringList generateTypingUsers(const QString &room_id, const std::vector<std::string> &typing_users); @@ -242,7 +244,7 @@ private: UserInfoWidget *user_info_widget_; - UserMentionsWidget *user_mentions_widget_; + popups::UserMentions *user_mentions_popup_; // Keeps track of the users currently typing on each room. std::map<QString, QList<QString>> typingUsers_; diff --git a/src/Logging.cpp b/src/Logging.cpp
index 686274d8..32287582 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp
@@ -16,6 +16,8 @@ constexpr auto MAX_LOG_FILES = 3; } namespace nhlog { +bool enable_debug_log_from_commandline = false; + void init(const std::string &file_path) { diff --git a/src/Logging.h b/src/Logging.h
index 2feae60d..e54f3c3f 100644 --- a/src/Logging.h +++ b/src/Logging.h
@@ -18,4 +18,6 @@ db(); std::shared_ptr<spdlog::logger> crypto(); + +extern bool enable_debug_log_from_commandline; } diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp
index 9bcce134..8aadbea2 100644 --- a/src/RoomInfoListItem.cpp +++ b/src/RoomInfoListItem.cpp
@@ -19,8 +19,10 @@ #include <QDebug> #include <QMouseEvent> #include <QPainter> +#include <QSettings> #include <QtGlobal> +#include "AvatarProvider.h" #include "Cache.h" #include "Config.h" #include "RoomInfoListItem.h" @@ -140,6 +142,8 @@ RoomInfoListItem::resizeEvent(QResizeEvent *) void RoomInfoListItem::paintEvent(QPaintEvent *event) { + bool rounded = QSettings().value("user/avatar/circles", true).toBool(); + Q_UNUSED(event); QPainter p(this); @@ -287,7 +291,8 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) p.setPen(Qt::NoPen); p.setBrush(brush); - p.drawEllipse(avatarRegion.center(), wm.iconSize / 2, wm.iconSize / 2); + rounded ? p.drawEllipse(avatarRegion.center(), wm.iconSize / 2, wm.iconSize / 2) + : p.drawRoundedRect(avatarRegion, 3, 3); QFont bubbleFont; bubbleFont.setPointSizeF(bubbleFont.pointSizeF() * 1.4); @@ -300,7 +305,9 @@ RoomInfoListItem::paintEvent(QPaintEvent *event) p.save(); QPainterPath path; - path.addEllipse(wm.padding, wm.padding, wm.iconSize, wm.iconSize); + rounded ? path.addEllipse(wm.padding, wm.padding, wm.iconSize, wm.iconSize) + : path.addRoundedRect(avatarRegion, 3, 3); + p.setClipPath(path); p.drawPixmap(avatarRegion, roomAvatar_); @@ -434,10 +441,12 @@ RoomInfoListItem::mousePressEvent(QMouseEvent *event) } void -RoomInfoListItem::setAvatar(const QImage &img) +RoomInfoListItem::setAvatar(const QString &avatar_url) { - roomAvatar_ = utils::scaleImageToPixmap(img, IconSize); - update(); + AvatarProvider::resolve(avatar_url, IconSize, this, [this](const QPixmap &img) { + roomAvatar_ = img; + update(); + }); } void diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h
index 40c938c1..54e02a76 100644 --- a/src/RoomInfoListItem.h +++ b/src/RoomInfoListItem.h
@@ -73,7 +73,7 @@ public: bool isPressed() const { return isPressed_; } int unreadMessageCount() const { return unreadMsgCount_; } - void setAvatar(const QImage &avatar_image); + void setAvatar(const QString &avatar_url); void setDescriptionMessage(const DescInfo &info); DescInfo lastMessageInfo() const { return lastMsgInfo_; } diff --git a/src/RoomList.cpp b/src/RoomList.cpp
index 1abf3533..c5e05621 100644 --- a/src/RoomList.cpp +++ b/src/RoomList.cpp
@@ -89,40 +89,7 @@ RoomList::updateAvatar(const QString &room_id, const QString &url) if (url.isEmpty()) return; - QByteArray savedImgData; - - if (cache::client()) - savedImgData = cache::client()->image(url); - - if (savedImgData.isEmpty()) { - mtx::http::ThumbOpts opts; - opts.mxc_url = url.toStdString(); - http::client()->get_thumbnail( - opts, [room_id, opts, this](const std::string &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn( - "failed to download room avatar: {} {} {}", - opts.mxc_url, - mtx::errors::to_string(err->matrix_error.errcode), - err->matrix_error.error); - return; - } - - if (cache::client()) - cache::client()->saveImage(opts.mxc_url, res); - - auto data = QByteArray(res.data(), res.size()); - QPixmap pixmap; - pixmap.loadFromData(data); - - emit updateRoomAvatarCb(room_id, pixmap); - }); - } else { - QPixmap img; - img.loadFromData(savedImgData); - - updateRoomAvatar(room_id, img); - } + emit updateRoomAvatarCb(room_id, url); } void @@ -252,7 +219,7 @@ RoomList::highlightSelectedRoom(const QString &room_id) } void -RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img) +RoomList::updateRoomAvatar(const QString &roomid, const QString &img) { if (!roomExists(roomid)) { nhlog::ui()->warn("avatar update on non-existent room_id: {}", @@ -260,7 +227,7 @@ RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img) return; } - rooms_[roomid]->setAvatar(img.toImage()); + rooms_[roomid]->setAvatar(img); // Used to inform other widgets for the new image data. emit roomAvatarChanged(roomid, img); diff --git a/src/RoomList.h b/src/RoomList.h
index 155a969c..95fc0d9b 100644 --- a/src/RoomList.h +++ b/src/RoomList.h
@@ -61,12 +61,12 @@ signals: void totalUnreadMessageCountUpdated(int count); void acceptInvite(const QString &room_id); void declineInvite(const QString &room_id); - void roomAvatarChanged(const QString &room_id, const QPixmap &img); + void roomAvatarChanged(const QString &room_id, const QString &img); void joinRoom(const QString &room_id); - void updateRoomAvatarCb(const QString &room_id, const QPixmap &img); + void updateRoomAvatarCb(const QString &room_id, const QString &img); public slots: - void updateRoomAvatar(const QString &roomid, const QPixmap &img); + void updateRoomAvatar(const QString &roomid, const QString &img); void highlightSelectedRoom(const QString &room_id); void updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount); void updateRoomDescription(const QString &roomid, const DescInfo &info); diff --git a/src/TopRoomBar.cpp b/src/TopRoomBar.cpp
index a8049e3a..712fe9aa 100644 --- a/src/TopRoomBar.cpp +++ b/src/TopRoomBar.cpp
@@ -46,9 +46,8 @@ TopRoomBar::TopRoomBar(QWidget *parent) topLayout_->setContentsMargins( 2 * widgetMargin, widgetMargin, 2 * widgetMargin, widgetMargin); - avatar_ = new Avatar(this); + avatar_ = new Avatar(this, fontHeight * 2); avatar_->setLetter(""); - avatar_->setSize(fontHeight * 2); textLayout_ = new QVBoxLayout(); textLayout_->setSpacing(0); @@ -183,7 +182,7 @@ TopRoomBar::reset() } void -TopRoomBar::updateRoomAvatar(const QImage &avatar_image) +TopRoomBar::updateRoomAvatar(const QString &avatar_image) { avatar_->setImage(avatar_image); update(); diff --git a/src/TopRoomBar.h b/src/TopRoomBar.h
index 5f2c936e..3243064e 100644 --- a/src/TopRoomBar.h +++ b/src/TopRoomBar.h
@@ -44,7 +44,7 @@ class TopRoomBar : public QWidget public: TopRoomBar(QWidget *parent = 0); - void updateRoomAvatar(const QImage &avatar_image); + void updateRoomAvatar(const QString &avatar_image); void updateRoomAvatar(const QIcon &icon); void updateRoomName(const QString &name); void updateRoomTopic(QString topic); diff --git a/src/TypingDisplay.cpp b/src/TypingDisplay.cpp
index 6059601d..43fabcd8 100644 --- a/src/TypingDisplay.cpp +++ b/src/TypingDisplay.cpp
@@ -33,6 +33,14 @@ TypingDisplay::setUsers(const QStringList &uid) text_.clear(); + QString temp = text_ += + tr("%1 and %2 are typing", + "Multiple users are typing. First argument is a comma separated list of potentially " + "multiple users. Second argument is the last user of that list. (If only one user is " + "typing, %1 is empty. You should still use it in your string though to silence Qt " + "warnings.)", + uid.size()); + if (uid.isEmpty()) { hide(); update(); @@ -40,12 +48,9 @@ TypingDisplay::setUsers(const QStringList &uid) return; } - text_ = uid.join(", "); - - if (uid.size() == 1) - text_ += tr(" is typing"); - else if (uid.size() > 1) - text_ += tr(" are typing"); + QStringList uidWithoutLast = uid; + uidWithoutLast.pop_back(); + text_ = temp.arg(uidWithoutLast.join(", ")).arg(uid.back()); show(); update(); diff --git a/src/UserInfoWidget.cpp b/src/UserInfoWidget.cpp
index 5345fb2a..7a910340 100644 --- a/src/UserInfoWidget.cpp +++ b/src/UserInfoWidget.cpp
@@ -1,3 +1,4 @@ + /* * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr> * @@ -52,10 +53,9 @@ UserInfoWidget::UserInfoWidget(QWidget *parent) textLayout_->setSpacing(widgetMargin / 2); textLayout_->setContentsMargins(widgetMargin * 2, widgetMargin, widgetMargin, widgetMargin); - userAvatar_ = new Avatar(this); + userAvatar_ = new Avatar(this, fontHeight * 2.5); userAvatar_->setObjectName("userAvatar"); userAvatar_->setLetter(QChar('?')); - userAvatar_->setSize(fontHeight * 2.5); QFont nameFont; nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); @@ -135,14 +135,6 @@ UserInfoWidget::reset() } void -UserInfoWidget::setAvatar(const QImage &img) -{ - avatar_image_ = img; - userAvatar_->setImage(img); - update(); -} - -void UserInfoWidget::setDisplayName(const QString &name) { if (name.isEmpty()) @@ -160,6 +152,14 @@ UserInfoWidget::setUserId(const QString &userid) { user_id_ = userid; userIdLabel_->setText(userid); + update(); +} + +void +UserInfoWidget::setAvatar(const QString &url) +{ + userAvatar_->setImage(url); + update(); } void diff --git a/src/UserInfoWidget.h b/src/UserInfoWidget.h
index 65de7be9..263dd0c2 100644 --- a/src/UserInfoWidget.h +++ b/src/UserInfoWidget.h
@@ -33,9 +33,9 @@ class UserInfoWidget : public QWidget public: UserInfoWidget(QWidget *parent = 0); - void setAvatar(const QImage &img); void setDisplayName(const QString &name); void setUserId(const QString &userid); + void setAvatar(const QString &url); void reset(); diff --git a/src/UserMentionsWidget.cpp b/src/UserMentionsWidget.cpp deleted file mode 100644
index a28db930..00000000 --- a/src/UserMentionsWidget.cpp +++ /dev/null
@@ -1,309 +0,0 @@ -#include <QDateTime> -#include <QDebug> -#include <QMouseEvent> -#include <QPainter> -#include <QtGlobal> - -#include "MainWindow.h" -#include "UserMentionsWidget.h" -#include "Utils.h" -#include "ui/Ripple.h" -#include "ui/RippleOverlay.h" - -constexpr int MaxUnreadCountDisplayed = 99; - -struct WMetrics -{ - int maxHeight; - int iconSize; - int padding; - int unit; - - int unreadLineWidth; - int unreadLineOffset; - - int inviteBtnX; - int inviteBtnY; -}; - -WMetrics -getWMetrics(const QFont &font) -{ - WMetrics m; - - const int height = QFontMetrics(font).lineSpacing(); - - m.unit = height; - m.maxHeight = std::ceil((double)height * 3.8); - m.iconSize = std::ceil((double)height * 2.8); - m.padding = std::ceil((double)height / 2.0); - m.unreadLineWidth = m.padding - m.padding / 3; - m.unreadLineOffset = m.padding - m.padding / 4; - - m.inviteBtnX = m.iconSize + 2 * m.padding; - m.inviteBtnX = m.iconSize / 2.0 + m.padding + m.padding / 3.0; - - return m; -} - -UserMentionsWidget::UserMentionsWidget(QWidget *parent) - : QWidget(parent) - , isPressed_(false) - , unreadMsgCount_(0) -{ - init(parent); - - QFont f; - f.setPointSizeF(f.pointSizeF()); - - const int fontHeight = QFontMetrics(f).height(); - const int widgetMargin = fontHeight / 3; - const int contentHeight = fontHeight * 3; - - setFixedHeight(contentHeight + widgetMargin); - - topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setMargin(widgetMargin); -} - -void -UserMentionsWidget::init(QWidget *parent) -{ - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - setMouseTracking(true); - setAttribute(Qt::WA_Hover); - - setFixedHeight(getWMetrics(QFont{}).maxHeight); - - QPainterPath path; - path.addRect(0, 0, parent->width(), height()); - - ripple_overlay_ = new RippleOverlay(this); - ripple_overlay_->setClipPath(path); - ripple_overlay_->setClipping(true); - - unreadCountFont_.setPointSizeF(unreadCountFont_.pointSizeF() * 0.8); - unreadCountFont_.setBold(true); - - bubbleDiameter_ = QFontMetrics(unreadCountFont_).averageCharWidth() * 3; -} - -// void -// UserMentionsWidget::resizeEvent(QResizeEvent *event) -// { -// Q_UNUSED(event); - -// const auto sz = utils::calculateSidebarSizes(QFont{}); - -// if (width() <= sz.small) { -// topLayout_->setContentsMargins(0, 0, logoutButtonSize_, 0); - -// } else { -// topLayout_->setMargin(5); -// } - -// QWidget::resizeEvent(event); -// } - -void -UserMentionsWidget::setPressedState(bool state) -{ - if (isPressed_ != state) { - isPressed_ = state; - update(); - } -} - -void -UserMentionsWidget::resizeEvent(QResizeEvent *) -{ - // Update ripple's clipping path. - QPainterPath path; - path.addRect(0, 0, width(), height()); - - const auto sidebarSizes = utils::calculateSidebarSizes(QFont{}); - - if (width() > sidebarSizes.small) - setToolTip(""); - else - setToolTip(""); - - ripple_overlay_->setClipPath(path); - ripple_overlay_->setClipping(true); -} - -void -UserMentionsWidget::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() == Qt::RightButton) { - QWidget::mousePressEvent(event); - return; - } - - emit clicked(); - - setPressedState(true); - - // Ripple on mouse position by default. - QPoint pos = event->pos(); - qreal radiusEndValue = static_cast<qreal>(width()) / 3; - - Ripple *ripple = new Ripple(pos); - - ripple->setRadiusEndValue(radiusEndValue); - ripple->setOpacityStartValue(0.15); - ripple->setColor(QColor("white")); - ripple->radiusAnimation()->setDuration(200); - ripple->opacityAnimation()->setDuration(400); - - ripple_overlay_->addRipple(ripple); -} - -void -UserMentionsWidget::paintEvent(QPaintEvent *event) -{ - Q_UNUSED(event); - - QPainter p(this); - p.setRenderHint(QPainter::TextAntialiasing); - p.setRenderHint(QPainter::SmoothPixmapTransform); - p.setRenderHint(QPainter::Antialiasing); - - auto wm = getWMetrics(QFont{}); - - QPen titlePen(titleColor_); - QPen subtitlePen(subtitleColor_); - - QFontMetrics metrics(QFont{}); - - if (isPressed_) { - p.fillRect(rect(), highlightedBackgroundColor_); - titlePen.setColor(highlightedTitleColor_); - subtitlePen.setColor(highlightedSubtitleColor_); - } else if (underMouse()) { - p.fillRect(rect(), hoverBackgroundColor_); - titlePen.setColor(hoverTitleColor_); - subtitlePen.setColor(hoverSubtitleColor_); - } else { - p.fillRect(rect(), backgroundColor_); - titlePen.setColor(titleColor_); - subtitlePen.setColor(subtitleColor_); - } - - // Description line with the default font. - int bottom_y = wm.maxHeight - wm.padding - metrics.ascent() / 2; - - const auto sidebarSizes = utils::calculateSidebarSizes(QFont{}); - - if (width() > sidebarSizes.small) { - QFont headingFont; - headingFont.setWeight(QFont::Medium); - p.setFont(headingFont); - p.setPen(titlePen); - - QFont tsFont; - tsFont.setPointSizeF(tsFont.pointSizeF() * 0.9); -#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) - const int msgStampWidth = QFontMetrics(tsFont).width("timestamp") + 4; -#else - const int msgStampWidth = QFontMetrics(tsFont).horizontalAdvance("timestamp") + 4; -#endif - // We use the full width of the widget if there is no unread msg bubble. - // const int bottomLineWidthLimit = (unreadMsgCount_ > 0) ? msgStampWidth : 0; - - // Name line. - QFontMetrics fontNameMetrics(headingFont); - int top_y = 2 * wm.padding + fontNameMetrics.ascent() / 2; - - const auto name = metrics.elidedText( - "Mentions", - Qt::ElideRight, - (width() - wm.iconSize - 2 * wm.padding - msgStampWidth) * 0.8); - p.drawText(QPoint(2 * wm.padding + wm.iconSize, top_y), name); - - p.setFont(QFont{}); - p.setPen(subtitlePen); - - // The limit is the space between the end of the avatar and the start of the - // timestamp. - int usernameLimit = - std::max(0, width() - 3 * wm.padding - msgStampWidth - wm.iconSize - 20); - auto userName = - metrics.elidedText("Show Mentioned Messages", Qt::ElideRight, usernameLimit); - - p.setFont(QFont{}); - p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), userName); - - // We show the last message timestamp. - p.save(); - if (isPressed_) { - p.setPen(QPen(highlightedTimestampColor_)); - } else if (underMouse()) { - p.setPen(QPen(hoverTimestampColor_)); - } else { - p.setPen(QPen(timestampColor_)); - } - - // p.setFont(tsFont); - // p.drawText(QPoint(width() - wm.padding - msgStampWidth, top_y), "timestamp"); - p.restore(); - } - - p.setPen(Qt::NoPen); - - if (unreadMsgCount_ > 0) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - - brush.setColor(mentionedColor()); - - if (isPressed_) - brush.setColor(bubbleFgColor()); - - p.setBrush(brush); - p.setPen(Qt::NoPen); - p.setFont(unreadCountFont_); - - // Extra space on the x-axis to accomodate the extra character space - // inside the bubble. - const int x_width = unreadMsgCount_ > MaxUnreadCountDisplayed - ? QFontMetrics(p.font()).averageCharWidth() - : 0; - - QRectF r(width() - bubbleDiameter_ - wm.padding - x_width, - bottom_y - bubbleDiameter_ / 2 - 5, - bubbleDiameter_ + x_width, - bubbleDiameter_); - - if (width() == sidebarSizes.small) - r = QRectF(width() - bubbleDiameter_ - 5, - height() - bubbleDiameter_ - 5, - bubbleDiameter_ + x_width, - bubbleDiameter_); - - p.setPen(Qt::NoPen); - p.drawEllipse(r); - - p.setPen(QPen(bubbleFgColor())); - - if (isPressed_) - p.setPen(QPen(bubbleBgColor())); - - auto countTxt = unreadMsgCount_ > MaxUnreadCountDisplayed - ? QString("99+") - : QString::number(unreadMsgCount_); - - p.setBrush(Qt::NoBrush); - p.drawText(r.translated(0, -0.5), Qt::AlignCenter, countTxt); - } - - if (!isPressed_ && hasUnreadMessages_) { - QPen pen; - pen.setWidth(wm.unreadLineWidth); - pen.setColor(highlightedBackgroundColor_); - - p.setPen(pen); - p.drawLine(0, wm.unreadLineOffset, 0, height() - wm.unreadLineOffset); - } -} \ No newline at end of file diff --git a/src/UserMentionsWidget.h b/src/UserMentionsWidget.h deleted file mode 100644
index 179f0026..00000000 --- a/src/UserMentionsWidget.h +++ /dev/null
@@ -1,164 +0,0 @@ -#pragma once - -#include <QColor> -#include <QHBoxLayout> -#include <QLabel> -#include <QLayout> -#include <QWidget> - -class FlatButton; -class RippleOverlay; - -class UserMentionsWidget : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor) - - Q_PROPERTY(QColor highlightedBackgroundColor READ highlightedBackgroundColor WRITE - setHighlightedBackgroundColor) - Q_PROPERTY( - QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor) - Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) - - Q_PROPERTY(QColor avatarBgColor READ avatarBgColor WRITE setAvatarBgColor) - Q_PROPERTY(QColor avatarFgColor READ avatarFgColor WRITE setAvatarFgColor) - - Q_PROPERTY(QColor bubbleBgColor READ bubbleBgColor WRITE setBubbleBgColor) - Q_PROPERTY(QColor bubbleFgColor READ bubbleFgColor WRITE setBubbleFgColor) - - Q_PROPERTY(QColor titleColor READ titleColor WRITE setTitleColor) - Q_PROPERTY(QColor subtitleColor READ subtitleColor WRITE setSubtitleColor) - - Q_PROPERTY(QColor timestampColor READ timestampColor WRITE setTimestampColor) - Q_PROPERTY(QColor highlightedTimestampColor READ highlightedTimestampColor WRITE - setHighlightedTimestampColor) - Q_PROPERTY(QColor hoverTimestampColor READ hoverTimestampColor WRITE setHoverTimestampColor) - - Q_PROPERTY( - QColor highlightedTitleColor READ highlightedTitleColor WRITE setHighlightedTitleColor) - Q_PROPERTY(QColor highlightedSubtitleColor READ highlightedSubtitleColor WRITE - setHighlightedSubtitleColor) - - Q_PROPERTY(QColor hoverTitleColor READ hoverTitleColor WRITE setHoverTitleColor) - Q_PROPERTY(QColor hoverSubtitleColor READ hoverSubtitleColor WRITE setHoverSubtitleColor) - - Q_PROPERTY(QColor mentionedColor READ mentionedColor WRITE setMentionedColor) - Q_PROPERTY(QColor btnColor READ btnColor WRITE setBtnColor) - Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor) - -public: - UserMentionsWidget(QWidget *parent = 0); - - void updateUnreadMessageCount(int count); - void clearUnreadMessageCount() { updateUnreadMessageCount(0); }; - bool isPressed() const { return isPressed_; } - int unreadMessageCount() const { return unreadMsgCount_; } - QColor borderColor() const { return borderColor_; } - void setBorderColor(QColor &color) { borderColor_ = color; } - QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; } - QColor hoverBackgroundColor() const { return hoverBackgroundColor_; } - QColor hoverTitleColor() const { return hoverTitleColor_; } - QColor hoverSubtitleColor() const { return hoverSubtitleColor_; } - QColor hoverTimestampColor() const { return hoverTimestampColor_; } - QColor backgroundColor() const { return backgroundColor_; } - QColor avatarBgColor() const { return avatarBgColor_; } - QColor avatarFgColor() const { return avatarFgColor_; } - - QColor highlightedTitleColor() const { return highlightedTitleColor_; } - QColor highlightedSubtitleColor() const { return highlightedSubtitleColor_; } - QColor highlightedTimestampColor() const { return highlightedTimestampColor_; } - - QColor titleColor() const { return titleColor_; } - QColor subtitleColor() const { return subtitleColor_; } - QColor timestampColor() const { return timestampColor_; } - QColor btnColor() const { return btnColor_; } - QColor btnTextColor() const { return btnTextColor_; } - - QColor bubbleFgColor() const { return bubbleFgColor_; } - QColor bubbleBgColor() const { return bubbleBgColor_; } - QColor mentionedColor() const { return mentionedFontColor_; } - - void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; } - void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; } - void setHoverSubtitleColor(QColor &color) { hoverSubtitleColor_ = color; } - void setHoverTitleColor(QColor &color) { hoverTitleColor_ = color; } - void setHoverTimestampColor(QColor &color) { hoverTimestampColor_ = color; } - void setBackgroundColor(QColor &color) { backgroundColor_ = color; } - void setTimestampColor(QColor &color) { timestampColor_ = color; } - void setAvatarFgColor(QColor &color) { avatarFgColor_ = color; } - void setAvatarBgColor(QColor &color) { avatarBgColor_ = color; } - - void setHighlightedTitleColor(QColor &color) { highlightedTitleColor_ = color; } - void setHighlightedSubtitleColor(QColor &color) { highlightedSubtitleColor_ = color; } - void setHighlightedTimestampColor(QColor &color) { highlightedTimestampColor_ = color; } - - void setTitleColor(QColor &color) { titleColor_ = color; } - void setSubtitleColor(QColor &color) { subtitleColor_ = color; } - - void setBtnColor(QColor &color) { btnColor_ = color; } - void setBtnTextColor(QColor &color) { btnTextColor_ = color; } - - void setBubbleFgColor(QColor &color) { bubbleFgColor_ = color; } - void setBubbleBgColor(QColor &color) { bubbleBgColor_ = color; } - void setMentionedColor(QColor &color) { mentionedFontColor_ = color; } - -signals: - void clicked(); - -public slots: - void setPressedState(bool state); - -protected: - void mousePressEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - -private: - void init(QWidget *parent); - - RippleOverlay *ripple_overlay_; - - bool isPressed_ = false; - - bool hasUnreadMessages_ = true; - - int unreadMsgCount_ = 0; - - QHBoxLayout *topLayout_; - - QColor borderColor_; - QColor highlightedBackgroundColor_; - QColor hoverBackgroundColor_; - QColor backgroundColor_; - - QColor highlightedTitleColor_; - QColor highlightedSubtitleColor_; - - QColor titleColor_; - QColor subtitleColor_; - - QColor hoverTitleColor_; - QColor hoverSubtitleColor_; - - QColor btnColor_; - QColor btnTextColor_; - - QRectF acceptBtnRegion_; - QRectF declineBtnRegion_; - - // Fonts - QColor mentionedFontColor_; - QFont unreadCountFont_; - int bubbleDiameter_; - - QColor timestampColor_; - QColor highlightedTimestampColor_; - QColor hoverTimestampColor_; - - QColor avatarBgColor_; - QColor avatarFgColor_; - - QColor bubbleBgColor_; - QColor bubbleFgColor_; -}; \ No newline at end of file diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 49cb2c1f..9fd033e9 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp
@@ -22,9 +22,11 @@ #include <QLabel> #include <QLineEdit> #include <QMessageBox> +#include <QProcessEnvironment> #include <QPushButton> #include <QScrollArea> #include <QSettings> +#include <QString> #include <QTextStream> #include "Config.h" @@ -49,8 +51,9 @@ UserSettings::load() isGroupViewEnabled_ = settings.value("user/group_view", true).toBool(); isTypingNotificationsEnabled_ = settings.value("user/typing_notifications", true).toBool(); isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool(); - theme_ = settings.value("user/theme", "light").toString(); + theme_ = settings.value("user/theme", defaultTheme_).toString(); font_ = settings.value("user/font_family", "default").toString(); + avatarCircles_ = settings.value("user/avatar/circles", true).toBool(); emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); @@ -116,6 +119,10 @@ UserSettings::save() settings.setValue("start_in_tray", isStartInTrayEnabled_); settings.endGroup(); + settings.beginGroup("avatar"); + settings.setValue("circles", avatarCircles_); + settings.endGroup(); + settings.setValue("font_size", baseFontSize_); settings.setValue("typing_notifications", isTypingNotificationsEnabled_); settings.setValue("read_receipts", isReadReceiptsEnabled_); @@ -190,6 +197,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge groupViewLayout->addWidget(groupViewLabel); groupViewLayout->addWidget(groupViewToggle_, 0, Qt::AlignRight); + auto avatarViewLayout = new QHBoxLayout; + avatarViewLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); + auto avatarViewLabel = new QLabel(tr("Circular Avatars"), this); + avatarViewLabel->setFont(font); + avatarCircles_ = new Toggle(this); + + avatarViewLayout->addWidget(avatarViewLabel); + avatarViewLayout->addWidget(avatarCircles_, 0, Qt::AlignRight); + auto typingLayout = new QHBoxLayout; typingLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin); auto typingLabel = new QLabel(tr("Typing notifications"), this); @@ -366,6 +382,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge mainLayout_->addLayout(startInTrayOptionLayout_); mainLayout_->addWidget(new HorizontalLine(this)); mainLayout_->addLayout(groupViewLayout); + mainLayout_->addLayout(avatarViewLayout); mainLayout_->addWidget(new HorizontalLine(this)); mainLayout_->addLayout(typingLayout); mainLayout_->addLayout(receiptsLayout); @@ -446,6 +463,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge settings_->setGroupView(!isDisabled); }); + connect(avatarCircles_, &Toggle::toggled, this, [this](bool isDisabled) { + settings_->setAvatarCircles(!isDisabled); + }); + connect(typingNotifications_, &Toggle::toggled, this, [this](bool isDisabled) { settings_->setTypingNotifications(!isDisabled); }); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index ffff1e20..28236e83 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h
@@ -22,6 +22,7 @@ #include <QFrame> #include <QLabel> #include <QLayout> +#include <QProcessEnvironment> #include <QSharedPointer> #include <QWidget> @@ -85,7 +86,13 @@ public: save(); } - QString theme() const { return !theme_.isEmpty() ? theme_ : "light"; } + void setAvatarCircles(bool state) + { + avatarCircles_ = state; + save(); + } + + QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } bool isTrayEnabled() const { return isTrayEnabled_; } bool isStartInTrayEnabled() const { return isStartInTrayEnabled_; } bool isGroupViewEnabled() const { return isGroupViewEnabled_; } @@ -100,6 +107,11 @@ signals: void groupViewStateChanged(bool state); private: + // Default to system theme if QT_QPA_PLATFORMTHEME var is set. + QString defaultTheme_ = + QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() + ? "light" + : "system"; QString theme_; bool isTrayEnabled_; bool isStartInTrayEnabled_; @@ -107,6 +119,7 @@ private: bool isTypingNotificationsEnabled_; bool isReadReceiptsEnabled_; bool hasDesktopNotifications_; + bool avatarCircles_; double baseFontSize_; QString font_; QString emojiFont_; @@ -156,6 +169,7 @@ private: Toggle *typingNotifications_; Toggle *readReceipts_; Toggle *desktopNotifications_; + Toggle *avatarCircles_; QLabel *deviceFingerprintValue_; QLabel *deviceIdValue_; diff --git a/src/Utils.cpp b/src/Utils.cpp
index d6b092b1..8c02b1c2 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp
@@ -4,6 +4,7 @@ #include <QComboBox> #include <QDesktopWidget> #include <QGuiApplication> +#include <QProcessEnvironment> #include <QScreen> #include <QSettings> #include <QTextDocument> @@ -99,13 +100,13 @@ utils::descriptiveTime(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return then.toString("HH:mm"); + return then.time().toString(Qt::DefaultLocaleShortDate); else if (days < 2) - return QString("Yesterday"); - else if (days < 365) - return then.toString("dd/MM"); + return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); + else if (days < 7) + return then.toString("dddd"); - return then.toString("dd/MM/yy"); + return then.date().toString(Qt::DefaultLocaleShortDate); } DescInfo @@ -147,7 +148,7 @@ utils::getMessageDescription(const TimelineEvent &event, DescInfo info; if (sender == localUser) - info.username = "You"; + info.username = QCoreApplication::translate("utils", "You"); else info.username = username; @@ -323,10 +324,25 @@ utils::linkifyMessage(const QString &body) return doc; } +QByteArray escapeRawHtml(const QByteArray &data) { + QByteArray buffer; + const size_t length = data.size(); + buffer.reserve(length); + for(size_t pos = 0; pos != length; ++pos) { + switch(data.at(pos)) { + case '&': buffer.append("&amp;"); break; + case '<': buffer.append("&lt;"); break; + case '>': buffer.append("&gt;"); break; + default: buffer.append(data.at(pos)); break; + } + } + return buffer; +} + QString utils::markdownToHtml(const QString &text) { - const auto str = text.toUtf8(); + const auto str = escapeRawHtml(text.toUtf8()); const char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_DEFAULT); @@ -366,16 +382,16 @@ utils::getQuoteBody(const RelatedInfo &related) return markdownToHtml(related.quoted_body); } case MsgType::File: { - return QString("sent a file."); + return QString(QCoreApplication::translate("utils", "sent a file.")); } case MsgType::Image: { - return QString("sent an image."); + return QString(QCoreApplication::translate("utils", "sent an image.")); } case MsgType::Audio: { - return QString("sent an audio file."); + return QString(QCoreApplication::translate("utils", "sent an audio file.")); } case MsgType::Video: { - return QString("sent a video"); + return QString(QCoreApplication::translate("utils", "sent a video")); } default: { return related.quoted_body; @@ -387,14 +403,20 @@ QString utils::linkColor() { QSettings settings; - const auto theme = settings.value("user/theme", "light").toString(); + // Default to system theme if QT_QPA_PLATFORMTHEME var is set. + QString defaultTheme = + QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty() + ? "light" + : "system"; + const auto theme = settings.value("user/theme", defaultTheme).toString(); - if (theme == "light") + if (theme == "light") { return "#0077b5"; - else if (theme == "dark") + } else if (theme == "dark") { return "#38A3D8"; - - return QPalette().color(QPalette::Link).name(); + } else { + return QPalette().color(QPalette::Link).name(); + } } uint32_t diff --git a/src/Utils.h b/src/Utils.h
index 756dc1e3..225754be 100644 --- a/src/Utils.h +++ b/src/Utils.h
@@ -9,6 +9,7 @@ #include "timeline/widgets/ImageItem.h" #include "timeline/widgets/VideoItem.h" +#include <QCoreApplication> #include <QDateTime> #include <QPixmap> #include <mtx/events/collections.hpp> @@ -79,7 +80,9 @@ event_body(const mtx::events::collections::TimelineEvents &event); //! Match widgets/events with a description message. template<class T> QString -messageDescription(const QString &username = "", const QString &body = "") +messageDescription(const QString &username = "", + const QString &body = "", + const bool isLocal = false) { using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>; using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>; @@ -91,24 +94,41 @@ messageDescription(const QString &username = "", const QString &body = "") using Video = mtx::events::RoomEvent<mtx::events::msg::Video>; using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>; - if (std::is_same<T, AudioItem>::value || std::is_same<T, Audio>::value) - return QString("sent an audio clip"); - else if (std::is_same<T, ImageItem>::value || std::is_same<T, Image>::value) - return QString("sent an image"); - else if (std::is_same<T, FileItem>::value || std::is_same<T, File>::value) - return QString("sent a file"); - else if (std::is_same<T, VideoItem>::value || std::is_same<T, Video>::value) - return QString("sent a video clip"); - else if (std::is_same<T, StickerItem>::value || std::is_same<T, Sticker>::value) - return QString("sent a sticker"); - else if (std::is_same<T, Notice>::value) - return QString("sent a notification"); - else if (std::is_same<T, Text>::value) + // Sometimes the verb form of sent changes in some languages depending on the actor. + auto remoteSent = QCoreApplication::translate( + "message-description: ", "sent", "For when you are the sender"); + auto localSent = QCoreApplication::translate( + "message-description:", "sent", "For when someone else is the sender"); + QString sentVerb = isLocal ? localSent : remoteSent; + if (std::is_same<T, AudioItem>::value || std::is_same<T, Audio>::value) { + return QCoreApplication::translate("message-description sent:", "%1 an audio clip") + .arg(sentVerb); + } else if (std::is_same<T, ImageItem>::value || std::is_same<T, Image>::value) { + return QCoreApplication::translate("message-description sent:", "%1 an image") + .arg(sentVerb); + } else if (std::is_same<T, FileItem>::value || std::is_same<T, File>::value) { + return QCoreApplication::translate("message-description sent:", "%1 a file") + .arg(sentVerb); + } else if (std::is_same<T, VideoItem>::value || std::is_same<T, Video>::value) { + return QCoreApplication::translate("message-description sent:", "%1 a video clip") + .arg(sentVerb); + } else if (std::is_same<T, StickerItem>::value || std::is_same<T, Sticker>::value) { + return QCoreApplication::translate("message-description sent:", "%1 a sticker") + .arg(sentVerb); + } else if (std::is_same<T, Notice>::value) { + return QCoreApplication::translate("message-description sent:", "%1 a notification") + .arg(sentVerb); + } else if (std::is_same<T, Text>::value) { return QString(": %1").arg(body); - else if (std::is_same<T, Emote>::value) + } else if (std::is_same<T, Emote>::value) { return QString("* %1 %2").arg(username).arg(body); - else if (std::is_same<T, Encrypted>::value) - return QString("sent an encrypted message"); + } else if (std::is_same<T, Encrypted>::value) { + return QCoreApplication::translate("message-description sent:", + "%1 an encrypted message") + .arg(sentVerb); + } else { + return QCoreApplication::translate("utils", "Unknown Message Type"); + } } template<class T, class Event> @@ -129,10 +149,12 @@ createDescriptionInfo(const Event &event, const QString &localUser, const QStrin return DescInfo{ QString::fromStdString(msg.event_id), - isEmote ? "" : (sender == localUser ? "You" : username), + isEmote ? "" + : (sender == localUser ? QCoreApplication::translate("utils", "You") : username), sender, (isText || isEmote) - ? messageDescription<T>(username, QString::fromStdString(msg.content.body).trimmed()) + ? messageDescription<T>( + username, QString::fromStdString(msg.content.body).trimmed(), sender == localUser) : QString(" %1").arg(messageDescription<T>()), utils::descriptiveTime(ts), ts}; diff --git a/src/dialogs/MemberList.cpp b/src/dialogs/MemberList.cpp
index f4167143..9e973efa 100644 --- a/src/dialogs/MemberList.cpp +++ b/src/dialogs/MemberList.cpp
@@ -9,7 +9,6 @@ #include "dialogs/MemberList.h" -#include "AvatarProvider.h" #include "Cache.h" #include "ChatPage.h" #include "Config.h" @@ -28,17 +27,10 @@ MemberItem::MemberItem(const RoomMember &member, QWidget *parent) textLayout_->setMargin(0); textLayout_->setSpacing(0); - avatar_ = new Avatar(this); - avatar_->setSize(44); + avatar_ = new Avatar(this, 44); avatar_->setLetter(utils::firstChar(member.display_name)); - if (!member.avatar.isNull()) - avatar_->setImage(member.avatar); - else - AvatarProvider::resolve(ChatPage::instance()->currentRoom(), - member.user_id, - this, - [this](const QImage &img) { avatar_->setImage(img); }); + avatar_->setImage(ChatPage::instance()->currentRoom(), member.user_id); QFont nameFont; nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); @@ -97,7 +89,7 @@ MemberList::MemberList(const QString &room_id, QWidget *parent) topLabel_->setAlignment(Qt::AlignCenter); topLabel_->setFont(font); - auto okBtn = new QPushButton("OK", this); + auto okBtn = new QPushButton(tr("OK"), this); auto buttonLayout = new QHBoxLayout(); buttonLayout->setSpacing(15); @@ -126,7 +118,7 @@ MemberList::MemberList(const QString &room_id, QWidget *parent) qCritical() << e.what(); } - auto closeShortcut = new QShortcut(QKeySequence(tr("ESC")), this); + auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); connect(closeShortcut, &QShortcut::activated, this, &MemberList::close); connect(okBtn, &QPushButton::clicked, this, &MemberList::close); } diff --git a/src/dialogs/ReadReceipts.cpp b/src/dialogs/ReadReceipts.cpp
index dc4145db..58ad59c3 100644 --- a/src/dialogs/ReadReceipts.cpp +++ b/src/dialogs/ReadReceipts.cpp
@@ -37,8 +37,7 @@ ReceiptItem::ReceiptItem(QWidget *parent, auto displayName = Cache::displayName(room_id, user_id); - avatar_ = new Avatar(this); - avatar_->setSize(44); + avatar_ = new Avatar(this, 44); avatar_->setLetter(utils::firstChar(displayName)); // If it's a matrix id we use the second letter. @@ -56,10 +55,7 @@ ReceiptItem::ReceiptItem(QWidget *parent, topLayout_->addWidget(avatar_); topLayout_->addLayout(textLayout_, 1); - AvatarProvider::resolve(ChatPage::instance()->currentRoom(), - user_id, - this, - [this](const QImage &img) { avatar_->setImage(img); }); + avatar_->setImage(ChatPage::instance()->currentRoom(), user_id); } void @@ -78,13 +74,15 @@ ReceiptItem::dateFormat(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return QString("Today %1").arg(then.toString("HH:mm")); + return tr("Today %1").arg(then.time().toString(Qt::DefaultLocaleShortDate)); else if (days < 2) - return QString("Yesterday %1").arg(then.toString("HH:mm")); - else if (days < 365) - return then.toString("dd/MM HH:mm"); + return tr("Yesterday %1").arg(then.time().toString(Qt::DefaultLocaleShortDate)); + else if (days < 7) + return QString("%1 %2") + .arg(then.toString("dddd")) + .arg(then.time().toString(Qt::DefaultLocaleShortDate)); - return then.toString("dd/MM/yy"); + return then.toString(Qt::DefaultLocaleShortDate); } ReadReceipts::ReadReceipts(QWidget *parent) @@ -131,7 +129,7 @@ ReadReceipts::ReadReceipts(QWidget *parent) layout->addWidget(userList_); layout->addLayout(buttonLayout); - auto closeShortcut = new QShortcut(QKeySequence(tr("ESC")), this); + auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); connect(closeShortcut, &QShortcut::activated, this, &ReadReceipts::close); connect(okBtn, &QPushButton::clicked, this, &ReadReceipts::close); } diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp
index b2344f23..00b034cc 100644 --- a/src/dialogs/RoomSettings.cpp +++ b/src/dialogs/RoomSettings.cpp
@@ -350,12 +350,12 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) keyRequestsToggle_->hide(); } - avatar_ = new Avatar(this); - avatar_->setSize(128); + avatar_ = new Avatar(this, 128); if (avatarImg_.isNull()) avatar_->setLetter(utils::firstChar(QString::fromStdString(info_.name))); else - avatar_->setImage(avatarImg_); + avatar_->setImage(room_id_, + QString::fromStdString(http::client()->user_id().to_string())); if (canChangeAvatar(room_id_.toStdString(), utils::localUser().toStdString())) { auto filter = new ClickableFilter(this); @@ -438,7 +438,7 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) resetErrorLabel(); }); - auto closeShortcut = new QShortcut(QKeySequence(tr("ESC")), this); + auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); connect(closeShortcut, &QShortcut::activated, this, &RoomSettings::close); connect(okBtn, &QPushButton::clicked, this, &RoomSettings::close); } @@ -487,7 +487,7 @@ RoomSettings::retrieveRoomInfo() try { usesEncryption_ = cache::client()->isRoomEncrypted(room_id_.toStdString()); info_ = cache::client()->singleRoomInfo(room_id_.toStdString()); - setAvatar(QImage::fromData(cache::client()->image(info_.avatar_url))); + setAvatar(); } catch (const lmdb::error &e) { nhlog::db()->warn("failed to retrieve room info from cache: {}", room_id_.toStdString()); @@ -633,14 +633,13 @@ RoomSettings::displayErrorMessage(const QString &msg) } void -RoomSettings::setAvatar(const QImage &img) +RoomSettings::setAvatar() { stopLoadingSpinner(); - avatarImg_ = img; - if (avatar_) - avatar_->setImage(img); + avatar_->setImage(room_id_, + QString::fromStdString(http::client()->user_id().to_string())); } void @@ -668,12 +667,12 @@ RoomSettings::updateAvatar() QFile file{fileName, this}; if (format != "image") { - displayErrorMessage(tr("The selected media is not an image")); + displayErrorMessage(tr("The selected file is not an image")); return; } if (!file.open(QIODevice::ReadOnly)) { - displayErrorMessage(tr("Error while reading media: %1").arg(file.errorString())); + displayErrorMessage(tr("Error while reading file: %1").arg(file.errorString())); return; } @@ -733,7 +732,7 @@ RoomSettings::updateAvatar() return; } - emit proxy->avatarChanged(QImage::fromData(content)); + emit proxy->avatarChanged(); }); }); } diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettings.h
index 6667b68b..e1807ba1 100644 --- a/src/dialogs/RoomSettings.h +++ b/src/dialogs/RoomSettings.h
@@ -52,7 +52,7 @@ class ThreadProxy : public QObject signals: void error(const QString &msg); - void avatarChanged(const QImage &img); + void avatarChanged(); void nameEventSent(const QString &); void topicEventSent(); }; @@ -140,7 +140,7 @@ private: void resetErrorLabel(); void displayErrorMessage(const QString &msg); - void setAvatar(const QImage &img); + void setAvatar(); void setupEditButton(); //! Retrieve the current room information from cache. void retrieveRoomInfo(); diff --git a/src/dialogs/UserMentions.cpp b/src/dialogs/UserMentions.cpp deleted file mode 100644
index 8f56ec93..00000000 --- a/src/dialogs/UserMentions.cpp +++ /dev/null
@@ -1,58 +0,0 @@ -#include <QTimer> - -#include "UserMentions.h" -#include "timeline/TimelineItem.h" - -using namespace dialogs; - -UserMentions::UserMentions(QWidget *parent) - : QWidget{parent} -{ - top_layout_ = new QVBoxLayout(this); - top_layout_->setSpacing(0); - top_layout_->setMargin(0); - - scroll_area_ = new QScrollArea(this); - scroll_area_->setWidgetResizable(true); - scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - scroll_widget_ = new QWidget(this); - scroll_widget_->setObjectName("scroll_widget"); - - // Height of the typing display. - QFont f; - f.setPointSizeF(f.pointSizeF() * 0.9); - const int bottomMargin = QFontMetrics(f).height() + 6; - - scroll_layout_ = new QVBoxLayout(scroll_widget_); - scroll_layout_->setContentsMargins(4, 0, 15, bottomMargin); - scroll_layout_->setSpacing(0); - scroll_layout_->setObjectName("timelinescrollarea"); - - scroll_area_->setWidget(scroll_widget_); - scroll_area_->setAlignment(Qt::AlignBottom); - - top_layout_->addWidget(scroll_area_); - - setLayout(top_layout_); -} - -void -UserMentions::pushItem(const QString &event_id, - const QString &user_id, - const QString &body, - const QString &room_id) -{ - TimelineItem *view_item = new TimelineItem( - mtx::events::MessageType::Text, user_id, body, true, room_id, scroll_widget_); - view_item->setEventId(event_id); - setUpdatesEnabled(false); - view_item->hide(); - - scroll_layout_->addWidget(view_item); - QTimer::singleShot(0, this, [view_item, this]() { - view_item->show(); - view_item->adjustSize(); - setUpdatesEnabled(true); - }); -} \ No newline at end of file diff --git a/src/dialogs/UserMentions.h b/src/dialogs/UserMentions.h deleted file mode 100644
index e995b207..00000000 --- a/src/dialogs/UserMentions.h +++ /dev/null
@@ -1,28 +0,0 @@ -#pragma once - -#include <QScrollArea> -#include <QScrollBar> -#include <QVBoxLayout> -#include <QWidget> - -namespace dialogs { - -class UserMentions : public QWidget -{ - Q_OBJECT -public: - UserMentions(QWidget *parent = nullptr); - void pushItem(const QString &event_id, - const QString &user_id, - const QString &body, - const QString &room_id); - -private: - QVBoxLayout *top_layout_; - QVBoxLayout *scroll_layout_; - - QScrollArea *scroll_area_; - QWidget *scroll_widget_; -}; - -} \ No newline at end of file diff --git a/src/dialogs/UserProfile.cpp b/src/dialogs/UserProfile.cpp
index b8040f9f..5ad3afa2 100644 --- a/src/dialogs/UserProfile.cpp +++ b/src/dialogs/UserProfile.cpp
@@ -114,9 +114,8 @@ UserProfile::UserProfile(QWidget *parent) btnLayout->setSpacing(8); btnLayout->setMargin(0); - avatar_ = new Avatar(this); + avatar_ = new Avatar(this, 128); avatar_->setLetter("X"); - avatar_->setSize(128); QFont font; font.setPointSizeF(font.pointSizeF() * 2); @@ -183,7 +182,7 @@ UserProfile::UserProfile(QWidget *parent) qRegisterMetaType<std::vector<DeviceInfo>>(); - auto closeShortcut = new QShortcut(QKeySequence(tr("ESC")), this); + auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); connect(closeShortcut, &QShortcut::activated, this, &UserProfile::close); connect(okBtn, &QPushButton::clicked, this, &UserProfile::close); } @@ -210,8 +209,7 @@ UserProfile::init(const QString &userId, const QString &roomId) displayNameLabel_->setText(displayName); avatar_->setLetter(utils::firstChar(displayName)); - AvatarProvider::resolve( - roomId, userId, this, [this](const QImage &img) { avatar_->setImage(img); }); + avatar_->setImage(roomId, userId); auto localUser = utils::localUser(); diff --git a/src/popups/PopupItem.cpp b/src/popups/PopupItem.cpp
index f905983a..c4d4327f 100644 --- a/src/popups/PopupItem.cpp +++ b/src/popups/PopupItem.cpp
@@ -11,7 +11,7 @@ constexpr int PopupItemMargin = 3; PopupItem::PopupItem(QWidget *parent) : QWidget(parent) - , avatar_{new Avatar(this)} + , avatar_{new Avatar(this, conf::popup::avatar)} , hovering_{false} { setMouseTracking(true); @@ -40,7 +40,6 @@ UserItem::UserItem(QWidget *parent) : PopupItem(parent) { userName_ = new QLabel("Placeholder", this); - avatar_->setSize(conf::popup::avatar); avatar_->setLetter("P"); topLayout_->addWidget(avatar_); topLayout_->addWidget(userName_, 1); @@ -52,7 +51,6 @@ UserItem::UserItem(QWidget *parent, const QString &user_id) { auto displayName = Cache::displayName(ChatPage::instance()->currentRoom(), userId_); - avatar_->setSize(conf::popup::avatar); avatar_->setLetter(utils::firstChar(displayName)); // If it's a matrix id we use the second letter. @@ -87,16 +85,7 @@ UserItem::updateItem(const QString &user_id) void UserItem::resolveAvatar(const QString &user_id) { - AvatarProvider::resolve( - ChatPage::instance()->currentRoom(), userId_, this, [this, user_id](const QImage &img) { - // The user on the widget when the avatar is resolved, - // might be different from the user that made the call. - if (user_id == userId_) - avatar_->setImage(img); - else - // We try to resolve the avatar again. - resolveAvatar(userId_); - }); + avatar_->setImage(ChatPage::instance()->currentRoom(), user_id); } void @@ -116,7 +105,6 @@ RoomItem::RoomItem(QWidget *parent, const RoomSearchResult &res) auto name = QFontMetrics(QFont()).elidedText( QString::fromStdString(res.info.name), Qt::ElideRight, parentWidget()->width() - 10); - avatar_->setSize(conf::popup::avatar + 6); avatar_->setLetter(utils::firstChar(name)); roomName_ = new QLabel(name, this); @@ -125,8 +113,7 @@ RoomItem::RoomItem(QWidget *parent, const RoomSearchResult &res) topLayout_->addWidget(avatar_); topLayout_->addWidget(roomName_, 1); - if (!res.img.isNull()) - avatar_->setImage(res.img); + avatar_->setImage(QString::fromStdString(res.info.avatar_url)); } void @@ -141,10 +128,7 @@ RoomItem::updateItem(const RoomSearchResult &result) roomName_->setText(name); - if (!result.img.isNull()) - avatar_->setImage(result.img); - else - avatar_->setLetter(utils::firstChar(name)); + avatar_->setImage(QString::fromStdString(result.info.avatar_url)); } void @@ -154,4 +138,4 @@ RoomItem::mousePressEvent(QMouseEvent *event) emit clicked(selectedText()); QWidget::mousePressEvent(event); -} \ No newline at end of file +} diff --git a/src/popups/UserMentions.cpp b/src/popups/UserMentions.cpp new file mode 100644
index 00000000..3480959a --- /dev/null +++ b/src/popups/UserMentions.cpp
@@ -0,0 +1,161 @@ +#include <QPainter> +#include <QStyleOption> +#include <QTabWidget> +#include <QTimer> + +#include "Cache.h" +#include "ChatPage.h" +#include "Logging.h" +#include "UserMentions.h" +#include "timeline/TimelineItem.h" + +using namespace popups; + +UserMentions::UserMentions(QWidget *parent) + : QWidget{parent} +{ + setAttribute(Qt::WA_ShowWithoutActivating, true); + setWindowFlags(Qt::FramelessWindowHint | Qt::Popup); + + tab_layout_ = new QTabWidget(this); + + top_layout_ = new QVBoxLayout(this); + top_layout_->setSpacing(0); + top_layout_->setMargin(0); + + local_scroll_area_ = new QScrollArea(this); + local_scroll_area_->setWidgetResizable(true); + local_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + local_scroll_widget_ = new QWidget(this); + local_scroll_widget_->setObjectName("local_scroll_widget"); + + all_scroll_area_ = new QScrollArea(this); + all_scroll_area_->setWidgetResizable(true); + all_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + all_scroll_widget_ = new QWidget(this); + all_scroll_widget_->setObjectName("all_scroll_widget"); + + // Height of the typing display. + QFont f; + f.setPointSizeF(f.pointSizeF() * 0.9); + const int bottomMargin = QFontMetrics(f).height() + 6; + + local_scroll_layout_ = new QVBoxLayout(local_scroll_widget_); + local_scroll_layout_->setContentsMargins(4, 0, 15, bottomMargin); + local_scroll_layout_->setSpacing(0); + local_scroll_layout_->setObjectName("localscrollarea"); + + all_scroll_layout_ = new QVBoxLayout(all_scroll_widget_); + all_scroll_layout_->setContentsMargins(4, 0, 15, bottomMargin); + all_scroll_layout_->setSpacing(0); + all_scroll_layout_->setObjectName("allscrollarea"); + + local_scroll_area_->setWidget(local_scroll_widget_); + local_scroll_area_->setAlignment(Qt::AlignBottom); + + all_scroll_area_->setWidget(all_scroll_widget_); + all_scroll_area_->setAlignment(Qt::AlignBottom); + + tab_layout_->addTab(local_scroll_area_, tr("This Room")); + tab_layout_->addTab(all_scroll_area_, tr("All Rooms")); + top_layout_->addWidget(tab_layout_); + + setLayout(top_layout_); +} + +void +UserMentions::initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs) +{ + nhlog::ui()->debug("Initializing " + std::to_string(notifs.size()) + " notifications."); + + for (const auto &item : notifs) { + for (const auto notif : item.notifications) { + const auto event_id = QString::fromStdString(utils::event_id(notif.event)); + + try { + const auto room_id = QString::fromStdString(notif.room_id); + const auto user_id = utils::event_sender(notif.event); + const auto body = utils::event_body(notif.event); + + pushItem(event_id, + user_id, + body, + room_id, + ChatPage::instance()->currentRoom()); + + } catch (const lmdb::error &e) { + nhlog::db()->warn("error while sending desktop notification: {}", + e.what()); + } + } + } +} + +void +UserMentions::showPopup() +{ + for (auto widget : all_scroll_layout_->findChildren<QWidget *>()) { + delete widget; + } + for (auto widget : local_scroll_layout_->findChildren<QWidget *>()) { + delete widget; + } + + auto notifs = cache::client()->getTimelineMentions(); + + initializeMentions(notifs); + show(); +} + +void +UserMentions::pushItem(const QString &event_id, + const QString &user_id, + const QString &body, + const QString &room_id, + const QString &current_room_id) +{ + setUpdatesEnabled(false); + + // Add to the 'all' section + TimelineItem *view_item = new TimelineItem( + mtx::events::MessageType::Text, user_id, body, true, room_id, all_scroll_widget_); + view_item->setEventId(event_id); + view_item->hide(); + + all_scroll_layout_->addWidget(view_item); + QTimer::singleShot(0, this, [view_item, this]() { + view_item->show(); + view_item->adjustSize(); + setUpdatesEnabled(true); + }); + + // if it matches the current room... add it to the current room as well. + if (QString::compare(room_id, current_room_id, Qt::CaseInsensitive) == 0) { + // Add to the 'local' section + TimelineItem *local_view_item = new TimelineItem(mtx::events::MessageType::Text, + user_id, + body, + true, + room_id, + local_scroll_widget_); + local_view_item->setEventId(event_id); + local_view_item->hide(); + local_scroll_layout_->addWidget(local_view_item); + + QTimer::singleShot(0, this, [local_view_item]() { + local_view_item->show(); + local_view_item->adjustSize(); + }); + } +} + +void +UserMentions::paintEvent(QPaintEvent *) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} \ No newline at end of file diff --git a/src/popups/UserMentions.h b/src/popups/UserMentions.h new file mode 100644
index 00000000..d7dfc575 --- /dev/null +++ b/src/popups/UserMentions.h
@@ -0,0 +1,51 @@ +#pragma once + +#include <mtx/responses.hpp> + +#include <QApplication> +#include <QEvent> +#include <QMap> +#include <QPaintEvent> +#include <QScrollArea> +#include <QScrollBar> +#include <QString> +#include <QTabWidget> +#include <QVBoxLayout> +#include <QWidget> +#include <Qt> + +#include "Logging.h" + +namespace popups { + +class UserMentions : public QWidget +{ + Q_OBJECT +public: + UserMentions(QWidget *parent = nullptr); + + void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs); + void showPopup(); + +protected: + void paintEvent(QPaintEvent *) override; + +private: + void pushItem(const QString &event_id, + const QString &user_id, + const QString &body, + const QString &room_id, + const QString &current_room_id); + QTabWidget *tab_layout_; + QVBoxLayout *top_layout_; + QVBoxLayout *local_scroll_layout_; + QVBoxLayout *all_scroll_layout_; + + QScrollArea *local_scroll_area_; + QWidget *local_scroll_widget_; + + QScrollArea *all_scroll_area_; + QWidget *all_scroll_widget_; +}; + +} \ No newline at end of file diff --git a/src/timeline/.TimelineItem.cpp.swp b/src/timeline/.TimelineItem.cpp.swp new file mode 100644
index 00000000..75e03aeb --- /dev/null +++ b/src/timeline/.TimelineItem.cpp.swp
Binary files differdiff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp
index e52dce7b..7916bd80 100644 --- a/src/timeline/TimelineItem.cpp +++ b/src/timeline/TimelineItem.cpp
@@ -282,6 +282,7 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty, const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(ty) , room_id_{room_id} { init(); @@ -325,8 +326,7 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty, generateBody(userid, displayName, formatted_body); setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(userid); } else { generateBody(formatted_body); setupSimpleLayout(); @@ -341,6 +341,7 @@ TimelineItem::TimelineItem(ImageItem *image, const QString &room_id, QWidget *parent) : QWidget{parent} + , message_type_(mtx::events::MessageType::Image) , room_id_{room_id} { init(); @@ -356,6 +357,7 @@ TimelineItem::TimelineItem(FileItem *file, const QString &room_id, QWidget *parent) : QWidget{parent} + , message_type_(mtx::events::MessageType::File) , room_id_{room_id} { init(); @@ -369,6 +371,7 @@ TimelineItem::TimelineItem(AudioItem *audio, const QString &room_id, QWidget *parent) : QWidget{parent} + , message_type_(mtx::events::MessageType::Audio) , room_id_{room_id} { init(); @@ -382,6 +385,7 @@ TimelineItem::TimelineItem(VideoItem *video, const QString &room_id, QWidget *parent) : QWidget{parent} + , message_type_(mtx::events::MessageType::Video) , room_id_{room_id} { init(); @@ -395,6 +399,7 @@ TimelineItem::TimelineItem(ImageItem *image, const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::Image) , room_id_{room_id} { setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>( @@ -426,6 +431,7 @@ TimelineItem::TimelineItem(FileItem *file, const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::File) , room_id_{room_id} { setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>( @@ -440,6 +446,7 @@ TimelineItem::TimelineItem(AudioItem *audio, const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::Audio) , room_id_{room_id} { setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>( @@ -454,6 +461,7 @@ TimelineItem::TimelineItem(VideoItem *video, const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::Video) , room_id_{room_id} { setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>( @@ -470,6 +478,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::Notice) , room_id_{room_id} { init(); @@ -499,8 +508,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice generateBody(sender, displayName, formatted_body); setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(sender); } else { generateBody(formatted_body); setupSimpleLayout(); @@ -517,6 +525,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::Emote) , room_id_{room_id} { init(); @@ -547,8 +556,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> generateBody(sender, displayName, formatted_body); setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(sender); } else { generateBody(formatted_body); setupSimpleLayout(); @@ -565,6 +573,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> const QString &room_id, QWidget *parent) : QWidget(parent) + , message_type_(mtx::events::MessageType::Text) , room_id_{room_id} { init(); @@ -595,8 +604,7 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> generateBody(sender, displayName, formatted_body); setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(sender); } else { generateBody(formatted_body); setupSimpleLayout(); @@ -781,9 +789,8 @@ TimelineItem::setupAvatarLayout(const QString &userName) QFont f; f.setPointSizeF(f.pointSizeF()); - userAvatar_ = new Avatar(this); + userAvatar_ = new Avatar(this, QFontMetrics(f).height() * 2); userAvatar_->setLetter(QChar(userName[0]).toUpper()); - userAvatar_->setSize(QFontMetrics(f).height() * 2); // TODO: The provided user name should be a UserId class if (userName[0] == '@' && userName.size() > 1) @@ -810,12 +817,12 @@ TimelineItem::setupSimpleLayout() } void -TimelineItem::setUserAvatar(const QImage &avatar) +TimelineItem::setUserAvatar(const QString &userid) { if (userAvatar_ == nullptr) return; - userAvatar_->setImage(avatar); + userAvatar_->setImage(room_id_, userid); } void @@ -899,8 +906,7 @@ TimelineItem::addAvatar() setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(userid); } void @@ -951,4 +957,4 @@ TimelineItem::openRawMessageViewer() const "failed to serialize event ({}, {})", room_id, event_id); } }); -} \ No newline at end of file +} diff --git a/src/timeline/TimelineItem.h b/src/timeline/TimelineItem.h
index c0dab6b8..356976e5 100644 --- a/src/timeline/TimelineItem.h +++ b/src/timeline/TimelineItem.h
@@ -215,7 +215,7 @@ public: void setBackgroundColor(const QColor &color) { backgroundColor_ = color; } QColor backgroundColor() const { return backgroundColor_; } - void setUserAvatar(const QImage &pixmap); + void setUserAvatar(const QString &userid); DescInfo descriptionMessage() const { return descriptionMsg_; } QString eventId() const { return event_id_; } void setEventId(const QString &event_id) { event_id_ = event_id; } @@ -277,7 +277,7 @@ private: QFutureWatcher<QString> *colorGenerating_; QString event_id_; - mtx::events::MessageType message_type_; + mtx::events::MessageType message_type_ = mtx::events::MessageType::Unknown; QString room_id_; DescInfo descriptionMsg_; @@ -336,8 +336,7 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool generateBody(userid, displayName, ""); setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(userid); } else { setupSimpleLayout(); } @@ -381,8 +380,7 @@ TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSen generateBody(sender, displayName, ""); setupAvatarLayout(displayName); - AvatarProvider::resolve( - room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); }); + setUserAvatar(sender); } else { setupSimpleLayout(); } diff --git a/src/timeline/TimelineView.cpp b/src/timeline/TimelineView.cpp
index 18b73eb0..ed783e90 100644 --- a/src/timeline/TimelineView.cpp +++ b/src/timeline/TimelineView.cpp
@@ -306,7 +306,10 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events: dummy.origin_server_ts = e.origin_server_ts; dummy.event_id = e.event_id; dummy.sender = e.sender; - dummy.content.body = "-- Encrypted Event (No keys found for decryption) --"; + dummy.content.body = + tr("-- Encrypted Event (No keys found for decryption) --", + "Placeholder, when the message was not decrypted yet or can't be decrypted") + .toStdString(); try { if (!cache::client()->inboundMegolmSessionExists(index)) { @@ -319,7 +322,10 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events: } } catch (const lmdb::error &e) { nhlog::db()->critical("failed to check megolm session's existence: {}", e.what()); - dummy.content.body = "-- Decryption Error (failed to communicate with DB) --"; + dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --", + "Placeholder, when the message can't be decrypted, because " + "the DB access failed when trying to lookup the session.") + .toStdString(); return {dummy, false}; } @@ -335,7 +341,10 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events: index.sender_key, e.what()); dummy.content.body = - "-- Decryption Error (failed to retrieve megolm keys from db) --"; + tr("-- Decryption Error (failed to retrieve megolm keys from db) --", + "Placeholder, when the message can't be decrypted, because the DB access " + "failed.") + .toStdString(); return {dummy, false}; } catch (const mtx::crypto::olm_exception &e) { nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}", @@ -343,7 +352,12 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events: index.session_id, index.sender_key, e.what()); - dummy.content.body = "-- Decryption Error (" + std::string(e.what()) + ") --"; + dummy.content.body = + tr("-- Decryption Error (%1) --", + "Placeholder, when the message can't be decrypted. In this case, the Olm " + "decrytion returned an error, which is passed ad %1") + .arg(e.what()) + .toStdString(); return {dummy, false}; } @@ -365,7 +379,11 @@ TimelineView::parseEncryptedEvent(const mtx::events::EncryptedEvent<mtx::events: if (events.size() == 1) return {events.at(0), true}; - dummy.content.body = "-- Encrypted Event (Unknown event type) --"; + dummy.content.body = + tr("-- Encrypted Event (Unknown event type) --", + "Placeholder, when the message was decrypted, but we couldn't parse it, because " + "Nheko/mtxclient don't support that event type yet") + .toStdString(); return {dummy, false}; } diff --git a/src/ui/Avatar.cpp b/src/ui/Avatar.cpp
index 4b4cd272..501a8968 100644 --- a/src/ui/Avatar.cpp +++ b/src/ui/Avatar.cpp
@@ -1,12 +1,14 @@ #include <QPainter> +#include <QSettings> +#include "AvatarProvider.h" #include "Utils.h" #include "ui/Avatar.h" -Avatar::Avatar(QWidget *parent) +Avatar::Avatar(QWidget *parent, int size) : QWidget(parent) + , size_(size) { - size_ = ui::AvatarSize; type_ = ui::AvatarType::Letter; letter_ = "A"; @@ -61,35 +63,31 @@ Avatar::setBackgroundColor(const QColor &color) } void -Avatar::setSize(int size) +Avatar::setLetter(const QString &letter) { - size_ = size; - - if (!image_.isNull()) - pixmap_ = utils::scaleImageToPixmap(image_, size_); - - QFont _font(font()); - _font.setPointSizeF(size_ * (ui::FontSize) / 40); - - setFont(_font); + letter_ = letter; + type_ = ui::AvatarType::Letter; update(); } void -Avatar::setLetter(const QString &letter) +Avatar::setImage(const QString &avatar_url) { - letter_ = letter; - type_ = ui::AvatarType::Letter; - update(); + AvatarProvider::resolve(avatar_url, size_, this, [this](QPixmap pm) { + type_ = ui::AvatarType::Image; + pixmap_ = pm; + update(); + }); } void -Avatar::setImage(const QImage &image) +Avatar::setImage(const QString &room, const QString &user) { - image_ = image; - type_ = ui::AvatarType::Image; - pixmap_ = utils::scaleImageToPixmap(image_, size_); - update(); + AvatarProvider::resolve(room, user, size_, this, [this](QPixmap pm) { + type_ = ui::AvatarType::Image; + pixmap_ = pm; + update(); + }); } void @@ -103,6 +101,8 @@ Avatar::setIcon(const QIcon &icon) void Avatar::paintEvent(QPaintEvent *) { + bool rounded = QSettings().value("user/avatar/circles", true).toBool(); + QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); @@ -116,7 +116,8 @@ Avatar::paintEvent(QPaintEvent *) painter.setPen(Qt::NoPen); painter.setBrush(brush); - painter.drawEllipse(r.center(), hs, hs); + rounded ? painter.drawEllipse(r.center(), hs, hs) + : painter.drawRoundedRect(r, 3, 3); } switch (type_) { @@ -129,7 +130,10 @@ Avatar::paintEvent(QPaintEvent *) } case ui::AvatarType::Image: { QPainterPath ppath; - ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_); + + rounded ? ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_) + : ppath.addRoundedRect(r, 3, 3); + painter.setClipPath(ppath); painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_), pixmap_); diff --git a/src/ui/Avatar.h b/src/ui/Avatar.h
index 41967af5..a643c8c4 100644 --- a/src/ui/Avatar.h +++ b/src/ui/Avatar.h
@@ -15,13 +15,13 @@ class Avatar : public QWidget Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) public: - explicit Avatar(QWidget *parent = 0); + explicit Avatar(QWidget *parent = 0, int size = ui::AvatarSize); void setBackgroundColor(const QColor &color); void setIcon(const QIcon &icon); - void setImage(const QImage &image); + void setImage(const QString &avatar_url); + void setImage(const QString &room, const QString &user); void setLetter(const QString &letter); - void setSize(int size); void setTextColor(const QColor &color); QColor backgroundColor() const; @@ -41,7 +41,6 @@ private: QColor background_color_; QColor text_color_; QIcon icon_; - QImage image_; QPixmap pixmap_; int size_; }; diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp
index fa575d76..27bc0a5f 100644 --- a/src/ui/InfoMessage.cpp +++ b/src/ui/InfoMessage.cpp
@@ -2,6 +2,7 @@ #include "Config.h" #include <QDateTime> +#include <QLocale> #include <QPainter> #include <QPen> #include <QtGlobal> @@ -61,14 +62,14 @@ DateSeparator::DateSeparator(QDateTime datetime, QWidget *parent) { auto now = QDateTime::currentDateTime(); - QString fmt; + QString fmt = QLocale::system().dateFormat(QLocale::LongFormat); - if (now.date().year() != datetime.date().year()) - fmt = QString("ddd d MMMM yy"); - else - fmt = QString("ddd d MMMM"); + if (now.date().year() == datetime.date().year()) { + QRegularExpression rx("[^a-zA-Z]*y+[^a-zA-Z]*"); + fmt = fmt.remove(rx); + } - msg_ = datetime.toString(fmt); + msg_ = datetime.date().toString(fmt); QFontMetrics fm{font()}; #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)